引言:AI工程的"造词游戏"
如果你刚接触AI工程,肯定会被一堆高大上的术语搞晕:Prompt、SystemPrompt、Memory/Context、Function Calling、Tool、MCP、Agent、Skill、A2A......
这些概念看起来很深奥,感觉每个都是突破性创新。但说白了:
- Prompt = 你给模型的输入
- SystemPrompt = 你给模型的固定背景
- Memory = 历史对话记录
- Function Calling = 让模型返回个JSON告诉你该调用什么函数
- Tool = 给函数包了层壳
- MCP = 给Tool的接口定了个标准
- Agent = 让模型循环调用Tool直到完成任务
- Skill = 把一堆Prompt和Tool打包
- A2A = 让多个Agent互相调用
核心观点:这些术语本质上都是在做一件事——管理"上下文"(Context)。整个AI工程就是"上下文工程",这些花里胡哨的名词,很大程度上是没活硬整出来的。
但话说回来,理解这些术语的演进逻辑,确实能帮你快速掌握AI应用开发的全貌。本文用最直白的语言,带你看透这场"造词运动"。
第一幕:接词游戏变成了聊天机器人
预训练模型的本质:接词游戏
大语言模型(LLM)在预训练阶段做的事情非常简单:给定前面的词,预测下一个词。
# 预训练模型的训练目标
输入:"今天天气"
模型预测:"很好" (概率: 0.3)
"不错" (概率: 0.25)
"真棒" (概率: 0.15)
...
# 就是个接词游戏
输入:"从前有座山,山里有座"
模型:"庙"
这就是个统计接龙机器,没啥智能可言。你给它灌海量文本,它学会了"哪些词经常一起出现"。
ChatGPT的魔法:指令微调让模型有了"人感"
OpenAI做了个关键改进:把接词游戏改造成一问一答的形式。这就是指令微调(Instruction Tuning)。
# 预训练模型(原始状态)
输入:"翻译成英文:你好"
输出:"世界" (因为"你好世界"经常一起出现)
# 指令微调后(ChatGPT)
输入:"翻译成英文:你好"
输出:"Hello"
# 怎么做到的?微调数据长这样
训练数据 = [
{"input": "翻译成英文:你好", "output": "Hello"},
{"input": "翻译成英文:再见", "output": "Goodbye"},
{"input": "写一首诗", "output": "春风拂面..."},
... # 几万到几十万条"问题-答案"对
]
通过这种微调,模型学会了:
- 理解人类的指令意图
- 用"助手"的口吻回答
- 拒绝不当请求
这就有了"人感" —— 你感觉在跟一个助手对话,而不是一个接词机器。
第二幕:给输入起了个高大上的名字
Prompt:把"输入"包装成专业术语
既然模型能一问一答了,那我们给用户的输入起个名字吧。
就叫Prompt(提示词)。
# 其实就是字符串
prompt = "帮我写一首关于春天的诗"
response = model.generate(prompt)
为什么不直接叫"输入"?
因为LLM的"输入"和传统编程不同:
- 传统编程:
print("Hello")—— 精确指令 - LLM:
"帮我写代码打印Hello"—— 自然语言描述
所以搞个新词"Prompt"区分一下,顺便引出了"Prompt Engineering"(提示工程)这个听起来很专业的领域。
Prompt Engineering的本质:怎么写输入,让模型给你想要的输出。
# 差劲的Prompt
"写代码" # 模型一脸懵逼
# 好的Prompt
"""
用Python写一个函数:
- 输入:整数列表
- 输出:所有偶数的平方和
- 要求:用列表推导式
- 示例:[1,2,3,4] → 20
"""
# 模型:这下懂了!
小结:Prompt = 你的输入文本。起这个名字是为了和传统编程的"指令"区分,顺便搞出一个新领域。
第三幕:重复的东西提炼出来
SystemPrompt:避免每次都说废话
用ChatGPT用久了,你会发现一个问题:有些话每次都要重复说。
# 每次对话都要交代背景
对话1: "你是专业翻译,请翻译:Hello"
对话2: "你是专业翻译,请翻译:World"
对话3: "你是专业翻译,请翻译:AI"
# "你是专业翻译"说了3遍,烦不烦?
OpenAI想了个办法:把重复的背景信息单独拎出来,只说一次。
这就是SystemPrompt(系统提示词)。
# OpenAI API的设计
messages = [
{
"role": "system", # SystemPrompt(角色设定)
"content": "你是专业翻译助手"
},
{"role": "user", "content": "Hello"}, # 输出:"你好"
{"role": "user", "content": "World"}, # 输出:"世界"
{"role": "user", "content": "AI"}, # 输出:"人工智能"
]
本质:SystemPrompt = 所有对话共享的固定背景,避免重复。
为什么搞个新名词?
因为SystemPrompt和Prompt的生命周期不同:
- SystemPrompt:整个会话期间不变
- Prompt:每次对话不同
| 概念 | 作用 | 生命周期 | 说人话 |
|---|---|---|---|
| SystemPrompt | 角色设定 | 整个会话 | 固定背景 |
| Prompt (UserPrompt) | 具体问题 | 单次对话 | 当前输入 |
小结:SystemPrompt = 提炼出来的固定背景。起这个名字是为了区分"固定的"和"临时的"输入。
第四幕:让模型"记住"历史对话
Memory/Context:历史消息记录
LLM本身是无状态的 —— 每次调用都是全新的,它不记得你之前说过啥。
# 第一次调用
messages = [{"role": "user", "content": "我叫小明"}]
response = model.generate(messages)
# AI: "你好小明!"
# 第二次调用(全新的)
messages = [{"role": "user", "content": "我叫什么?"}]
response = model.generate(messages)
# AI: "抱歉,我不知道你的名字" —— 它忘了!
怎么办?把历史对话都传给它。
# 带历史的调用
messages = [
{"role": "user", "content": "我叫小明"},
{"role": "assistant", "content": "你好小明!"},
{"role": "user", "content": "我叫什么?"} # ← 现在它能看到完整历史
]
response = model.generate(messages)
# AI: "你叫小明"
这个"历史消息列表"有两个名字:
- Memory(记忆)—— 更直白
- Context(上下文)—— 更学术
本质:就是把所有历史对话拼成一个大字符串传给模型。
# 实际传给模型的
完整输入 = """
System: 你是Python助手
User: 什么是列表?
Assistant: 列表是Python的数据结构...
User: 如何添加元素?
Assistant: 使用append()方法...
User: 那如何删除元素? ← 当前问题
"""
# 模型看到整个"Context",才能理解"那"指的是列表
核心问题:Context长度限制
模型的上下文窗口是有限的(比如GPT-4是128K tokens ≈ 10万字)。
聊天聊久了,历史消息会超长,怎么办?
# 方案1:截断(简单粗暴)
messages = messages[-20:] # 只保留最近20轮
# 方案2:总结(复杂但好)
old_summary = summarize(messages[:-10]) # 把旧对话总结成摘要
messages = [old_summary] + messages[-10:]
# 方案3:RAG(外挂知识库)
# 把历史存到数据库,需要时检索相关片段
小结:Memory/Context = 历史对话记录。起这些名字是为了让"历史消息列表"听起来更专业。
第五幕:让模型能"动手"
Function Calling:让模型返回JSON,告诉你该调用什么函数
早期的ChatGPT只能"说话",不能"做事"。
用户:"现在几点?"
AI:"抱歉,我无法获取实时时间"
用户:"北京天气如何?"
AI:"抱歉,我的知识截止到2024年..."
OpenAI想了个办法:让模型不直接执行,而是告诉你该调用什么函数。
这就是Function Calling(函数调用)。
工作流程
# 1. 告诉模型有哪些函数可用
functions = [
{
"name": "get_weather",
"description": "获取城市天气",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string"}
}
}
}
]
# 2. 用户提问
user_message = "北京天气如何?"
# 3. 模型返回函数调用请求(JSON格式)
response = model.generate(user_message, functions=functions)
# 返回:
# {
# "function_call": {
# "name": "get_weather",
# "arguments": '{"city": "北京"}'
# }
# }
# 4. 你的代码真正执行函数
result = get_weather(city="北京") # {"temp": 15, "condition": "晴"}
# 5. 把结果传回模型
messages.append({
"role": "function",
"name": "get_weather",
"content": json.dumps(result)
})
# 6. 模型生成最终回答
final_response = model.generate(messages)
# "北京现在15°C,天气晴朗。"
核心理解:
- 模型不执行函数(安全考虑)
- 模型只决定调用什么函数、传什么参数
- 真正执行是你的代码做的
用户提问 → 模型决策 → 返回JSON → 代码执行 → 结果回传 → 模型整合 → 最终回答
为什么叫Function Calling不叫Function Execution?
因为模型只"Call"(请求调用),不"Execute"(执行)。执行权在开发者手里,防止模型乱来。
小结:Function Calling = 让模型返回个JSON,告诉你该调用什么函数、传什么参数。实际执行是你的代码做的。
第六幕:给函数包层壳,起个新名字
Tool:Function的标准化包装
有了Function Calling,你可以定义很多函数给模型用。但问题来了:函数越来越多,怎么管理?
# 散装函数
def get_weather(city): ...
def search_web(query): ...
def read_file(path): ...
def write_file(path, content): ...
def send_email(to, subject, body): ...
# ... 100个函数
# 每个函数的描述、参数都散落各处,混乱!
于是有人想:不如给函数包层壳,统一格式。
这个"包了壳的函数"就叫Tool(工具)。
# 原始函数
def get_weather(city: str) -> dict:
return call_weather_api(city)
# 包装成Tool
class WeatherTool:
name = "get_weather"
description = "获取城市实时天气"
parameters = {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名"}
}
}
def run(self, city: str) -> dict:
return call_weather_api(city)
# 现在函数有了统一的"壳"
tools = [WeatherTool(), SearchTool(), FileReadTool(), ...]
Tool vs Function的区别:
| 维度 | Function | Tool |
|---|---|---|
| 本质 | 一个函数 | 函数 + 元数据(描述、参数schema) |
| 组织方式 | 散装 | 统一格式 |
| 类比 | 裸函数 | 包装好的模块 |
为什么搞个新名词?
因为LangChain等框架需要统一接口,方便管理。叫"Tool"听起来也更专业一点。
本质:Tool = 给Function包了层标准化的壳。
小结:Tool就是Function的包装。起这个名字是为了标准化、听起来更专业。
第七幕:没活硬整的巅峰
MCP:给Tool的接口定个标准
有了Tool,每个AI平台都可以定义自己的Tool格式。于是问题来了:每家格式不一样!
# OpenAI的Tool格式
{
"type": "function",
"function": {
"name": "get_weather",
"parameters": {...}
}
}
# Anthropic的Tool格式
{
"name": "get_weather",
"input_schema": {...}
}
# Google的Tool格式
{
"function_declarations": [{
"name": "get_weather",
"parameters": {...}
}]
}
# 同一个工具要写3遍!
Anthropic说:不如我定个标准,大家都遵守?
这就是MCP(Model Context Protocol,模型上下文协议)。
MCP想做什么?
- 统一Tool的定义格式
- 统一AI访问Tool的协议
- 让Tool可以跨平台使用
# 用MCP定义Tool(一次)
@mcp.tool()
def get_weather(city: str) -> dict:
"""获取天气"""
return call_api(city)
# 自动转换成各家格式
openai_format = mcp.to_openai(get_weather)
anthropic_format = mcp.to_anthropic(get_weather)
google_format = mcp.to_google(get_weather)
类比:MCP就像USB标准 —— 统一接口,随便插。
MCP的三个核心概念
# 1. Resources(资源):AI可以读取的数据
{
"resource": {
"uri": "file:///path/to/doc.txt",
"text": "内容..."
}
}
# 2. Tools(工具):AI可以调用的函数
{
"tool": {
"name": "search",
"inputSchema": {...}
}
}
# 3. Prompts(提示模板):预定义的Prompt
{
"prompt": {
"name": "code_review",
"arguments": [...]
}
}
评价:MCP的想法很好,但目前还在推广阶段。大多数人用的还是各家自己的格式。
为什么说是"没活硬整"?
因为:
- 各家已经有自己的Tool格式,迁移成本高
- MCP主要是Anthropic在推,OpenAI、Google未必买账
- 本质上就是个"接口标准",非得起个Protocol的名字
小结:MCP = 给Tool的接口定了个标准。想法不错,但更多是Anthropic的市场策略。
第八幕:循环调用Tool,就成了Agent
Agent:让模型自己决定调用多少次Tool
有了Function Calling,模型可以调用一个函数。但如果任务需要多步操作呢?
# 用户:"帮我分析这份财报"
# 需要多步:
# 1. 读取PDF文件
# 2. 提取数据
# 3. 计算关键指标
# 4. 生成图表
# 5. 撰写总结
传统方式:你要写代码控制每一步。
Agent方式:让模型自己决定调用多少次、调用什么。
# Agent的核心:循环调用
def agent_loop(task, tools, max_iterations=10):
messages = [{"role": "user", "content": task}]
for i in range(max_iterations):
# 1. 模型决策:下一步做什么?
response = model.generate(messages, tools=tools)
# 2. 如果模型说"完成了",退出循环
if response.finish_reason == "stop":
return response.content
# 3. 如果模型要调用函数,执行之
if response.function_call:
func_name = response.function_call.name
func_args = response.function_call.arguments
result = execute_function(func_name, func_args)
# 4. 把结果传回模型
messages.append({
"role": "function",
"name": func_name,
"content": result
})
# 循环继续...
return "达到最大迭代次数"
# 使用
agent_loop(
task="帮我分析这份财报",
tools=[read_pdf, extract_data, calculate, plot, write_summary]
)
Agent的核心:
- 模型自己决定下一步做什么(思考)
- 调用Tool(行动)
- 看结果(观察)
- 重复1-3,直到完成
这就是ReAct循环(Reasoning + Acting):
循环:
Thought(思考):分析当前状态,决定下一步
Action(行动):调用Tool
Observation(观察):查看结果
→ 回到Thought
为什么叫Agent?
因为它有"自主性" —— 你只给目标,它自己想办法完成。
| 对比 | 传统AI | Agent |
|---|---|---|
| 交互模式 | 一问一答 | 给目标,自己搞定 |
| 工具调用 | 调用一次 | 循环调用多次 |
| 决策能力 | 无 | 自己决定下一步 |
本质:Agent = 给模型加个循环,让它自己反复调用Tool直到完成任务。
小结:Agent就是个while循环,让模型不断"思考-行动-观察"直到任务完成。起这个名字是为了让它听起来很智能。
第九幕:把Prompt和Tool打包就是Skill
Skill:预定义的Prompt + Tool组合
Agent能自主调用Tool了,但你会发现:有些任务的流程是固定的。
比如"代码审查":
- 读取代码文件
- 运行静态分析
- 运行测试
- 生成报告
与其每次让Agent自己摸索,不如把这个流程固定下来。
这就是Skill(技能)。
# Skill = 预定义的Prompt模板 + Tool列表 + 执行流程
class CodeReviewSkill:
"""代码审查技能"""
# 固定的Prompt模板
system_prompt = """
你是代码审查专家。
你需要:
1. 检查代码规范
2. 发现潜在bug
3. 给出改进建议
"""
# 需要用到的Tool
tools = [
ReadFileTool(),
RunLinterTool(),
RunTestsTool(),
GenerateReportTool()
]
# 执行流程
def execute(self, file_path):
# 步骤1:读取代码
code = self.tools[0].run(file_path)
# 步骤2:运行linter
lint_result = self.tools[1].run(code)
# 步骤3:运行测试
test_result = self.tools[2].run(file_path)
# 步骤4:生成报告
report = self.tools[3].run({
"code": code,
"lint": lint_result,
"test": test_result
})
return report
# 使用Skill
skill = CodeReviewSkill()
skill.execute("main.py")
Skill vs Agent的区别:
| 维度 | Agent | Skill |
|---|---|---|
| 灵活性 | 自主决策 | 固定流程 |
| 适用场景 | 开放任务 | 重复任务 |
| 类比 | 助理(自己想办法) | SOP(标准操作流程) |
为什么搞个Skill?
因为:
- 有些任务流程固定,不需要Agent每次重新规划
- 固定流程更可控、更高效
- 可以把常用流程打包复用
本质:Skill = Prompt模板 + Tool列表 + 固定流程。就是把Agent的"自主决策"换成了"预定义流程"。
小结:Skill就是把Prompt和Tool打包成固定流程。起这个名字是为了区分"自主的Agent"和"固定流程的Skill"。
第十幕:让多个Agent互相调用
A2A:Agent间通信协议
有了Agent,Google想:能不能让多个Agent协作?
比如软件开发:
- 需求分析Agent:理解需求
- 架构设计Agent:设计架构
- 开发Agent:写代码
- 测试Agent:测试
- 部署Agent:部署
问题:Agent之间怎么通信?
# Agent1想把任务交给Agent2,但格式不一样
# Agent1的接口
agent1.execute(task="分析需求", data=requirements)
# Agent2的接口
agent2.run(action="design", input=requirements)
# Agent3的接口
agent3.process({"type": "coding", "spec": requirements})
# 没法统一!
Google说:不如我定个标准?
这就是A2A(Agent-to-Agent Protocol,Agent间通信协议)。
A2A的核心
# 统一的消息格式
class A2AMessage:
sender: str # 发送者Agent ID
receiver: str # 接收者Agent ID
task: str # 任务描述
context: dict # 上下文
priority: int # 优先级
# 所有Agent都用这个格式通信
response = a2a.send(
to=agent2,
message=A2AMessage(
sender="agent1",
receiver="agent2",
task="设计架构",
context={"requirements": ...}
)
)
典型协作模式:
# 1. 流水线模式
需求分析Agent → 设计Agent → 开发Agent → 测试Agent
# 2. 管理者-工人模式
管理Agent
├─→ 工人Agent1
├─→ 工人Agent2
└─→ 工人Agent3
# 3. 对等协作模式
Agent1 ←→ Agent2 ←→ Agent3 # 互相讨论
评价:和MCP一样,A2A的想法很好,但:
- 目前还在推广阶段
- 主要是Google在推,其他家未必买账
- 本质上就是"接口标准",搞个Protocol的名字
为什么说是"没活硬整"?
因为多Agent协作可以用很多方式实现,不一定需要A2A。比如:
- 直接函数调用
- 共享消息队列
- HTTP API
A2A更多是个标准化尝试,而不是必需品。
小结:A2A = 定义了Agent之间的通信格式。想法不错,但更多是Google的市场策略。
总结:这些都叫"上下文工程"
回看这些术语,你会发现:本质上都在管理"上下文"(Context)。
┌───────────────────────────────────────┐
│ 上下文工程 (Context Engineering) │
├───────────────────────────────────────┤
│ │
│ Prompt → 当前输入 │
│ SystemPrompt → 固定背景 │
│ Memory/Context → 历史对话 │
│ Function Call → 函数调用信息 │
│ Tool → 可用工具列表 │
│ Agent → 多轮对话上下文 │
│ Skill → 预定义上下文模板 │
│ MCP/A2A → 跨系统的上下文共享 │
│ │
└───────────────────────────────────────┘
为什么说是"没活硬整"?
- Prompt —— 就是输入,非得起个新名字
- SystemPrompt —— 就是固定背景,非得单独拎出来起名
- Memory/Context —— 就是历史记录,搞两个名字
- Function Calling —— 就是让模型返回JSON,搞得很高级
- Tool —— 给Function包层壳,起个新名字
- MCP —— Tool的接口标准,搞个Protocol
- Agent —— 加个while循环,就成智能体了
- Skill —— Prompt和Tool打包,又是新名字
- A2A —— Agent通信标准,又是个Protocol
但话说回来:这些术语确实有存在价值。
它们帮助我们:
- 清晰地表达概念 —— "Agent"比"带循环的模型调用"简洁
- 构建抽象层级 —— 从Prompt → Tool → Agent,逐层抽象
- 形成技术标准 —— MCP、A2A虽然还在推广,但方向是对的
三条演进主线
主线1:上下文的范围越来越大
Prompt → 单条输入
SystemPrompt → 固定背景
Memory → 历史对话
Agent → 多轮交互
A2A → 跨Agent共享
主线2:从静态到动态
Prompt → 静态文本
Function Call → 动态调用
Tool → 标准化调用
Agent → 循环调用
主线3:从单一到复合
Function → 单个函数
Tool → 封装的工具
Skill → 打包的流程
Multi-Agent → 多Agent协作
技术栈全景
┌─────────────────── 应用层 ────────────────────┐
│ Multi-Agent System (多Agent协作) │
│ └─ A2A Protocol (通信标准) │
└──────────────────────────────────────────────┘
↓
┌─────────────────── 能力层 ────────────────────┐
│ Agent (循环调用) │
│ └─ Skill (固定流程) │
└──────────────────────────────────────────────┘
↓
┌─────────────────── 工具层 ────────────────────┐
│ Tool (标准化工具) │
│ └─ MCP Protocol (工具标准) │
│ └─ Function Calling (函数调用) │
└──────────────────────────────────────────────┘
↓
┌─────────────────── 交互层 ────────────────────┐
│ Context / Memory (历史记录) │
│ SystemPrompt (固定背景) │
│ Prompt (当前输入) │
└──────────────────────────────────────────────┘
↓
┌─────────────────── 模型层 ────────────────────┐
│ Large Language Model (接词游戏) │
│ └─ Instruction Tuning (指令微调) │
└──────────────────────────────────────────────┘
实用建议:你需要用到哪些?
根据你的应用场景,选择合适的抽象层级:
场景1:简单问答 → 只需Prompt
# 翻译、总结、问答等简单任务
prompt = "翻译成英文:你好"
response = model.generate(prompt)
场景2:固定角色 → 加上SystemPrompt
# 需要特定风格的助手
messages = [
{"role": "system", "content": "你是专业翻译"},
{"role": "user", "content": "你好"}
]
场景3:多轮对话 → 管理Memory
# 聊天机器人、客服等需要记住上下文的场景
messages = [] # 历史记录
# 每轮对话
messages.append({"role": "user", "content": user_input})
response = model.generate(messages)
messages.append({"role": "assistant", "content": response})
场景4:需要实时信息 → 使用Function Calling
# 查天气、搜索、读文件等需要外部数据的场景
functions = [get_weather, search_web, read_file]
response = model.generate(user_input, functions=functions)
if response.function_call:
result = execute_function(response.function_call)
final_response = model.generate([..., result])
场景5:复杂任务 → 用Agent
# 需要多步操作的任务:数据分析、代码生成、报告撰写等
agent = Agent(tools=[read, analyze, plot, write])
agent.run("分析这份数据并生成报告")
场景6:重复流程 → 封装成Skill
# 代码审查、文档生成等固定流程
skill = CodeReviewSkill()
skill.execute("main.py")
场景7:多领域协作 → Multi-Agent
# 软件开发、复杂项目等需要专业分工的场景
team = [AnalystAgent, DesignerAgent, DeveloperAgent, TesterAgent]
result = team.collaborate("开发一个博客系统")
原则:
- 简单任务用简单工具 —— 不要为了用Agent而用Agent
- 复杂任务逐步升级 —— 从Prompt开始,需要时再加功能
- 不要被术语吓倒 —— 理解本质,选择合适的抽象
结语:穿透术语看本质
AI工程的"造词运动"看似复杂,本质上就是在解决一个问题:如何更好地管理上下文(Context)。
从这个角度看:
- Prompt/SystemPrompt/Memory = 上下文的组成部分
- Function Calling/Tool = 扩展上下文(加入外部信息)
- Agent = 动态管理上下文(循环更新)
- Skill = 预定义上下文模板
- MCP/A2A = 跨系统共享上下文
这些术语有些确实有必要(比如Prompt、Agent),有些确实有点"没活硬整"(比如MCP)。
但理解这些术语的演进逻辑,能帮你:
- 快速掌握AI应用开发的全貌
- 选择合适的抽象层级
- 不被新名词吓倒(它们可能就是老概念换了个名字)
最重要的:技术是为了解决问题,不是为了炫耀名词。
当你遇到新术语时,问自己三个问题:
- 它解决了什么问题?
- 和已有概念有什么区别?
- 我的项目需要它吗?
不要被术语绑架,做个清醒的工程师。