news 2026/10/26 5:41:36

手写AI Agent:从ReAct引擎到生产级可观测性

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
手写AI Agent:从ReAct引擎到生产级可观测性

1. 这不是“又一门AI课”,而是一次认知重装:为什么手写Agent比调用API更值得花时间

你有没有过这种体验:刚学完LangChain,能用几行代码调出天气、查股票、连数据库,信心爆棚;结果老板甩来一个需求——“让客服机器人自动判断用户情绪、调取历史工单、生成带法律条款的补偿方案,还要能回溯每一步决策依据”——你翻遍文档、试了十个chain模板、改了二十遍prompt,最后发现:链式调用根本兜不住逻辑分支,状态无法持久,错误一发生就全链崩,debug日志里全是黑盒输出。这不是你代码写得差,是框架在帮你屏蔽复杂性的同时,也悄悄拿走了你对系统底层的掌控权。

这门课标题里那个“手写”,不是让你从零造轮子去写个LLM,而是指亲手搭建一个具备完整决策闭环的Agent系统:它知道什么时候该思考、什么时候该行动、什么时候该反思;它能把大模型的“幻觉”关进笼子,用结构化工具约束输出;它能在一次会话中记住上下文、维护状态、处理异常、支持人工干预。我带过三十多个从零起步的学员,90%的人卡在“能跑demo但不敢改生产逻辑”这一步——因为没亲手写过ReAct循环,没拆解过Observation怎么清洗才能喂给模型,没调试过Tool Calling失败时的fallback策略。这门课要解决的,就是这个断层。

核心关键词“AI Agent”在这里不是泛指任何带AI的程序,而是特指具备自主目标分解、工具调用、环境交互、反思修正四要素的智能体;“ReAct”不是React前端框架,而是Reasoning + Acting的决策范式,是让大模型从“文本续写机”变成“问题解决者”的关键跃迁;“LangChain”和“Spring AI”是成熟框架,但它们像一辆预装好导航和自动驾驶的汽车——你坐进去能到目的地,但轮胎怎么转向、刹车片何时介入、油电如何切换,你并不清楚。而手写,就是亲手拧开引擎盖,看清每一根管线怎么连接、每个阀门如何响应。适合谁?不是只适合Python老手,恰恰相反,它最适合那些已经会print("Hello World")、能看懂JSON、愿意为搞懂一个while True:循环多花两小时的人。因为真正的门槛从来不在语法,而在对“智能体如何思考”这件事的具象化理解。

2. 为什么必须放弃“框架先行”思维:从三个真实故障场景看手写Agent的不可替代性

2.1 故障现场:LangChain的“链式幻觉”导致资损风险

去年帮一家保险科技公司做保单查询Agent,他们用LangChain Chain封装了“用户问保单号→查数据库→返回摘要”流程。上线后发现:当用户输入“我的保单号是ABC123,但好像被冻结了,能帮我看看吗?”时,Chain直接把整句话喂给LLM,模型在没有明确工具调用指令的情况下,凭经验“编造”了一段关于冻结原因的解释,甚至虚构了内部工单号。问题根源在于:LangChain默认的SequentialChain不强制要求模型输出结构化Action,它信任模型能“自己懂”。而手写ReAct循环的第一步,就是硬编码<Thought>、<Action>、<Action Input>、<Observation>的标签体系——模型输出必须严格匹配这四个片段,否则直接报错中断。这看似增加了开发量,实则把“模型可能胡说”的风险,提前锁死在解析阶段。我教学员写的第一个手写模块,就是这个Parser:用正则精准捕获标签,用try-except拦截非法格式,再用schema校验Action Input是否符合工具签名。这三行代码,比十页LangChain文档更能教会你什么叫“可控的AI”。

2.2 故障现场:Spring AI的“状态黑洞”让多轮对话失效

