LangChain 生产级实战(八):结构化输出与语义路由 —— 让 AI 听话且精准
1. 痛点:LLM 的“话痨”属性与“偏科”问题
痛点一:解析的噩梦
当你要求 LLM “请提取简历中的姓名和电话,并以 JSON 格式返回”时,它经常会给你返回这样的东西:
1 2 3 4 5 6 好的,这是我提取的结果: ```json { "name": "张三", "phone": "13800000000" }
希望这对你有帮助!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 你的 JSON 解析器(`json.loads`)会立刻报错,因为它包含了 ```json 标记和多余的废话。以前我们只能写正则表达式来清洗,非常痛苦。 ### 痛点二:全能 Prompt 的平庸 如果你试图写一个“万能 Prompt”来处理所有类型的客户咨询(退货、维修、投诉),这个 Prompt 会变得非常长且效果平庸。更好的做法是:**专人专办**。但是,怎么知道用户这句话该找哪个“专人”呢? ## 2. 解决方案一:结构化输出 (Structured Output) 从 2023 年底开始,OpenAI 等模型厂商引入了 **Function Calling (Tool Calling)** 和 **JSON Mode**。LangChain 对此进行了完美的封装,提供了 `.with_structured_output()` 方法。 ### 实战:提取标准化的用户信息 我们需要使用 Python 的 `Pydantic` 库来定义我们想要的“数据模具”。 #### 环境准备 ```bash pip install langchain langchain-openai pydantic
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 from typing import Optional , List from langchain_core.pydantic_v1 import BaseModel, Fieldfrom langchain_openai import ChatOpenAIclass UserInfo (BaseModel ): name: str = Field(description="用户的全名" ) age: Optional [int ] = Field(description="用户的年龄,如果未提及则为 None" ) interests: List [str ] = Field(description="用户的兴趣爱好列表" ) is_student: bool = Field(description="用户是否是学生" ) llm = ChatOpenAI(model="gpt-3.5-turbo" , temperature=0 ) structured_llm = llm.with_structured_output(UserInfo) text_input = "我是李四,今年20岁,在北大读大二。平时喜欢打篮球和看科幻小说。" print (f"--- 输入文本: {text_input} ---" )result = structured_llm.invoke(text_input) print ("\n--- 结构化输出结果 (UserInfo 对象) ---" )print (f"Name: {result.name} (Type: {type (result.name)} )" )print (f"Age: {result.age} (Type: {type (result.age)} )" )print (f"Interests: {result.interests} (Type: {type (result.interests)} )" )print (f"Is Student: {result.is_student} (Type: {type (result.is_student)} )" )print ("\n--- 导出的 JSON ---" )print (result.dict ())
深度解析
.with_structured_output() 在底层其实是利用了 OpenAI 的 Tool Calling 机制。它把 UserInfo 转换成了 JSON Schema 传给模型,并告诉模型:“你必须调用这个 Tool”。模型生成参数后,LangChain 自动截获并转换回 Pydantic 对象。
这意味着:0 正则解析,100% 类型安全。
3. 解决方案二:语义路由 (Semantic Routing)
传统的路由是基于关键字的(if "退货" in query)。但如果用户说“我不想要这个东西了”,关键字匹配就会失效。
语义路由 利用 Embedding(向量) 技术。我们把每一类问题的“典型描述”算成向量。当新问题进来时,看它和哪个向量最接近,就去哪。
实战:构建一个智能分诊台
我们将定义两条路线:
Math Route : 擅长回答数学物理问题。
General Route : 擅长回答日常闲聊。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 from langchain_openai import OpenAIEmbeddingsfrom langchain_core.prompts import ChatPromptTemplatefrom langchain_core.runnables import RunnableLambda, RunnablePassthroughfrom langchain_core.output_parsers import StrOutputParserfrom langchain.utils.math import cosine_similaritymath_template = """你是一个物理学家。请用严谨的公式和科学术语回答。 问题: {query}""" general_template = """你是一个幽默的脱口秀演员。请用搞笑的方式回答。 问题: {query}""" route_samples = { "math" : ["什么是牛顿第二定律?" , "计算圆的面积" , "积分怎么算" , "光速是多少" ], "general" : ["你好" , "给我讲个笑话" , "今天天气不错" , "你是谁" ] } embeddings = OpenAIEmbeddings() print ("正在初始化路由向量..." )math_vectors = embeddings.embed_documents(route_samples["math" ]) general_vectors = embeddings.embed_documents(route_samples["general" ]) def router (info ): query = info["query" ] query_vector = embeddings.embed_query(query) math_score = max (cosine_similarity([query_vector], math_vectors)[0 ]) general_score = max (cosine_similarity([query_vector], general_vectors)[0 ]) print (f"--- 路由打分: Math={math_score:.4 f} , General={general_score:.4 f} ---" ) if math_score > general_score: return ChatPromptTemplate.from_template(math_template) else : return ChatPromptTemplate.from_template(general_template) chain = ( {"query" : RunnablePassthrough()} | RunnableLambda(router) | ChatOpenAI() | StrOutputParser() ) print ("\n--- 测试 1: 数学问题 ---" )print (chain.invoke("这就好比那个 E=mc平方 是什么意思?" ))print ("\n--- 测试 2: 闲聊问题 ---" )print (chain.invoke("嘿,最近过得怎么样?" ))
深度解析
当你问“E=mc平方是什么意思”时,虽然这句话里没有“物理”二字,但它在向量空间里与“什么是牛顿第二定律”等问题的距离,远远近于“你好”。
Semantic Routing 让你的应用能够理解“弦外之音”,这是传统 if-else 无法做到的。
4. 进阶展望:未来的 Agent 会是什么样?
至此,我们的 LangChain 实战系列博客就告一段落了。但技术的演进从来不会停止。当你掌握了结构化输出和语义路由后,还有哪些更前沿的方向值得去探索?
1. 本地化与隐私 (Local Agents)
随着 Llama 3、Mistral 等开源模型的强大,越来越多的企业开始尝试在本地部署 Agent。结合 Ollama + LangChain ,你可以构建完全离线、隐私安全的 Agent,且无需支付 API 费用。这对于处理敏感数据的金融、医疗场景至关重要。
2. 多模态交互 (Multimodality)
现在的 Agent 大多还停留在“文字对话”阶段。但在 GPT-4o 和 Gemini 1.5 Pro 的推动下,未来的 Agent 将具备“视觉”。
想象一下:用户不再是打字描述“网页报错了”,而是直接截个图发给 Agent。Agent 通过 多模态 RAG 识别图片中的报错代码,并自动给出修复建议。
3. 提示词工程的自动化 (DSPy)
我们还在手动写 Prompt("你是一个物理学家...")。斯坦福大学提出的 DSPy 理念正在改变这一点:它主张通过编程的方式,让模型根据数据自动优化 Prompt,而不是靠人去“猜”哪个 Prompt 更好。虽然 LangChain 也有类似尝试,但这绝对是未来的一大趋势。
结束语
构建 LLM 应用就像搭建乐高积木,LangChain 和 LangGraph 提供了最丰富的零件库。希望这 8 篇文章能成为你手中的图纸,帮你搭建出属于自己的精彩作品。感谢你的阅读!