想开发自己的Agent却毫无头绪?这篇文章给你一张清晰的思考地图

admin 2026-05-11 08:02:05 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文系统性地阐述了AIAgent开发的完整思维框架,强调设计优先于编码的理念。文章通过打车助手案例详细拆解了四步开发路径:定义能力边界、设计工具接口、编写系统Prompt、跑通最小可用版本。重点探讨了Prompt设计的六大要素、意图理解优化策略、多轮对话管理技巧以及测试集构建方法,为开发者提供了可落地的工程实践指南。 综合评分: 85 文章分类: AI安全,安全开发,解决方案,安全工具,安全培训


cover_image

想开发自己的 Agent 却毫无头绪?这篇文章给你一张清晰的思考地图

逆向OneByOne

2026年5月9日 08:04 浙江

在小说阅读器读本章

去阅读

编者荐语:

Agent开发的系统性工程指南,难点在于设计而非编码。作者通过“打车助手”的案例,拆解了从需求到上线的全流程思维框架。开发路径:遵循“先设计后编码”的四步法:定义能力边界 → 设计工具接口 → 编写系统Prompt → 跑通最小可用版本

以下文章来源于混迹在程序员圈子的摇滚乐迷 ,作者不太优秀的文笔

混迹在程序员圈子的摇滚乐迷 .

即是程序员,也是曾经的摇滚乐迷……

摘要

这篇文章为Agent开发提供了一张清晰的思考地图,强调在编写代码之前进行设计的重要性。它涵盖了从定义Agent能力边界、设计有效的Prompt、管理多轮对话到构建自动化测试的全流程。笔记旨在帮助开发者建立系统性思维框架,提升Agent的稳定性和可靠性。

知识图谱

Agent开发学习笔记

有时候,我在接到一个Agent需求的时候,我觉得自己的思绪有时候是凌乱的,这篇文,主要是在回忆自己曾经在Agent开发上学习和做过的一些步骤。希望能对一些朋友有帮助。

许多教程教你如何调用 API、如何使用框架,但很少有人告诉你:在写下第一行代码之前,你应该如何思考。构建一个稳定、可靠的 Agent,其核心挑战并非来自代码,而是源于设计的缺失和思考的混乱。

这篇笔记,正是我在一线实践中摸爬滚打后,为自己总结的一套系统性思考框架。它将带你暂别代码,回归设计的本源,一步步理清思路:

  • 从 定义能力边界 开始,而不是直接看 API;
  • 学习 设计有效的 Prompt,而不只是简单地“下指令”;
  • 掌握 管理多轮对话 的技巧,让 Agent 不再“健忘”;
  • 学会 构建自动化测试,用工程化的方法确保 Agent 的可靠性。

希望这份笔记,能帮你搭建起设计 Agent 的思维骨架,在探索的道路上少走一些弯路。


一、从零开始做一个 Agent

第一步:定义能力清单

做 Agent 的第一步不是写代码,而是想清楚 Agent 的边界和能力。把用户所有可能的需求列出来,判断哪些由 Agent 处理,以打车需求举例:

| 能力 | 说明 | | — | — | | 叫车 | 告诉 Agent “我要去机场”,它帮你下单 | | 查询订单状态 | “司机到哪了?” | | 取消订单 | 带条件判断(是否收违约金) | | 推荐目的地 | 基于历史记录或语境猜你要去哪 | | 价格估算 | “去南京西路大概多少钱?” | | 投诉/反馈 | 引导用户完成投诉流程 |

这步的产出是一份工具列表(Tools),每个能力对应一个 tool。

第二步:定义每个 Tool 的接口

每个 tool 就是一个函数,Agent 决定什么时候调它、传什么参数。接口定义要尽可能精确。

from typing import TypedDict
# 使用 TypedDict 或 Pydantic 模型替代泛泛的 dict
# 这能让返回的数据结构更清晰,便于静态检查和代码维护
class RideOrder(TypedDict):    order_id: str    estimated_wait_time_minutes: int
def book_ride(origin: str, destination: str, ride_type: str = "standard") -> RideOrder:    """叫一辆车,返回订单ID和预计等待时间"""    ...
def get_order_status(order_id: str) -> dict:    """查询订单实时状态,包括司机位置"""    ...

第三步:写 System Prompt

告诉 Agent:它是谁、它的边界在哪、出错时怎么办、语气风格。

第四步:跑最小可用版本

跑通最简单的一条链路:

用户说”我要去虹桥机场” → Agent 调 book_ride → 返回”已为您叫车,预计3分钟到达”


二、一个好的 Prompt 需要具备哪些条件

角色定义      → 你是谁能力边界      → 你能做什么边界说明      → 模糊时怎么办Few-shot     → 举例让模型学输出格式      → 返回什么结构异常兜底      → 出错了怎么办

1. 角色定义

告诉模型它是谁、职责范围是什么:

你是一个打车助手,负责帮乘客完成叫车、查单、取消订单等操作。你只处理与打车相关的请求。

2. 能力边界

明确告诉模型什么能做、什么不能做:

你可以:叫车、查订单状态、取消订单、估算费用你不可以:回答与打车无关的任何问题

注意: 边界不是越窄越好。用户说”我要去接我妈妈从医院回家”,Agent 应该能理解这是叫车需求,要给模型一定的意图理解弹性

3. 边界说明

模糊场景提前定义处理方式:

如果用户没提供目的地 → 追问目的地如果一句话有多个意图 → 逐一确认再执行如果意图不明确 → 先复述你的理解,再执行

4. Few-shot 示例

Few-shot(少量示例)就是在 prompt 里直接给模型几个输入→输出的例子