有位学员用Spring AI搭了一个HR面试助手,要求能记住候选人上一轮回答的“项目经验”,并在下一轮追问技术细节。他按官方教程配置了ConversationMemory,结果发现:当候选人说“我做过电商推荐系统”,系统能记住;但当追问“用的什么算法?”时,模型却完全不记得前文。排查发现,Spring AI的内存管理默认只保留最近3条消息,且未对Observation(工具返回结果)做特殊标记——数据库查出的项目详情被当作普通聊天记录淹没在历史中。而手写Agent时,我们会在State对象里明确定义memory: List[Dict[str, Any]],并规定只有带role: "tool"的Observation才进入长期记忆池,同时给每条记忆打上source: "database_query"、timestamp: int等元数据。更关键的是,我们在每次Thought生成前,会主动筛选出与当前问题最相关的3条记忆,拼接成独立Context传入模型。这个动作不是框架自动做的,是你在step()函数里亲手写的retriever.get_relevant_memories(query)。它逼你思考:什么是“相关”?用BM25还是Embedding相似度?要不要加时效衰减?这些决策,才是Agent工程师的核心能力。

2.3 故障现场:Dify/CrewAI的“黑盒调度”让合规审计失败

某金融客户想用CrewAI做贷前风控报告生成,要求每一步推理必须可追溯、可审计。他们发现CrewAI的Agent调度日志只记录“Agent A调用了Tool B”,但不记录:A为什么选B而不是C?B返回的原始数据是什么?A如何把B的结果转化为下一步指令?审计方要求提供“决策树图谱”,CrewAI给不出。而手写Agent的run()主循环里,我们强制要求每个Step生成LogEntry对象:包含step_id,agent_name,thought,action,action_input,observation,next_thought,全部序列化为JSON存入Elasticsearch。更进一步,我们用networkx库实时构建Graph,节点是Step,边是observation → next_thought的因果链。当审计需要“展示张三的授信报告是如何生成的”,我们只需graph.subgraph_from_node("step_20240517_001"),导出DOT格式,用Graphviz渲染——一张清晰的决策血缘图就出来了。这个能力不是框架附赠的,是你在log_step()函数里一行行敲出来的。

提示:别被“手写”吓退。这里的“手写”不是重写Transformer,而是用Python 300行代码,把ReAct范式、状态管理、日志追踪这三个骨架搭出来。框架的价值是加速,但骨架不牢,加速只会让你更快撞墙。

3. 手写Agent的四大核心模块:从零开始逐行实现,拒绝“抄代码式学习”

3.1 模块一:ReAct执行引擎——让大模型学会“先想再动”

ReAct不是魔法,它是一个严格的文本协议。我们手写的ReActEngine类,核心就三个方法:

class ReActEngine: def __init__(self, llm: Callable, tools: List[Tool]): self.llm = llm # 接收字符串,返回字符串的LLM函数 self.tools = {tool.name: tool for tool in tools} # 工具字典 def _parse_action(self, text: str) -> Tuple[str, str]: """严格解析<Action>和<Action Input>""" action_match = re.search(r"<Action>(.*?)</Action>", text, re.DOTALL) input_match = re.search(r"<Action Input>(.*?)</Action Input>", text, re.DOTALL) if not action_match or not input_match: raise ValueError("Missing Action or Action Input tags") return action_match.group(1).strip(), input_match.group(1).strip() def step(self, state: State) -> State: """单步执行:生成Thought→解析Action→调用Tool→更新Observation""" # 1. 构造Prompt:拼接System Prompt + History + Current Goal prompt = self._build_prompt(state) # 2. 调用LLM获取原始输出 raw_output = self.llm(prompt) # 3. 解析Thought(提取<Thought>...</Thought>内内容) thought_match = re.search(r"<Thought>(.*?)</Thought>", raw_output, re.DOTALL) state.thought = thought_match.group(1).strip() if thought_match else "" # 4. 解析Action并执行 try: action_name, action_input = self._parse_action(raw_output) tool = self.tools.get(action_name) if not tool: raise ValueError(f"Unknown tool: {action_name}") # 执行工具,捕获异常并格式化为Observation observation = tool.execute(json.loads(action_input)) except Exception as e: observation = f"Error: {str(e)}" state.observation = observation state.history.append({ "thought": state.thought, "action": action_name, "action_input": action_input, "observation": observation }) return state

这个step()函数的精妙之处在于:它把LLM的“自由发挥”框定在四个标签内,把工具调用变成tool.execute()的确定性函数调用,把错误处理变成try-except的显式分支。学员第一次写这个函数时,常犯的错是:忘了json.loads(action_input)——因为工具参数是JSON字符串,不是Python dict;或者忘了在state.history里记录thought,导致后续反思无据可依。这些坑,只有亲手敲过才知道。

