CFzjy AI Engineer |
|
| 「 让 AI 交互更智能,让技术落地更高效 」|欢迎技术探讨 / 项目合作,关注解锁大模型与智能交互的无限可能! | |
从 LangChain 到 DeepSeek Harness:Agent 框架迁移的思维切换与概念映射对照
📚《DeepSeek Harness 插件架构》连载专栏 · 第 12 篇
很多做 Agent 的同学是从 LangChain 入门的,现在公司项目要迁到 DeepSeek Harness(DSH),第一反应是"把 LangChain 代码一行行翻译过去"。这个直觉恰恰是最大的坑——LangChain 和 DSH 是两套不同的思维范式:LangChain 让你"把组件串成链",DSH 让你"把能力写成插件"。硬翻译代码只会水土不服,正确的迁移是先换脑子,再做概念映射,最后落地对照。这一篇就按这个顺序讲。
痛点一:把 LangChain 的 Chain 直接搬成 DSH 的"链"。结果写出一堆过程式编排,丢了 DSH 声明式装配的威力。
痛点二:概念对不上号,不知道 Memory、Callback、OutputParser 在 DSH 里叫什么。每个词都要重新找一遍。
痛点三:迁移后行为"怪怪的",说不清哪里不对。因为还抱着"我自己写 agent 循环"的惯性,没意识到 DSH 的循环是运行时驱动的。
网上讲 DSH 迁移的资料很少,本文给一张完整的概念映射表 + 三个落地场景对照 + 五个思维切换。读完你会拿到:LangChain 每个核心概念在 DSH 里的对应物、工具/记忆/回调三个场景的迁移代码对照、以及一张迁移避坑速查表。想追完整系列(下一篇:发布全链路 npm/市场 PR),可以先关注我的 CSDN 主页。
文章目录
- 从 LangChain 到 DeepSeek Harness:Agent 框架迁移的思维切换与概念映射对照
- 一、先换脑子:编排 vs 插件
- 二、概念映射表:LangChain → DSH 一一对应
- 三、三个落地场景的代码对照
- 3.1 场景一:工具调用
- 3.2 场景二:记忆 / 会话
- 3.3 场景三:回调 / 可观测
- 四、五个思维切换:迁移最容易翻车的地方
- 五、迁移避坑指南:高频问题与一键解决速查表
- 结语
一、先换脑子:编排 vs 插件
LangChain 的核心思维是编排(orchestration):把一个个可运行单元(Runnable)用 LCEL 的管道符串成一条链。
# LangChain:用管道把组件串成链fromlangchain_core.promptsimportChatPromptTemplatefromlangchain_core.output_parsersimportStrOutputParser chain=prompt|model|output_parser# 一条显式的链result=chain.invoke({"topic":"MCP"})DSH 的核心思维是插件化(pluginization):你写的是一个个独立插件(name/inject/apply),运行时根据依赖关系声明式装配它们,agent 循环由框架驱动,你从不自己写while循环。
# DSH:cordis.yml 声明装配哪些插件,顺序和依赖由框架处理-name:'@deepseek-ai/dsh-tool-bash'-name:'@deepseek-ai/dsh-tool-fs'-name:'./my-company-tool.ts'# 你自己的插件用一个类比:LangChain 像"你亲手串一串糖葫芦"——每一步(山楂、糖衣、竹签)都自己捏、自己串,顺序和流程完全在你的控制里;DSH 像"插积木"——每块积木(插件)自带接口(inject/apply),你只管把需要的积木插到底座上,底座(运行时)负责让它们协同工作。串糖葫芦灵活但每一步都要手写,插积木规整但换块积木零成本。
二、概念映射表:LangChain → DSH 一一对应
这是本篇最实用的东西,先收藏。左侧是你熟悉的 LangChain 概念,右侧是 DSH 里的对应物:
| LangChain 概念 | DSH 对应物 | 关键差异 |
|---|---|---|
| Chain(LCEL 管道) | 插件组合(cordis.yml) | 过程式串链 vs 声明式装配 |
| Runnable(invoke/stream) | Fiber(插件实例的 apply) | 类接口 vs 副作用+依赖注入 |
@tool装饰器 | defineTool+ctx.tools.register | 独立函数 vs 注册进服务容器 |
| AgentExecutor(agent 循环) | ctx.agents+agent/assistant-stream | 你自己写循环 vs 框架驱动循环 |
| Memory(内存记忆) | session/event+ctx.sessions | 进程内对象 vs 持久化事件流 |
| CallbackHandler | ctx.on事件监听 | 回调函数 vs 五分发模式事件 |
| OutputParser | output.schema+output.render | 事后解析 vs 声明式契约 |
| Retriever / VectorStore | 插件 +ctx.tools.register(或 MCP) | 类库调用 vs 工具注册 |
Model wrapper(ChatOpenAI) | ctx.llm.registerAdapter | 直接实例化 vs 注册适配器 |
| Agent state / scratchpad | agent.inject()/ctx.goals | 手动管理 vs 持久化上下文注入 |
这张表的价值不在"记住名词",在于看出范式差异的规律:LangChain 的每一项都是"一个你要自己实例化、自己调用的对象",DSH 的每一项都是"一个你要注册进容器、由框架调度的副作用"。从"对象调用"到"注册副作用",就是迁移的核心切换。
三、三个落地场景的代码对照
3.1 场景一:工具调用
LangChain 里一个工具是一个独立函数:
# LangChain:@tool 装饰一个函数fromlangchain_core.toolsimporttool@tooldefread_file(path:str)->str:"""Read a file from disk."""returnopen(path).read()DSH 里同一个工具是注册进ctx.tools的副作用(第 08 篇的完整契约):
importtype{Context}from'@deepseek-ai/cordis'import{defineTool}from'@deepseek-ai/dsh-tools'import{readFile}from'node:fs/promises'exportconstname='read-file-tool'exportconstinject=['tools']exportfunctionapply(ctx:Context){ctx.tools.register(defineTool({name:'read_file',description:'Read a file from disk.',// 模型看到的说明书parameters:{path:{type:'string',required:true,description:'Absolute path'}},output:{schema:{type:'string'},render:(_args,value)=>[{type:'text',text:value}]},asyncexecute(args,exec){returnreadFile(args.path,{encoding:'utf8',signal:exec.signal})},}))}看出关键差异:LangChain 的工具是"函数 + docstring 当描述",DSH 的工具是"契约四件套"——描述、参数 schema、输出 schema、执行主体,且exec.signal让工具能被取消/超时真正终止。DSH 把工具的"正确用法"全写成了契约,而不只是给函数贴个标签。
3.2 场景二:记忆 / 会话
LangChain 用 Memory 对象在进程内攒对话历史:
# LangChain:进程内记忆对象fromlangchain.memoryimportConversationBufferMemory memory=ConversationBufferMemory()# 进程重启就丢DSH 的会话是持久化的事件流,用session/event记录,可回放、可跨进程:
importtype{Context}from'@deepseek-ai/cordis'exportfunctionapply(ctx:Context){// 监听持久化会话事件:助手结算、轮次边界、工具活动ctx.on('session/event',(_session,event)=>{if(event.type==='assistant/message'){// 持久化 + 可回放,而不是进程内一个数组}})}第 10 篇官方文档反复出现 “KV Cache 影响 / Token 影响 / 回放” 这些词,根源就在这:DSH 把"会话状态"当作一等公民的持久化事件流,而不是一个易失的内存对象。迁移后你会天然获得会话回放、跨进程续聊、上下文压缩这些 LangChain 里要自己拼的能力。
3.3 场景三:回调 / 可观测
LangChain 用 CallbackHandler 在关键节点挂钩子:
# LangChain:回调类fromlangchain_core.callbacksimportBaseCallbackHandlerclassMyHandler(BaseCallbackHandler):defon_tool_start(self,tool,input_str,**kwargs):print(f"tool start:{tool.name}")DSH 用类型化事件 + 五分发模式(第 04 篇),同一个"在工具执行前干点事"的需求:
importtype{Context}from'@deepseek-ai/cordis'exportfunctionapply(ctx:Context){ctx.on('tools/pre-execute',async(exec,next)=>{console.log(`tool${exec.name}about to run`)returnnext()// 观察者必须 next,让链路继续})}差异在可组合性:LangChain 的回调是"你传一个 handler 列表",DSH 的事件是"多个插件各自监听,用 emit/waterfall/bail 等模式协同"。想加一个"拒绝某工具的权限门禁",不用改任何现有代码,写一个新插件监听tools/pre-execute即可——这是第 10 篇"策略外置"思想的直接应用。
四、五个思维切换:迁移最容易翻车的地方
切换一:从"写链"到"写插件"。不要再用管道串业务流程,而是问"这段能力拆成几个插件、各自 inject 什么"。链是过程式的、一次性的;插件是可复用、可组合、可热替换的。
切换二:从"类继承"到"副作用 + 依赖注入"。LangChain 里你继承Runnable、BaseCallbackHandler;DSH 里你导出apply(ctx),用inject声明依赖,用ctx.effect管理生命周期。没有继承树,只有注册副作用。
切换三:从"回调"到"事件"。回调是"调用方决定什么时候触发";事件是"多个监听者按分发模式协同"。观察者必须next(),决策者可以短路(bail/waterfall)。
切换四:从"内存记忆"到"持久会话"。别再往进程内塞历史数组,交给session/event持久化。你会白得回放、压缩、跨进程续聊。
切换五:从"单体 agent"到"能力分层"。LangChain 习惯把工具、记忆、回调全塞在一个 agent 脚本里;DSH 按第 10 篇的"能力分层"把它们拆成 Definition / Provider / Consumer,各自独立演进。
五、迁移避坑指南:高频问题与一键解决速查表
| 序号 | 遇到的报错或现象 | 背后的真正原因 | 一句话解决办法 |
|---|---|---|---|
| 1 | 迁移后行为怪,说不清 | 还抱着"自己写 agent 循环"的惯性 | 循环交给运行时,你只写插件 |
| 2 | 想迁移 Chain,发现没"链"这个概念 | 范式是编排 vs 插件 | 把链拆成插件,cordis.yml 装配 |
| 3 | 工具在 DSH 里不被模型正确调用 | description 写太含糊,没进提示词 | 学 DSH 工具契约四件套,description 是说明书 |
| 4 | 记忆进程重启就丢 | 还在用进程内 Memory 对象 | 用 session/event 持久化事件流 |
| 5 | 回调逻辑散落各处难维护 | 没把回调升级成事件 | 用 ctx.on + 分发模式,插件各自监听 |
| 6 | 输出解析和业务耦合 | 用 OutputParser 事后解析 | 用 output.schema + render 声明契约 |
| 7 | 迁移后权限/审计没地方加 | 把策略写进了工具主体 | 用 tools/pre-execute 等扩展点外置策略 |
这张表的心法回到本篇开头:迁移不是翻译代码,是切换范式。LangChain 给你"对象和链",DSH 给你"插件和事件";前者灵活但你要自己扛循环、记忆、回放、策略,后者规整但框架帮你扛。想清楚这一点,迁移就从"逐行翻译"变成了"逐能力映射"。
结语
这一篇把 LangChain → DSH 的迁移拆成了三层:换脑子(编排 vs 插件的范式差异)、概念映射(一张 10 行的对照表)、落地对照(工具/记忆/回调三个场景的代码对比),最后落在五个思维切换上。核心一句话:LangChain 让你"串对象",DSH 让你"注册副作用"——从"对象调用"到"注册副作用"的切换,比任何一行代码的翻译都重要。
知识要点文字回顾:① 范式差异 = 编排(串链) vs 插件(声明式装配);② 概念映射:Chain→cordis.yml、@tool→defineTool、Memory→session/event、Callback→ctx.on、OutputParser→output.schema+render、AgentExecutor→ctx.agents;③ 工具契约四件套 vs 函数+docstring;④ 记忆从进程内对象升级为持久化事件流;⑤ 五个切换:写插件、副作用+DI、事件、持久会话、能力分层。
下一篇连载预告:《DSH 插件发布全链路:npm 打包、插件市场 PR 与用户安装一条龙》——把前十一篇的成果交付出去,关注 CSDN 主页 不错过更新。
你从 LangChain 迁到过别的 Agent 框架吗?最卡你的是哪个概念对不上号?评论区聊聊,我挨个回。本文 LangChain 侧基于其公开文档与通用用法,DSH 侧基于官方文档(2026-10 版本),有出入评论区提醒我。
参考资料(正文数据来源):LangChain 公开文档(LCEL、Tools、Memory、Callbacks、Output Parsers);DeepSeek Harness 官方文档《实操手册:扩展插件形态》《工具编写参考》《插件与生命周期》。