用户:"叫辆车去虹桥"   → 意图:book_ride,目的地:虹桥机场用户:"拼一个"         → 意图:book_ride,车型:拼车用户:"司机到哪了"     → 意图:query_status用户:"算了取消吧"     → 意图:cancel_ride用户:"今天天气怎样"   → 与打车无关,礼貌拒绝

本质上就是用例子代替复杂的文字说明。

5. 输出格式

固定返回格式,防止代码解析出错:

始终返回 JSON 格式:{"intent": "book_ride", "destination": "虹桥机场", "ride_type": "standard"}

6. 异常兜底

如果完全无法理解用户意图,回复:"抱歉我没理解您的意思,请问您需要叫车吗?"

三、意图理解能力如何提升

优先顺序

Prompt 优化(Few-shot + 意图确认)    ↓ 还不够?收集真实用户对话,分析哪类意图经常出错    ↓ 找到规律?针对性加 Few-shot 或调整 prompt 逻辑    ↓ 还有系统性偏差?再考虑微调

80% 的意图理解问题在 Prompt 阶段就能解决。

RAG 和微调分别解决什么问题

  • RAG 解决的是”知识”问题,不是”理解”问题。适合动态知识(计价规则、优惠活动等)。
  • 微调 解决的是”风格/领域适配”问题。需要几千条高质量标注数据,门槛很高,不是首选。

RAG 的高级应用:动态 Few-shot

除了用 RAG 查询外部知识库,它还可以用来优化意图理解。当用户的输入很模糊时,可以用 RAG 动态构建 Few-shot 示例,这比在 Prompt 中写死几个例子更灵活。

流程如下:

  1. 建立“疑难样本”数据库:将历史上处理过的、有代表性的“模糊输入 → 清晰意图”的案例存入向量数据库。
  2. 实时检索:当 Agent 收到新的用户请求时,先将其作为查询,从数据库中检索出最相似的 1-3 个历史案例。
  3. 动态拼接 Prompt:将检索到的案例动态地插入到 Prompt 的 Few-shot 部分。
  4. LLM 决策:LLM 参考这些最相关的例子,做出更准确的判断。

这种方法能让 Agent 拥有“举一反三”的能力,对未见过的边界 Case 鲁棒性更强。


四、Prompt 出问题如何分析

定位问题层级

用户输入    ↓意图理解有没有出错?    ← 第一层    ↓tool 选择对不对?       ← 第二层    ↓tool 参数传对了吗?     ← 第三层    ↓最终回复合不合理?      ← 第四层

分析方法

1. 看中间推理过程

让模型输出思考链,看到模型”在想什么”:

SYSTEM_PROMPT = """...原始prompt...
在回答之前,先用<thinking>标签写出你的推理过程:- 用户意图是什么- 应该调用哪个tool- 参数是什么</thinking>"""

2. 做消融测试

把 prompt 拆开,逐段注释掉,看哪段影响最大:

完整prompt &nbsp; &nbsp; &nbsp; &nbsp;→ 通过率 72%去掉Few-shot &nbsp; &nbsp; &nbsp;→ 通过率 51% &nbsp; ← 这段影响最大去掉边界说明 &nbsp; &nbsp; &nbsp;→ 通过率 68%

3. 对比不同模型

同一批测试集跑多个模型,如果换个模型就好了,说明问题在 prompt 和模型的适配上。

分析流程

发现问题&nbsp; &nbsp; ↓看中间推理过程 → 定位断在哪一层&nbsp; &nbsp; ↓做消融测试 → 找到影响最大的片段&nbsp; &nbsp; ↓针对性修改&nbsp; &nbsp; ↓跑测试集验证&nbsp; &nbsp; ↓确认提升,记录版本

五、更换后端模型的注意事项

换模型可能带来的问题:

  • 指令遵循能力不一样 — 原来调好的 prompt 可能直接失效
  • 输出格式不稳定 — JSON 格式可能夹杂多余文字导致解析出错
  • 推理能力差异 — 多意图、隐含意图处理能力差距大
  • 上下文处理不同 — 多轮对话里”忘事”情况变多

应对策略

做一层抽象,把模型调用解耦:

class&nbsp;LLMClient:&nbsp; &nbsp;&nbsp;def&nbsp;chat(self, messages): ...
class&nbsp;OpenAIClient(LLMClient): ...class&nbsp;ClaudeClient(LLMClient): ...class&nbsp;QwenClient(LLMClient): ...

灰度切换: 先用新模型跑 10% 流量,对比效果再逐步放量。

核心:换模型前先跑测试集,用数字说话。


六、多轮对话如何管理

单轮的“意图识别 → 工具调用”是 Agent 的基础,但真正实用的 Agent 必须能处理连续的多轮对话。核心挑战在于上下文管理

1. 状态跟踪 (State Tracking)

Agent 需要在对话中持续维护一个“状态”,用来记住关键信息和用户偏好。

例如,用户说“帮我叫辆车去机场”,Agent 下单后,需要将 order_id 存入当前对话的状态中。当用户接着问“司机到哪了?”,Agent 就能从状态中取出 order_id 去调用查询接口。

这个状态可以是一个简单的字典或一个结构化的对象,随着对话进行被不断更新。

# 简化的对话状态示例
dialogue_state = {&nbsp; &nbsp;&nbsp;"user_id":&nbsp;"u123",&nbsp; &nbsp;&nbsp;"history": [...],&nbsp; &nbsp;&nbsp;"active_order": {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"order_id":&nbsp;"d456",&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"destination":&nbsp;"虹桥机场"&nbsp; &nbsp; },&nbsp; &nbsp;&nbsp;"user_preferences": {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"ride_type":&nbsp;"standard"&nbsp; &nbsp; }}

