为什么我很少用框架
做 LLM 应用这两年,LangChain、LangGraph、CrewAI、Dify 这些东西我基本都没碰过。不管是做 agent 还是 workflow,我就 instructor 加 OpenAI SDK 打天下。
这不是偶然。我自己做过框架——之前在大厂主导设计过 Python 版类 Spring 框架——所以很清楚框架能帮你做什么、不能帮你做什么。框架的价值在于统一那些真正有共性的东西。而 LLM 应用里那些看起来很唬人的概念,拆开看既不复杂,也不通用。
那些概念都只是函数组合
记忆?维护一个列表,把历史对话塞进去,下次调用带上。
RAG?调用前查一下相关信息,塞进上下文。
ReAct?让模型先想再选工具,调完把结果喂回去继续。
Agent?LLM + 工具调用 + 一个循环。
每一个拆开看都是几个函数的事。它们在论文里有名字,是因为学术界需要给范式命名。写到业务代码里,就是列表操作、一次查询、一个 while。
但"agent 框架"听起来比"LLM 函数库"高大上太多了。对投资人讲故事、对市场做包装,"我们做了一个 agent 编排平台"比"我们封装了几个 API 调用"性感得多。很多框架的复杂性不是技术驱动的,是叙事驱动的。
没有统一做法
框架的预设是这些概念有最佳实践可以抽象。但实际上每个场景都不一样。
客服场景的"记忆"是最近 10 轮对话。代码助手的"记忆"是整个项目的文件树。分析助手的"记忆"是中间计算结果。三种东西名字一样,实现完全不同。
法律场景的"RAG"要精确匹配条款编号。技术文档的"RAG"要语义相似度。客服知识库的"RAG"要意图分类后定向查找。你没法写一个通用 Retriever 覆盖这三种。
Agent 什么时候该停?有的一轮就完事,有的要迭代到收敛,有的要人审批。终止条件是 agent 最关键的设计决策,也恰恰是最不可能通用化的部分。
就像"人"没有模板——你不能定义一套通用人类行为框架然后靠配参数生成不同的人。agent 也一样,每个 agent 的灵魂是它独特的业务逻辑,不是框架提供的骨架。
instructor 打天下
看几个场景用 instructor 做是什么样。
客服意图路由——模型判断意图,代码分发处理:
class CustomerIntent(BaseModel):
category: Literal["退款", "物流", "投诉", "咨询"] = Field(
description="客户消息的意图分类"
)
urgency: Literal["高", "中", "低"] = Field(
description="基于上述 category 和消息语气判断紧急程度"
)
extracted_order_id: Optional[str] = Field(
description="消息中提到的订单号(如有)"
)意图识别、紧急程度判断、实体提取,一个模型搞定。拿到结果之后 if/else 分发到不同处理函数。这就是所谓的"意图路由 agent"。
投研分析——从研报里提取结构化信息:
class ResearchExtraction(BaseModel):
ticker: str = Field(description="股票代码")
revenue_growth: Optional[float] = Field(
description="营收同比增长率,原文未提及则为 null"
)
risk_factors: list[str] = Field(
description="报告中提及的主要风险因素",
max_length=5
)
bull_case: str = Field(
description="基于上述信息总结看多逻辑"
)
bear_case: str = Field(
description="基于上述信息总结看空逻辑"
)字段顺序就是推理路径——先提取事实,再给判断。这就是所谓的"声明式思维链",不需要在 prompt 里写"请先做 A 再做 B"。
带工具的 agent——需要循环的场景:
class Step(BaseModel):
thinking: str = Field(description="分析当前状态,决定下一步")
action: Literal["query_db", "call_api", "finish"] = Field(
description="基于上述 thinking 选择动作"
)
action_input: str = Field(description="动作的输入")
async def run(task: str):
history = [{"role": "user", "content": task}]
for _ in range(10):
step = await instructor_call(history, Step)
if step.action == "finish":
return step.action_input
result = await tools[step.action](step.action_input)
history.extend([
{"role": "assistant", "content": step.model_dump_json()},
{"role": "user", "content": f"结果:{result}"}
])thinking 在前 action 在后,模型先想再选。工具选择是类型约束不是自然语言祈祷。循环就是个 for,退出就是 "finish"。这就是全部的 agent 了。
三个场景,没有 Chain、Graph、Node、Edge、Crew、Role、Canvas。就是 Pydantic 模型加普通 Python 代码。
框架解决的是谁的问题
不知道怎么组织 LLM 代码的人。框架给你一个现成的结构,告诉你代码往哪放,这对新手有价值,对快速跑通 demo 有价值。
但如果你清楚那些概念背后就是函数组合,而且每个场景的组合方式都不一样——框架那层抽象就是纯开销。你得学它的概念体系,受它的架构约束,出了问题还得穿透它的抽象层去定位。
真到了需要复杂编排的时候——超过 10 步的工作流、持久化状态、分布式执行——该用的是 Temporal、Prefect 这种通用编排工具,不是 LLM 专属框架。
最简单的能跑的方案就是最好的方案。不是因为简单有什么美德,是因为你以后要改它、调试它、教别人理解它。每多一层抽象,这些事都变难一点。