从iOS老炮到Prompt工程师:一次MCP集成的血泪踩坑实录

监控面板盯梢人
2026-03-30 14:36
阅读 2846

六年前,我写的第一行 Swift 代码还是用 var 声明变量、手写 delegate 回调。如今在家远程办公,一边调试 SwiftUI 预览器抽风的问题,一边在 Jupyter Notebook 里跑 Prompt 工程实验——这世界变化太快,连我家猫都学会了在我敲 Cmd + R 时跳上键盘。

去年底,团队接了个“智能客服助手”的需求,产品经理拍着胸脯说:“就加个 AI 功能,两周上线,双十二前搞定。” 我当时差点把刚泡好的挂耳咖啡喷屏幕上——这哥们怕不是以为 AI 是 npm install 一下就能跑的?但转念一想,反正居家办公通勤时间省了,不如趁机深入搞搞最近火出天际的 MCP(Model Context Protocol)Prompt 工程。毕竟,现在不学点 AI,简历都不好意思投。


起因:一个“简单”的需求,一堆复杂的上下文

我们的 App 是个电商导购平台,用户经常问“这件衣服适合梨形身材吗?”、“能不能搭配牛仔裤?” 这类问题传统规则引擎根本扛不住。于是技术总监拍板:接入大模型,用自然语言理解用户意图,再结合商品知识库生成回复。

关键来了:不能直接把用户 query 扔给 LLM。一来涉及用户隐私(比如身高体重),二来商品数据分散在多个微服务里,三来……我们可不想因为 prompt 写得不好,让 AI 建议用户“穿睡衣去参加婚礼”。

所以架构上我们决定采用 MCP —— 这玩意儿说白了就是一套标准化协议,让 LLM 能安全地调用外部工具(比如查库存、读用户画像),同时把上下文信息结构化传递,避免 prompt 泄露敏感字段。

听起来很美,对吧?现实很快教我做人。


踩坑第一站:MCP 的“安全上下文”其实不安全

我们一开始图快,直接用 OpenAI 的 function calling 模拟 MCP。把用户 ID、订单历史一股脑塞进 system prompt:

{
  "role": "system",
  "content": "你是XX商城的智能导购。当前用户ID: U12345, 最近购买: [连衣裙, 高跟鞋], 身高: 165cm..."
}

结果 QA 测试时发现:当模型 token 不够时,会直接把 user ID 当作回复内容吐出来!

“根据U12345的历史偏好,推荐这款...”

线上要是这么干,分分钟被用户投诉到 GDPR 罚款。我当时后背一凉——这哪是智能客服,这是人肉数据泄露器啊!

教训:MCP 的核心不是“传数据”,而是“可控地暴露能力”。正确的做法是只暴露工具接口,让 LLM 通过工具调用来获取必要信息,而不是把原始数据塞进 prompt。

于是我们重构了 MCP 层:

  • 定义 UserProfileTool:LLM 可请求“获取用户体型特征”
  • 定义 ProductMatcherTool:输入商品ID,返回适配建议
  • 所有工具调用走内部鉴权网关,用户原始数据永不进 LLM 上下文
// 伪代码:MCP 工具注册
MCP.register(tool: UserProfileTool.self) { request in
    guard let userID = request.context.userID else { return .unauthorized }
    let profile = UserDB.fetchProfile(for: userID)
    // 只返回脱敏后的特征
    return ToolResponse(body: ["bodyType": profile.bodyType])
}

这一步改完,安全基线才算达标。顺便吐槽一句:有些开源 MCP 实现默认把整个 context 序列化进 prompt,简直是埋雷。


踩坑第二站:Prompt 工程 ≠ 拼夕夕式堆砌指令

解决了安全问题,以为能松口气?Too young。

初期 prompt 写得像产品经理的需求文档:

“你是一个专业的时尚顾问,请根据用户的身材、风格偏好、当前季节、商品材质、价格区间、历史购买记录、社交平台点赞数据……给出不超过3条、每条不超过50字、带emoji、语气亲切但不过分热情的建议。”

结果模型要么输出超长小作文,要么干脆罢工:“抱歉,我无法访问您的社交数据。”