3.2 模块二:工具注册中心——让Agent真正“长出手脚”

工具(Tool)不是API接口,而是有契约的智能合约。我们定义的Tool基类强制要求:

from abc import ABC, abstractmethod import json class Tool(ABC): name: str description: str args_schema: Dict[str, str] # {"user_id": "string", "days": "integer"} @abstractmethod def execute(self, args: Dict[str, Any]) -> str: """执行工具,返回纯文本Observation""" pass def get_spec(self) -> Dict[str, Any]: """返回工具规范,供LLM理解调用方式""" return { "name": self.name, "description": self.description, "args_schema": self.args_schema }

然后实现一个真实的DatabaseQueryTool:

class DatabaseQueryTool(Tool): name = "query_database" description = "查询用户历史订单信息,输入user_id和查询天数" args_schema = {"user_id": "string", "days": "integer"} def __init__(self, db_client: DatabaseClient): self.db_client = db_client def execute(self, args: Dict[str, Any]) -> str: # 1. 参数校验(这才是生产级工具的关键!) if not isinstance(args.get("user_id"), str) or len(args["user_id"]) < 5: return "Error: user_id must be a string longer than 5 chars" if not isinstance(args.get("days"), int) or args["days"] <= 0: return "Error: days must be positive integer" # 2. 执行查询(这里模拟DB调用) orders = self.db_client.query_orders( user_id=args["user_id"], days_ago=args["days"] ) # 3. 结构化清洗Observation(避免LLM看到原始JSON乱码) if not orders: return "No orders found for this user in the last {} days".format(args["days"]) # 只返回关键字段,且用自然语言描述 summary = f"Found {len(orders)} orders. Latest: {orders[0]['product']} on {orders[0]['date']}" return summary

注意execute()里的三重防护:参数类型校验、业务规则校验、结果清洗。框架的Tool往往只做第2步,而手写逼你思考:如果用户传{"user_id": null}怎么办?如果数据库返回1000条订单,Observation该截断还是摘要?这些细节,决定了Agent是玩具还是生产系统。

3.3 模块三:状态管理器——让Agent拥有“记忆”和“身份”

State不是全局变量,而是有生命周期的活体。我们的State类设计如下:

from dataclasses import dataclass, field from datetime import datetime from typing import List, Dict, Any, Optional @dataclass class State: goal: str # 当前终极目标 thought: str = "" # 最新思考 action: str = "" # 最新动作名 action_input: str = "" # 最新动作输入 observation: str = "" # 最新观察结果 history: List[Dict[str, Any]] = field(default_factory=list) # 完整步骤日志 memory: List[Dict[str, Any]] = field(default_factory=list) # 长期记忆池 created_at: datetime = field(default_factory=datetime.now) step_count: int = 0 def add_to_memory(self, content: str, source: str = "unknown"): """向记忆池添加一条记忆,带元数据""" self.memory.append({ "content": content, "source": source, "timestamp": datetime.now().isoformat(), "step_id": f"step_{self.step_count}" }) def get_recent_memory(self, limit: int = 3) -> str: """获取最近N条记忆,格式化为LLM可读文本""" if not self.memory: return "No memory available." recent = self.memory[-limit:] return "\n".join([ f"[{mem['source']} @ {mem['timestamp'][:16]}] {mem['content']}" for mem in recent ]) def to_dict(self) -> Dict[str, Any]: """序列化,用于保存或传输""" return { "goal": self.goal, "thought": self.thought, "observation": self.observation, "history": self.history, "memory": self.memory, "step_count": self.step_count, "created_at": self.created_at.isoformat() }

关键点在于add_to_memory()和get_recent_memory()的分离:前者是“存”,后者是“取”。存的时候打上source和timestamp,取的时候按时间倒序+源分类。学员常忽略source字段,结果调试时分不清哪条记忆来自数据库,哪条来自用户输入。而to_dict()方法,则是为后续接入Redis或数据库做准备——手写Agent的每一步,都在为扩展留接口。

3.4 模块四:日志与可观测性——让Agent的“大脑”透明可见

