LangChain 快速入门

LangChain 快速入门

"""LangChain lessons playground.

Each lesson is a standalone function so you can run and maintain them independently.
Set `LESSON` at the bottom to choose which lesson to run.
"""

from __future__ import annotations

from typing import Any, Callable

LESSON_MODEL = "deepseek:deepseek-chat"
LESSON = 2


def _init_model() -> Any:
	"""Create a chat model instance for lessons."""
	from langchain.chat_models import init_chat_model

	return init_chat_model(LESSON_MODEL)


def _print_agent_messages(result: dict[str, Any]) -> None:
	"""Print all messages in an agent result payload."""
	for message in result["messages"]:
		print(f"{type(message).__name__}: {message.content}")


# 第 1 关:召唤模型

def lesson_1_invoke_model() -> None:
	"""Invoke a chat model with a single string prompt."""
	response = _init_model().invoke("请用一句话解释什么是 LangChain。")
	print("类型:", type(response).__name__)
	print("内容:", response.content)
	print("响应元数据:", response.response_metadata)


# 第 2 关:消息与 Prompt

def lesson_2_messages_and_prompt() -> None:
	"""Send explicit system/human messages to the model."""
	from langchain_core.messages import HumanMessage, SystemMessage

	messages = [
		SystemMessage(content="你是一位只用比喻讲解技术的老师。"),
		HumanMessage(content="解释什么是列表推导式。"),
	]
	response = _init_model().invoke(messages)
	print(response.content)
	print("关键点: SystemMessage 定义角色,HumanMessage 表达用户问题。")


# 第 3 关:Runnable 数据管道

def lesson_3_runnable_pipeline() -> None:
	"""Compose prompt -> model -> parser as one runnable pipeline."""
	from langchain_core.output_parsers import StrOutputParser
	from langchain_core.prompts import ChatPromptTemplate

	prompt = ChatPromptTemplate.from_messages([
		("system", "你是一位简洁的技术老师。"),
		("human", "请用三句话解释:{topic}"),
	])
	chain = prompt | _init_model() | StrOutputParser()
	result = chain.invoke({"topic": "Runnable"})

	print(result)
	print("链类型:", type(chain).__name__)


# 第 4 关:让模型调用工具

def lesson_4_tool_calling() -> None:
	"""Build an agent with one tool and let model decide when to call it."""
	from langchain.agents import create_agent
	from langchain_core.tools import tool

	@tool
	def get_temperature(city: str) -> str:
		"""查询城市温度。"""
		temperatures = {
			"北京": "25 摄氏度",
			"上海": "28 摄氏度",
		}
		return temperatures.get(city, "未知城市")

	agent = create_agent(model=_init_model(), tools=[get_temperature])
	result = agent.invoke({
		"messages": [{"role": "user", "content": "北京现在多少度?"}],
	})

	print(">>> result:", result)
	print("=" * 50)
	_print_agent_messages(result)


# 第 5 关:理解 Agent 核心循环

def lesson_5_agent_loop() -> None:
	"""Observe model -> tool -> model loop with a safe calculator tool."""
	import ast
	import operator

	from langchain.agents import create_agent
	from langchain_core.tools import tool

	binary_ops = {
		ast.Add: operator.add,
		ast.Sub: operator.sub,
		ast.Mult: operator.mul,
		ast.Div: operator.truediv,
		ast.Mod: operator.mod,
		ast.Pow: operator.pow,
	}
	unary_ops = {
		ast.UAdd: operator.pos,
		ast.USub: operator.neg,
	}

	def safe_eval(expression: str) -> float:
		"""Evaluate arithmetic expressions with AST instead of `eval`."""
		node = ast.parse(expression, mode="eval")

		def visit(current: ast.AST) -> float:
			if isinstance(current, ast.Expression):
				return visit(current.body)
			if isinstance(current, ast.Constant) and isinstance(current.value, (int, float)):
				return float(current.value)
			if isinstance(current, ast.BinOp) and type(current.op) in binary_ops:
				left = visit(current.left)
				right = visit(current.right)
				return float(binary_ops[type(current.op)](left, right))
			if isinstance(current, ast.UnaryOp) and type(current.op) in unary_ops:
				return float(unary_ops[type(current.op)](visit(current.operand)))
			raise ValueError("不支持的表达式")

		return visit(node)

	@tool
	def calculator(expression: str) -> str:
		"""计算一个简单数学表达式。"""
		try:
			value = safe_eval(expression)
		except Exception as exc:  # noqa: BLE001
			return f"计算失败: {exc}"
		if value.is_integer():
			return str(int(value))
		return str(value)

	agent = create_agent(model=_init_model(), tools=[calculator])
	result = agent.invoke({
		"messages": [{"role": "user", "content": "计算 12 * (8 + 3)"}],
	})

	print(">>> result:", result)
	print("最终回答:", result["messages"][-1].content)
	print("关键点: Agent 会在模型和工具之间循环,直到得到最终回答。")


# 第 6 关:流式输出

