最近一段时间,我一直在折腾基于大语言模型的自动化工作流,手头两个主力模型分别是DeepSeek和Qwen,另外搭了一套自己写的Agent框架。被朋友问得最多的一个问题是:同样做Agent,到底该选哪一个?我一般会先反问一句:你的Agent主要做长文档总结,还是要频繁调用工具去完成多步任务?这两个答案对应的选型完全不同。
这篇记录主要围绕选型、Agent框架设计、实测踩坑和工程化改造四个部分展开,不会给你一份排行榜,只会告诉你我实际跑下来之后,哪些环节值得花时间、哪些坑可以提前绕开。如果你正准备把LLM接入自己的业务工作流,或者已经在用大模型API但总觉得输出不稳定、只知道堆提示词,那这篇文章应该能给你一些可以直接抄作业的参考。
1. 选型先想清楚:DeepSeek和Qwen分别解决什么问题
1.1 两条模型路线的本质差异
先说我对DeepSeek的直观感受。它在数学推理、代码生成和超长文本理解这几块表现相当突出,尤其是长上下文版本,处理那种“丢进来几十页材料,最后只要一段结论”的任务特别合适。我拿它处理过一份十几万字的行业资料,它能跨章节把线索串起来,这种能力在对话型模型里并不多见。如果你的任务重心是“理解大量材料并提炼”,DeepSeek是一个很稳的底座。
但稳重不代表全能。到了多轮工具调用场景,DeepSeek有时候会表现得比较“内向”——你问一句它答一句,不太会主动追问缺失信息,工具描述稍微含糊,它就容易在关键参数上犯迷糊。这一点在Agent任务里比较吃亏,因为Agent的核心不是“答得漂亮”,而是“在正确的时机做出正确的调用决策”。
Qwen给我的感觉刚好是另一个方向。它在指令遵循和Function Calling上打磨得更顺,调用工具的时候能相对准确地按schema补全参数,多轮对话过程中也更愿意做澄清。我拿它做Agent的“主脑”,让它决定调哪个工具、按什么顺序执行,整体稳定性明显更好。需要说明的是,这里说的Qwen是指带工具调用能力的版本,不是让它处理超长输入再做深度推理——这个方向它和DeepSeek是互补的。
很多人喜欢拿综合榜单说事,但在Agent场景里,综合榜单的误导性反而最强。榜单一般测的是单轮回答质量,而Agent拼的是多轮决策、工具调用、格式遵守和错误恢复,系统考察的能力完全不同。我拿自己业务里最刁钻的20个Case分别跑过两个模型,结论是:谁更聪明不一定重要,谁更“听劝”才重要。
1.2 一份对实际项目更有用的对比参照
我整理了一份偏实战视角的对比表,定期会根据版本更新调整,目前跑下来大致是这个情况:
| 能力维度 | DeepSeek | Qwen | 我的倾向 |
|---|---|---|---|
| 上下文处理 | 超长文本优势明显,适合一次性塞入大量资料 | 常规长度稳健,工具结果太多时上下文需要裁剪 | 长文重活优先DeepSeek |
| 工具调用 | 简单工具可用,复杂多工具场景稳定性一般 | Function Calling较成熟,参数填充更可靠 | Agent主控优先Qwen |
| 中文指令遵循 | 理解和总结很好,但操作步骤类指令偶尔会跳步 | 指令跟随稳定,“说了不做”的情况更少 | 复杂流程优先Qwen |
| 成本量级 | 低成本路线代表,批量任务友好 | 多个规格可选,成本分布宽 | 批量大或者对预算敏感,DeepSeek更划算 |
| 适合角色 | 资料整理、长文总结、代码分析 | 多轮对话、工具调度、流程控制 | 两者组合成梯队 |
同一套提示词在两个模型上的表现经常差很多。我自己的经验是:DeepSeek更适合把约束写成“事实说明型”,比如“如果信息不足请直接说明”;Qwen则更适合带步骤编号的“流程指令型”,比如“先做A,再做B,最后输出C”。这些细节不跑一遍很难发现。
1.3 三个可落地的选型标准
我把自己的选型逻辑浓缩成三条,每次换项目都按这个顺序过一遍。
第一,看任务重心。重推理、重长文理解的任务,比如研究报告生成、代码库分析、复杂计算,优先DeepSeek。重工具编排、多步骤流程控制的,比如客服Agent、流程自动化、数据查询助手,优先Qwen。
第二,看上下文压力。如果会频繁用到超长上下文,比如动不动丢进去几十万字资料,DeepSeek的长上下文版本会更合适。如果Agent的多轮对话里塞满了检索结果和工具中间步骤,上下文会快速膨胀,这时候需要一个调度能力强的主控模型,把Qwen放主控位。
第三,看调试效率。这里容易被忽略:做Agent框架时,我每天可能改几十轮提示词。如果模型对提示词改动反应不敏感,调试周期会拉得非常长。Qwen对系统提示词格式变化更敏感,这其实是好事,能快速验证想法;DeepSeek对系统指令的敏感度相对低一些,更适合提示词已经调稳之后的稳定运行环境。
2. Agent框架的整体拼图:模型之外真正决定效果的四块基础
模型决定上限,框架决定下限。DeepSeek再聪明,工具调用逻辑写乱了,Agent照样会跑偏。我在搭Agent框架时,把注意力放在四块内容上:提示词契约、工具注册、状态管理、结果校验。这四块是模型之外真正决定成败的骨架。
2.1 提示词不是“写一段话”,而是“定义一份接口契约”
很多初学者写Agent提示词,就是复制一段角色设定“你是一个智能助手……”,这远远不够。在Agent场景里,提示词的本质是一份接口契约:模型必须知道它可以调用什么、被期望输出什么、遇到未知情况该怎样兜底。
我的做法是把系统提示词固定成五段:角色与目标、工作流程、工具列表、输出格式、兜底规则。结构大致如下:
你是信息分析助手。 工作流程: 1. 先理解用户任务,拆成最多3个检索子问题; 2. 每个子问题调用search工具; 3. 根据检索结果生成结构化报告。 工具列表: - search(query, date_range) 输出格式: { "summary": "一句话结论", "points": ["要点1", "要点2"], "source_notes": "来源说明" } 兜底规则: 如果某个子问题没有检索结果,明确写"未获取相关信息",不要编造。这段提示词里最重要的是最后一条兜底规则。模型在信息不足时倾向于编造一个符合输出格式的答案,如果你不写明“允许承认不知道”,它就能把缺口圆过去。这个经验在长文本Agent里尤其适用——信息越庞杂,模型越容易用“看起来很合理”的内容填补空白。
提示:Agent提示词的关键不是“塑造角色”,而是“限制行为”。把边界画清楚,比给它更多自由发挥空间有用得多。
2.2 工具注册:把函数变成模型眼中的“说明书”
工具调用的接口设计,直接影响模型选择工具的成功率。我见过很多人把工具描述写成一句话:“搜索网络。”模型根本不知道这个工具能搜什么、不能搜什么、参数格式是什么。我习惯用类似JSON Schema的结构来描述每一个工具,字段说明要精确到取值范围。
以搜索工具为例:
{ "name": "search_web", "description": "检索互联网信息,返回标题、摘要和URL列表。适合查询最新事件、技术资料和事实校验。", "parameters": { "type": "object", "properties": { "query": { "type": "string", "description": "检索关键词,尽量简短,避免包含修饰词" }, "max_results": { "type": "integer", "description": "返回条数,默认5,最大10" }, "time_range": { "type": "string", "enum": ["day", "week", "month", "year"], "description": "时间过滤范围,例如day表示仅返回24小时内的结果" } }, "required": ["query"] } }这里的时间范围用枚举值,而不是让模型自由填日期,原因很现实:如果让模型填一个具体时间区间,它经常生成一个看似合理但存在偏差的日期;给它四个选项,犯错概率会小很多。工具注册阶段多花点心思写description,后面能省下大量纠错成本。
在工具调用这一层,我的个人习惯是用Qwen的Function Calling能力去选择函数和参数。它的函数名、参数名和描述匹配相对稳定,较少出现“选错工具”这类低级问题。把DeepSeek放在后面做文本生成和总结,两个模型各管一段,整体效果比我单个模型硬扛要好。
2.3 状态管理:多轮对话与上下文裁剪
Agent跑起来之后,上下文消耗会快速增长。检索结果、工具返回、中间分析都是Token,三轮任务下来很可能就把上下文撑爆。如果不做状态管理,再强的长上下文能力也扛不住持续累积。
我的简化方案是分三层管理:短期上下文、中期摘要、长期事实表。
| 层级 | 内容 | 策略 |
|---|---|---|
| 短期上下文 | 当前任务相关的最近几轮对话和最近一次工具返回 | 完整保留,供模型立即使用 |
| 中期摘要 | 已经完成的子任务结论 | 每隔几轮压缩成摘要,替换原始记录 |
| 长期事实表 | 用户偏好、关键数据点、跨轮引用的事实 | 存成键值对,每次注入提示词前部 |
这套结构做起来不难,关键是坚持“替换”而不是“追加”。很多人会顺手把历史记录越堆越多,最后Token爆炸。我的经验是:如果某个中间结果在后续步骤中大概率不会被引用,就果断丢弃;只保留摘要和事实表。这比任何提示词优化都省钱。
2.4 结果校验:不要信任模型的输出格式
即使提示词里写了输出格式,模型仍可能返回JSON以外的内容。最常见的情况是:JSON里混进一段解释性文字,或者某个字段漏了,或者多了一个逗号。所以我在Agent循环里加了一个结构化输出校验层。
def safe_parse_json(text): try: return json.loads(text) except json.JSONDecodeError: start = text.find("{") end = text.rfind("}") if start != -1 and end != -1: try: return json.loads(text[start:end+1]) except json.JSONDecodeError: return None return None如果解析失败,我会把原始输出作为上下文,让模型再生成一次。重试时把温度调低,并明确告诉它“上次没有按格式输出,请只输出JSON”。这种二次纠错在绝大多数情况下能解决问题。如果连续两次都失败,再考虑降级到备用模型。记住一个原则:模型输出不校验,Agent就是定时炸弹。
3. 搭一个带工具调用的Agent:完整运行链路
框架的四个基础讲完,下面用一个具体例子把整个运行链路串起来。这个例子是一个信息检索Agent,任务输入是一句话,输出是一份结构化的资料综述。它虽然简单,但工具调用、多轮决策、结果汇总这些核心环节全都能覆盖到。
3.1 场景定义与任务拆解
我给Agent定的目标是:输入“整理一下近三年新能源行业在电池回收领域的主要技术路线”,它需要先拆成几个子问题,比如“主要技术路线有哪些”“各路线成熟度如何”“有哪些代表企业和落地案例”,然后分别检索,最后综合成一份报告。
这里的拆解逻辑很关键。直接让模型回答一个大问题,它很容易凭已有知识“脑补”,而不是去检索最新信息。把任务拆成多个可验证的子问题,再逐个检索,本质上是用工具来约束模型,逼它基于真实信息回答。
3.2 核心循环的实现
核心代码并不复杂,最重要的是先让模型输出执行计划,再按计划逐步执行:
class SearchAgent: def __init__(self, llm, search_client): self.llm = llm self.search = search_client self.memory = [] def run(self, task): system = build_agent_prompt() # 第一步:让主控模型输出执行计划 plan_text = self.llm.chat( system + "\n本次任务:" + task, response_format={"type": "json"} ) plan = safe_parse_json(plan_text) if not plan or "steps" not in plan: return {"error": "plan_parse_failed"} collected = [] for step in plan["steps"]: if step.get("type") == "search": query = step.get("query", "") results = self.search.query( query, time_range=step.get("time_range", "month"), max_results=step.get("max_results", 5) ) collected.append({"query": query, "results": results}) self.memory.append({"query": query, "results": results}) elif step.get("type") == "summary": # 中间步骤也可以让模型对已有信息做小结 segment = self.llm.chat( "请总结以下材料,不超过200字:" + format_search_results(self.memory[-3:]) ) collected.append({"summary": segment}) self.memory.append({"summary": segment}) final_prompt = ( "基于以下全部检索结果,生成一份信息综述报告。\n" + format_search_results(collected) ) return self.llm.chat(final_prompt)这里有两个细节值得展开讲。
第一,第一步要求模型输出一个计划,而不是直接生成最终答案。分解任务能显著降低对整体任务的理解偏差,让每一步检索更有针对性。模型输出的计划质量,直接影响后续所有环节,值得多花几次调试来把计划格式稳定住。
第二,中间的summary步骤不是必须的,但当检索结果很多时,先做分段小结再统一汇总,能大幅减少最终综合时的上下文压力。我用它来处理长度差异特别大的检索材料,效果不错。
3.3 一次运行的耗时与成本估算
我用这个结构跑过一次对比测试,就是上面那个电池回收案例。总共检索了12次,原始材料加中间输出大约7.3万Token,最终报告约2400字。按当时两个模型的公开价位粗算,单次完整运行大概在0.2到0.5元之间,取决于选用哪个规格的模型。整体耗时大多花在串行检索上,约30秒到1分钟。
这个结果说明一件事:工具调用型Agent的瓶颈往往不在模型质量,而在工程链路设计。串行检索次数多了,延迟自然上去;上下文重复累计,成本自然上去。这两个问题都不是换一个“更贵”的模型能解决的。
3.4 主控模型与重活模型分离
从上面的链路能看出,Agent里有两种不同类型的模型调用:一种是意图理解、工具调度、流程控制,需要模型反应快、指令跟随稳;另一种是长文本总结、报告生成、代码分析,需要模型逻辑深、上下文长。把这两类任务交给同一个模型,虽然也能跑,但总有一头不太顺手。
我的实践做法是把模型按角色拆分:主控模型我优先用Qwen,负责“下一步该干什么”的决策;重活模型用DeepSeek,负责“把一堆材料变成结论性内容”。上层业务通过一个统一接口调用,不用关心底层到底调的是谁。
这种拆分还有一个额外好处:容错。某个模型临时不可用或输出质量波动时,另一个模型能顶上,Agent不会整个瘫痪。后面第五部分会具体讲路由和降级怎么实现。
4. 实测中那些文档里查不到的坑
下面这部分是实操里最有价值的内容,全部是我自己踩过之后总结出来的。每一条都对应真实发生的故障,有的甚至排查了很久才定位到原因。
4.1 “参数幻觉”比回答幻觉更隐蔽
工具调用里最常出问题的不是模型选错工具,而是参数填得“看起来对、实际错”。比如用户说“最近三个月”,模型不会把时间范围翻译成具体日期区间,而是直接填一个time_range: "3 months";如果你的接口只接收枚举值,整个调用就废了。
我调试Qwen的Function Calling功能时,就在参数Schema里把所有时间范围都做成枚举,并在description里写明“必须使用给定枚举值,禁止自行构造时间表达式”。这个改动看起来很小,但工具调用准确率能提升一大截。
还有一个坑是类型问题:max_results参数里,模型可能填一个5.0或者字符串"5",不同模型对类型宽容度差异很大。我的做法是解析完参数后做一次强制类型转换,不符合就直接重试。在Agent工程里,要把工具参数当成用户输入来校验,而不是当成可信输出。
4.2 长上下文不等于全量注意力
DeepSeek的长上下文确实能塞下大量文本,但实测下来:文本越长,模型对关键细节的“注意力”就越淡。我做过一个测试,把一份长篇报告的章节顺序打乱后丢进上下文,模型仍然能生成一通看似完整的总结,但具体引用的页码和出处明显错位。这说明长上下文擅长处理“整体理解”,不擅长精确引用。
应对方式就是“分割+局部召回”:把长文档切成2000到5000字的小段,用检索或向量召回找到与当前问题最相关的片段,再把这些片段放在提示词前部。你也可以简单一点,让工具先返回标题和摘要,再决定要不要展开全文。这样既保住了精确性,又省Token。
提示:长上下文是给“必要时候”用的,不是给“偷懒不裁剪”用的。全量塞入永远是最后手段,不是默认方案。
4.3 模型混用时的格式兼容问题
把DeepSeek和Qwen放在同一个Agent里,会遇到一个很隐蔽的问题:两个模型对系统角色、工具返回结果这些消息格式的支持并不完全一致。在一套统一的消息结构里,一个模型能理解的格式,另一个模型可能直接忽略工具返回的上下文字段,导致后续回答缺少信息。
解决办法是写一个简单的适配层,把内部统一格式转换成各模型的API格式。核心代码并不复杂:
class LLMAdapter: def __init__(self, model_name): self.model_name = model_name def chat(self, messages, tools=None): if self.model_name.startswith("qwen"): payload = self.to_qwen_format(messages, tools) elif self.model_name.startswith("deepseek"): payload = self.to_deepseek_format(messages, tools) else: payload = self.to_default_format(messages, tools) return self._post(payload)适配层不解决模型能力差异,但能让你在做模型对比时不被格式问题干扰。这一步看着简单,但很多项目里模型混用跑飞,都是因为没做这一层,调试时又误以为是模型能力问题,白走了不少弯路。
4.4 你以为的并发不是并发
还有一个很现实的工程问题。很多人在写Agent时把多个搜索任务放在async函数里,看起来是并发调用,实际却因为协程里嵌套了同步API调用,还是串行执行。串行本身不可怕,可怕的是你以为它并行,结果生产环境一跑,整体耗时翻倍,用户直接失去耐心。
我的做法是给Agent增加一个并发控制层,用信号量限制同时发出的请求数量,比如最多3个请求并发,并给每个请求设置超时时间。超时重试不能太激进,否则一个小故障就会把服务配额打光。退避策略我一般用初始1秒、最大10秒的指数退避,同时设置总重试次数上限。
import asyncio semaphore = asyncio.Semaphore(3) async def limited_call(coro): async with semaphore: return await asyncio.wait_for(coro, timeout=10)这一条虽然不直接涉及模型选择,但没有它,前面所有优化都白搭。Agent的稳定性是系统工程,模型只是其中一环。
5. 从能跑到能用:路由、缓存、降级与指标
跑通一个Agent Demo很容易,但要让它稳定地服务真实业务,还差一层工程改造。我把它总结成四件事:路由、缓存、降级和指标。
5.1 模型路由:不要把鸡蛋放在同一个模型里
在混合使用DeepSeek和Qwen的架构里,路由规则是整个系统的大脑。我目前的路由策略大致如下:
| 请求特征 | 优先模型 | 原因 |
|---|---|---|
| 长文总结类 | DeepSeek | 长上下文加低成本,适合先粗读再精读 |
| 多工具调度类 | Qwen | Function Calling更稳,工具参数更可控 |
| 短问答类 | 任意经济型号 | 没必要用高规格模型处理简单问题 |
| 高并发批量类 | DeepSeek | 成本优先,批量场景下质量差异不敏感 |
路由逻辑建议写成一个独立函数,不要散落在业务代码里。它后期维护成本极低,却能让成本和质量同时受益。
def route_request(task_type, prompt_tokens): if task_type == "long_doc": return "deepseek-chat" if task_type == "tool_loop": return "qwen-plus" if task_type == "quick_chat" and prompt_tokens < 4000: return "cheapest_model" return "qwen-plus"路由规则要结合你的实际配额和价格表不断调整,没有一劳永逸的配置。每次大版本更新后,我都会重新跑一遍自己的Case集,看看路由规则有没有需要微调的地方。
5.2 缓存:同样的材料别让模型读第二遍
Agent场景里有一类请求非常浪费:用户把同一份材料反复问好几遍,模型就全文读好几遍。给这类任务加缓存,收益非常明显。
最简单的缓存是精确匹配:把任务的关键参数和一个“语义指纹”存下来,如果指纹一致就直接返回缓存结果。稍微进阶一点的做法是用向量库做语义缓存,但这会引入不少复杂度,我的建议是前期先不碰,用简单缓存把成本降下来再说。
更值得缓存的是工具调用结果。比如某个搜索请求在10分钟内再次到来,完全可以复用第一次的搜索结果,省一次调用成本。我给工具结果设一个短TTL,一般10分钟到1小时。这个策略在信息检索型Agent里作用尤其突出。
5.3 降级链路:模型不可用时还能怎么办
生产环境一定会遇到模型可用性问题,这不是概率问题,而是时间问题。降级策略应该提前写好,而不是出问题了再讨论。我的降级顺序是:主模型、备用模型、静态兜底模板。
def chat_with_fallback(adapter, messages, tools): for model in ["qwen-max", "deepseek-chat", "fallback"]: try: if model == "fallback": return build_static_reply(messages) return adapter.chat(messages, model=model, tools=tools) except TimeoutError: continue静态兜底模板的用意不是提供一个好回答,而是保证Agent不静默失败。比如工具型Agent在主模型不可用且备用模型也超时的情况下,可以直接返回最近一次检索结果的摘要,哪怕措辞生硬,也比直接报错强。可用性优先,这是我做Agent工程的一条底线。
5.4 真正值得长期盯的指标
很多人做Agent只看“回答好不好”,但“好”是主观的,很难量化。我把精力放在下面四个能直接反映系统状态的指标上:
| 指标 | 含义 | 为什么重要 |
|---|---|---|
| 工具调用准确率 | 选对工具且参数正确的比例 | 决定Agent能不能真正干成事 |
| 端到端响应耗时 | 从提问到最终回复的总时长 | 决定用户愿不愿意等 |
| 平均上下文Token数 | 每轮请求的Token消耗 | 直接决定成本 |
| 用户纠偏率 | 用户是否经常纠正Agent的中间结论 | 间接反映前置决策质量 |
这四个指标里,工具调用准确率和平均上下文Token数最值得每天看,其余可以按周复盘。特别是工具调用准确率,一旦掉到某个阈值以下,问题通常不在模型,而在工具描述或参数Schema上,需要回到第二章的内容里去查。
最后聊两句我的个人习惯。现在我接手任何Agent项目,都会先用最便宜的模型把最小闭环跑通,再逐步替换成表现更好的模型。原因很简单:模型差异只有在流程稳定之后才看得清楚;流程都不稳定,用好模型只会掩盖问题,拖慢排查速度。DeepSeek和Qwen各有各的强项,实际项目中把它们组合成梯队,比争论“谁更强”有意义得多。