LLM角色重构:从对话生成到RAG与Agent编排的工程实践
2026/9/7 13:09:45 网站建设 项目流程

先说结论:我之前对 LLM 在软件世界里该扮演什么角色,判断确实出现了偏差。

最早接触大语言模型时,我的认知还停留在“它是个更强的搜索引擎”“它能写文案、能总结文档”。真正开始做 LLM 应用开发之后才发现,LLM 正在重新定义我们和软件交互的方式,也在重构“软件开发”这件事本身的流程。它不只是对话窗口背后的“智能内核”,它开始变成 RAG 检索增强的入口、Agent 自动决策的大脑、甚至一套知识组织的范式。

这篇文章我不想只写“LLM 很厉害”这种空话。我会结合最近半年的项目落地经验,聊清楚 LLM 的新角色、RAG 与 Agent 的工程实现思路、模型精度和成本的关系,以及为什么 LLM 应用必须引入编排框架。文章里会有大量可复制的代码骨架、配置思路和排查清单,适合正在从“调 API 写 Demo”向“做正式 LLM 应用”阶段过渡的开发者。

1. 背景:LLM 的角色为什么被重新定义

1.1 过去:把 LLM 当成“对话式搜索”

在很长一段时间里,包括我自己在内,对 LLM 的定位是:

给它一个问题,它返回一段看起来合理的文本。

所以大家做的最多的 Demo 是“企业知识库问答助手”“ChatPDF”“智能客服”。整个技术架构基本是:

  1. 把文档切片成 chunk。
  2. 用向量模型把 chunk 转成向量。
  3. 用户提问时,把问题向量化,在向量库里做相似度检索。
  4. 把命中的文档片段拼进 Prompt,让 LLM 生成答案。

这个形态确实是 RAG 的雏形。它解决了 LLM“知识截止时间固定”“会一本正经胡说八道”的问题。但它本质还是把 LLM 当作一个“文本生成器”,输入是文本,输出也是文本。

这个定位本身没有错,只是太窄了。

1.2 现在:LLM 正在变成应用的“控制层”

真正改变我认知的,是 LLM 开始承担“决策和执行”的职责。

当 Agent 这个概念出现之后,LLM 的输出不再只是给人看的文本,而是可执行的工具调用指令。举个最简单的例子:

用户:帮我查一下本周用户增长数据,然后生成一份日报发送到群里。

在传统软件里,这个需求要拆成三步,每一步都是人肉操作:

  1. 登录数据后台,查询指标。
  2. 复制数据,套模板写日报。
  3. 打开群聊,发送消息。

在 Agent 场景下,LLM 会把这段话解析成一个计划:

[ { "tool": "query_analytics", "params": { "metric": "user_growth", "period": "week" } }, { "tool": "generate_report", "params": { "template": "daily_report" } }, { "tool": "send_message", "params": { "channel": "group", "content": "<report>" } } ]

然后由程序去执行这些工具。LLM 不再只是“回答问题”,而是“理解意图、拆分任务、按顺序调用工具、并根据工具返回结果继续决策”。这就是所谓的 LLM as a Control Layer,LLM 成为应用的控制层。

这个角色的转变,对后端开发者的影响远大于前端。因为这意味着我们的系统架构要从“预先编排好的业务逻辑”变成“动态编排的业务逻辑”。以前用户点击按钮触发固定的 API 调用链,现在用户一句话,调用链可能完全不同。

1.3 判断被修正的三个关键点

回顾这段时间的认知变化,有三次比较关键的修正:

时间阶段我对 LLM 的判断现实情况
初期LLM 只是文本生成工具,替换搜索和写作场景LLM 能生成结构化指令,驱动工具链执行
中期做好 Prompt 工程就能解决业务问题Prompt 只是起点,数据质量、上下文工程、工具设计更关键
现在单次调用能处理大多数任务复杂任务必须多轮编排,LLM 需要记忆和反思机制

特别是第三点。如果你做过稍微复杂一点的 LLM Agent,就会发现“让模型一次性完成一个长任务”几乎不可能。要么上下文溢出,要么中间某一步工具调用失败导致后续全部混乱。真正可用的方案是把任务拆成很小的步骤,每步都让 LLM 基于上一步的结果做决策,这其实就是编排框架要解决的核心问题。

