刚开始学 LangChain 的时候,我心里其实有点不屑:不就是封装一下 OpenAI API 吗?我自己用 requests.post 也能调,为什么要多装一个框架?
直到我接到一个实际需求------从一条运维日志里提取出级别、服务名、错误类型、时间,我才意识到:调用 API 只是第一步,让模型按我想要的格式稳定输出,才是 LangChain 真正帮我解决的问题。
这篇文章记录我从"直接调 API"到"写出第一条 Chain"的过程。
一、我当时想干什么
我手里有一条这样的日志:
text
2026-07-02 ERROR web_scrm 数据库连接超时 导致支付接口返回超时请及时处理
我需要把它变成结构化的数据,比如:
json
{
"level": "ERROR",
"service": "web_scrm",
"error_type": "数据库连接超时",
"message": "导致支付接口返回超时请及时处理",
"timestamp": "2026-07-02T00:00:00"
}
第一反应:让大模型帮我提取。我试了直接发一条请求,模型确实能返回 JSON,但格式每次都不太一样。有时候带解释,有时候缺字段,有时候 JSON 外面套了一层 markdown 代码块。
这让我意识到:光会调 API 不够,你还得约束输出。
二、我写的代码
LangChain 里的 Chain,简单说就是 prompt | model | parser 这三个东西串起来。
我的第一个 Chain 长这样:
python
from pydantic import BaseModel, Field
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
class LogInfo(BaseModel):
"""从日志中提取关键信息"""
level: str = Field(description="日志级别:INFO、WARN、ERROR")
service: str = Field(description="服务名")
error_type: str = Field(description="错误类型")
message: str = Field(description="错误信息")
timestamp: datetime = Field(description="告警时间")
llm = ChatOpenAI(
model="deepseek-chat",
temperature=0,
openai_api_key="你的 api key",
openai_api_base="https://api.deepseek.com/v1",
)
parser = JsonOutputParser(pydantic_object=LogInfo)
prompt = ChatPromptTemplate.from_messages([
("system", "你是日志分析助手,只返回 JSON,不要任何解释。"),
("human", "请从这条日志中提取信息:{log}\n\n{format_instructions}"),
])
chain = prompt | llm | parser
result = chain.invoke({
"log": "2026-07-02 ERROR web_scrm 数据库连接超时 导致支付接口返回超时请及时处理",
"format_instructions": parser.get_format_instructions(),
})
print(result)
输出:
python
{
'level': 'ERROR',
'service': 'web_scrm',
'error_type': '数据库连接超时',
'message': '导致支付接口返回超时请及时处理',
'timestamp': datetime.datetime(2026, 7, 2, 0, 0)
}
看起来简单,但这里每一部分都有它的作用。
三、我遇到的坑
坑 1:temperature 没调,JSON 偶尔格式不对
我一开始写的代码:
python
llm = ChatOpenAI(
model="deepseek-chat",
temperature=0.7, # 默认值,问题出在这里
openai_api_key="你的 api key",
openai_api_base="https://api.deepseek.com/v1",
)
跑出来的输出有时候是这样:
text
以下是根据日志提取的结构化信息:
```json
{
"level": "ERROR",
"service": "web_scrm",
...
}
javascript
模型"自由发挥",在 JSON 前面加了一句解释,还套了 markdown 代码块。`JsonOutputParser` 解析时直接报错:
```text
OutputParserException: Failed to parse response
改成 temperature=0 后,输出稳定了。对于需要结构化输出的任务,temperature=0 几乎是标配。
坑 2:忘了把 format_instructions 传给 prompt
JsonOutputParser 会根据 Pydantic schema 自动生成一段格式说明,但这段说明不会自动塞进 prompt。
我当时犯的错:
python
prompt = ChatPromptTemplate.from_messages([
("system", "你是日志分析助手,只返回 JSON,不要任何解释。"),
("human", "请从这条日志中提取信息:{log}"), # 漏了 format_instructions
])
result = chain.invoke({
"log": "2026-07-02 ERROR web_scrm 数据库连接超时...",
# 这里虽然传了,但 prompt 里没有对应的占位符
"format_instructions": parser.get_format_instructions(),
})
模型根本不知道我要哪些字段,输出完全是随机的:
json
{
"时间": "2026-07-02",
"类型": "错误",
"内容": "web_scrm 数据库连接超时"
}
正确的 prompt 必须显式留出位置:
python
("human", "请从这条日志中提取信息:{log}\n\n{format_instructions}")
坑 3:以为 | 只是字符串拼接
我开始以为 prompt | llm | parser 和下面这种写法差不多:
python
# 我最初的理解(错误的)
response = llm.invoke(prompt.invoke({"log": log}))
result = parser.invoke(response)
结果发现手动调的时候,类型转换、流式输出、错误追踪全要自己处理。prompt | llm | parser 这个写法叫 LCEL,它会自动处理这些事。
比如流式输出:
python
for chunk in chain.stream({"log": log, "format_instructions": parser.get_format_instructions()}):
print(chunk)
不用我自己拆响应里的 token。
坑 4:langchain_core 和 langchain 分不清
我一开始写的导入:
python
from langchain.prompts import ChatPromptTemplate
from langchain.output_parsers import JsonOutputParser
运行没问题,但看文档的时候发现很多示例用的是 langchain_core.prompts。我一度以为这两个是同一个东西的不同写法。
后来才明白区别:
langchain_core:核心抽象,稳定、轻量,不依赖具体集成langchain:高层封装,包含更多社区集成和便捷方法
写基础 Chain 的时候,优先从 langchain_core 导入。这样你的代码更稳定,后面换模型提供商时改动也更小。
改成:
python
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import JsonOutputParser
四、搞清楚后的结论
写出第一条 Chain 后,我对 LangChain 的理解变了。
它不是一个"更好用的 API 客户端",而是一个把 LLM 应用拆成标准积木的框架。
这三个积木各自负责:
| 组件 | 职责 | 对应我的代码 |
|---|---|---|
| Prompt | 构造给模型的输入 | ChatPromptTemplate |
| LLM | 执行推理 | ChatOpenAI |
| Parser | 把模型输出转成程序能用的格式 | JsonOutputParser |
如果只用 requests.post 调 API,这三件事你都得自己写。LangChain 把它们标准化了,所以后面加工具、加记忆、加 Agent 的时候,每一步都是可插拔的。
五、我的收获
LangChain 的价值不是封装 API,而是把"输入 → 思考 → 输出"拆成可组合的积木。
我原来的思路是:有一个需求,就写一段 prompt,调一次 API,再写一段正则解析结果。需求一多,代码就成了一团乱麻。
现在我的思路是:先定义 schema,再拼 Chain,最后把 Chain 当成一个可测试、可复用的单元。下一步 Agent 能调用的"工具",其实也就是这种 Chain 或函数。
这条 Chain 虽然小,但它是我后面写 Agent 的起点。
下一篇我会写 Tool------怎么把 query_elk_mcp 这种函数变成 Agent 能调用的工具。