【Agent架构探索】之Agent和大模型交互基本工作过程

昨天提到了提到 system prompt(系统提示词)
那他这个和user prompt(用户提示词)(用户角色的提示词到底有什么区别? 
为什么system prompt不能太多? 
为什么要使用多Agent架构,直接使用1个Agent 只要它具有更强大的工作能力是不是就一个agent就足够了?
首先回答问题:
1.system prompt 和 user prompt 从大模型的设计上和设计时就已经区分开

大模型在训练的时候,就被专门用特定的标记(Token)区分不同的角色。system提示词:相当于“上帝视角/元指令”。用户的所有输入全都以system的提示词最高标准去对齐。

2.system prompt 不要直接写入user prompt的开头位置(虽然也可以),否则多轮对话后就会因为上下文压缩和剪枝导致 注意力权重下降。 而system prompt不会发生该问题。 (例如我们可以在 用户提示词的输入框,开头写下,请以一个 测试工程师 、高级质量工程师的角色来分析如下代码并生成测试用例) 这个其实就属于我们把system prompt的提示词 写入了user prompt。
3.system prompt不宜过多,越精简越好。一般建议300-1000tokens。大了反而会导致注意力丢失,只关注首尾规则,丢失中间。 大模型的上下文大小越来越大,都是为了用户和skill、工具的内容。然而系统提示词不建议过大。
4.单agent架构下完全可以做到并行处理,即一个单独任务独立的线程处理,虽然都是同一个agent和LLM交互,但是上下文隔离。 但因system prompt 定义不能越多越好,上面问题3 已经回答了。   多agent可以做到跨角色的agent的协作和抵抗。也就是说  质量保障、测试工程师 Agent  vs  开发工程师 Agent。 否则开发工程师自己测试自己代码永远都觉的是正确的。
我们用一个生动的例子,用“蜜蜂采蜜与酿蜜”来做这个比方!

蜜蜂它吃进去花粉和花蜜,在体内加工,既能产出对人类和自然极有价值的“蜂蜜”(有用输出),也会排出无用的渣滓(拉屎/剪枝)。

和 Agent / 大模型的 Token 治理一一对应起来:

Image

蜂巢与蜜蜂的“吞吐系统”大比方

1. 蜜蜂的胃(Context Window 上下文窗口)

  • 胃的容量= 大模型的 Total Context Window(比如 128k Token)。
  • 蜜蜂的胃容量是有限的,不管是吸进去的花蜜、吞进去的花粉、正在酿造的蜂蜜,还是准备排出的渣滓,全都要占胃的空间!

2. 蜜蜂吸进去和存着的东西(输入 Token / Input Tokens)

蜜蜂飞出去采蜜,吃进肚子里的各种东西,对应大模型输入的各个部分:

  • 主食蜜水(System Prompt 元规则): 相当于蜜蜂维持生命必须先喝的那一口底蜜(压肚子的基础)。不管干啥活,这部分基础规则始终占着胃里的固定空间。
  • 高浓度花粉(水分少的干性花粉)(Tools / Functions 定义): 高营养但极其硬核、占地方!挂载的工具越多,就像蜜蜂塞了一肚子浓缩花粉,还没开始酿蜜,胃就被塞饱一大半了。
  • 飞过的路线与采到的花蜜(液体花蜜露)(Messages 历史对话 + 用户的提问): 蜜蜂一路飞一路吸花蜜(源源不断地聊天、输入长文档)。吸得越多,肚子里的液体就积得越多。
  • 未过滤的花粉壳与杂质(以往 Tool 返回的几万行冗余日志/数据): Agent 跑终端或查数据库吐出来的一大堆垃圾日志,就像蜜蜂吸进肚子里那些无法被酿成蜂蜜的粗糙花粉壳,纯粹死死堵在胃里,极占地方。

3. 产出蜂蜜 vs 排出渣滓(输出给我们用户的回答内容 Response vs 剪枝 Pruning  把肚子的空间腾出来)

吐出真正的蜂蜜 

  • 吐蜜= 大模型最终给用户的 真正的有效回答。
  • 蜜蜂在胃里经过一系列复杂的生物酶转化(大模型 Transformer 推理思考),把吸进去的粗糖转化成极具价值的蜂蜜,吐进蜂巢(返回给用户)。
  • 关键约束:蜜蜂在吐蜜之前,胃里必须留有足够的压强和空间,否则胃装太满,它连转化生成蜜的空间都没有,直接卡死。

排出花渣/废料(上下文剪枝 )

  • 排渣/拉屎= 上下文剪枝 。
  • 蜜蜂在酿蜜的过程中,会把那些无法转化成蜂蜜的硬壳、杂质和多余水分排出体外(剪枝/清理历史废话)。
  • 拉屎(剪枝)的意义:把肚子里没用的花渣赶快排出去,腾出胃里的空间,蜜蜂才能继续吸新花蜜(输入新问题)、吐出更多浓郁的蜂蜜(输出长回答)

进入正题,大模型的基本工作过程, 本次我们举例2个实例

1)用户一次问题,Agent和LLM 2轮交互