2. LLM 应用的四个核心角色

既然 LLM 的角色发生了变化,那我们在实际开发中,应该从哪几个维度去设计 LLM 应用?我把它总结成四个角色,这四者是层层递进的关系。

2.1 角色一:对话生成器(Chat Generator)

这是最基础的角色,也是大多数人最先接触的。

from openai import OpenAI client = OpenAI(api_key="your-api-key", base_url="your-endpoint") response = client.chat.completions.create( model="your-model-name", messages=[ {"role": "system", "content": "你是一名资深后端工程师,回答要简洁。"}, {"role": "user", "content": "什么是 RAG?"} ] ) print(response.choices[0].message.content)

这个角色的核心是“理解上下文并生成自然语言”。它不需要外部知识,也不需要工具调用,做好 Prompt 就能有不错的效果。

但它的问题也很明显:知识受限、无法访问实时数据、无法执行操作。所以我们需要第二个角色。

2.2 角色二:知识检索器(RAG Retriever)

RAG(Retrieval-Augmented Generation,检索增强生成)是解决 LLM 知识盲区的标准方案。

它的核心链路是:

文档 -> 切片 -> 向量化 -> 存储到向量库 用户提问 -> 向量化 -> 相似度检索 -> 取TopK -> 拼进Prompt -> 生成回答

为什么要有这个角色?因为企业场景下,模型不可能知道你的内部文档、私有代码、历史订单。与其重新训练模型,不如在运行时把相关知识“喂”给模型。这样成本低、更新快、可控性更强。

2.3 角色三:工具调用者(Tool Caller / Agent)

这是 LLM 从“被动回答”走向“主动执行”的关键角色。

tools = [ { "type": "function", "function": { "name": "get_weather", "description": "查询指定城市当前天气", "parameters": { "type": "object", "properties": { "city": {"type": "string", "description": "城市名称"} }, "required": ["city"] } } } ] response = client.chat.completions.create( model="your-model-name", messages=[ {"role": "user", "content": "北京今天天气怎么样?"} ], tools=tools ) # 判断模型是否请求调用工具 if response.choices[0].message.tool_calls: print(response.choices[0].message.tool_calls)

模型会输出一个结构化的工具调用请求,然后由代码真正去请求天气 API,再把结果返回给模型继续生成。这就是 Tool Calling / Function Calling 的基础。

MCP(Model Context Protocol,模型上下文协议)其实是把这种工具调用标准化了。它定义了模型、客户端、服务器之间的通信规范,让工具接入不需要为每个模型写专门适配层。如果你有多个 Agent 要接入同一个内部系统,MCP 就很有价值。

2.4 角色四:流程编排者(Orchestrator)

最后一个角色是把前面三种能力串起来。这也就是“LLM 应用为什么需要编排框架”这个问题的答案。

举个例子:用户问“查看上周数据库 CPU 使用率,并生成优化建议”。这个任务需要:

  1. 调用监控 API,拉取 Prometheus 指标数据。
  2. 根据数据峰值时间,找到嫌疑慢查询。
  3. 调用 LLM 分析慢查询日志,生成优化建议。
  4. 把建议格式化输出。

单次 LLM 调用根本无法完成这个链路。你需要一个流程编排器,决定每一步调用哪个工具、把上一步结果如何传给下一步、如果失败如何重试或回退。市面上主流的编排方案包括 LangChain、Spring AI、Dify,以及更轻量的自研 Python 脚本。它们的核心价值不是“封装 API”,而是帮你管理状态、记忆、工具注册和错误恢复。

3. 环境准备与模型选型

3.1 环境版本说明

LLM 生态变化太快,我不建议照搬网上过时的版本号。下面给出的是当前开发常用的环境组合,实际情况请以你的项目为准:

组件推荐环境说明
操作系统macOS / Linux / Windows WSL2本文章节不依赖特定系统
Python3.10 及以上推荐 3.11,兼容性和性能均衡
模型接入方式OpenAI 兼容 API国内外的模型基本都提供兼容接口
向量数据库Chroma / Milvus / pgvector小型 Demo 用 Chroma 即可
编排框架LangChain / Spring AI根据团队技术栈选择