2. 上下文压缩与总结

LLM 的上下文窗口是有限的。随着对话变长,不可能把所有历史记录都传给模型。必须对上下文进行处理。

  • 滑动窗口:只保留最近的 N 轮对话。最简单,但可能丢失早期重要信息。

  • 对话总结:当对话达到一定长度时,用一次额外的 LLM 调用,将之前的对话内容“总结”成一段摘要,然后用这个摘要替代原始对话历史。

  • Prompt 示例"请将以下对话总结为一段要点,保留关键信息(如订单号、目的地、用户偏好等):\n\n[对话历史]"

  • 混合策略:保留最近几轮的完整对话 + 之前所有对话的摘要。

3. 长程依赖 (Long-term Dependencies)

有时,用户在对话早期提到的信息在很久之后才用到。对话总结可能会丢失这些细节。更高级的方案是建立一个记忆库 (Memory Store),通常使用向量数据库实现。

  • 写入记忆:在每一轮对话结束后,将关键信息(如“用户今天去了机场”、“用户偏爱安静的司机”)作为文档存入向量数据库。
  • 读取记忆:在处理新请求时,先将用户当前的输入和部分对话历史在记忆库中做一次向量检索,找出最相关的几条“记忆”,然后将这些记忆和 Prompt 一起喂给 LLM。

这让 Agent 拥有了长期记忆,能更好地理解用户的深层意图和个性化需求。


七、测试集如何构建和存储

测试集数据来源

来源一:人工构造(冷启动)

覆盖四类 case:

  • 正常 case — “叫辆车去虹桥机场”
  • 边界 case — “拼一个”、”便宜点”
  • 异常 case — 问天气、输入乱码
  • 多意图 case — “取消上一个,再叫一辆去浦东”

来源二:真实对话挖掘(上线后)

从真实日志里捞:用户差评的对话、tool 调用失败的对话、用户反复重复同一句话的对话。

来源三:模型自动生成(数据增强)

给我生成20条用户想叫车去机场的不同表达方式,包括口语化、方言味、隐含意图等变体

来源四:回归测试用例(上线后)

当线上发现一个 Bug 并通过优化 Prompt 修复后,应将这个出错的 Case 制作成一个永久的、必须通过的测试用例,加入“回归测试集”。每次更新 Prompt 后,除了跑通用测试集,还必须 100% 通过这个回归测试集,确保旧的问题不会复现。

什么是”边界 case”

边界 = 模型勉强答对侥幸没出错的情况。判断方法:

  • 看输出稳定性 — 同一条输入跑3次,结果不一致就是边界
  • 看置信度 — 模型低置信度的输出
  • 看 Agent 有没有追问 — Agent 选择追问说明它自己也不确定
  • 看输入模糊程度 — 少于5个字、没有明确地点、包含多个动词

小企业测试集工程化方案

Prompt 存储:

prompts/&nbsp; taxi_agent/&nbsp; &nbsp; system_prompt_v1.txt&nbsp; &nbsp; system_prompt_v2.txt&nbsp; &nbsp; changelog.md &nbsp; ← 记录每次改了什么、效果如何

数据库结构:

-- prompt 表CREATE TABLE&nbsp;prompts (&nbsp; id &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;INT&nbsp;PRIMARY KEY,&nbsp; name &nbsp; &nbsp; &nbsp;&nbsp;VARCHAR,&nbsp; version &nbsp; &nbsp;VARCHAR,&nbsp; content &nbsp; &nbsp;TEXT,&nbsp; is_active &nbsp;BOOLEAN,&nbsp; created_at&nbsp;TIMESTAMP);
-- 测试集表CREATE TABLE&nbsp;test_cases (&nbsp; id &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;INT&nbsp;PRIMARY KEY,&nbsp; input &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; TEXT,&nbsp; expected_intent &nbsp; &nbsp;&nbsp;VARCHAR,&nbsp; expected_arguments &nbsp;TEXT, &nbsp; &nbsp; &nbsp;-- 存储 JSON 字符串&nbsp; source &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;VARCHAR, &nbsp;&nbsp;-- manual / user_feedback / generated&nbsp; tags &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;VARCHAR, &nbsp;&nbsp;-- edge_case / normal / regression&nbsp; created_at &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;TIMESTAMP);
-- 评估结果表CREATE TABLE&nbsp;evaluation_results (&nbsp; id &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;INT&nbsp;PRIMARY KEY,&nbsp; prompt_name &nbsp;VARCHAR,&nbsp; version &nbsp; &nbsp; &nbsp;VARCHAR,&nbsp; score &nbsp; &nbsp; &nbsp; &nbsp;FLOAT,&nbsp; passed &nbsp; &nbsp; &nbsp;&nbsp;INT,&nbsp; total &nbsp; &nbsp; &nbsp; &nbsp;INT,&nbsp; run_at &nbsp; &nbsp; &nbsp;&nbsp;TIMESTAMP&nbsp;&nbsp;-- 建议增加一个字段存储详细的失败 case 列表&nbsp;&nbsp;-- failed_cases_details TEXT);

评估脚本:

一个完整的评估不仅要检查意图(Intent),还应该检查**参数(Arguments)**是否被正确提取。

