Skip to content

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 能从合同中提取结构化信息了——甲方、乙方、期限、条款,不再是"一堆字"。