版本不需要刻意追求最新。越新的版本,社区踩坑资料越少,生产环境稳定性反而不如保守版本。建议锁定你项目使用的 SDK 小版本。

3.2 模型选型两派:API 调用还是本地部署

到底用云端 API 还是本地搭建推理,这是每个 LLM 工程落地都要做的选择题。两者没有绝对优劣,取决于你的场景。

API 调用的优势是省心、模型强、迭代快。你不需要关心显存和推理优化,只要处理网络和稳定性。劣势是数据出域风险、单次调用成本、以及可能存在的网络延迟。

本地部署的优势是数据可掌控、可以按需定制、单次推理边际成本趋近于零。劣势是显存需求高、需要处理模型量化、推理加速,以及开源模型的能力上限通常低于顶尖闭源模型。

我的建议是:

  • 如果只是做内部工具、POC 验证,优先 API 调用。
  • 如果涉及敏感数据、高并发但业务逻辑简单,考虑本地部署。
  • 如果两者混合:敏感文档检索走本地小模型,复杂语义理解走云端大模型。这就是 Hybrid RAG 的思路。

3.3 本地部署需要考虑的配置

本地跑模型时,很多人会卡在“显存不够”或“生成速度太慢”。这里有一个核心概念叫 KV Cache。推理时,模型需要缓存前面所有 token 的关键/值向量,导致实际显存占用远大于模型权重本身。所以选卡时不能只看“这个模型 7B,只要 14GB 显存”。

另一个关键概念是精度。后续我会单独展开讲解 fp16、fp32、bf16 的区别,因为很多人模型是能配好,但精度一改,输出质量下降、数值溢出,完全没有概念。

4. LLM 应用开发的核心机制拆解

4.1 Token 与上下文窗口

Token 是 LLM 处理文本的最小单位。英文里大概一个 token 对应 3~4 个字符,中文一个字往往也要消耗 1~2 个 token。模型能处理的最大 token 数量叫上下文窗口。

理解 Token 有三个工程意义:

  1. Prompt 过长,会挤占模型生成空间。
  2. 上下文窗口有限,超长文档不能全塞进去,必须切片检索。
  3. Token 消耗直接决定成本,中文场景尤其明显。

写应用时,建议在关键节点记录 token 用量。不仅为了账单核算,更是为了定位“为什么输出被截断”这类问题。

4.2 模型精度问题:fp16、fp32 与 bf16

这是一个非常容易踩坑的领域。同样一个 7B 模型,用 fp32、fp16、bf16 加载,显存占用和推理结果都可能完全不同。

精度类型位数表示范围典型用途注意事项
fp3232 位范围大,精度高训练时的标准精度显存占用最大
fp1616 位范围相对窄部分推理加速大数值容易溢出,梯度更新可能不稳定
bf1616 位范围和 fp32 一致,但尾数少训练与推理的主流选择在支持 bf16 的显卡/云实例上优先使用

为什么要区别它们?因为现代 GPU 推理通常用半精度来节省显存、提高吞吐,但 fp16 的小指数范围会导致大数值溢出,影响训练稳定性。bf16 的指数位和 fp32 一样,所以不会溢出,只是尾数精度低,在训练场景反而更稳。如果你在本地部署模型出现 NaN、loss 异常,先查精度设置。

对于推理应用,结论可以简化:

  • 高端 NVIDIA 显卡(如 A100、H100、4090),优先 bf16。
  • 低端显卡显存不足时,用 int8 / int4 量化。
  • 追求极致显存效率且不敏感精度,再用 fp16 或量化方案。

4.3 向量化与相似度检索

RAG 的底层依赖是文本向量化。一个文本向量就是一个浮点数数组,语义相近的文本,向量距离也更近。

向量检索的相似度计算常用余弦相似度:

import numpy as np def cosine_similarity(vec_a, vec_b): """ 计算两个向量的余弦相似度 """ dot = np.dot(vec_a, vec_b) norm_a = np.linalg.norm(vec_a) norm_b = np.linalg.norm(vec_b) return dot / (norm_a * norm_b + 1e-9)

实际项目中很少自己写这个函数,直接用向量数据库即可。但理解底层逻辑有助于你判断检索质量:如果检索结果不准确,首先检查切片大小、向量模型质量、TopK 参数,而不是怀疑数据库。

5. 实战:搭建一个“RAG + Agent 工具调用”的 LLM 应用骨架

下面我们用 Python 搭建一个精简但完整的 LLM 应用骨架。它包含三个能力:

  1. 从网页抓取正文内容(对应输入材料中的“实现抓取网页内容功能”)。
  2. 把抓取内容切片后做向量检索,实现简单的 RAG。
  3. 增加一个历史命令查询工具,让 LLM 能够根据意图调用工具。

这个例子不依赖重量级框架,重点是让你看清链路。后续上生产再替换成 LangChain 或 Spring AI。

5.1 创建项目结构

llm-app-demo/ ├── app.py # 主入口,包含完整流程 ├── requirements.txt # 依赖列表 ├── utils/ │ ├── __init__.py │ ├── fetcher.py # 网页抓取工具 │ └── vector_store.py # 简易向量存储 └── storage/ # 存放抓取后的文本缓存

5.2 添加依赖

requirements.txt:

requests==2.31.0 openai==1.35.0 numpy==1.26.4 beautifulsoup4==4.12.3

这里版本号只是示例,请根据实际环境调整。如果你使用的是国内模型服务,只要它提供 OpenAI 兼容接口,代码里的base_url改成你的服务地址即可。

5.3 编写网页抓取工具

文件路径:utils/fetcher.py

import requests from bs4 import BeautifulSoup def fetch_web_content(url: str) -> str: """ 抓取指定 URL 的正文文本,去掉 HTML 标签,返回纯文本。 注意:只用于你有权访问的页面。 """ headers = { "User-Agent": "Mozilla/5.0 (compatible; LLMDemo/1.0)" } resp = requests.get(url, headers=headers, timeout=15) resp.raise_for_status() soup = BeautifulSoup(resp.text, "html.parser") # 优先提取正文区域,这里简化处理为整个 body 的文本 for tag in soup(["script", "style", "nav", "footer"]): tag.decompose() text = soup.get_text(separator="\n") # 把多行空行压成一行 lines = [line.strip() for line in text.splitlines() if line.strip()] return "\n".join(lines)

说明:抓取网页前必须确认你有权访问和使用该页面内容,测试阶段建议抓取自己的站点或公开文档。不要用这个逻辑去绕过访问控制。

5.4 编写简易向量存储

文件路径:utils/vector_store.py

import hashlib from typing import List, Tuple import numpy as np class SimpleVectorStore: """ 一个极简向量存储。 生产环境建议替换为 Chroma、Milvus 或 pgvector。 """ def __init__(self): self.chunks: List[str] = [] self.vectors: List[np.ndarray] = [] def add_texts(self, texts: List[str], embed_fn) -> None: for text in texts: vec = np.array(embed_fn(text), dtype=np.float32) self.chunks.append(text) self.vectors.append(vec) def search(self, query: str, embed_fn, top_k: int = 3) -> List[Tuple[str, float]]: query_vec = np.array(embed_fn(query), dtype=np.float32) scores = [] for idx, vec in enumerate(self.vectors): # 余弦相似度 cosine = np.dot(query_vec, vec) / ( np.linalg.norm(query_vec) * np.linalg.norm(vec) + 1e-9 ) scores.append((self.chunks[idx], float(cosine))) scores.sort(key=lambda x: x[1], reverse=True) return scores[:top_k] def cached_id(self, url: str) -> str: """根据 URL 生成缓存键,避免重复抓取。""" return hashlib.md5(url.encode("utf-8")).hexdigest()

这个类很小,但已经包含了 RAG 最核心的两个方法:添加文本、相似度检索。实际产品中,向量化函数一般来自“Embedding API”或本地向量模型。

5.5 编写工具调用函数