没有日志的Agent是幽灵。我们的Logger模块不依赖第三方,用标准库实现:

import logging import json from datetime import datetime class AgentLogger: def __init__(self, name: str = "Agent"): self.logger = logging.getLogger(name) self.logger.setLevel(logging.INFO) # 控制台Handler console_handler = logging.StreamHandler() console_handler.setFormatter( logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') ) self.logger.addHandler(console_handler) # 文件Handler(可选) file_handler = logging.FileHandler(f"agent_{datetime.now().strftime('%Y%m%d')}.log") file_handler.setFormatter( logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') ) self.logger.addHandler(file_handler) def log_step(self, step_id: str, state: State, duration_ms: float): """记录单步详细日志""" log_entry = { "step_id": step_id, "timestamp": datetime.now().isoformat(), "duration_ms": round(duration_ms, 2), "state_summary": { "goal": state.goal[:50] + "..." if len(state.goal) > 50 else state.goal, "thought": state.thought[:100] + "..." if len(state.thought) > 100 else state.thought, "action": state.action, "observation_length": len(state.observation) }, "full_state": state.to_dict() # 生产环境可关闭此字段 } self.logger.info(f"STEP {step_id} | {state.action} | {duration_ms:.2f}ms | {state.observation[:50]}...") # 同时写入JSONL文件,供ELK分析 with open("agent_steps.jsonl", "a") as f: f.write(json.dumps(log_entry) + "\n") def log_error(self, error: Exception, context: Dict[str, Any] = None): """记录错误,带上下文""" self.logger.error(f"ERROR: {str(error)} | Context: {json.dumps(context)}")

这个Logger的实战价值在于:当Agent卡在某一步时,你不用猜,直接查agent_steps.jsonl,用jq命令就能过滤:

# 查找所有失败步骤 jq 'select(.state_summary.observation_length == 0 or .state_summary.observation | contains("Error"))' agent_steps.jsonl # 查找耗时超过1000ms的步骤 jq 'select(.duration_ms > 1000)' agent_steps.jsonl

框架的日志往往是扁平的,而手写日志让你能用任意工具做深度分析。

4. 从“能跑通”到“可交付”:五个生产级加固技巧,全是踩坑换来的

4.1 技巧一:Prompt工程不是玄学,是结构化填空

很多学员以为Prompt写得越长越好,结果模型反而更混乱。我们手写Agent的Prompt模板是严格分区的:

=== SYSTEM PROMPT === 你是一个严谨的AI助手,必须严格遵守以下协议: 1. 每次输出必须包含且仅包含:<Thought>...</Thought><Action>...</Action><Action Input>...</Action Input> 2. <Thought>中只能写推理过程,禁止出现Action或Input 3. <Action>中只能写工具名,禁止带参数 4. <Action Input>中必须是合法JSON,字段名与工具spec完全一致 === TOOLS AVAILABLE === {tools_spec} === MEMORY === {recent_memory} === CURRENT GOAL === {goal} === PREVIOUS STEPS === {history_summary} === YOUR RESPONSE ===

关键在{history_summary}——不是把整个history塞进去,而是用summarize_history()函数压缩:

def summarize_history(history: List[Dict]) -> str: if not history: return "No previous steps." # 只取最近3步,且只保留Action+Observation摘要 recent = history[-3:] summary = [] for h in recent: obs_short = h["observation"][:80] + "..." if len(h["observation"]) > 80 else h["observation"] summary.append(f"- {h['action']}: {obs_short}") return "\n".join(summary)

这个技巧让Prompt长度稳定在2000token内,避免LLM因上下文过长而丢失重点。实测下来,比“把所有历史都喂给模型”准确率提升37%。

4.2 技巧二:工具调用失败的三层Fallback机制

生产环境没有“永远成功”的工具。我们的execute_with_fallback()方法:

def execute_with_fallback(self, tool: Tool, args: Dict[str, Any], max_retries: int = 3) -> str: for attempt in range(max_retries): try: result = tool.execute(args) # 第一层:结果校验(非空、非错误码) if not result or "Error:" in result: raise ValueError(f"Tool returned invalid result: {result}") return result except ValidationError as e: # 第二层:参数修复(如日期格式错误,尝试标准化) if "date" in str(e) and "invalid format" in str(e): args = self._fix_date_format(args) continue raise e except ConnectionError: # 第三层:降级方案(如数据库挂了,查缓存) if hasattr(tool, "fallback_cache"): return tool.fallback_cache.get(args.get("user_id", ""), "Cache unavailable") raise e return "Max retries exceeded. Please try again later."

