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. 实操过程与核心环节实现从零搭一个能查资料的 Agent4.1 环境准备与依赖安装这一节我以 Python 为例因为 AI 生态里 Python 的库最全。前端朋友别慌语法看看就懂逻辑才是关键。首先建一个干净的虚拟环境这是好习惯避免依赖冲突python -m venv agent-env source agent-env/bin/activate # Windows 用 agent-env\Scripts\activate然后安装核心依赖。我尽量精简只装必要的pip install openai requests numpyopenai是模型调用的 SDKrequests用来发 HTTP 请求比如调外部搜索接口numpy用来做向量计算。就这三个够跑一个基础 Agent 了。提示不要一上来就装一堆框架。依赖越少出问题时排查范围越小。等你的 Agent 真的需要复杂功能了再按需引入。4.2 模型接口封装把通信层做扎实模型接口层是整个系统的地基我把它单独写成一个类方便统一管理import openai import json class LLMClient: def __init__(self, api_key, modelgpt-4): self.client openai.OpenAI(api_keyapi_key) self.model model def chat(self, messages, toolsNone, temperature0.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( namesearch_web, description根据关键词搜索网络信息返回相关结果列表。当需要获取最新信息或不确定的事实时使用。, parameters{ type: object, properties: { query: { type: string, description: 搜索关键词尽量具体 } }, required: [query] }, funcsearch_web )这里的关键是描述要写得足够清楚。模型完全靠这段描述来判断什么时候该用这个工具。我一般会写清楚三件事这个工具能做什么、什么时候该用、参数怎么填。4.4 执行循环Agent 的心脏把上面几个模块串起来就是 Agent 的执行循环def run_agent(user_input, max_turns10): messages [ {role: system, content: SYSTEM_PROMPT}, {role: user, content: user_input} ] for turn in range(max_turns): response llm_client.chat( messagesmessages, toolsregistry.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_asciiFalse) }) 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_k3): 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, default0): 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 集成到实际业务系统里。路还长一起走。