在实际项目里,LLM 需要调用外部工具。下面定义一个简单的“命令行历史查询”工具:

TOOL_DESCRIPTIONS = [ { "type": "function", "function": { "name": "query_command_history", "description": "查询用户在服务器上执行过的历史命令,用于安全检查。", "parameters": { "type": "object", "properties": { "limit": {"type": "integer", "description": "返回条数,默认10"}, "keyword": {"type": "string", "description": "按关键字母过滤"} }, "required": [] } } } ] def query_command_history(limit: int = 10, keyword: str = "") -> str: """ 模拟命令历史查询。 真实场景这里应该连接审计系统或读取日志库,注意权限校验。 """ mock_history = [ "mysql -h 10.0.0.1 -u root -p", "curl http://internal-service/api/v1/order", "vim /etc/nginx/nginx.conf", "pkill -f celery", "ssh deploy@172.16.1.8", ] if keyword: mock_history = [h for h in mock_history if keyword in h] return "\n".join(mock_history[:limit])

重点:工具函数在生产环境一定要做权限隔离。用户通过 Agent 调用工具,本质上是把操作权限交给了模型判断,必须有操作审计、参数白名单、权限分级,否则非常危险。

5.6 编写主流程:RAG + Agent 编排

文件路径:app.py

from openai import OpenAI from utils.fetcher import fetch_web_content from utils.vector_store import SimpleVectorStore # 初始化客户端 client = OpenAI( api_key="your-api-key", base_url="your-endpoint", ) def embed_texts(texts: list[str]) -> list[list[float]]: """调用 Embedding 接口,将文本列表转为向量。""" resp = client.embeddings.create( model="your-embedding-model", input=texts, ) return [item.embedding for item in resp.data] def build_rag_context(url: str, question: str, store: SimpleVectorStore) -> str: """抓取网页、切片入库、检索相关片段。""" # 1. 抓取正文 content = fetch_web_content(url) # 2. 简单切片:每 500 字符一段,重叠 50 字符 chunk_size = 500 overlap = 50 chunks = [] for i in range(0, len(content), chunk_size - overlap): chunks.append(content[i : i + chunk_size]) # 3. 向量化并入库 vectors = embed_texts(chunks) for chunk, vec in zip(chunks, vectors): store.chunks.append(chunk) store.vectors.append(vec) # 4. 检索 Top-K 片段 results = store.search(question, embed_fn=lambda q: embed_texts([q])[0], top_k=3) return "\n---\n".join([r[0] for r in results])

主对话流程:

def run_agent(question: str, url: str | None = None): store = SimpleVectorStore() messages = [ {"role": "system", "content": "你是智能助手。优先使用检索到的资料回答;如果需要查命令历史,请调用工具。"} ] # 如果有 URL,先构造 RAG 上下文 if url: rag_context = build_rag_context(url, question, store) messages.append({ "role": "user", "content": f"请根据下面资料回答问题:\n\n{rag_context}\n\n问题:{question}" }) else: messages.append({"role": "user", "content": question}) # 第一轮调用,让模型决定是否调用工具 response = client.chat.completions.create( model="your-chat-model", messages=messages, tools=TOOL_DESCRIPTIONS, ) message = response.choices[0].message if message.tool_calls: # 构造工具调用结果 for tool_call in message.tool_calls: if tool_call.function.name == "query_command_history": import json args = json.loads(tool_call.function.arguments) tool_result = query_command_history(args.get("limit", 10), args.get("keyword", "")) # 把工具结果加入上下文,让模型生成最终回答 messages.append(message) messages.append({ "role": "tool", "tool_call_id": message.tool_calls[0].id, "content": tool_result }) final_response = client.chat.completions.create( model="your-chat-model", messages=messages, ) print(final_response.choices[0].message.content) else: print(message.content) if __name__ == "__main__": # 示例:抓取一个网页内容,回答相关问题 run_agent( question="这篇文章主要讲了什么?", url="https://example.com/your-doc-page" )

5.7 运行与验证

运行命令:

pip install -r requirements.txt python app.py