2) 用户和Agent有2次问题交互

示例一:用户一次问答,Agent和LLM 有2轮交互

下面以 OpenAI  API 协议为例, 展示一个典型用户一次问答, 但是Agent和LLM需要进行2轮对话的过程。

场景设定:天气与服装推荐 Agent

  • 工具:get_current_weather(获取实时天气)
  • 目标:用户询问城市天气,并要求根据天气推荐穿着。

Agent第一次和LLM交互: LLM仅返回格式化工具调用信息

1.1 客户端/ Agent 发送请求 (Request)

Agent 把 System Prompt、User Message以及定义的 Tools Schema组装发给大模型。

POST /v1/chat/completions

Content-Type: application/json


{

"model"
: 
"gpt-4o"
,

"temperature"
: 
0.2
,

"messages"
: [

    {

"role"
: 
"system"
,

"content"
: 
"你是一个智能生活助手。你需要根据用户提问,先查询天气,再给出穿衣建议。如果需要调用工具,请直接输出 Function Call。"

    },

    {

"role"
: 
"user"
,

"content"
: 
"今天北京的天气怎么样?我该穿什么衣服?"

    }

  ],

"tools"
: [

    {

"type"
: 
"function"
,

"function"
: {

"name"
: 
"get_current_weather"
,

"description"
: 
"获取指定城市的当前天气情况"
,

"parameters"
: {

"type"
: 
"object"
,

"properties"
: {

"location"
: {

"type"
: 
"string"
,

"description"
: 
"城市名称,例如:北京、上海"

            }

          },

"required"
: [
"location"
]

        }

      }

    }

  ]

}

1.2 LLM 返回响应 (Response)

大模型发现回答问题需要先拿到实际数据,因此不直接返回文本,而是返回 tool_calls指令。这时候 Agent需要执行 工具获取 天气信息。 

HTTP/
1.1
200
 OK

Content-Type: application/json


{

"id"
: 
"chatcmpl-round1-call"
,

"choices"
: [

    {

"index"
: 
0
,

"message"
: {

"role"
: 
"assistant"
,

"content"
: 
null
,

"tool_calls"
: [

          {

"id"
: 
"call_weather"
,

"type"
: 
"function"
,

"function"
: {

"name"
: 
"get_current_weather"
,

"arguments"
: 
"{\"location\": \"北京\"}"

            }

          }

        ]

      },

"finish_reason"
: 
"tool_calls"

    }

  ]

}

Agent 调用tool_calls后:

  1. 解析出函数名 get_current_weather和参数 {"location": "北京"}。
  2. 调用天气 API,得到天气数据结果:

    {"location": "北京", "temperature": "8°C", "condition": "微风,多云转晴"}

第二轮和LLM交互

2.1 客户端/ Agent 发送更新后的上下文请求 

Agent 必须把第 1 轮的原始对话、LLM 吐出的 tool_calls消息,以及工具执行返回的 tool结果,全部拼接到 messages队列中发回给 LLM。

POST /v1/chat/completions

Content-Type: application/json


{

"model"
: 
"gpt-4o"
,

"temperature"
: 
0.2
,

"messages"
: [

    {

"role"
: 
"system"
,

"content"
: 
"你是一个智能生活助手。你需要根据用户提问,先查询天气,再给出穿衣建议。如果需要调用工具,请直接输出 Function Call。"

    },

    {

"role"
: 
"user"
,

"content"
: 
"今天北京的天气怎么样?我该穿什么衣服?"

    },

    {

"role"
: 
"assistant"
,

"content"
: 
null
,

"tool_calls"
: [

        {

"id"
: 
"call_weather"
,

"type"
: 
"function"
,

"function"
: {

"name"
: 
"get_current_weather"
,

"arguments"
: 
"{\"location\": \"北京\"}"

          }

        }

      ]

    },

    {

"role"
: 
"tool"
,

"tool_call_id"
: 
"call_weather"
,

"content"
: 
"{\"location\": \"北京\", \"temperature\": \"8°C\", \"condition\": \"微风,多云转晴\"}"

    }

  ],

"tools"
: [ ... ]

}

2.2 LLM 返回最终答案 (Response)

大模型结合了完整上下文(用户需求 + 工具执行结果),生成最终的自然语言总结。

HTTP/
1.1
200
 OK

Content-Type: application/json


{

"id"
: 
"chatcmpl-round2-final"
,

"choices"
: [

    {

"index"
: 
0
,

"message"
: {

"role"
: 
"assistant"
,

"content"
: 
"今天北京的天气为多云转晴,伴有微风,气温约为 8°C。\n\n**穿衣建议**:天气较冷,建议穿着风衣、轻薄羽绒服或夹克,内搭羊毛衫或保暖内衣。早晚温差较大,请注意保暖!"

      },

"finish_reason"
: 
"stop"

    }

  ]

}