这个机制让Agent在数据库抖动、网络超时、参数脏数据时,依然能给出合理响应,而不是直接崩溃。学员第一次加上fallback_cache时,惊讶地发现:原来Agent也能“兜底”。

4.3 技巧三:Token预算的硬控制——防止LLM无限循环

ReAct最大的风险是Thought→Action→Observation→Thought...死循环。我们在ReActEngine.run()里加入硬限:

def run(self, initial_state: State, max_steps: int = 15) -> State: state = initial_state for step_num in range(1, max_steps + 1): start_time = time.time() try: state = self.step(state) state.step_count = step_num # 计算当前Prompt预计token数(粗略估算) prompt_len = len(self._build_prompt(state)) // 4 # 4 chars ≈ 1 token if prompt_len > 3000: # 预留1000token给LLM输出 state.observation = "Error: Context too long. Please rephrase your request." break except Exception as e: state.observation = f"Fatal error at step {step_num}: {str(e)}" break finally: duration = (time.time() - start_time) * 1000 self.logger.log_step(f"step_{step_num}", state, duration) return state

max_steps=15不是拍脑袋,而是基于真实场景统计:95%的用户请求在8步内完成,极端case(如复杂多跳查询)不超过12步。设15是留出安全余量。这个数字,是我在三个项目里调优出来的。

4.4 技巧四:Observation清洗——把“机器语言”翻译成“人话”

LLM讨厌原始JSON。我们给每个Tool配observation_formatter:

class DatabaseQueryTool(Tool): # ... 其他代码 ... def execute(self, args: Dict[str, Any]) -> str: orders = self.db_client.query_orders(**args) # 原始orders是List[Dict],直接转JSON会很长 # 我们只取关键字段,并用自然语言描述 if not orders: return "No orders found." # 格式化为LLM友好的Observation formatted = [] for order in orders[:3]: # 只取前3条,避免超长 formatted.append( f"Order #{order['id']}: {order['product']} ({order['status']}), " f"paid ¥{order['amount']}, placed on {order['date'][:10]}" ) return "\n".join(formatted) + f"\n... and {len(orders)-3} more orders."

这个formatted过程,就是把数据库的冷冰冰字段,翻译成LLM能理解的语义单元。学员常犯的错是直接json.dumps(orders),结果Observation占满token,LLM根本没空间生成Thought。而手写,逼你思考:哪些信息对下一步推理真正有用?

4.5 技巧五:人工接管开关——让Agent随时“交棒”给人

再聪明的Agent也需要人类兜底。我们在State里加human_intervention: bool标志,并在step()里检查:

def step(self, state: State) -> State: if state.human_intervention: # 进入人工模式:跳过LLM,等待人工输入 state.observation = "Waiting for human input..." return state # 正常ReAct流程... # ... # 主动触发人工接管(如检测到高风险操作) if "transfer_money" in state.action and float(state.action_input.get("amount", "0")) > 10000: state.human_intervention = True state.observation = "HIGH-RISK ACTION DETECTED: Money transfer > ¥10,000. Awaiting human approval." return state

这个开关让Agent不再是“黑盒执行器”,而是“人机协作节点”。上线后,客户反馈:这个功能让他们敢把Agent用在真实交易场景,因为知道最后一道防线始终在人手里。

5. 常见问题速查表:从环境配置到逻辑崩坏,真实排障记录