import&nbsp;json
def&nbsp;run_evaluation(prompt_name:&nbsp;str, version:&nbsp;str):&nbsp; &nbsp; prompt = db.get_prompt(prompt_name, version)&nbsp; &nbsp; test_cases = db.get_test_cases()
&nbsp; &nbsp; results = []&nbsp; &nbsp;&nbsp;for&nbsp;case&nbsp;in&nbsp;test_cases:&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# agent.run 需要返回包含 intent 和 arguments 的结构化数据&nbsp; &nbsp; &nbsp; &nbsp; agent_output = agent.run(prompt,&nbsp;case.input)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 1. 检查意图是否正确&nbsp; &nbsp; &nbsp; &nbsp; intent_passed = (agent_output.get('intent') ==&nbsp;case.expected_intent)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 2. 检查参数是否正确&nbsp; &nbsp; &nbsp; &nbsp; expected_args = json.loads(case.expected_arguments)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 简单的字典比对很脆弱,实际场景需要更复杂的逻辑&nbsp; &nbsp; &nbsp; &nbsp; args_passed = advanced_args_comparison(agent_output.get('arguments', {}), expected_args)
&nbsp; &nbsp; &nbsp; &nbsp; passed = intent_passed&nbsp;and&nbsp;args_passed&nbsp; &nbsp; &nbsp; &nbsp; results.append(passed)
&nbsp; &nbsp; score =&nbsp;sum(results) /&nbsp;len(results)&nbsp; &nbsp;&nbsp;print(f"通过率:&nbsp;{score:.0%}&nbsp;({sum(results)}/{len(results)})")&nbsp; &nbsp; db.save_evaluation_result(prompt_name, version, score)
def&nbsp;advanced_args_comparison(actual_args, expected_args):&nbsp; &nbsp;&nbsp;# 这是一个示例,真实逻辑会更复杂&nbsp; &nbsp;&nbsp;if&nbsp;not&nbsp;isinstance(actual_args,&nbsp;dict)&nbsp;or&nbsp;not&nbsp;isinstance(expected_args,&nbsp;dict):&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;False
&nbsp; &nbsp;&nbsp;for&nbsp;key, expected_value&nbsp;in&nbsp;expected_args.items():&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;key&nbsp;not&nbsp;in&nbsp;actual_args:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;False&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 此处可以加入更丰富的比较逻辑&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 例如,地理位置比较、数值范围比较等&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;actual_args[key] != expected_value:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;False
&nbsp; &nbsp;&nbsp;return&nbsp;True

评估逻辑的深化:“模糊”验证

上面脚本中的 args_passed = (agent_output.get('arguments') == expected_args) 方式过于严格,在真实场景中很脆弱。例如,用户说“去陆家嘴”,模型抽取的目的地可能是“陆家嘴”,也可能是“陆家嘴地铁站”,简单的字符串比对会判定为失败。

更鲁棒的评估脚本应该包含“模糊”验证逻辑:

  • 关键参数检查:只检查 expected_arguments 中定义的关键参数是否存在且符合基本格式,忽略模型可能附带抽取的其他次要参数。
  • 语义等价性检查:对于地名等实体,可以接入 GEO-API,将预期地址和实际地址都转换为经纬度,只要两者在几百米范围内,就视为正确。
  • 数值范围检查:对于价格、时间等参数,可以验证其是否落在一个可接受的区间内,而不是强求精确匹配。

效果可视化:

v1.0&nbsp; → &nbsp;72%v1.1&nbsp; → &nbsp;78% &nbsp; ← 加了Few-shot之后v2.0&nbsp; → &nbsp;91% &nbsp; ← 重写了边界处理

小企业实践路径

我帮你生成冷启动数据(50条)&nbsp; &nbsp; → 上线后用👍👎收集真实反馈&nbsp; &nbsp; &nbsp; &nbsp; → 人工审核入库&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; → 模型做数据增强扩充

工具选型:

团队 < 5人 &nbsp; &nbsp;→ Git + SQLite + 评估脚本 &nbsp; (零成本)团队 5-20人 &nbsp; → Git + PostgreSQL + 评估脚本有预算 &nbsp; &nbsp; &nbsp; &nbsp;→ LangSmith

八、相关工具汇总

核心开发框架

手写完整的 ReAct 循环逻辑较为繁琐,使用现有框架能极大提升开发效率。

| 框架 | 特点 | 适合场景 | | — | — | — | | LangChain | 功能最全面,生态成熟,文档和社区庞大 | 快速原型验证,适合需要胶水代码粘合多种工具的复杂场景 | | LlamaIndex | 专注于 RAG,在数据索引和检索方面能力强大 | 数据密集型、以知识库问答为核心的 Agent | | CrewAI | 专注于多 Agent 协作,通过角色、任务、流程定义协同工作 | 需要多个 Agent 分工合作完成复杂任务的场景 | | AutoGen | 微软出品,以可定制的多 Agent 对话为核心 | 模拟复杂工作流,进行自动化研究和代码生成 |

Prompt 评估与管理

| 工具 | 特点 | 适合场景 | | — | — | — | | LangSmith | 功能最全,可视化好,付费 | 有一定规模的团队 | | PromptFoo | 开源免费,本地跑 | 小团队、早期项目 | | Braintrust | 轻量,专注测试集管理 | 中小团队 | | DSPy | 自动优化 prompt,需标注数据 | 有 200 条以上标注数据时 |

需求文档与规划

| 工具 | 用途 | | — | — | | Notion AI | 根据描述自动生成结构化文档 | | Miro / FigJam | 画用户旅程图和对话流 | | Linear | 把需求拆解成开发任务 |