预期流程:

  1. 程序抓取指定 URL 正文。
  2. 切片、向量化、存到本地内存。
  3. 用户问题向量化后检索相关片段。
  4. 模型结合片段生成回答,或者在需要时调用工具。

需要注意:示例代码用的是内存存储,程序重启后向量会丢失。生产环境应换成真正的向量数据库,并考虑数据持久化和索引更新策略。

6. 常见问题与排查思路

6.1 检索结果不相关

问题现象常见原因解决思路
RAG 回答经常答非所问切片大小不合理,语义被切断调整 chunk size 和 overlap,按标题/段落结构切分
检索到的文本和问题无关Embedding 模型能力不足换更强的 Embedding 模型,或做查询改写
top_k 太小,关键上下文被截掉top_k 设置过大/过小都可能出问题根据文档长度和问题复杂度调整 top_k
向量库和文档版本不一致索引未及时更新建立文档变更同步机制,定期重建索引

6.2 工具调用失败或参数错误

问题现象常见原因解决思路
模型没有触发工具调用Prompt 中没有说明工具用途在 system prompt 中明确“需要查询时调用工具”
工具参数格式错误函数描述不规范,模型无法理解参数含义保证 parameters 是 JSON Schema 格式,字段带 description
工具调用后回答仍然不对没有把工具结果传回模型必须把 tool_call_id 和 content 一起作为 tool 角色消息提交

6.3 上下文窗口溢出

问题现象常见原因解决思路
报错提示 token 超限把整篇文档塞进 Prompt使用 RAG 只塞检索后的 TopK 片段
多轮对话越聊越慢越贵历史消息无限累积设置滑动窗口,旧消息做摘要压缩
长文档摘要中途截断生成 max_tokens 不够或输入超限分块摘要,再做层级合并

6.4 本地模型输出 NaN 或异常数值

问题现象常见原因解决思路
输出乱码/NaNfp16 在大数值时溢出改用 bf16
训练 loss 异常高精度导致梯度不稳定检查混合精度策略,必要时回退 fp32
量化后效果骤降量化粒度过大使用更细粒度的量化方案,如 4-bit GPTQ/AWQ

7. 工程最佳实践与生产建议

7.1 成本控制:Token 是可观测的

LLM 应用的成本大头是 Token,尤其是中文。一个完整 Agent 任务往往需要多轮调用,Token 消耗可能是你预期的 3 到 5 倍。建议:

  • 每个出入口都记录 input tokens、output tokens。
  • Prompt 模板复用公共前缀,减少重复消息。
  • 对多轮对话做旧消息合并或摘要,而不是无限保留。
  • 设置单用户预算上限,防止异常循环调用消耗成本。

7.2 安全边界:LLM 没有“权限”概念

很多初学者把工具函数直接暴露给模型,这是非常危险的做法。模型本质上是根据概率生成文本的,它不具备真正的“授权判断”能力。生产环境必须做到:

  • 工具层做权限校验,模型只负责“意图解析”,不负责“越权决策”。
  • 所有工具调用都要有审计日志,记录是谁、在什么时间、让模型执行了什么操作。
  • 参数做白名单校验,禁止模型自由拼接 SQL、Shell 命令。
  • 涉及删除、写库、发消息的操作,加二次确认机制。

7.3 可观测性:追踪每一次推理决策

LLM 应用是概率系统,同样的输入可能得到不同输出。调试时如果没有完整日志,几乎无法定位问题。建议至少记录:

  • 每次调用的输入输出。
  • Prompt 的完整内容(尤其是 RAG 拼接后的)。
  • 工具调用参数和结果。
  • Token 消耗。
  • 模型版本和 temperature 参数。

有条件可以接 LangSmith、Langfuse、OpenTelemetry 这类链路追踪工具,把一次 Agent 行动从入口到工具调用到最终输出串成一条完整轨迹。

7.4 流式输出与用户体验

LLM 生成时间较长,如果不做流式输出,用户会以为服务卡住了。Python 端用stream=True,后端用 SSE(Server-Sent Events)把 token 逐步推给前端,这是 LLM 应用的基本体验要求。

response = client.chat.completions.create( model="your-chat-model", messages=messages, stream=True, ) for chunk in response: delta = chunk.choices[0].delta.content if delta: yield delta