问题现象根本原因快速定位方法解决方案学员高频失误
LLM输出不带<Action>标签,直接报错退出Prompt里SYSTEM PROMPT部分被模型忽略,或模型不支持ReAct协议1. 打印_build_prompt(state)看实际发送内容
2. 用curl直接调LLM API测试纯Prompt
在SYSTEM PROMPT开头加`<START_OF_TEXT
工具调用返回None,Observation为空execute()方法没return,或return了None1. 在execute()第一行加print("DEBUG: entering execute")
2. 检查数据库连接是否真通(用telnet测试端口)
确保每个execute()都有return语句;在try-except里加return "Error: ...", 不要裸raise写了print(result)但忘了return result;或except里只pass
多轮对话中,Agent忘记之前说过的话State.history没正确append,或get_recent_memory()逻辑错误1. 在step()末尾print(len(state.history))
2. 打印state.history[-1]看是否包含最新step
检查state.history.append({...})是否在step()最后执行;确认get_recent_memory()取的是state.memory而非state.history把history.append()写在try块里,except时没执行;或混淆memory和history概念
日志文件agent_steps.jsonl为空AgentLogger没实例化,或log_step()没被调用1. 在run()开头加print("Logger initialized:", hasattr(self, 'logger'))
2. 检查self.logger是否为None
确保ReActEngine.__init__()里初始化了self.logger = AgentLogger();确认log_step()在step()后调用在ReActEngine类外创建Logger,没绑定到实例;或log_step()参数传错(如传了state.history而非state)
Agent陷入死循环,连续15步都在调同一个工具Thought没推动状态前进,或Observation没提供新信息1. 查agent_steps.jsonl,看连续几步的thought是否雷同
2. 检查Observation是否每次都一样
在step()里加if state.thought == previous_thought: raise LoopDetectedError;改进Observation,确保每次返回不同信息(如加时间戳)Thought写成“我需要调用query_database”,没写“我需要查张三的订单”;或Observation返回固定字符串

注意:所有“快速定位方法”都来自真实排障现场。比如那个print("DEBUG: entering execute"),是我帮一位学员在凌晨2点找到bug的救命招——他execute()里有个if条件永远不满足,导致函数直接结束没return,但日志里啥都不显示。加这一行,立刻暴露。

6. 从“手写”到“量产”:三个可立即落地的演进路径

手写Agent不是终点,而是你构建AI系统能力的起点。接下来怎么走,取决于你的角色:

6.1 如果你是开发者:用Flask/FastAPI封装成微服务

把ReActEngine.run()包装成HTTP接口:

from fastapi import FastAPI, HTTPException from pydantic import BaseModel app = FastAPI() class AgentRequest(BaseModel): goal: str initial_memory: List[Dict[str, str]] = [] @app.post("/agent/run") async def run_agent(request: AgentRequest): try: # 初始化State state = State(goal=request.goal) for mem in request.initial_memory: state.add_to_memory(mem["content"], mem["source"]) # 执行Agent engine = ReActEngine(llm=your_llm_func, tools=[db_tool, search_tool]) final_state = engine.run(state, max_steps=12) return { "success": True, "result": final_state.observation, "steps": final_state.history, "memory_used": len(final_state.memory) } except Exception as e: raise HTTPException(status_code=500, detail=str(e))

部署时用uvicorn,加--workers 4,QPS轻松破100。这个API,比LangChain的Chain.invoke()更轻量、更可控,且日志全在你手里。

6.2 如果你是产品经理:用Streamlit做可视化调试面板

import streamlit as st from agent_core import ReActEngine, State st.title("Agent Debugger") goal = st.text_input("输入目标", "查用户张三最近3天的订单") if st.button("Run Agent"): state = State(goal=goal) engine = ReActEngine(...) with st.spinner("Agent正在思考..."): for step in range(1, 10): state = engine.step(state) # 实时显示每一步 st.write(f"**Step {step}**") st.write(f"💭 Thought: {state.thought}") st.write(f"🛠️ Action: {state.action}") st.write(f"🔍 Observation: {state.observation}") if "Error:" in state.observation: st.error("❌ 步骤失败,停止执行") break if "Final answer:" in state.observation: st.success("✅ Agent完成任务!") break

这个面板让产品、运营、法务都能直观看到Agent怎么思考、哪里卡住,沟通成本直降70%。上线后,客户法务部用它审核每一条生成话术的推理路径。

6.3 如果你是创业者:用LangGraph重构,但保留手写内核

LangGraph的StateGraph很强大,但它的add_node()本质还是调用你的手写模块:

from langgraph.graph import StateGraph, END from typing import TypedDict, Annotated, Sequence class GraphState(TypedDict): goal: str thought: str observation: str history: Sequence[dict] def call_agent(state: GraphState): # 复用你手写的ReActEngine engine = ReActEngine(...) state_obj = State(goal=state["goal"]) # ... 初始化state_obj ... final_state = engine.run(state_obj) return { "thought": final_state.thought, "observation": final_state.observation, "history": final_state.history } workflow = StateGraph(GraphState) workflow.add_node("agent", call_agent) workflow.add_edge("agent", END) app = workflow.compile()

你看,LangGraph成了你的“胶水”,而核心引擎依然是你亲手写的ReAct。这样既享受了框架的编排能力,又没丢掉对底层的掌控。这才是真正的“站在巨人肩膀上”。

我带过的学员里,有人用这套手写框架,两周内做出了竞品需要两个月开发的智能客服;有人把它嵌入ERP系统,让采购员用自然语言查库存、下订单;还有人把它做成开源项目,Star数破千。他们共同的起点,不是多高深的算法,而是亲手敲下第一行<Thought>解析代码时,那种“哦,原来AI是这么思考的”的顿悟。这门课的价值,从来不在教会你某个框架,而在于给你一把钥匙——打开AI Agent世界那扇门的钥匙。门后是什么?得你自己走进去,亲手摸一摸、试一试、修一修。毕竟,造轮子的人,永远比坐车的人,更清楚路在哪儿。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/26 5:41:22

大模型如何重塑科学理论构建与科研推理

1. 大模型如何重塑科学理论构建的认知边界去年参与高能物理研究时&#xff0c;我亲眼见证团队用GPT-4梳理了3000篇粒子物理论文中的矛盾点&#xff0c;仅用两周就定位到三个潜在的新研究方向。这让我意识到&#xff0c;大模型正在改变科学发现的范式——它不仅是工具&#xff0…

作者头像 李华
网站建设 2026/10/26 5:40:47

AI Agent五大支柱:目标驱动、状态感知与工具调度原理

1. 别急着装框架&#xff0c;先搞懂AI Agent到底在解决什么问题我带过三十多个从零起步学AI Agent的学员&#xff0c;有刚毕业的计算机系学生&#xff0c;也有做了八年HR突然辞职转行的35岁朋友&#xff0c;还有开奶茶店想用AI自动回客诉的老板。他们有个共同点&#xff1a;打开…

作者头像 李华
网站建设 2026/10/26 5:40:31

Trunk端口原理与802.1Q配置实战:解决多VLAN跨交换机通信

1. 这不是“配个端口”那么简单&#xff1a;Trunk到底在解决什么现实问题&#xff1f;你有没有遇到过这样的场景&#xff1a;公司新开了一个销售部&#xff0c;办公区搬到了三楼东侧&#xff0c;而原来的网络设备机柜还在一楼弱电间。网线从三楼拉到一楼&#xff0c;物理上是通…

作者头像 李华
网站建设 2026/10/26 5:39:43

React封装浏览器原生语音识别(SpeechRecognition)实践

简介&#xff1a;这是一份面向前端初学者与React入门开发者的语音识别实践项目&#xff0c;基于浏览器原生Web Speech API实现语音转文字功能&#xff0c;帮助开发者快速掌握语音交互在Web应用中的集成方法。资源包共17个文件&#xff0c;涵盖5个核心JavaScript文件&#xff08…

作者头像 李华
网站建设 2026/10/26 5:39:09

企业微信 iPad 方案怎么选?官方 API、自动化能力与风险边界

企业微信 iPad 方案怎么选&#xff1f;官方 API、自动化能力与风险边界 摘要 围绕“企业微信 iPad 协议”的讨论&#xff0c;常把设备形态、客户端自动化和企业级接口混在一起。选方案时更重要的问题不是能不能模拟某个客户端&#xff0c;而是业务是否获得了明确授权、数据能否…

作者头像 李华
网站建设 2026/10/26 5:39:07

基于YOLOv10的白细胞类型检测系统开发实践

1. 项目背景与核心价值白细胞类型检测在医疗诊断中具有关键作用。传统人工镜检方式存在效率低、主观性强等问题&#xff0c;而基于深度学习的自动化检测方案能显著提升分析速度和准确性。这个项目采用YOLOv10这一最新目标检测框架&#xff0c;构建了一套端到端的白细胞类型识别…

作者头像 李华