九、核心原则总结

  1. 先把 Prompt 做好,再考虑微调和 RAG
  2. 换模型前必须有测试集,用数字说话
  3. 测试集不是全量收集,只收集出错的和边界的
  4. 每次改 Prompt 都要跑回归测试集,确保基本盘不崩
  5. 早期不要追求工具,先把数据和评估流建立起来
  6. 出错时先看中间推理过程,不只看最终输出
  7. 管理好多轮对话的上下文,这是 Agent 从“玩具”到“工具”的关键构建一个稳定、可靠的 Agent,其核心挑战并非来自代码,而是源于设计的缺失和思考的混乱。

这篇笔记,正是我在一线实践中摸爬滚打后,为自己总结的一套系统性思考框架。它将带你暂别代码,回归设计的本源,一步步理清思路:

  • 从 定义能力边界 开始,而不是直接看 API;
  • 学习 设计有效的 Prompt,而不只是简单地“下指令”;
  • 掌握 管理多轮对话 的技巧,让 Agent 不再“健忘”;
  • 学会 构建自动化测试,用工程化的方法确保 Agent 的可靠性。

希望这份笔记,能帮你搭建起设计 Agent 的思维骨架,在探索的道路上少走一些弯路。


一、从零开始做一个 Agent

第一步:定义能力清单

做 Agent 的第一步不是写代码,而是想清楚 Agent 的边界和能力。把用户所有可能的需求列出来,判断哪些由 Agent 处理,以打车需求举例:

| 能力 | 说明 | | — | — | | 叫车 | 告诉 Agent “我要去机场”,它帮你下单 | | 查询订单状态 | “司机到哪了?” | | 取消订单 | 带条件判断(是否收违约金) | | 推荐目的地 | 基于历史记录或语境猜你要去哪 | | 价格估算 | “去南京西路大概多少钱?” | | 投诉/反馈 | 引导用户完成投诉流程 |

这步的产出是一份工具列表(Tools),每个能力对应一个 tool。

第二步:定义每个 Tool 的接口

每个 tool 就是一个函数,Agent 决定什么时候调它、传什么参数。接口定义要尽可能精确。

from&nbsp;typing&nbsp;import&nbsp;TypedDict
# 使用 TypedDict 或 Pydantic 模型替代泛泛的 dict
# 这能让返回的数据结构更清晰,便于静态检查和代码维护
class&nbsp;RideOrder(TypedDict):&nbsp; &nbsp; order_id:&nbsp;str&nbsp; &nbsp; estimated_wait_time_minutes:&nbsp;int
def&nbsp;book_ride(origin:&nbsp;str, destination:&nbsp;str, ride_type:&nbsp;str&nbsp;=&nbsp;"standard") -> RideOrder:&nbsp; &nbsp;&nbsp;"""叫一辆车,返回订单ID和预计等待时间"""&nbsp; &nbsp; ...
def&nbsp;get_order_status(order_id:&nbsp;str) ->&nbsp;dict:&nbsp; &nbsp;&nbsp;"""查询订单实时状态,包括司机位置"""&nbsp; &nbsp; ...

第三步:写 System Prompt

告诉 Agent:它是谁、它的边界在哪、出错时怎么办、语气风格。

第四步:跑最小可用版本

跑通最简单的一条链路:

用户说”我要去虹桥机场” → Agent 调 book_ride → 返回”已为您叫车,预计3分钟到达”


二、一个好的 Prompt 需要具备哪些条件

角色定义 &nbsp; &nbsp; &nbsp;→ 你是谁能力边界 &nbsp; &nbsp; &nbsp;→ 你能做什么边界说明 &nbsp; &nbsp; &nbsp;→ 模糊时怎么办Few-shot &nbsp; &nbsp; → 举例让模型学输出格式 &nbsp; &nbsp; &nbsp;→ 返回什么结构异常兜底 &nbsp; &nbsp; &nbsp;→ 出错了怎么办

1. 角色定义

告诉模型它是谁、职责范围是什么:

你是一个打车助手,负责帮乘客完成叫车、查单、取消订单等操作。你只处理与打车相关的请求。

2. 能力边界

明确告诉模型什么能做、什么不能做:

你可以:叫车、查订单状态、取消订单、估算费用你不可以:回答与打车无关的任何问题

注意: 边界不是越窄越好。用户说”我要去接我妈妈从医院回家”,Agent 应该能理解这是叫车需求,要给模型一定的意图理解弹性

3. 边界说明

模糊场景提前定义处理方式:

如果用户没提供目的地 → 追问目的地如果一句话有多个意图 → 逐一确认再执行如果意图不明确 → 先复述你的理解,再执行

4. Few-shot 示例

Few-shot(少量示例)就是在 prompt 里直接给模型几个输入→输出的例子

用户:"叫辆车去虹桥"&nbsp; &nbsp;→ 意图:book_ride,目的地:虹桥机场用户:"拼一个"&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;→ 意图:book_ride,车型:拼车用户:"司机到哪了"&nbsp; &nbsp; &nbsp;→ 意图:query_status用户:"算了取消吧"&nbsp; &nbsp; &nbsp;→ 意图:cancel_ride用户:"今天天气怎样"&nbsp; &nbsp;→ 与打车无关,礼貌拒绝

本质上就是用例子代替复杂的文字说明。

5. 输出格式

固定返回格式,防止代码解析出错:

始终返回&nbsp;JSON&nbsp;格式:{"intent":&nbsp;"book_ride",&nbsp;"destination":&nbsp;"虹桥机场",&nbsp;"ride_type":&nbsp;"standard"}

6. 异常兜底

如果完全无法理解用户意图,回复:"抱歉我没理解您的意思,请问您需要叫车吗?"

三、意图理解能力如何提升

优先顺序