注意:如果 Agent 中间还要调用工具,那就不能一次性流式输出到底。常见做法是分阶段:先输出“正在检索文档…”,工具出结果后再流式输出最终回答。

7.5 模型版本固定与灰度

模型服务方经常更新模型,每次更新都可能带来行为变化。如果生产环境直接跟随最新版本,回答风格、Tool Calling 准确性都可能漂移。建议在配置中心固定模型版本号,升级前先跑回归用例,再灰度放量。

8. 为什么现在的 LLM 生态需要 Agent 和编排框架

8.1 单次调用解决不了复杂问题

我之前犯的最大错误,就是认为把 Prompt 写好,单次调用模型就能解决大部分业务问题。真实业务中,用户输入往往是模糊的、多步骤的。比如“检查下线上 service 最近有没有异常,如果有就帮我查一下相关日志”。

这句话至少包含:

  • 判断服务健康状态。
  • 条件分支:如果异常,继续查日志;如果正常,直接给出结论。
  • 多轮工具调用,并要求中间结果参与决策。

这种情况下,单次 Prompt 无法保证输出质量。你需要一个“外层控制逻辑”来管理状态和流程。这就是 Agent + 编排框架存在的意义。

8.2 编排框架到底解决了什么

很多人觉得 LangChain 是多余的,自己用 requests 调 API 也能做 Agent。这种看法有道理,但只适用于玩具项目。编排框架真正解决的痛点包括:

  • 工具注册和路由:统一管理工具列表,让模型能按 description 选择工具。
  • 多步推理状态管理:维护中间变量和上下文。
  • 记忆机制:短期记忆、长期记忆、摘要记忆。
  • 容错重试:工具调用失败后自动重试或降级。
  • 可插拔组件:Embedding、VectorStore、LLM 都可以切换。

如果你用 Spring Boot,Spring AI 也是类似的思路,它把模型接入、Prompt 模板、结构化输出都封装成了 Spring 风格,Java 后端接入成本低很多。

8.3 编排框架不是银弹

同时要承认,编排框架也有成本:

  • 抽象层次多,出了问题更难排查。
  • 版本迭代快,API 变动频繁。
  • 源码本身复杂,不适合简单场景。

如果你的应用只是“单个知识库问答”,不需要 Agent 工具调用,直接用 Python 脚本调用 Embedding API + LLM API 就够了。引入框架之前,先问自己:我要不要多步工具调用?要不要记忆?要不要多模型切换?

9. 总结与下一步学习路线

回到最初那个判断错误:我把 LLM 定义成“更好的搜索引擎”,但它真正的位置,是“重新定义应用交互方式的基础设施”。

学习 LLM 应用开发,不要满足于“会调 API”。你还需要掌握:

  1. 掌握 Token 和上下文机制,理解为什么 Prompt 越长越贵、越容易出错。
  2. 掌握 RAG 链路,能独立搭建“文档入库 -> 切片 -> 向量化 -> 检索 -> 生成”的完整流程。
  3. 掌握 Function Calling / MCP 协议,理解模型如何驱动外部工具执行。
  4. 掌握一种编排框架,或者至少理解状态、记忆、重试这些抽象层。
  5. 掌握工程化能力:成本监控、日志追踪、权限校验、灰度发布。

未来可以继续关注 Karpathy 等研究者提出的 LLM 知识组织思路,把 LLM 当作“知识检索与组织层”来看待,而不是一个孤立生成器。你也可以试着在 Obsidian 或 Wiki 系统里维护自己的 LLM 学习笔记,用结构化方式组织 Model、Agent、RAG、MCP 这些概念,建立自己的知识库。

最后,回到文章标题那句话说:

我不该只把 LLM 看作“回答问题的模型”,它更是一个“能理解任务、调用工具、编排流程”的通用执行体。这种认知转变,决定了你是继续在“调 Prompt”层面打转,还是进入“用 LLM 重新设计产品架构”的新阶段。

如果这篇文章对你理解 LLM 角色有帮助,可以收藏备用,后续实践遇到问题也欢迎在评论区一起讨论。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询