1. 一个前端Leader的转型焦虑与破局思路
做了八年多前端,带过十几人的团队,从 jQuery 时代一路写到 React Server Components,我原以为自己会一直在这条路上走到黑。直到去年年底,团队开始接一些智能化需求,产品经理张口闭口就是“大模型”“Agent”“RAG”,我才意识到,如果继续只守着组件、状态管理和构建工具这一亩三分地,迟早会被时代甩下车。于是从今年年初开始,我给自己定了一个硬性计划:每天至少投入两小时,系统学习 AI Agent 相关的知识,并且把学习过程记录下来,这就是“DAY73”这个系列的由来。
先说说这个系列到底在写什么。它不是那种从零开始的 AI 入门教程,也不是纯理论的大模型原理讲解,而是一个有工程背景、有团队管理经验的前端 Leader,在边工作边转型过程中,把每天踩过的坑、跑通的代码、想明白的架构问题,原原本本记录下来。核心关键词就三个:前端转型、AI Agent、工程化落地。适合谁来读?如果你也是前端、后端或者全栈开发者,正在观望要不要往 AI 方向靠,或者已经开始学但总觉得“学了一堆概念却做不出东西”,那这个系列应该能帮到你。我不会跟你扯太多 Transformer 的数学推导,而是把重点放在“一个 Agent 系统到底怎么搭起来、怎么调通、怎么让它稳定干活”这些真正卡人的地方。
DAY73 这个节点,我已经过了最初那种“调个 API 就兴奋半天”的阶段,开始进入比较系统的工程化实践。今天这篇,我想把这段时间积累的整体思路、核心细节、实操过程和踩坑经验,做一次比较完整的梳理。不是流水账,而是把那些真正有价值的东西提炼出来,让你少走弯路。
2. 整体设计与思路拆解:为什么我不建议一上来就啃框架
2.1 从“会用 API”到“会设计 Agent”的分水岭
刚开始学的时候,我和大多数人一样,找了个大模型的 API,写了个函数,传个 prompt,拿到返回,就觉得“AI 不过如此”。但真正开始做一个能自主完成任务的 Agent 时,问题就来了:它要怎么记住上下文?怎么决定下一步做什么?怎么调用外部工具?怎么在出错时自己纠正?这些问题,光靠一个 API 调用是解决不了的。
我踩过的第一个大坑,就是过早地扎进某个 Agent 框架里。当时看网上都在推 LangChain、AutoGPT 之类的,我也跟着装了一套,结果发现文档看得云里雾里,一个简单的功能要套好几层抽象,出了问题根本不知道是哪一层挂了。后来我强迫自己退回来,先用最原始的方式——直接调 API、手写循环、手动管理状态——把 Agent 的核心逻辑跑通一遍。这个过程虽然笨,但让我彻底搞明白了 Agent 到底是怎么回事。
我的建议是:先手写,再用框架。手写一遍,你会清楚地知道 Agent 的本质就是一个“感知-决策-行动”的循环,框架只是帮你把这个循环封装得更优雅而已。等你手写跑通了,再去看框架的源码,会有一种“原来如此”的通透感。
2.2 前端背景在 Agent 开发中的真实优势与短板
很多人觉得前端转 AI 是跨行,劣势很大。我一开始也这么想,但做了两个多月之后,我发现前端背景其实有几个被低估的优势。
第一个优势是对异步和状态管理的敏感度。Agent 的执行过程本质上就是一连串异步操作,中间还夹杂着状态流转。前端天天跟 Promise、async/await、状态机打交道,处理这些简直是肌肉记忆。我见过一些后端背景的朋友,写 Agent 循环时经常在并发和状态同步上翻车,而前端出身的人反而更容易理清。
第二个优势是对用户体验的直觉。Agent 最终是要给人用的,流式输出怎么展示、工具调用过程怎么反馈、出错时怎么提示,这些都是前端的老本行。一个功能再强的 Agent,如果交互做得稀烂,用户也不会买账。
但短板也很明显:对模型本身的理解不够深。前端很少接触向量、嵌入、概率分布这些东西,刚开始看 embedding、temperature、top_p 这些概念时,确实有点吃力。我的应对方式是,不追求把数学原理搞透,而是先搞清楚每个参数“调大调小分别会有什么效果”,用实验的方式去建立直觉。比如 temperature,我就固定一个 prompt,分别用 0.1、0.5、1.0 跑十次,看输出的差异,很快就明白了。
2.3 我的技术选型逻辑:够用就好,别追新
在选型上,我给自己定了一条原则:能用简单的方案解决,就绝不引入复杂的依赖。具体来说:
- 模型调用:直接用官方 SDK,不套中间层。中间层虽然方便切换模型,但也会屏蔽掉很多细节,出问题时不好排查。
- 状态管理:Agent 的对话历史和中间状态,我用一个简单的 JSON 结构存,配合文件持久化,够用了。没必要上数据库。
- 工具调用:自己写一个工具注册表,每个工具就是一个函数加一段描述,模型根据描述决定调哪个。简单直接。
- 向量检索:需要做知识库时,我用最基础的嵌入加余弦相似度,先跑通再说。等数据量大了再考虑专业的向量库。
这套选型看起来“土”,但胜在可控。每一个环节我都能说清楚它在干什么,出了问题也能快速定位。等业务真正复杂到简单方案扛不住了,再升级也不迟。
3. 核心细节解析与实操要点:Agent 到底由哪些零件组成
3.1 一个最小可用 Agent 的四个核心模块
把 Agent 拆开来看,不管多复杂的系统,核心都离不开这四个模块:
第一,模型接口层。负责和模型通信,处理请求的组装和响应的解析。这一层要处理的关键问题包括:怎么设置系统提示词、怎么控制输出格式、怎么处理流式返回、怎么应对超时和限流。
第二,记忆模块。负责管理对话历史和长期知识。短期记忆就是最近几轮对话,长期记忆则是通过向量检索从知识库里召回的相关内容。这里最容易踩的坑是上下文长度管理——对话轮数一多,token 就爆了,必须有一套裁剪或摘要机制。
第三,工具系统。这是 Agent 区别于普通聊天机器人的关键。工具就是一组可以被模型调用的函数,每个函数有明确的名称、描述和参数定义。模型根据当前任务,决定调用哪个工具、传什么参数。
第四,执行循环。这是 Agent 的“心脏”,负责把上面三个模块串起来:接收用户输入,组装上下文,调用模型,解析模型的决策,执行工具,把结果喂回模型,如此循环,直到任务完成或达到终止条件。
这四个模块,我建议你一开始就分开写,每个模块一个文件,接口定义清楚。这样后面调试和替换都会方便很多。
3.2 提示词工程:不是玄学,是结构化的沟通
很多人把提示词工程说得神乎其神,其实在我看来,它就是用结构化的方式把任务说清楚。我总结了一个自己一直在用的提示词模板,分四个部分:
角色定义:你是一个 XXX 助手,擅长 XXX。 任务描述:用户会给你 XXX,你需要完成 XXX。 可用工具:你可以使用以下工具:XXX。 输出要求:请以 XXX 格式输出,注意 XXX。这个模板看起来简单,但每一条都有讲究。角色定义决定了模型的“人设”和知识范围;任务描述要具体到可执行;工具说明要让模型知道每个工具能干什么、什么时候用;输出要求则约束了返回格式,方便后续程序解析。
我踩过的一个坑是:工具描述写得太模糊。比如我写了一个“查询信息”的工具,描述就写了“查询相关信息”,结果模型经常在该用的时候不用,不该用的时候乱用。后来我把描述改成“根据用户提供的订单号,查询该订单的详细状态,包括发货时间、物流信息、当前所在位置”,模型的使用准确率立刻上来了。工具描述要写到“一个完全不了解你系统的人看了也知道什么时候该用”的程度。
3.3 上下文管理:token 是稀缺资源,要省着用
上下文窗口是 Agent 最宝贵的资源。我见过太多人把整个对话历史一股脑塞进去,结果没几轮就爆了。我的做法是分层管理:
- 系统提示词:固定不变,永远保留。
- 最近 N 轮对话:完整保留,保证短期连贯性。
- 更早的对话:用模型做一次摘要,压缩成一段话保留。
- 知识库召回内容:按相关度排序,只取 top K 条,且每条做长度截断。
这里有个经验值:如果模型上下文是 8K token,我一般会把系统提示词控制在 500 以内,最近对话控制在 2000 以内,摘要控制在 500 以内,剩下的留给知识库召回和模型输出。这个分配不是死的,要根据实际任务调整,但核心思路是给每一部分都设一个预算,不能让它无限膨胀。
注意:做摘要的时候,不要让模型自由发挥,要给它明确的指令,比如“请用不超过 200 字总结以下对话的关键信息和结论,保留所有数字和专有名词”。否则摘要会越来越模糊,最后丢失关键信息。
3.4 工具调用的参数校验:别信模型,要信代码
模型返回的工具调用参数,永远不要直接信任。我吃过这个亏:让模型生成一个日期参数,它有时候返回“2024-01-01”,有时候返回“2024年1月1日”,有时候甚至返回“明天”。如果不做校验,后面的代码直接崩。
我的做法是在每个工具函数入口处做严格的参数校验:
def query_order(order_id: str, date: str): # 校验 order_id 格式 if not re.match(r'^[A-Z0-9]{10}$', order_id): return {"error": "订单号格式不正确,应为10位大写字母或数字"} # 校验日期格式 try: datetime.strptime(date, '%Y-%m-%d') except ValueError: return {"error": "日期格式不正确,应为 YYYY-MM-DD"} # 通过校验后再执行实际逻辑 ...校验失败时,不要直接抛异常,而是返回一个结构化的错误信息,让模型知道哪里错了,它下一轮就会自己纠正。这个机制我称之为“温和纠错”,比直接报错让整个流程中断要优雅得多。
4. 实操过程与核心环节实现:从零搭一个能查资料的 Agent
4.1 环境准备与依赖安装
这一节我以 Python 为例,因为 AI 生态里 Python 的库最全。前端朋友别慌,语法看看就懂,逻辑才是关键。
首先建一个干净的虚拟环境,这是好习惯,避免依赖冲突:
python -m venv agent-env source agent-env/bin/activate # Windows 用 agent-env\Scripts\activate然后安装核心依赖。我尽量精简,只装必要的:
pip install openai requests numpyopenai是模型调用的 SDK,requests用来发 HTTP 请求(比如调外部搜索接口),numpy用来做向量计算。就这三个,够跑一个基础 Agent 了。
提示:不要一上来就装一堆框架。依赖越少,出问题时排查范围越小。等你的 Agent 真的需要复杂功能了,再按需引入。
4.2 模型接口封装:把通信层做扎实
模型接口层是整个系统的地基,我把它单独写成一个类,方便统一管理:
import openai import json class LLMClient: def __init__(self, api_key, model="gpt-4"): self.client = openai.OpenAI(api_key=api_key) self.model = model def chat(self, messages, tools=None, temperature=0.7): params = { "model": self.model, "messages": messages, "temperature": temperature, } if tools: params["tools"] = tools params["tool_choice"] = "auto" try: response = self.client.chat.completions.create(**params) return response.choices[0].message except Exception as e: return {"error": str(e)}这个封装做了三件事:统一了调用入口、支持工具调用、捕获了异常。异常捕获很重要,网络请求随时可能失败,不能让一个超时把整个 Agent 搞崩。
关于 temperature 的设置,我的经验是:需要精确执行任务的场景用 0.1-0.3,需要创意生成的场景用 0.7-1.0。Agent 大多数时候是在执行任务,所以我默认用 0.3,保证输出稳定。
4.3 工具注册与执行:让模型知道它能干什么
工具系统的设计,我采用“注册表”模式。每个工具是一个字典,包含名称、描述、参数定义和执行函数:
class ToolRegistry: def __init__(self): self.tools = {} def register(self, name, description, parameters, func): self.tools[name] = { "definition": { "type": "function", "function": { "name": name, "description": description, "parameters": parameters } }, "func": func } def get_definitions(self): return [t["definition"] for t in self.tools.values()] def execute(self, name, arguments): if name not in self.tools: return {"error": f"工具 {name} 不存在"} try: args = json.loads(arguments) return self.tools[name]["func"](**args) except Exception as e: return {"error": f"工具执行失败:{str(e)}"}用的时候这样注册:
registry = ToolRegistry() def search_web(query: str): # 实际搜索逻辑,这里用伪代码 results = do_search(query) return {"results": results} registry.register( name="search_web", description="根据关键词搜索网络信息,返回相关结果列表。当需要获取最新信息或不确定的事实时使用。", parameters={ "type": "object", "properties": { "query": { "type": "string", "description": "搜索关键词,尽量具体" } }, "required": ["query"] }, func=search_web )这里的关键是描述要写得足够清楚。模型完全靠这段描述来判断什么时候该用这个工具。我一般会写清楚三件事:这个工具能做什么、什么时候该用、参数怎么填。
4.4 执行循环:Agent 的心脏
把上面几个模块串起来,就是 Agent 的执行循环:
def run_agent(user_input, max_turns=10): messages = [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": user_input} ] for turn in range(max_turns): response = llm_client.chat( messages=messages, tools=registry.get_definitions() ) # 如果模型没有调用工具,说明它认为可以直接回答 if not response.tool_calls: return response.content # 把模型的决策加入历史 messages.append(response) # 执行所有工具调用 for tool_call in response.tool_calls: result = registry.execute( tool_call.function.name, tool_call.function.arguments ) messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": json.dumps(result, ensure_ascii=False) }) return "达到最大轮次限制,任务未完成"这个循环的逻辑很清晰:调用模型 → 如果模型要调工具就执行 → 把结果喂回去 → 再调用模型 → 直到模型给出最终答案或达到轮次上限。
max_turns这个参数很重要,它是防止死循环的保险丝。我一般设 10,复杂任务设 15。超过这个轮次还没完成,说明要么任务太复杂需要拆分,要么提示词有问题,需要人工介入。
4.5 记忆模块的实现:短期与长期结合
记忆模块我分两部分实现。短期记忆就是上面循环里的messages列表,随着对话自然增长。长期记忆则是一个简单的向量检索:
import numpy as np class Memory: def __init__(self, llm_client): self.llm_client = llm_client self.documents = [] # 存储文本 self.embeddings = [] # 存储向量 def add(self, text): embedding = self.get_embedding(text) self.documents.append(text) self.embeddings.append(embedding) def search(self, query, top_k=3): if not self.embeddings: return [] query_embedding = self.get_embedding(query) # 计算余弦相似度 similarities = [] for emb in self.embeddings: sim = np.dot(query_embedding, emb) / ( np.linalg.norm(query_embedding) * np.linalg.norm(emb) ) similarities.append(sim) # 取 top_k indices = np.argsort(similarities)[-top_k:][::-1] return [self.documents[i] for i in indices] def get_embedding(self, text): # 调用嵌入接口,这里省略具体实现 return call_embedding_api(text)这个实现很朴素,但对小规模知识库完全够用。等文档数量上千了,再考虑换成专业的向量数据库。
注意:余弦相似度计算前一定要做归一化,否则向量长度会影响结果。我一开始忘了归一化,检索出来的结果乱七八糟,排查了半天才发现是这个原因。
5. 常见问题与排查技巧实录:那些让我熬夜的坑
5.1 模型不调用工具怎么办
这是最常见的问题。模型明明有工具可用,却选择直接回答,或者该用 A 工具却用了 B 工具。排查思路如下:
第一,检查工具描述是否清晰。这是最常见的原因。描述太模糊,模型不知道什么时候该用。解决办法是把描述写具体,最好包含使用场景的举例。
第二,检查系统提示词是否强调了工具的使用。有时候需要在系统提示词里明确写“当需要获取实时信息时,必须使用搜索工具,不要凭记忆回答”。
第三,检查工具数量是否过多。工具太多会让模型选择困难。我一般把同时可用的工具控制在 5 个以内,超过就分组,根据任务类型动态加载。
第四,调整tool_choice参数。如果某个任务确定必须用工具,可以把它设成强制调用某个工具,而不是auto。
5.2 工具调用参数格式错误
模型返回的参数经常不符合预期,比如该传字符串的传了数字,该传数组的传了字符串。我的应对策略是双重保险:
一是在工具定义里把参数类型写清楚,type字段要准确。二是在工具函数入口做校验和转换,能自动转换的就转换,不能转换的返回明确错误。
def safe_int(value, default=0): try: return int(value) except (ValueError, TypeError): return default这种小工具函数多写几个,能省很多事。
5.3 上下文超长导致报错
对话轮数一多,token 必然超限。我的处理流程是:
- 每次调用模型前,先估算当前 messages 的总 token 数。
- 如果超过预算的 80%,触发压缩。
- 压缩策略:保留系统提示词和最近 3 轮对话,中间的对话用模型做摘要。
- 摘要后的内容作为一条 system 消息插入。
估算 token 有个粗略公式:英文大约 4 个字符 1 个 token,中文大约 1.5 个字符 1 个 token。这个不精确,但用来做预警足够了。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方向 | 解决办法 |
|---|---|---|---|
| 模型不调用工具 | 描述模糊/提示词未强调 | 检查工具描述和系统提示词 | 写具体描述,明确使用场景 |
| 参数格式错误 | 类型定义不清 | 检查参数 schema | 入口做校验和转换 |
| 上下文超长 | 对话历史未压缩 | 估算 token 数 | 触发摘要压缩 |
| 循环不终止 | 工具反复返回错误 | 查看工具执行日志 | 修复工具,设置最大轮次 |
| 响应速度慢 | 模型选择过大/网络问题 | 检查模型和网络 | 换小模型或加缓存 |
| 输出格式不稳定 | temperature 过高 | 检查温度设置 | 降到 0.3 以下 |
5.5 几个让我印象深刻的踩坑经历
坑一:工具执行结果太长,把上下文撑爆。有一次我让 Agent 搜索资料,搜索工具返回了整整一页的原始网页内容,直接塞进上下文,下一轮就超限了。后来我在工具函数里加了截断逻辑,只返回最相关的前 500 字,问题解决。
坑二:模型陷入“道歉循环”。工具报错后,模型不停地道歉然后重试同一个错误操作。后来我在系统提示词里加了一句“如果工具连续两次返回相同错误,请停止重试并告知用户”,才止住这个循环。
坑三:并发调用导致状态混乱。我一开始让多个工具并行执行,结果它们同时修改同一个状态变量,数据错乱。后来改成串行执行,或者对共享状态加锁,才稳定下来。Agent 的工具执行,能串行就别并行,除非你确定它们互不干扰。
6. 工程化落地的一些思考
6.1 日志与可观测性:Agent 的“黑匣子”
Agent 最大的问题是“黑盒”——你不知道它为什么做了某个决策。所以日志系统必须做扎实。我记录的内容包括:每一轮的完整 messages、模型的原始响应、工具调用的名称和参数、工具返回的结果、每一轮的耗时。
这些日志我按会话 ID 存成 JSON 文件,出问题时可以完整回放整个执行过程。这个习惯帮我定位了无数问题,强烈建议你也这么做。
6.2 成本控制:token 就是钱
Agent 跑起来,token 消耗是实打实的成本。我的控制手段有几个:
- 缓存:相同的查询直接返回缓存结果,不重复调用模型。
- 模型分级:简单任务用小模型,复杂任务才用大模型。
- 上下文精简:前面说的压缩策略,本质上也是在省钱。
- 设置预算上限:每个会话设置最大 token 消耗,超了就终止。
6.3 从 Demo 到产品的距离
跑通一个 Demo 可能只需要一天,但把它变成能稳定服务的产品,需要的工作量是 Demo 的十倍。这中间的差距主要在:错误处理、边界情况、性能优化、监控告警、用户体验。我现在的做法是,每做一个功能,先问自己三个问题:出错了怎么办?输入异常怎么办?用户等太久怎么办?把这三个问题想清楚,代码的健壮性会提升一大截。
7. 给同样在转型路上的朋友几句实在话
写了这么多,最后分享几点我自己的真实体会,不构成建议,就是个人经验。
第一,别被“AI 焦虑”裹挟。网上天天有人喊“再不学 AI 就被淘汰了”,搞得人心惶惶。但冷静下来想,AI 是工具,不是替代品。你原有的工程能力、架构思维、业务理解,一样都不会白费,反而会在 AI 时代变得更值钱。转型不是推倒重来,是在原有基础上加一层新能力。
第二,动手比看书重要一百倍。我见过太多人收藏了一堆教程、买了一堆课,结果一行代码没写。AI Agent 这东西,看十篇文章不如自己跑通一个 Demo。哪怕是最简单的“调用模型 + 一个工具”,跑通了你就超过了 80% 只停留在概念层面的人。
第三,接受“不完美”。模型会犯错,Agent 会抽风,这是常态。不要追求一次做到完美,先让它跑起来,再一点点优化。我现在的 Agent 也经常出问题,但每次出问题都是一次学习机会,修着修着就越来越稳了。
第四,保持记录的习惯。这个 DAY 系列对我来说最大的价值,不是给别人看,而是逼自己每天复盘。今天学了什么、踩了什么坑、想通了什么,写下来,过段时间回头看,进步一目了然。如果你也在转型,强烈建议你也开一个自己的记录系列,哪怕没人看,对自己也是极好的沉淀。
这个系列我还会继续写下去,后面可能会聊聊多 Agent 协作、Agent 的评估体系、以及怎么把 Agent 集成到实际业务系统里。路还长,一起走。