Prompt&nbsp;优化(Few-shot + 意图确认)&nbsp; &nbsp; ↓ 还不够?收集真实用户对话,分析哪类意图经常出错&nbsp; &nbsp; ↓ 找到规律?针对性加 Few-shot 或调整 prompt 逻辑&nbsp; &nbsp; ↓ 还有系统性偏差?再考虑微调

80% 的意图理解问题在 Prompt 阶段就能解决。

RAG 和微调分别解决什么问题

  • RAG 解决的是”知识”问题,不是”理解”问题。适合动态知识(计价规则、优惠活动等)。
  • 微调 解决的是”风格/领域适配”问题。需要几千条高质量标注数据,门槛很高,不是首选。

RAG 的高级应用:动态 Few-shot

除了用 RAG 查询外部知识库,它还可以用来优化意图理解。当用户的输入很模糊时,可以用 RAG 动态构建 Few-shot 示例,这比在 Prompt 中写死几个例子更灵活。

流程如下:

  1. 建立“疑难样本”数据库:将历史上处理过的、有代表性的“模糊输入 → 清晰意图”的案例存入向量数据库。
  2. 实时检索:当 Agent 收到新的用户请求时,先将其作为查询,从数据库中检索出最相似的 1-3 个历史案例。
  3. 动态拼接 Prompt:将检索到的案例动态地插入到 Prompt 的 Few-shot 部分。
  4. LLM 决策:LLM 参考这些最相关的例子,做出更准确的判断。

这种方法能让 Agent 拥有“举一反三”的能力,对未见过的边界 Case 鲁棒性更强。


四、Prompt 出问题如何分析

定位问题层级

用户输入&nbsp; &nbsp; ↓意图理解有没有出错? &nbsp; &nbsp;← 第一层&nbsp; &nbsp; ↓tool 选择对不对? &nbsp; &nbsp; &nbsp; ← 第二层&nbsp; &nbsp; ↓tool 参数传对了吗? &nbsp; &nbsp; ← 第三层&nbsp; &nbsp; ↓最终回复合不合理? &nbsp; &nbsp; &nbsp;← 第四层

分析方法

1. 看中间推理过程

让模型输出思考链,看到模型”在想什么”:

SYSTEM_PROMPT&nbsp;=&nbsp;"""...原始prompt...
在回答之前,先用<thinking>标签写出你的推理过程:- 用户意图是什么- 应该调用哪个tool- 参数是什么</thinking>"""

2. 做消融测试

把 prompt 拆开,逐段注释掉,看哪段影响最大:

完整prompt &nbsp; &nbsp; &nbsp; &nbsp;→ 通过率 72%去掉Few-shot &nbsp; &nbsp; &nbsp;→ 通过率 51% &nbsp; ← 这段影响最大去掉边界说明 &nbsp; &nbsp; &nbsp;→ 通过率 68%

3. 对比不同模型

同一批测试集跑多个模型,如果换个模型就好了,说明问题在 prompt 和模型的适配上。

分析流程

发现问题&nbsp; &nbsp; ↓看中间推理过程 → 定位断在哪一层&nbsp; &nbsp; ↓做消融测试 → 找到影响最大的片段&nbsp; &nbsp; ↓针对性修改&nbsp; &nbsp; ↓跑测试集验证&nbsp; &nbsp; ↓确认提升,记录版本

五、更换后端模型的注意事项

换模型可能带来的问题:

  • 指令遵循能力不一样 — 原来调好的 prompt 可能直接失效
  • 输出格式不稳定 — JSON 格式可能夹杂多余文字导致解析出错
  • 推理能力差异 — 多意图、隐含意图处理能力差距大
  • 上下文处理不同 — 多轮对话里”忘事”情况变多

应对策略

做一层抽象,把模型调用解耦:

class&nbsp;LLMClient:&nbsp; &nbsp;&nbsp;def&nbsp;chat(self, messages): ...
class&nbsp;OpenAIClient(LLMClient): ...class&nbsp;ClaudeClient(LLMClient): ...class&nbsp;QwenClient(LLMClient): ...

灰度切换: 先用新模型跑 10% 流量,对比效果再逐步放量。

核心:换模型前先跑测试集,用数字说话。


六、多轮对话如何管理

单轮的“意图识别 → 工具调用”是 Agent 的基础,但真正实用的 Agent 必须能处理连续的多轮对话。核心挑战在于上下文管理

1. 状态跟踪 (State Tracking)

Agent 需要在对话中持续维护一个“状态”,用来记住关键信息和用户偏好。

例如,用户说“帮我叫辆车去机场”,Agent 下单后,需要将 order_id 存入当前对话的状态中。当用户接着问“司机到哪了?”,Agent 就能从状态中取出 order_id 去调用查询接口。

这个状态可以是一个简单的字典或一个结构化的对象,随着对话进行被不断更新。

# 简化的对话状态示例
dialogue_state = {&nbsp; &nbsp;&nbsp;"user_id":&nbsp;"u123",&nbsp; &nbsp;&nbsp;"history": [...],&nbsp; &nbsp;&nbsp;"active_order": {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"order_id":&nbsp;"d456",&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"destination":&nbsp;"虹桥机场"&nbsp; &nbsp; },&nbsp; &nbsp;&nbsp;"user_preferences": {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"ride_type":&nbsp;"standard"&nbsp; &nbsp; }}

2. 上下文压缩与总结

