ARTICLE DETAIL

资讯详情

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

LangGraph 大模型智能体实战:用 Deep Agent 与 Agent Skills 做上下文工程

LangGraph 大模型智能体实战:用 Deep Agent 与 Agent Skills 做上下文工程 1. 从一次多步推理翻车说起LangGraph 大模型智能体上下文工程到底难在哪先说一个我踩过的坑。去年底我做一个「竞品调研助手」需求很朴素给一个品类让它自己去搜资料、比价格、读评论、最后输出三件推荐。用 LangGraph 搭了个 ReAct 循环工具也就五六个本地跑单步 demo 一切正常。可一旦把任务拉长到十几步问题全冒出来了模型开始忘记前面搜过什么重复调用同一个搜索工具上下文越堆越长到第八九轮的时候响应明显变慢偶尔还直接报context length exceeded更离谱的是中间某一步工具返回了一个超长的 HTML把后面所有推理都带偏了。这就是典型的上下文工程Context Engineering没做好。很多人以为 LangGraph 大模型智能体的难点在「怎么把节点连起来」其实连图是最简单的一步。真正决定一个智能体能不能跑长任务、能不能稳定多步推理的是上下文怎么组织、怎么裁剪、怎么在节点之间传递。我后来把架构换成了 Deep Agent 的思路主智能体负责规划和调度子智能体负责隔离执行文件系统负责把大块上下文「卸载」出去再用 Agent Skills 把业务知识做成按需加载的模块。改完之后同一个调研任务从「跑八步就崩」变成「连续跑四十多步还能收敛」。这篇文章就把这条链路完整拆给你包含可复制的 Agent Skills 配置片段、Deep Agent 节点编排示例以及上下文裁剪和状态传递的验证动作目标是让你在本地跑通一条完整链路。适合谁看已经会用 LangGraph 写基础 StateGraph、但一遇到长任务就翻车的同学想搞清楚 Deep Agent 和 Agent Skills 到底解决什么问题、而不是只停留在概念的同学以及需要把业务知识「丝滑」塞进智能体、又不想搞一套笨重 RAG 的同学。核心检索词先摆在这LangGraph 大模型智能体上下文工程本质就是通过合理的上下文组织让模型在 Test-Time 多消耗一些 Token去换更难的题目和更高的准确率。下面从环境准备开始。2. TaoToken 前置准备给 LangGraph 智能体接一个稳定的模型入口在写任何节点之前得先解决模型调用这一环。LangGraph 本身不绑定模型它通过 LangChain 的init_chat_model或ChatOpenAI这类封装去调后端。本地开发最烦的就是模型入口不稳定、Key 管理混乱、不同模型切换要改一堆代码。我的做法是统一走一个兼容 OpenAI 协议的入口把 Base URL、Key、Model ID 三件套固定下来后面所有节点都复用同一个 client。先拿 Key。打开 https://taotoken.net/api-keys 登录后在控制台创建 API Key复制出来形如sk-xxxx的字符串。注意这个 Key 只在创建时完整显示一次丢了就得重建。拿到之后不要硬编码进代码用环境变量管理export TAOTOKEN_API_KEYsk-你的key export TAOTOKEN_BASE_URLhttps://taotoken.net/api如果你更习惯用配置文件可以在项目根目录建一个.env配合python-dotenv加载# .env TAOTOKEN_API_KEYsk-你的key TAOTOKEN_BASE_URLhttps://taotoken.net/api然后在 Python 里这样初始化模型。这里我用init_chat_model因为它对多模型切换最友好import os from dotenv import load_dotenv from langchain.chat_models import init_chat_model load_dotenv() model init_chat_model( gpt-5.1, model_provideropenai, base_urlos.environ[TAOTOKEN_BASE_URL], api_keyos.environ[TAOTOKEN_API_KEY], temperature0, )如果你用的是ChatOpenAI写法等价from langchain_openai import ChatOpenAI llm ChatOpenAI( modelgpt-5.1, base_urlos.environ[TAOTOKEN_BASE_URL], api_keyos.environ[TAOTOKEN_API_KEY], temperature0, )这里有个细节值得说base_url一定要带上/api这一段很多人只写到域名结果请求 404。另外temperature在智能体场景建议设 0 或很低因为工具调用需要确定性温度高了模型容易「自由发挥」乱调工具。模型入口搞定后先做一次最小连通性验证别等到图搭完才发现 Key 是错的from langchain.messages import HumanMessage resp llm.invoke([HumanMessage(content只回复两个字通了)]) print(resp.content)看到「通了」就说明入口没问题。这一步花两分钟能省掉后面半小时的排查。关于模型对话的更多用法可以看 https://taotoken.net/models 这个页面里面有各模型的调用示例。如果你后面要做长期编码类 Agent也可以了解下 Coding Planhttps://taotoken.net/coding-plan 。3. 可复制配置Agent Skills 目录结构与 Deep Agent 节点编排这一节是全文的核心给你两样可以直接抄的东西一套 Agent Skills 的目录与SKILL.md配置一段 Deep Agent 的节点编排代码。3.1 Agent Skills 的渐进式披露结构Agent Skills 最优雅的地方在于「渐进式披露」启动时只把每个技能的name和description预加载进系统提示模型判断相关了才去读完整的SKILL.md更细的内容再放到附加文件里按需读取。这样上下文窗口不会被一次性塞爆。目录结构长这样skills/ └── price-compare/ ├── SKILL.md ├── reference.md └── scripts/ └── extract_price.pySKILL.md必须以 YAML Frontmatter 开头name和description是必填项--- name: price-compare description: 当用户需要跨电商平台比价、汇总评论并给出推荐时使用。输入商品关键词与预算输出结构化比价结果。 --- # 比价技能 ## 使用步骤 1. 调用 search 工具在目标平台检索商品关键词由用户预算和品类组合而成。 2. 对每个候选商品调用 fetch_detail 获取价格与评论摘要。 3. 调用 scripts/extract_price.py 做价格归一化避免手工解析。 4. 按「价格 评论情感 品牌」三维打分输出前三名。 ## 注意事项 - 预算上限必须严格过滤超过预算的商品直接丢弃。 - 评论少于 50 条的商品标记为「样本不足」不进入推荐。 - 详细字段说明见 reference.md。reference.md放字段定义这类「只在需要时才看」的内容# 字段说明 | 字段 | 类型 | 说明 | | --- | --- | --- | | price | float | 归一化后的到手价单位元 | | rating | float | 平台评分0-5 | | review_count | int | 评论总数 | | sentiment | str | positive / neutral / negative |scripts/extract_price.py是确定性代码模型可以只运行它、不把脚本内容读进上下文import re def normalize_price(raw: str) - float: 把 1,299.00 这类字符串归一化成 1299.0 cleaned re.sub(r[^\d.], , raw) return float(cleaned) if cleaned else 0.0 if __name__ __main__: import sys print(normalize_price(sys.argv[1]))这套结构的好处是主提示里只有一行price-compare: 当用户需要跨电商平台比价...模型判断相关后才读SKILL.md需要字段细节才读reference.md。上下文占用是分级的不是一次性全灌进去。3.2 Deep Agent 节点编排Deep Agent 的四大支柱是 Planning、Sub-Agents、File System、System Prompt。用create_deep_agent时TodoList、Filesystem、SubAgent 会自动挂到智能体上。下面这段代码演示主智能体只做规划、把算术子任务分发给三个子智能体from langchain.tools import tool from langchain.chat_models import init_chat_model from langchain.messages import HumanMessage from deepagents import create_deep_agent model init_chat_model( gpt-5.1, model_provideropenai, base_urlhttps://taotoken.net/api, api_keysk-你的key, temperature0, ) tool def multiply(a: int, b: int) - int: Multiply a and b. return a * b tool def add(a: int, b: int) - int: Adds a and b. return a b tool def divide(a: int, b: int) - float: Divide a and b. return a / b def make_arithmetic_subagent(name, tool_fn, operation_hint, result_word): return { name: name, description: fHandles {operation_hint} requests with high accuracy., system_prompt: ( fYou are solely responsible for {operation_hint}. fCall the attached tool exactly once per request. fReturn: Result: number Reasoning: 10-word explanation. ), tools: [tool_fn], } system_prompt ( You orchestrate arithmetic workloads. Do NOT call math tools directly. Delegate via task for each primitive. Summarize the final answer plainly. ) subagents [ make_arithmetic_subagent(add-subagent, add, addition, sum), make_arithmetic_subagent(multiply-subagent, multiply, multiplication, product), make_arithmetic_subagent(divide-subagent, divide, division, quotient), ] agent create_deep_agent( modelmodel, tools[], system_promptsystem_prompt, subagentssubagents, ) messages [HumanMessage(content把 6 4 的结果乘以 3再除以 2告诉我最终数字。)] result_state agent.invoke({messages: messages}) for message in result_state[messages]: message.pretty_print()注意主智能体的tools[]它不直接碰数学工具全靠task分发给子智能体。这就是上下文隔离每个子智能体在自己的上下文里算完只把「Result: 10」这种高度综合的结果返回主智能体主上下文不会被中间过程污染。3.3 上下文裁剪与状态传递长任务里状态设计决定了上下文会不会失控。参考邮件客服 Agent 的状态定义只存「无法重建」和「重建成本高」的东西from typing import TypedDict, Literal class EmailClassification(TypedDict): intent: Literal[question, bug, billing, feature, complex] urgency: Literal[low, medium, high, critical] topic: str summary: str class EmailAgentState(TypedDict): email_content: str sender_email: str email_id: str classification: EmailClassification | None search_results: list[str] | None customer_history: dict | None draft_response: str | None messages: list[str] | None关键原则存原始数据不存格式化文本。比如search_results存的是原始文档块列表而不是拼好的长字符串。格式化在节点内部按需做用完即弃不留在状态里。这样状态体积可控checkpointer 序列化也快。4. 验证请求跑通一条完整链路并观察上下文变化配置写完了得验证它真的能跑。分三步先验证 Skills 能被触发再验证 Deep Agent 的子任务分发最后验证上下文裁剪是否生效。4.1 验证 Skills 触发把skills/目录挂给智能体后发一条会命中price-compare的消息观察模型是否先读SKILL.md。你可以用 Bash 工具读取的日志来确认# 伪代码观察工具调用序列 for msg in result_state[messages]: if hasattr(msg, tool_calls) and msg.tool_calls: for call in msg.tool_calls: print(call[name], call[args])预期看到类似read_file {path: skills/price-compare/SKILL.md}的调用说明渐进式披露的第一级元数据成功触发了第二级正文加载。如果模型直接开始瞎搜、没读 SKILL.md说明description写得不够「可判别」回去把触发条件写具体。4.2 验证 Deep Agent 分发跑 3.2 那段代码预期输出里能看到主智能体连续发出三个task调用分别指向add-subagent、multiply-subagent、divide-subagent然后每个子智能体返回一条Result: ...。实测下来主智能体的上下文里只有这三条综合结果没有中间的计算细节这就是上下文隔离在起作用。4.3 验证上下文裁剪在状态里加一个计数器观察每轮之后messages的长度config {configurable: {thread_id: ctx-test-001}} result agent.invoke({messages: messages}, config) print(消息条数:, len(result[messages]))如果消息条数随步数线性暴涨说明裁剪没生效大概率是你把工具原始返回直接塞进了messages。正确做法是工具节点只返回ToolMessage大块内容写进文件系统状态里只留引用。4.4 验证中断与恢复长任务常需要人工介入。用interrupt在关键节点暂停再用Command(resume...)恢复from langgraph.types import Command human_response Command( resume{approved: True, edited_response: 已确认继续执行。} ) final_result agent.invoke(human_response, config) print(恢复执行完成)注意interrupt被调用时整个函数会重新执行所以中断点之前的副作用要幂等。这是很多人第一次用会踩的坑。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth跑链路时最容易撞的几个报错我按出现频率排一下每个都给定位思路。401 Unauthorized。九成是 Key 没读到或写错了。先确认环境变量真的加载了print(os.environ.get(TAOTOKEN_API_KEY))。如果打印出None说明.env没被load_dotenv()加载或者变量名拼错。还有一种情况是 Key 复制时带了空格或换行strip 一下再传。local proxy failed / connection error。这类通常是base_url写错。检查是不是漏了/api或者多写了斜杠。正确值是https://taotoken.net/api。另外确认本机网络能正常访问该域名公司内网有时会拦。reading choices of undefined。这个报错说明返回体结构和你预期的不一样通常是请求根本没成功、返回的是错误 JSON但代码直接去取response.choices[0]。加一层防御data resp.model_dump() if choices not in data: raise RuntimeError(f异常返回: {data})定位到真实错误信息后多半还是 Key 或 base_url 的问题。OAuth / 认证相关报错。如果你用的是某些 CLI 工具比如 Claude Code 类它可能走的是 OAuth 流程而不是 API Key。这时候要在工具的配置里显式指定 Base URL、Key、Model ID 三件套。以settings.json为例{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的key, ANTHROPIC_MODEL: claude-sonnet-4-5 } }三件套缺一不可Base URL 决定请求打到哪Key 决定身份Model ID 决定用哪个模型。少任何一个都会报认证或模型不存在的错。如果你用 Codex 类工具对应的是auth.json{ base_url: https://taotoken.net/api, api_key: sk-你的key, model: gpt-5.1 }工具调用死循环。模型反复调同一个工具通常是should_continue判断逻辑太松或者工具返回内容里带了误导性文本。加一个最大轮次保护def should_continue(state): if state.get(llm_calls, 0) 15: return END last state[messages][-1] return tool_node if last.tool_calls else END子智能体返回空。检查子智能体的system_prompt是否要求了明确的返回格式。如果没规定「Result: 」这种结构主智能体可能拿到一段废话无法继续。格式约束是子智能体稳定性的关键。排障时如果拿不准是入口问题还是代码问题最快的办法是先用最小请求验证入口再逐步加复杂度。接入文档在 https://taotoken.net/doc 里面有各语言的完整示例。6. 把链路跑长从能跑到跑得稳的工程习惯最后聊几个让链路「跑得久」的工程习惯都是我在实际项目里验证过的。第一状态只存不可重建的东西。原始邮件、分类结果、草稿回复这些丢了就没了必须进状态搜索结果、客户数据能重新拉但成本高也进状态至于格式化后的提示词文本永远在节点内部现拼不进状态。这条原则能砍掉一大半上下文膨胀。第二大块内容一律卸载到文件系统。工具返回的长文档、HTML、日志写进文件状态里只留路径。模型需要时用read_file按需读不需要就不占上下文。这就是 File System 作为「上下文卸载区」的价值。第三子智能体只回传综合结果。子智能体的中间推理过程对主智能体是噪音让它自己消化完只返回结论。这样主上下文能一直保持精简KV cache 也能更好复用省钱又提速。第四给每个长任务设轮次上限和超时。再好的规划也可能跑偏硬性上限是最后的安全网。配合RetryPolicy处理瞬时失败from langgraph.types import RetryPolicy workflow.add_node( search_documentation, search_documentation, retry_policyRetryPolicy(max_attempts3), )第五Skills 的 description 要写成「触发条件」而不是「功能描述」。写「跨平台比价并推荐」比写「比价工具」更容易被正确触发。这是渐进式披露第一级能不能命中的关键。把这几条落地你的 LangGraph 大模型智能体就能从「跑八步崩」进化到「跑四十步稳」。上下文工程不是玄学它就是一套关于「什么该进上下文、什么该卸载、什么该隔离」的工程纪律。链路跑通之后你可以继续往 Skills 里加业务模块每加一个都是独立文件夹复制给同事就能复用这才是 Agent Skills 最舒服的地方。
返回列表