def lesson_6_streaming() -> None:
	"""Stream model tokens and agent updates."""
	from langchain.agents import create_agent

	model = _init_model()
	print("模型流式输出:")
	for chunk in model.stream("写一首关于程序员的短诗"):
		if chunk.content:
			print(chunk.content, end="", flush=True)
	print("\n")

	agent = create_agent(model=model, tools=[])
	print("Agent 流式更新:")
	for chunk in agent.stream({
		"messages": [{"role": "user", "content": "解释 Python 的装饰器"}],
	}, stream_mode="updates"):
		print(chunk)


# 第 7 关:记忆与持久化

def lesson_7_memory() -> None:
	"""Use a checkpointer to preserve messages across two agent runs."""
	from langchain.agents import create_agent
	from langgraph.checkpoint.memory import InMemorySaver

	agent = create_agent(model=_init_model(), checkpointer=InMemorySaver())
	config = {"configurable": {"thread_id": "lesson-7"}}

	first = agent.invoke(
		{"messages": [{"role": "user", "content": "我叫张三,请记住我的名字。"}]},
		config,
	)
	second = agent.invoke(
		{"messages": [{"role": "user", "content": "我叫什么?"}]},
		config,
	)

	print("第一次:", first["messages"][-1].content)
	print("第二次:", second["messages"][-1].content)
	print("关键点: 两次调用使用了相同的 thread_id。")


# 第 8 关:RAG 知识库

def lesson_8_rag() -> None:
	"""Retrieve local documents, then place them into the model prompt."""
	from langchain_core.documents import Document
	from langchain_core.prompts import ChatPromptTemplate

	documents = [
		Document(page_content="LangChain 用 Runnable 统一组件的执行接口。"),
		Document(page_content="Agent 通常由模型节点、工具节点和条件边组成。"),
		Document(page_content="ToolNode 负责执行模型返回的 tool_calls。"),
	]
	question = "Agent 的工具调用由谁执行?"
	query_words = ("Agent", "工具", "执行", "ToolNode", "tool_calls")
	retrieved = [
		document
		for document in documents
		if any(word in document.page_content for word in query_words)
	]
	context = "\n".join(document.page_content for document in retrieved)

	prompt = ChatPromptTemplate.from_messages([
		("system", "只依据下面的上下文回答,不确定时明确说不知道。\n上下文:\n{context}"),
		("human", "问题: {question}"),
	])
	chain = prompt | _init_model()
	response = chain.invoke({"context": context, "question": question})

	print("检索结果:", context)
	print("回答:", response.content)
	print("关键点: RAG = 检索 documents + 将 context 注入 prompt。")


# 第 9 关:Middleware 控制 Agent

def lesson_9_middleware() -> None:
	"""Limit an agent to one model call and observe middleware behavior."""
	from langchain.agents import create_agent
	from langchain.agents.middleware import ModelCallLimitMiddleware

	agent = create_agent(
		model=_init_model(),
		middleware=[ModelCallLimitMiddleware(run_limit=1, exit_behavior="end")],
	)
	result = agent.invoke({
		"messages": [{"role": "user", "content": "解释 Middleware 的作用。"}],
	})
	print(">>> result:", result)
	print("=" * 27)
	print(result["messages"][-1].content)
	print("关键点: Middleware 可以在模型节点前后控制执行过程。")


# 第 10 关:综合挑战

def lesson_10_capstone() -> None:
	"""Build a small DeepSeek agent with two deterministic local tools."""
	from datetime import datetime

	from langchain.agents import create_agent
	from langchain_core.tools import tool

	@tool
	def get_project_fact(topic: str) -> str:
		"""查询实验项目中的固定知识。"""
		facts = {
			"runnable": "Runnable 统一了 invoke、stream 和 batch 等执行接口。",
			"agent": "Agent 通过 model -> tools -> model 循环处理工具调用。",
		}
		return facts.get(topic.lower(), "没有找到这个知识点。")

	@tool
	def current_time() -> str:
		"""返回本机当前时间。"""
		return datetime.now().astimezone().isoformat(timespec="seconds")

	agent = create_agent(model=_init_model(), tools=[get_project_fact, current_time])
	for update in agent.stream({
		"messages": [{
			"role": "user",
			"content": "先告诉我 Agent 的核心循环,再告诉我现在的本机时间。",
		}],
	}, stream_mode="updates"):
		print(update)


def run_lesson(lesson: int) -> None:
	"""Run one lesson by lesson id."""
	lessons: dict[int, Callable[[], None]] = {
		1: lesson_1_invoke_model,
		2: lesson_2_messages_and_prompt,
		3: lesson_3_runnable_pipeline,
		4: lesson_4_tool_calling,
		5: lesson_5_agent_loop,
		6: lesson_6_streaming,
		7: lesson_7_memory,
		8: lesson_8_rag,
		9: lesson_9_middleware,
		10: lesson_10_capstone,
	}
	if lesson not in lessons:
		available = ", ".join(str(value) for value in sorted(lessons))
		raise ValueError(f"未知 LESSON={lesson},可选值: {available}")
	lessons[lesson]()


if __name__ == "__main__":
	run_lesson(LESSON)

评论

(0)
登录后即可发表评论,去登录
加载评论中...
最后更新:2026/9/3