LLM 的上下文窗口是有限的。随着对话变长,不可能把所有历史记录都传给模型。必须对上下文进行处理。

  • 滑动窗口:只保留最近的 N 轮对话。最简单,但可能丢失早期重要信息。

  • 对话总结:当对话达到一定长度时,用一次额外的 LLM 调用,将之前的对话内容“总结”成一段摘要,然后用这个摘要替代原始对话历史。

  • Prompt 示例"请将以下对话总结为一段要点,保留关键信息(如订单号、目的地、用户偏好等):\n\n[对话历史]"

  • 混合策略:保留最近几轮的完整对话 + 之前所有对话的摘要。

3. 长程依赖 (Long-term Dependencies)

有时,用户在对话早期提到的信息在很久之后才用到。对话总结可能会丢失这些细节。更高级的方案是建立一个记忆库 (Memory Store),通常使用向量数据库实现。

  • 写入记忆:在每一轮对话结束后,将关键信息(如“用户今天去了机场”、“用户偏爱安静的司机”)作为文档存入向量数据库。
  • 读取记忆:在处理新请求时,先将用户当前的输入和部分对话历史在记忆库中做一次向量检索,找出最相关的几条“记忆”,然后将这些记忆和 Prompt 一起喂给 LLM。

这让 Agent 拥有了长期记忆,能更好地理解用户的深层意图和个性化需求。


七、测试集如何构建和存储

测试集数据来源

来源一:人工构造(冷启动)

覆盖四类 case:

  • 正常 case — “叫辆车去虹桥机场”
  • 边界 case — “拼一个”、”便宜点”
  • 异常 case — 问天气、输入乱码
  • 多意图 case — “取消上一个,再叫一辆去浦东”

来源二:真实对话挖掘(上线后)

从真实日志里捞:用户差评的对话、tool 调用失败的对话、用户反复重复同一句话的对话。

来源三:模型自动生成(数据增强)

给我生成20条用户想叫车去机场的不同表达方式,包括口语化、方言味、隐含意图等变体

来源四:回归测试用例(上线后)

当线上发现一个 Bug 并通过优化 Prompt 修复后,应将这个出错的 Case 制作成一个永久的、必须通过的测试用例,加入“回归测试集”。每次更新 Prompt 后,除了跑通用测试集,还必须 100% 通过这个回归测试集,确保旧的问题不会复现。

什么是”边界 case”

边界 = 模型勉强答对侥幸没出错的情况。判断方法:

  • 看输出稳定性 — 同一条输入跑3次,结果不一致就是边界
  • 看置信度 — 模型低置信度的输出
  • 看 Agent 有没有追问 — Agent 选择追问说明它自己也不确定
  • 看输入模糊程度 — 少于5个字、没有明确地点、包含多个动词

小企业测试集工程化方案

Prompt 存储:

prompts/&nbsp; taxi_agent/&nbsp; &nbsp; system_prompt_v1.txt&nbsp; &nbsp; system_prompt_v2.txt&nbsp; &nbsp; changelog.md &nbsp; ← 记录每次改了什么、效果如何

数据库结构:

-- prompt 表CREATE TABLE&nbsp;prompts (&nbsp; id &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;INT&nbsp;PRIMARY KEY,&nbsp; name &nbsp; &nbsp; &nbsp;&nbsp;VARCHAR,&nbsp; version &nbsp; &nbsp;VARCHAR,&nbsp; content &nbsp; &nbsp;TEXT,&nbsp; is_active &nbsp;BOOLEAN,&nbsp; created_at&nbsp;TIMESTAMP);
-- 测试集表CREATE TABLE&nbsp;test_cases (&nbsp; id &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;INT&nbsp;PRIMARY KEY,&nbsp; input &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; TEXT,&nbsp; expected_intent &nbsp; &nbsp;&nbsp;VARCHAR,&nbsp; expected_arguments &nbsp;TEXT, &nbsp; &nbsp; &nbsp;-- 存储 JSON 字符串&nbsp; source &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;VARCHAR, &nbsp;&nbsp;-- manual / user_feedback / generated&nbsp; tags &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;VARCHAR, &nbsp;&nbsp;-- edge_case / normal / regression&nbsp; created_at &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;TIMESTAMP);
-- 评估结果表CREATE TABLE&nbsp;evaluation_results (&nbsp; id &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;INT&nbsp;PRIMARY KEY,&nbsp; prompt_name &nbsp;VARCHAR,&nbsp; version &nbsp; &nbsp; &nbsp;VARCHAR,&nbsp; score &nbsp; &nbsp; &nbsp; &nbsp;FLOAT,&nbsp; passed &nbsp; &nbsp; &nbsp;&nbsp;INT,&nbsp; total &nbsp; &nbsp; &nbsp; &nbsp;INT,&nbsp; run_at &nbsp; &nbsp; &nbsp;&nbsp;TIMESTAMP&nbsp;&nbsp;-- 建议增加一个字段存储详细的失败 case 列表&nbsp;&nbsp;-- failed_cases_details TEXT);

评估脚本:

一个完整的评估不仅要检查意图(Intent),还应该检查**参数(Arguments)**是否被正确提取。

