Day 2:LCEL 链式处理——让文档"自己说话"
昨天的问题
Day 1 只做了一件事:把文档读出来,显示前 150 字。这就像你打开了一个合同 PDF,盯着第一页——文件确实打开了,但里面的甲方是谁?合作内容是什么?有效期到什么时候?你什么都还不知道。 天的问题Day 1 只做了一件事:把文档读出来,显示前 150 字。这就像你打开了一个合同 PDF,盯着第一页——文件确实打开了,但里面的甲方是谁?合作内容是什么?有效期到什么时候?你什么都还不知道。
真正的文档分析不是"读",是提取——从一堆文字里捞出关键信息,变成能用的结构化数据。
今天做什么
搭一条 LCEL 分析链。LCEL 是 LangChain 的管道语法——用 | 符号把处理步骤串起来:
三步走:定义格式(Pydantic 模型)→ 写 Prompt(告诉 LLM 规则)→ 组装链(prompt | llm | parser)。
跟项目一的 day2 不同——项目一是"让 Agent 知道自己的身份",我们今天是"让 Agent 知道文档里有什么"。
你需要安装
bash
pip install langchain pydantic langchain-community📁 代码目录
doc_analyzer/
├── sample_contract.txt ← Day1 生成的测试文档
├── day1.py ← Day1 的解析 Pipeline
└── day2.py ← 新增!LCEL 分析链,60行代码
python
# day2.py — LCEL链式文档分析
from langchain.prompts import ChatPromptTemplate
from langchain.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field
from typing import List
# ===== Day1 的文档加载(复用)=====
from langchain_community.document_loaders import TextLoader
import os
TEST_DOC = "sample_contract.txt"
if not os.path.exists(TEST_DOC):
with open(TEST_DOC, "w", encoding="utf-8") as f:
f.write("""合作协议\n甲方:北京科技有限公司\n乙方:上海创新技术有限公司\n第一条 合作内容:共同开发智能客服系统\n第二条 合作期限:2025.1.1 - 2027.12.31\n第三条 知识产权:双方共同所有\n第四条 保密条款:双方互负保密义务\n签署日期:2025年1月1日""")
loader = TextLoader(TEST_DOC, encoding="utf-8")
document = loader.load()[0].page_content
print(f"📄 原文 ({len(document)}字):\n{document}\n")
# ===== Day1 结束 =====
# ===== Day2 新增:结构化输出定义 =====
class ContractInfo(BaseModel):
"""合同关键信息结构"""
party_a: str = Field(description="甲方名称")
party_b: str = Field(description="乙方名称")
subject: str = Field(description="合作内容/主题")
start_date: str = Field(description="开始日期")
end_date: str = Field(description="结束日期")
key_terms: List[str] = Field(description="关键条款(列出3-5条)")
parser = PydanticOutputParser(pydantic_object=ContractInfo)
# Prompt模板——告诉LLM要提取什么
EXTRACT_PROMPT = """你是一个合同分析助手。请从以下合同文本中提取关键信息。
规则:
1. 只提取文档中明确存在的信息,不要猜测
2. 日期格式统一为 YYYY-MM-DD
3. 关键条款每条不超过15个字
{format_instructions}
合同文本:
{document_content}
"""
prompt = ChatPromptTemplate.from_template(EXTRACT_PROMPT)
# ===== MockLLM —— 零成本,不用API Key =====
class MockLLM:
"""模拟LLM——返回预设的结构化数据"""
def invoke(self, messages):
import json
msg = str(messages[-1].content)
# 模拟提取结果
result = ContractInfo(
party_a="北京科技有限公司",
party_b="上海创新技术有限公司",
subject="共同开发智能客服系统",
start_date="2025-01-01",
end_date="2027-12-31",
key_terms=[
"合作期限三年",
"知识产权双方共有",
"双方互负保密义务"
]
)
return result.model_dump_json()
llm = MockLLM()
# ===== 组装LCEL链 ⭐ 核心一句 =====
chain = prompt | llm | parser
# ===== 运行分析 =====
print("=" * 50)
print("🔍 开始分析合同...")
print("=" * 50)
result = chain.invoke({
"document_content": document,
"format_instructions": parser.get_format_instructions()
})
print(f"\n✅ 分析完成!")
print(f" 甲方: {result.party_a}")
print(f" 乙方: {result.party_b}")
print(f" 合作内容: {result.subject}")
print(f" 合同期限: {result.start_date} 至 {result.end_date}")
print(f" 关键条款: ")
for i, term in enumerate(result.key_terms, 1):
print(f" {i}. {term}")运行
bash
python day2.py你应该看到:
📄 原文 (166字):
合作协议
甲方:北京科技有限公司
乙方:上海创新技术有限公司
...
==================================================
🔍 开始分析合同...
==================================================
✅ 分析完成!
甲方: 北京科技有限公司
乙方: 上海创新技术有限公司
合作内容: 共同开发智能客服系统
合同期限: 2025-01-01 至 2027-12-31
关键条款:
1. 合作期限三年
2. 知识产权双方共有
3. 双方互负保密义务对比一下:左边是杂乱无章的合同文本,右边是整齐的结构化对象。这就是文档分析的魔法——不是让 LLM"看着文档回答你",而是让 LLM"把文档整理成你需要的格式"。
你学到了什么
LCEL 管道让数据处理像流水线。 prompt | llm | parser——数据从左流到右,每一步都自动传递。这种模式在项目一里也出现过(prompt | mock_llm),这次多了一个 parser 环节。
PydanticOutputParser 让 LLM 的输出可预测。 没有解析器时,LLM 可能返回"甲方是北京科技"也可能返回 "party_a: 北京科技有限公司"——格式千奇百怪。有了 parser,永远返回一个规整的 ContractInfo 对象,属性名固定,类型固定。
再强调一次 MockLLM 的价值: 不用 API Key,不用花钱,代码能跑、能测试、能理解整个链路。MockLLM 返回了你预设的正确结果——在生产环境里,你把 MockLLM 替换成真实的 LLM 调用,其他代码一行不用改。这就是"接口即契约"的威力。
明天的预告
今天 Agent 一次只能分析一份文档。如果你手头有 50 份合同,串行处理每份 3 秒,就是 150 秒。明天用 asyncio 并发处理——50 份同时跑,十几秒搞定。
Day 2 完成。Agent 能从合同中提取结构化信息了——甲方、乙方、期限、条款,不再是"一堆字"。

