Files
zhuangang/topics/ai/langchain/examples/lc-basics/lc_basics.py
T

102 lines
4.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# requires: langchain, langchain_core, pydantic
"""LangChain 1.x 基础:LCEL 链、结构化输出解析 + 重试 + 备用模型、create_agent 工具调用 + 中间件。
用假模型离线运行(GenericFakeChatModel 按顺序返回预设消息)。换成真实模型:
from langchain.chat_models import init_chat_model
model = init_chat_model("openai:gpt-4.1-mini") # 模型名和提供方写法以官方文档为准
基于 langchain 1.4 / langchain-core 1.7 编写,以官方文档为准:https://docs.langchain.com/oss/python/langchain/overview
运行:python lc_basics.py
"""
from __future__ import annotations
from langchain.agents import create_agent
from langchain.agents.middleware import ModelCallLimitMiddleware
from langchain_core.language_models.fake_chat_models import GenericFakeChatModel
from langchain_core.messages import AIMessage
from langchain_core.output_parsers import PydanticOutputParser, StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableLambda
from langchain_core.tools import tool
from pydantic import BaseModel, Field
def fake(*texts_or_msgs) -> GenericFakeChatModel:
msgs = [m if isinstance(m, AIMessage) else AIMessage(content=m) for m in texts_or_msgs]
return GenericFakeChatModel(messages=iter(msgs))
# ---------- 1. LCEL:提示词 | 模型 | 解析器 ----------
def demo_lcel() -> str:
prompt = ChatPromptTemplate.from_messages([
("system", "你是客服,用一句话回答。"),
("human", "{question}"),
])
chain = prompt | fake("您好,开发票请在订单详情页申请。") | StrOutputParser()
return chain.invoke({"question": "怎么开发票"})
# ---------- 2. 结构化输出:解析 + 失败重试 + 备用模型 ----------
class Triage(BaseModel):
category: str = Field(pattern="^(refund|bug|question)$")
urgent: bool
def demo_structured() -> Triage:
parser = PydanticOutputParser(pydantic_object=Triage)
prompt = ChatPromptTemplate.from_messages([
("system", "对工单分类。{format_instructions}"),
("human", "{ticket}"),
]).partial(format_instructions=parser.get_format_instructions())
# 主模型第一次返回了不合法的 JSON,第二次正确:with_retry 对解析失败重试
primary = prompt | fake("类别是退款", '{"category": "refund", "urgent": true}') | parser
robust = primary.with_retry(stop_after_attempt=2)
# 主链彻底失败时切换到备用链(真实项目中是另一个模型 / 提供方)
def broken(_):
raise TimeoutError("主模型超时")
backup = prompt | fake('{"category": "question", "urgent": false}') | parser
with_fallback = RunnableLambda(broken).with_fallbacks([backup])
assert with_fallback.invoke({"ticket": "怎么改地址"}).category == "question"
return robust.invoke({"ticket": "我要退款,很急"})
# ---------- 3. create_agent:工具调用 + 调用次数限制 ----------
class ToolCallingFake(GenericFakeChatModel):
"""假模型不支持 bind_tools,这里直接返回自身;真实模型会把工具定义发给接口。"""
def bind_tools(self, tools, **kwargs):
return self
@tool
def get_order_status(order_id: str) -> str:
"""查询订单的物流状态。order_id 形如 A1001。"""
return {"A1001": "已发货,预计明天送达"}.get(order_id, "订单不存在")
def demo_agent() -> list:
model = ToolCallingFake(messages=iter([
AIMessage(content="", tool_calls=[{"name": "get_order_status", "args": {"order_id": "A1001"}, "id": "c1"}]),
AIMessage(content="您的订单 A1001 已发货,预计明天送达。"),
]))
agent = create_agent(
model,
tools=[get_order_status],
system_prompt="你是售后客服,查询订单要用工具。",
middleware=[ModelCallLimitMiddleware(run_limit=5)], # 防止无限循环
)
out = agent.invoke({"messages": [{"role": "user", "content": "A1001 到哪了"}]})
return out["messages"]
if __name__ == "__main__":
print("LCEL:", demo_lcel())
t = demo_structured()
print("结构化输出:", t)
assert t.category == "refund" and t.urgent
msgs = demo_agent()
for m in msgs:
print(f"{type(m).__name__:12s}", m.content or getattr(m, "tool_calls", ""))
assert [type(m).__name__ for m in msgs] == ["HumanMessage", "AIMessage", "ToolMessage", "AIMessage"]