ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

LangChain智能体公开分享追踪实战:从回调机制到取消分享

LangChain智能体公开分享追踪实战:从回调机制到取消分享 最近在做一个LangChain智能体项目目标是让Agent在执行任务时不仅跑得对还能看得清——也就是公开分享或取消分享追踪。所谓追踪并不只是打印几行日志那么简单。它要让外部调用方、业务方甚至审计人员随时知道智能体现在执行到哪一步、看到了什么上下文、为什么做出某个决策同时当任务完成或被撤回时还能干净利落地把追踪记录取消分享避免敏感信息留在系统里。这篇文章就聊聊我在LangChain智能体开发中实践分享追踪的完整思路从需求拆解到技术选型再到可落地的代码实现和踩坑记录适合正在做智能体工程化落地、尤其是涉及多智能体协同或对外提供服务的朋友参考。1. 需求拆解公开分享追踪到底在追什么1.1 追踪的三个核心对象执行轨迹、上下文状态、审计元信息公开分享追踪字面上看是把智能体的执行过程分享出去但真正落到工程上至少要追踪三类信息。第一是执行轨迹也就是智能体从接收输入到输出结果的完整调用链。一个Agent往往要经历模型推理、工具调用、结果解析、再次推理等多个环节追踪轨迹就是要记录每一步的触发顺序、耗时、入参出参让外部观察者能够还原这个智能体是怎么一步一步得出答案的。第二是上下文状态。LangChain智能体最让人头疼的就是上下文管理系统提示词、工具返回结果、历史对话、中间变量都会在Agent运行期间被反复组装。追踪上下文状态意味着要能随时看穿这个黑盒当前持有哪些信息这些信息是从哪里来的。我以前吃过亏Agent在工具调用失败后复用了旧的上下文继续推理结果给出了一个看起来合理但完全过时的答案。如果没有上下文快照这种问题排查起来极其痛苦。第三是审计元信息包括任务ID、用户标识、时间戳、模型版本、工具版本等。这些信息平时不起眼但在取消分享场景下就非常重要——取消分享不光是停止展示更需要在审计层面记录谁在什么时候撤回了哪个任务的可见性这是合规和治理的基础。1.2 分享场景的三种典型形态把追踪机制设计成可公开分享是因为智能体并不总是一个人在终端里对话。我梳理下来项目中最常见的分享形态有三种。第一种是团队内部协同分享。开发A调起一个Agent执行数据分析任务开发B需要看到执行流程和中间结果就可以通过分享链接或者内部接口实时围观。这时候追踪的粒度要细因为团队同事往往是真的要定位问题不能只给一个执行成功的结论要看中间每一步的日志。第二种是对业务方的异步展示。比如你给客服系统接入了一个智能体客服主管要能够在不接触底层代码的前提下看到当前工单处理到了哪一步、Agent是否调用了知识库、是否需要人工介入。这种形态下追踪内容要做一定的脱敏和聚焦把技术细节折叠成业务可读的过程描述。第三种是对审计合规系统的输出。这类分享通常不需要实时但要求数据结构严谨、不可篡改并且必须支持按时间范围和任务维度检索。取消分享在这个场景中尤其重要——一旦数据保留期限到期或者涉敏系统必须能批量撤回。1.3 为什么必须有取消分享权限回收与数据治理很多做智能体Demo的同学一开始根本不考虑取消分享。反正任务跑完记录留在数据库里有什么问题翻日志就行了。但一旦把智能体放到真实业务环境你会发现只增不减的追踪记录就是一座随时可能爆发的火山。首先是权限回收问题。一个任务刚完成时是可以公开给团队看的但可能后来涉及到了敏感字段或者项目进入保密期你就必须有能力把这个任务的追踪记录立刻从任何公开视图上拿掉。如果没有设计好的取消分享机制开发只能去改数据库既慢又容易漏。其次是数据治理的要求。很多场景下追踪记录不能无限期保留。我们项目在NF要求下明确约定追踪数据保留90天到期自动清理。这里清理不仅是删数据库记录还要处理缓存、消息队列里的残留事件否则别人通过旧链接仍然可能看到已经被取消的内容。所以我在设计追踪模块时把分享和取消分享放在同一个架构位置来考虑而不是事后补一个删除接口。这样可以保证分享状态的一致性也避免代码越写越乱。2. 技术选型LangChain回调机制与LangGraph状态机2.1 为什么选LangChain回调机制事件流的天然切面LangChain本身提供了非常成熟的事件通知机制也就是CallbackHandler。它的核心思路是你不需要侵入Agent的主流程代码只需要注册一个继承了BaseCallbackHandler的类LangChain就会在你定义好的生命周期节点上自动触发对应方法。我在项目里用到的核心回调方法大致有这些on_chain_start/on_chain_end追踪整条链路的开始和结束on_llm_start/on_llm_end记录模型调用的输入输出on_tool_start/on_tool_end记录工具的执行过程和返回结果on_agent_action/on_agent_finish记录Agent每次决策的Action和最终输出选择回调机制而不是自己手动埋点最大好处是解耦。追踪逻辑完全独立于Agent主体后续想升级追踪策略或者临时关闭追踪都不需要对核心代码大动干戈。这一点在真实项目中非常宝贵因为Agent的调度逻辑本身已经很复杂实在不想再和记账代码搅在一起。另外LangChain的LangGraph框架在langgraph包中也提供了类似的钩子机制可以在节点的前后执行自定义逻辑。如果你的Agent是基于LangGraph构建的回调依然适用同时还能配合状态机做更多操作。2.2 LangGraph状态管理的价值让追踪有骨架LangChain的Callback机制解决的是事件怎么被感知而LangGraph解决的是状态怎么被保存。在做分享追踪时两者缺一不可。LangGraph的核心思想是把Agent执行过程建模成一张图节点是动作边是状态流转。每个节点执行完后会把结果写入全局的State对象。这个State就是天然的分享追踪素材——只要把每次节点执行前后的State快照保存下来执行轨迹就自动完整了。我项目里用的LangGraph状态结构大致是这样class AgentState(TypedDict): messages: Annotated[list, add_messages] tool_calls: list context_snapshot: dict share_token: str trace_id: str重点说一下context_snapshot。这个字段是我单独加的用来保存当前节点的上下文摘要。因为messages是完整对话记录直接全部扔给回调可能太大而context_snapshot可以做轻量级快照只记录关键变量、工具返回的关键字段、当前步骤的决策理由。这样既保证了追踪的深度又控制了存储和传输成本。实测下来一个大任务的完整追踪记录可以从几十MB降到几MB对分享和取消分享都友好很多。2.3 方案选型对比回调 vs 中间件 vs 流式事件在确定方案之前我也对比过几种主流做法这里把对比结果整理出来供参考方案侵入性实时性复杂度适用场景手动埋点高高低小型脚本、一次性工具LangChain回调低高中标准Agent、可观测性建设LangGraph状态快照低中中基于LangGraph的复杂Agent流式事件监听低最高高需要前端实时展示的看板场景简单说如果你只是想在本地调试时看看执行过程手动埋点或者verboseTrue就够了但如果要做成公开分享追踪这种可对外、可撤回的产品化能力回调加状态快照的组合是目前最稳的路径。流式事件监听适合做实时大屏但工程复杂度会明显上升而且取消分享的追溯会比较麻烦。我在具体项目中采用的是回调为主、LangGraph状态为辅的混合方案。回调负责捕捉生命周期事件快速生成追踪节点状态快照负责保存关键上下文供后续分享时拼装展示。两者互相补充既不互相替代也不重复记录。3. 核心实现一个最小可用的分享追踪模块3.1 定义追踪记录的数据模型要支撑公开分享和取消分享追踪记录的数据模型设计得非常讲究。我最终落地的模型包含了三个分区trace_meta元信息、trace_steps执行步骤、share_status分享状态。from pydantic import BaseModel, Field from typing import Optional, List from datetime import datetime class TraceStep(BaseModel): step_id: str Field(..., description步骤唯一ID) step_type: str Field(..., description步骤类型: chain/llm/tool/agent) step_name: str Field(..., description步骤名称) input_payload: dict Field(default_factorydict) output_payload: dict Field(default_factorydict) started_at: datetime finished_at: Optional[datetime] None duration_ms: Optional[int] None status: str Field(defaultrunning, description状态: running/succeeded/failed) class ShareTrace(BaseModel): trace_id: str Field(..., description追踪记录全局唯一ID) task_name: str created_at: datetime Field(default_factorydatetime.utcnow) updated_at: datetime Field(default_factorydatetime.utcnow) status: str Field(defaultactive, descriptionactive/revoked) share_token: Optional[str] None steps: List[TraceStep] Field(default_factorylist) context_snapshot: dict Field(default_factorydict) owner_user: str allowed_users: List[str] Field(default_factorylist)这个模型有几个设计点是实际项目中磨出来的share_token是公开分享的凭证。每一次公开分享都会生成一个随机的token外部请求必须携带这个token才能查看追踪内容。这样取消分享的实现就变得很干净——只要把share_status改成revoked或者删除对应的分享记录任何拿旧token来访问的请求都会被拒绝不需要去翻执行日志。allowed_users是用来做定向分享的。有时不需要完全公开而是只给特定的几个人看。这块在模型里就先留好后续加权限过滤就很自然。3.2 实现一个LangChain回调处理器数据模型定义好后核心就是写一个自定义回调处理器。这部分是整个模块的心脏我把它命名为ShareTraceCallbackHandler。下面是一个简化但可直接运行的核心版本from langchain_core.callbacks import BaseCallbackHandler from langchain_core.agents import AgentAction, AgentFinish from typing import Any, Optional import uuid from datetime import datetime class ShareTraceCallbackHandler(BaseCallbackHandler): def __init__(self, trace_store, task_name: str, owner_user: str): self.trace_store trace_store self.trace_id str(uuid.uuid4()) self.task_name task_name self.owner_user owner_user # 用一个栈来记录当前执行链的层级 self._step_stack [] # 预先在存储中创建追踪记录 self.trace_store.create_trace( trace_idself.trace_id, task_nametask_name, owner_userowner_user, ) def get_trace_id(self) - str: return self.trace_id def on_chain_start(self, serialized: dict, inputs: dict, **kwargs) - None: step_id self._begin_step(chain, serialized.get(name, chain), inputs) def on_chain_end(self, outputs: dict, **kwargs) - None: self._end_step(chain, outputs) def on_llm_start(self, serialized: dict, prompts: list[str], **kwargs) - None: step_id self._begin_step(llm, serialized.get(name, llm), {prompts: prompts}) def on_llm_end(self, response: Any, **kwargs) - None: self._end_step(llm, {response: response}) def on_tool_start(self, serialized: dict, input_str: str, **kwargs) - None: step_id self._begin_step(tool, serialized.get(name, tool), {input: input_str}) def on_tool_end(self, output: Any, **kwargs) - None: self._end_step(tool, {output: output}) def on_agent_action(self, action: AgentAction, **kwargs) - None: self.trace_store.append_step( trace_idself.trace_id, stepTraceStep( step_idstr(uuid.uuid4()), step_typeagent_action, step_nameaction.tool, input_payload{tool_input: action.tool_input}, started_atdatetime.utcnow(), statussucceeded, ), ) def on_agent_finish(self, finish: AgentFinish, **kwargs) - None: self.trace_store.append_step( trace_idself.trace_id, stepTraceStep( step_idstr(uuid.uuid4()), step_typeagent_finish, step_namefinal_answer, input_payload{output: finish.return_values}, started_atdatetime.utcnow(), statussucceeded, ), ) def _begin_step(self, step_type: str, step_name: str, input_payload: dict) - str: step TraceStep( step_idstr(uuid.uuid4()), step_typestep_type, step_namestep_name, input_payloadinput_payload, started_atdatetime.utcnow(), statusrunning, ) self.trace_store.append_step(trace_idself.trace_id, stepstep) self._step_stack.append(step.step_id) return step.step_id def _end_step(self, step_type: str, output_payload: dict) - None: if not self._step_stack: return step_id self._step_stack.pop() self.trace_store.update_step( trace_idself.trace_id, step_idstep_id, output_payloadoutput_payload, statussucceeded if step_type ! llm else succeeded, finished_atdatetime.utcnow(), )对于为什么用栈来维护执行层级这里解释一下。LangChain的链和Agent在执行过程中会包含嵌套的调用比如一个Chain里调用了LLMLLM又触发工具调用。如果不区分当前正在执行的是哪个步骤记录就会有串线风险。用一个先进后出的栈每次on_chain_start压栈on_chain_end弹栈就能保证每个on_chain_end都对应到正确的开始节点。在真实项目中我还处理了一个细节——on_tool_end的output并不是都JSON可序列化的。有些自定义工具返回的是自定义对象直接扔进output_payload会导致后续存储时崩溃。所以我在_end_step里加了一段兜底逻辑如果output_payload不可序列化就尝试转成str或者截断到指定长度再保存。这个看起来很小的处理实际救了我很多次。3.3 把追踪回调挂载到智能体执行链回调处理器写完之后关键问题就变成怎么把它挂到Agent上。LangChain的API在不同版本间有差异但挂载思路是稳定的在执行器构造时传入callbacks参数。下面是挂载的示例from langchain.agents import create_tool_calling_agent, AgentExecutor from langchain_openai import ChatOpenAI from langchain_core.tools import tool # 定义智能体要用到的工具 tool def get_user_order(user_id: str) - str: 根据用户ID查询最近订单信息 # 这里简化处理真实场景中会调用业务API return forder_202502001: 商品A x2, 已发货 tool def calculate_total(order_info: str) - str: 计算订单金额 return 总金额: 199.00元 tools [get_user_order, calculate_total] llm ChatOpenAI(modelgpt-4o-mini, temperature0) prompt SystemMessagePromptTemplate.from_template( 你是订单助手。请根据用户问题调用工具给出简洁回答。 ) agent create_tool_calling_agent(llm, tools, prompt) agent_executor AgentExecutor(agentagent, toolstools) # 创建追踪器并绑定 trace_store InMemoryTraceStore() # 实际项目使用Redis/MySQL handler ShareTraceCallbackHandler( trace_storetrace_store, task_nameorder_assistant_demo, owner_userops_team, ) result agent_executor.invoke( {input: 帮我查一下用户U10001的订单并计算总金额}, {callbacks: [handler]}, ) # 拿到追踪ID后续就靠它来公开分享 trace_id handler.get_trace_id() print(trace_id:, trace_id) print(result:, result[output])注意一个关键点callbacks不是放在invoke的input里而是要放在第二个参数config中。这里指的langchain的RunnableConfig。很多新手在这里犯迷糊把callbacks直接塞进input字典结果回调完全没触发追踪记录里空无一物排查半天才发现是传递位置错了。另外AgentExecutor在内部创建子任务时会自动传递config中的callbacks。同样是create_tool_calling_agent创建的agent内部的LLM调用和工具调用都会被自动捕获不需要手动做二次传递。这一点LangChain做得是比较到位的但前提是你用的是标准AgentExecutor。如果自定义了复杂的Execution loop就要自己确认回调传播路径。3.4 上下文快照的保存策略前面数据模型里有context_snapshot字段这里展开讲一下具体怎么做。我在LangGraph节点中会在关键节点后执行一次快照收集而不是每次回调都全量保存。from langgraph.graph import StateGraph, END def node_call_tool(state: AgentState) - AgentState: # 执行工具调用... tool_result call_some_tool(state[tool_calls]) # 保存快照只记录关键信息 state[context_snapshot] { current_step: node_call_tool, tool_result_preview: str(tool_result)[:200], last_user_message: state[messages][-1].content if state[messages] else , } return state这里的技巧是摘要有损和关键信息无损。对于工具返回结果我会保存一个两三百字的前缀预览真正完整内容还是放在steps里的output_payload中对于决策理由、任务目标这类核心信息则完整保存。这样context_snapshot既能帮助快速理解上下文又不会因为体积过大拖慢分享接口的加载速度。4. 实战落地把分享追踪接成可调用API4.1 基于FastAPI接入分享接口追踪模块的最终形态是外部可用的服务。我在项目中用的是FastAPI因为它在异步支持和类型注解方面表现很好适合作为智能体服务的统一入口。分享接口要做的事情很直接接收一个trace_id和分享范围参数给这个追踪记录生成一个share_token并更新分享状态。下面是我落地的接口代码from fastapi import FastAPI, HTTPException, Depends from pydantic import BaseModel app FastAPI(titleAgent Trace Share Service) class ShareRequest(BaseModel): trace_id: str allowed_users: list[str] [] ttl_seconds: int 3600 # 默认分享有效期1小时 class ShareResponse(BaseModel): share_token: str share_url: str expires_at: datetime def require_auth(auth_header: str Header(None)): # 资源访问时校验登录态 if not auth_header: raise HTTPException(status_code401, detail未认证) return auth_header app.post(/traces/{trace_id}/share, response_modelShareResponse) async def share_trace(trace_id: str, req: ShareRequest, auth: str Depends(require_auth)): trace await get_trace(trace_id) if trace is None: raise HTTPException(status_code404, detail追踪记录不存在) share_token await generate_token() expires_at datetime.utcnow() timedelta(secondsreq.ttl_seconds) await update_trace_share_status( trace_idtrace_id, share_tokenshare_token, allowed_usersreq.allowed_users, expires_atexpires_at, statusshared, ) share_url fhttps://your-domain/agent-traces/view/{trace_id}?token{share_token} return ShareResponse( share_tokenshare_token, share_urlshare_url, expires_atexpires_at, )这里我加了一个ttl_seconds参数就是分享链接的有效期。主要是考虑到信息安全默认值设成1小时业务方可以根据需要传更长或更短。实际运营中很多公开分享都是短时行为超时自动取消分享不仅减轻了取消逻辑的负担也降低了安全事故的概率。4.2 取消分享接口不只是删记录更要斩断后路取消分享接口是整个模块的安全防线我在这里踩过不少坑所以重点讲一下。第一版我实现得很粗暴把数据库里的share_token置空、status改成revoked就完了。结果测试时发现之前已经分享出去的URL还能访问。排查后发现我的查询接口在读取追踪记录时只判断了记录是否存在没有判断share_status和expires_at。也就是说取消分享更新了存储但查询逻辑根本没有校验分享状态导致旧的URL仍然能命中。所以取消分享不能只写一个更新接口必须和查询接口联动起来。我的处理方式是在查询追踪记录的底层函数中统一加上有效性校验async def get_share_view(trace_id: str, token: str) - Optional[ShareTrace]: trace await get_trace(trace_id) if trace is None: return None if trace.status ! shared: return None if trace.share_token ! token: return None if trace.expires_at and datetime.utcnow() trace.expires_at: # 过期后顺带清理状态 await mark_expired(trace_id) return None return trace这样一来取消分享的思路就变成修改status为revoked或者删除share_token任何旧的token都自然无法通过校验。不仅是接口层面的防护连底层查询都加了兜底双保险。另外我还在取消分享时额外做了一步记录操作日志。谁在什么时间通过哪个接口取消了分享这个审计信息必须留存。因为有时候取消分享本身就是一个需要事后追溯的事件尤其牵扯到违规或安全事件时这些记录能救命。4.3 完整调用链从Agent执行到分享视图把上面这些模块串联起来整个调用的链路就比较清晰了客户端发起一个任务请求FastAPI接收到请求后创建一个AgentTask。创建ShareTraceCallbackHandler挂到AgentExecutor上执行智能体任务。Agent执行过程中回调处理器持续把步骤写入trace_store。任务执行完毕拿到了trace_id返回给调用方。调用方调用POST /traces/{trace_id}/share拿到share_token和share_url。外部用户通过share_url访问追踪详情页后端GET /agent-traces/view/{trace_id}校验token、状态和有效期。任务不再需要公开时调用DELETE /traces/{trace_id}/share状态改为revoked即刻失效。这个链路中每个环节我都预留了独立可测试的入口。比如share_trace接口可以单测get_share_view也可以单测Agent回调这块也能脱离API单独验证。这对于后续维护和扩展至关重要。我实际跑下来这个链路的平均额外开销大约在每个任务增加几十毫秒到一两百毫秒主要取决于步骤数和context_snapshot的大小。对于绝大多数业务场景来说是完全可以接受的。5. 常见问题与排查技巧实录5.1 回调方法根本没触发或者只触发了一部分这是最难排查的一类问题因为没有报错只是静默地没有记录。我遇到过好几种情况。最典型的是callbacks传递位置错误。在旧版LangChain里AgentExecutor.run()会接受callbacks参数但新版0.2迁移到了Runnable体系需要把回调放在config参数中。如果你是把callbacks直接加到invoke的输入字典里回调是肯定不会执行的。还有一种情况是自定义工具的装饰器函数里没有透传callbacks。如果你用tool装饰器定义了工具但工具内部又调用了另一个LLM链那么内部LLM的调用是否会被捕获取决于工具执行时是否收到了外层的config。我调试时遇到过一次工具内部的LLM虽然也属于整个Agent执行的一部分但由于我在工具实现里手动构造了新的链实例没有继承外层callback_manager导致内部调用完全丢失。解决办法是在工具内部也通过RunnableConfig传参或者在定义工具时用config参数接收callbacks。5.2 并发执行时追踪记录互相污染这个问题我在做性能压测时才暴露出来。多个用户同时调用Agent回调处理器如果使用了线程不安全的成员变量就会出现A任务的步骤被写进B任务的记录中。我之前那个_step_stack如果处理器实例在多个线程间共享栈就会被同时操作_begin_step和_end_step就错乱了。解决办法很简单每个任务独立创建ShareTraceCallbackHandler实例。也就是说handler的生命周期应该和Agent执行任务绑定而不是和整个AgentExecutor绑定。这样既保证了隔离也让trace_id自然地和任务一一对应。如果是全局变量像我在InMemoryTraceStore中用的内存map也要确保写入时的线程安全。我在真实项目中用的是Redis Lua脚本做原子操作省去了很多锁的麻烦。5.3 取消分享后记录还残留在缓存和日志里只把主存储里的分享状态改了不代表取消分享彻底完成。我遇到的实际情况是分享URL被某个网关缓存了即使后端已经拒绝继续返回追踪数据网关再扛一段时间用户还是能访问到旧内容。处理这种问题的思路可以从两个角度来看一是给所有分享接口的响应加上Cache-Control: no-store和Pragma: no-cache头从源头告诉缓存系统不要缓存分享内容。这样即使取消分享之前缓存过的内容也会很快失效。二是取消分享时清掉分布式缓存中的key。我用Redis存了share:{trace_id}:{share_token}这样的结构对应具体的追踪数据块、权限列表和过期时间。取消分享时会再用一个专门的清理函数批量删除相关的缓存key。另外还要意识到一个现实问题只要追踪数据被生成过它在内存、磁盘等各处就可能存在过副本。严格的安全场景下必须进行磁盘擦除但绝大多数业务场景要求的是分享链路失效而非物理消除。我处理时会在操作日志中明确标记逻辑删除还是物理清理避免未来审计时分不清。5.4 追踪粒度过细导致存储爆满技术上是越细越好现实中却是越细越贵。我有一版实现把LLM的完整prompt和完整响应都写进了追踪记录结果差不多每个任务多出几百KB的存储运行几个小时后数据库就光速膨胀。后来我加了两个优化。第一个是开始与结束分块input_payload默认只记录截断摘要完整payload只有在share接口被调用时才去读取有效规避了存量数据的存储成本。第二个是给追踪步骤做了等级标记像chain级别的开始结束这类中间事件可以设置成detail_level2只有当分享请求指定verbosetrue时才展示。这样日常态下追踪记录非常轻量只在需要深入排查时把细节展开。这两个优化落地后存储量降到了原方案的十分之一左右而需要看的细节一个都没丢。对做智能体工程化的朋友来说我强烈建议从第一天就把追踪粒度可配置考虑进去否则后面就是频繁返工。5.5 分享链接无法直接复用权限校验和过期策略的平衡我刚开始做公开分享时天真地以为一个token就够用了什么人都可以看。结果被安全团队打回要求增加定向可见。于是就有了allowed_users字段。但这里也要注意校验allowed_users的逻辑不要写在接口层而是要写在下层查询函数里。有一次我把用户校验写在路由函数里结果另一个内部接口直接调用了底层查询函数绕过了路由校验追踪数据就泄露了。这个教训给我很大的提醒所有权限类校验必须沉到最底层的查询逻辑中不能让上层调用者绕路。关于过期策略我的默认做法是分享接口返回的expires_at是硬过期到点后查询直接拒绝同时提供一个主动取消的接口给运营人员一个提前收回的手段。两者互补既保证安全下限也提供灵活性。写在最后一个小建议如果你打算在智能体项目中做分享追踪我现在会强烈建议你先想清楚取消分享的设计再做公开分享的功能。因为从工程角度看取消分享不只是把开关关掉它牵涉到查询校验、缓存清理、日志审计、权限下沉等一系列问题。先设计好撤回机制公开分享其实只是状态机里的一个朴素状态。我自己在实际项目中踩过最深的坑就是刚开始只想着怎么把Agent跑起来、把过程记录下来分享出去结果等要回撤时四处漏风。把这篇文章里提到的几个排查点提前规避掉后续上线会顺畅得多。追踪模块其实就是一棵树的根部根扎稳了上面的智能化应用才能放心地长。
返回列表