问题在哪? 我们把所有约束都塞进 system prompt,导致 LLM 注意力分散,关键指令反而被忽略。

后来学乖了,采用 分层 Prompt 设计

  1. 角色定义层(固定不变)
    "你是XX商城的AI时尚顾问,只回答穿搭相关问题"

  2. 能力声明层(由 MCP 动态注入)
    "可用工具:[getUserBodyType, getProductDetails]"

  3. 任务约束层(每次请求动态生成)
    "请用中文回复,最多2条建议,每条≤40字"

最关键的是:把格式要求转化为工具输出规范。比如我们自定义了一个 FormatResponseTool,LLM 只需调用它并传入原始建议文本,工具内部做截断+emoji处理。

# Prompt 工程实践:动态注入约束
def build_prompt(user_query: str, tools: List[str]) -> str:
    return f"""
    角色:专业时尚顾问
    能力:{', '.join(tools)}
    任务:针对"{user_query}"生成建议
    注意:仅调用工具获取必要信息,勿猜测用户未提供的数据
    """

效果立竿见影——输出稳定性提升 70%,连测试同学都说“这次终于不像机器人了”。


开发心得:老 iOSer 的跨界生存指南

作为一个写了六年 UIKit/SwiftUI 的开发者,突然扎进 AI 领域,最大的冲击不是技术,而是思维模式

在 iOS 开发中,我们追求确定性:某个按钮点击必然触发某个回调;而在 Prompt 工程里,你面对的是概率分布——同一个 prompt,可能今天输出完美,明天就胡言乱语。

我的三条血泪心得

  1. 不要迷信“一次写好”
    Prompt 和 UI 一样需要 A/B 测试。我们现在用 LangSmith 记录每次 LLM 调用的 input/output,每周 review bad case。上周就发现一个 bug:当用户问“显瘦吗?”,模型总回答“显胖”,后来发现训练数据里“显瘦”和“显胖”的样本比例失衡……

  2. MCP 不是银弹,但能救命
    别指望 LLM 自己搞定一切。把确定性逻辑(如权限校验、数据格式化)放在 MCP 工具里,让 LLM 专注它擅长的——理解模糊意图。这就像 iOS 里把业务逻辑放 ViewModel,View 只负责展示。

  3. 安全必须前置
    曾经有同事提议:“先上线再加安全策略”。我直接甩出 OWASP LLM Top 10 文档——Prompt Injection、训练数据污染、工具滥用……这些风险在设计阶段就要堵住。现在我们所有 MCP 工具强制要求:

    • 输入参数类型校验
    • 输出内容过滤敏感词
    • 调用频次限流

效果与反思:从“能用”到“敢用”

经过三轮迭代,新客服助手终于在双十二前灰度上线。数据还不错:

指标 旧规则引擎 新 MCP+Prompt 方案
用户满意度 68% 82%
平均响应时长 1.2s 2.4s
敏感信息泄露 0.3% 0%

虽然延迟翻倍(主要花在工具调用链路上),但安全性和体验提升值得。最让我欣慰的是,上周收到一条用户反馈:“你们的 AI 终于不说‘亲亲’了!”


写在最后:别做技术的观光客

回看这段历程,从最初对 MCP 一脸懵,到能设计安全的 Prompt 架构,最大的感悟是:技术没有边界,只有认知的牢笼

以前我觉得“iOS 开发”是个身份标签,现在明白——我们首先是问题解决者。无论是用 Auto Layout 对齐像素,还是用 Prompt 工程对齐语义,本质都是在混沌中建立秩序。

最近团队又在讨论把 MCP 用到 App 内搜索场景。我一边啃《Hands-On Prompt Engineering》电子书,一边想:或许明年这时候,我会写一篇《从 Prompt 工程师到 iOS AI 插件开发》?

谁知道呢。反正我家猫已经适应了我在深夜对着终端喃喃自语:“这个 embedding 为啥 cosine 相似度这么低……”

(完)

P.S. 如果你在搞类似项目,记住两件事:

  1. 永远假设 LLM 会犯错——它确实会
  2. 安全不是功能,是底线
    共勉。

评论 0

最热最新
暂无评论
监控面板盯梢人Lv.1
0
影响力
0
文章
0
粉丝