import&nbsp;json
def&nbsp;run_evaluation(prompt_name:&nbsp;str, version:&nbsp;str):&nbsp; &nbsp; prompt = db.get_prompt(prompt_name, version)&nbsp; &nbsp; test_cases = db.get_test_cases()
&nbsp; &nbsp; results = []&nbsp; &nbsp;&nbsp;for&nbsp;case&nbsp;in&nbsp;test_cases:&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# agent.run 需要返回包含 intent 和 arguments 的结构化数据&nbsp; &nbsp; &nbsp; &nbsp; agent_output = agent.run(prompt,&nbsp;case.input)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 1. 检查意图是否正确&nbsp; &nbsp; &nbsp; &nbsp; intent_passed = (agent_output.get('intent') ==&nbsp;case.expected_intent)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 2. 检查参数是否正确&nbsp; &nbsp; &nbsp; &nbsp; expected_args = json.loads(case.expected_arguments)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 简单的字典比对很脆弱,实际场景需要更复杂的逻辑&nbsp; &nbsp; &nbsp; &nbsp; args_passed = advanced_args_comparison(agent_output.get('arguments', {}), expected_args)
&nbsp; &nbsp; &nbsp; &nbsp; passed = intent_passed&nbsp;and&nbsp;args_passed&nbsp; &nbsp; &nbsp; &nbsp; results.append(passed)
&nbsp; &nbsp; score =&nbsp;sum(results) /&nbsp;len(results)&nbsp; &nbsp;&nbsp;print(f"通过率:&nbsp;{score:.0%}&nbsp;({sum(results)}/{len(results)})")&nbsp; &nbsp; db.save_evaluation_result(prompt_name, version, score)
def&nbsp;advanced_args_comparison(actual_args, expected_args):&nbsp; &nbsp;&nbsp;# 这是一个示例,真实逻辑会更复杂&nbsp; &nbsp;&nbsp;if&nbsp;not&nbsp;isinstance(actual_args,&nbsp;dict)&nbsp;or&nbsp;not&nbsp;isinstance(expected_args,&nbsp;dict):&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;False
&nbsp; &nbsp;&nbsp;for&nbsp;key, expected_value&nbsp;in&nbsp;expected_args.items():&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;key&nbsp;not&nbsp;in&nbsp;actual_args:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;False&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 此处可以加入更丰富的比较逻辑&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 例如,地理位置比较、数值范围比较等&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;actual_args[key] != expected_value:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;False
&nbsp; &nbsp;&nbsp;return&nbsp;True

评估逻辑的深化:“模糊”验证

上面脚本中的 args_passed = (agent_output.get('arguments') == expected_args) 方式过于严格,在真实场景中很脆弱。例如,用户说“去陆家嘴”,模型抽取的目的地可能是“陆家嘴”,也可能是“陆家嘴地铁站”,简单的字符串比对会判定为失败。

更鲁棒的评估脚本应该包含“模糊”验证逻辑:

  • 关键参数检查:只检查 expected_arguments 中定义的关键参数是否存在且符合基本格式,忽略模型可能附带抽取的其他次要参数。
  • 语义等价性检查:对于地名等实体,可以接入 GEO-API,将预期地址和实际地址都转换为经纬度,只要两者在几百米范围内,就视为正确。
  • 数值范围检查:对于价格、时间等参数,可以验证其是否落在一个可接受的区间内,而不是强求精确匹配。

效果可视化:

v1.0&nbsp; → &nbsp;72%v1.1&nbsp; → &nbsp;78% &nbsp; ← 加了Few-shot之后v2.0&nbsp; → &nbsp;91% &nbsp; ← 重写了边界处理

小企业实践路径

我帮你生成冷启动数据(50条)&nbsp; &nbsp; → 上线后用👍👎收集真实反馈&nbsp; &nbsp; &nbsp; &nbsp; → 人工审核入库&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; → 模型做数据增强扩充

工具选型:

ounter(lineounter(lineounter(lineounter(line团队 <&nbsp;5人 &nbsp; &nbsp;→ Git + SQLite + 评估脚本 &nbsp; (零成本)团队&nbsp;5-20人 &nbsp; → Git + PostgreSQL + 评估脚本有预算 &nbsp; &nbsp; &nbsp; &nbsp;→ LangSmith

八、相关工具汇总

核心开发框架

手写完整的 ReAct 循环逻辑较为繁琐,使用现有框架能极大提升开发效率。

| 框架 | 特点 | 适合场景 | | — | — | — | | LangChain | 功能最全面,生态成熟,文档和社区庞大 | 快速原型验证,适合需要胶水代码粘合多种工具的复杂场景 | | LlamaIndex | 专注于 RAG,在数据索引和检索方面能力强大 | 数据密集型、以知识库问答为核心的 Agent | | CrewAI | 专注于多 Agent 协作,通过角色、任务、流程定义协同工作 | 需要多个 Agent 分工合作完成复杂任务的场景 | | AutoGen | 微软出品,以可定制的多 Agent 对话为核心 | 模拟复杂工作流,进行自动化研究和代码生成 |

Prompt 评估与管理

| 工具 | 特点 | 适合场景 | | — | — | — | | LangSmith | 功能最全,可视化好,付费 | 有一定规模的团队 | | PromptFoo | 开源免费,本地跑 | 小团队、早期项目 | | Braintrust | 轻量,专注测试集管理 | 中小团队 | | DSPy | 自动优化 prompt,需标注数据 | 有 200 条以上标注数据时 |

需求文档与规划

| 工具 | 用途 | | — | — | | Notion AI | 根据描述自动生成结构化文档 | | Miro / FigJam | 画用户旅程图和对话流 | | Linear | 把需求拆解成开发任务 |


九、核心原则总结

  1. 先把 Prompt 做好,再考虑微调和 RAG
  2. 换模型前必须有测试集,用数字说话
  3. 测试集不是全量收集,只收集出错的和边界的
  4. 每次改 Prompt 都要跑回归测试集,确保基本盘不崩
  5. 早期不要追求工具,先把数据和评估流建立起来
  6. 出错时先看中间推理过程,不只看最终输出
  7. 管理好多轮对话的上下文,这是 Agent 从“玩具”到“工具”的关键

免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:逆向OneByOne 《想开发自己的 Agent 却毫无头绪?这篇文章给你一张清晰的思考地图》

评论:0   参与:  0