上下文:可以看到,第二轮和LLM请求包含了所有过往的历史状态(包括工具调用的 JSON 参数和返回日志)。这也是为什么需要“上下文剪枝(Pruning)”来降低 KV Cache 消耗的原因。

示例二、   用户 和 Agent 2次对话的示例

用户与 Agent 进行2 次对话(2 轮交互),在底层 API 维度上,主要是上下文(messages数组)的持续累加。

下面以 OpenAI标准的 API 格式展示完整的协议交互:


场景设定:代码优化与提问

  • 第 1 次对话:用户发送一段 Python 代码请求优化。
  • 第 2 次对话:用户针对第 1 次的回答追问“解释其中某句代码的含义”。

第 1 次用户与 Agent 对话

Agent 拼接静态的 System Prompt 和用户的第 1 个提问。

POST /v1/chat/completions
      
Content-Type: application/json
      
 
      
{
      
  ”model”: ”gpt-4o”,
      
  ”temperature”: 0.3,
      
  ”messages”: [
      
    {
      
      ”role”: ”system”,
      
      ”content”: ”你是一个资深的 Python 架构师,回答要求言简意赅,只推荐符合 PEP 8 规范的最优解。”
      
    },
      
    {
      
      ”role”: ”user”,
      
      ”content”: ”请用列表推导式把这个循环写得更简洁:\nresult = []\nfor x in range(10):\n    if x % 2 == 0:\n        result.append(x * 2)”
      
    }
      
  ]
      
}

大模型生成第 1 次的回答,返回给 Agent 并展示给用户。 此时大模型返回了 优化后的代码。 

HTTP/1.1 200 OK
      
Content-Type: application/json
      
 
      
{
      
  ”id”: ”chatcmpl-turn1-response”,
      
  ”choices”: [
      
    {
      
      ”index”: 0,
      
      ”message”: {
      
        ”role”: ”assistant”,
      
        ”content”: ”你可以使用带有条件判断的列表推导式重构为一行:\n\n```python\nresult = [x * 2 for x in range(10) if x % 2 == 0]\n```”
      
      },
      
      ”finish_reason”: ”stop”
      
    }
      
  ]
      
}

第 2 次用户与 Agent 对话(追问),肯定不会再次把第一次返回的 优化代码再发一次了 

核心工程细节:大模型本质是“无状态”的。为了让大模型“记住”刚才讨论过的代码,Agent必须把第 1 轮的user提问和assistant回答完整拼接到messages队列里,再附上用户的第 2 次提问。

POST /v1/chat/completions
      
Content-Type: application/json
      
 
      
{
      
  ”model”: ”gpt-4o”,
      
  ”temperature”: 0.3,
      
  ”messages”: [
      
    {
      
      ”role”: ”system”,
      
      ”content”: ”你是一个资深的 Python 架构师,回答要求言简意赅,只推荐符合 PEP 8 规范的最优解。”
      
    },
      
    {
      
      ”role”: ”user”,
      
      ”content”: ”请用列表推导式把这个循环写得更简洁:\nresult = []\nfor x in range(10):\n    if x % 2 == 0:\n        result.append(x * 2)”
      
    },
      
    {
      
      ”role”: ”assistant”,
      
      ”content”: ”你可以使用带有条件判断的列表推导式重构为一行:\n\n```python\nresult = [x * 2 for x in range(10) if x % 2 == 0]\n```”
      
    },
      
    {
      
      ”role”: ”user”,
      
      ”content”: ”其中的 if x % 2 == 0 为什么要放在最后面,而不是放在 x * 2 前面?”
      
    }
      
  ]
      
}

大模型读取了包含完整历史的 messages,基于上下文回答追问。

HTTP/1.1 200 OK
      
Content-Type: application/json
      
 
      
{
      
  ”id”: ”chatcmpl-turn2-response”,
      
  ”choices”: [
      
    {
      
      ”index”: 0,
      
      ”message”: {
      
        ”role”: ”assistant”,
      
        ”content”: ”这是 Python 列表推导式的语法规则:\n1. **过滤器(`if`)放在最后**:用于在元素进入计算前进行筛选。只有满足条件 `x % 2 == 0` 的元素,才会传给最左侧的表达式。\n2. **转换表达式(`x * 2`)放在最前**:只有当需要三元运算符(如 `x * 2 if x > 5 else x`)时,`if-else` 才会写在表达式位置。”
      
      },
      
      ”finish_reason”: ”stop”
      
    }
      
  ]
      
}

多轮对话工程

  1. 状态记忆由 Agent 维护:大模型服务器不帮你保存会话历史。每一次新的用户提问),Agent 客户端都要把前面的所有 user 和 assistant 消息完整重新上传一遍。
  2. 静态前缀命中 API 缓存:在第 2 次请求中,开头的 System Prompt 以及第 1 轮的问答完全没有改变。API 厂商(如 OpenAI、DeepSeek)的 Prompt Caching 机制 会自动命中这部分前缀,第 2 次请求中这部分重用 Token 会打折且首字响应极快。一个是降低了token的消耗量和提高了响应速度。

此篇文章已被阅读3 次