第7章 智能体开发框架LangChain-代码和提示词-林子雨编著《AI编程与智能体开发》

大数据学习路线图

林子雨编著《AI编程与智能体开发》(访问教材官网

7.5 LangChain快速入门

7.5.1 环境准备

推荐使用如下命令构建Python虚拟环境并安装依赖包:

python -m venv langchain-env  # 在当前目录下创建一个名字叫langchain-env的独立 Python 环境,用来隔离项目依赖,不污染系统全局Python
langchain-env\Scripts\activate
pip install -U langchain langchain-ollama langchain-community langchain-text-splitters faiss-cpu

7.5.2 第一个LangChain程序

# langchain_call_model.py
from langchain_ollama import ChatOllama
model = ChatOllama(
    model="gemma4:e4b",
    temperature=0,
    base_url="http://127.0.0.1:11434"
)
response = model.invoke("请用一句话解释什么是检索增强生成。")
print(response.content)

如果采用云端大模型,比如阿里的千问大模型,则可以采用类似如下代码:

# langchain_cloud_qwen.py
from langchain_community.chat_models import ChatTongyi

model = ChatTongyi(
    model="qwen-turbo",
    temperature=0,
    api_key="你的阿里云API_KEY"
)

response = model.invoke("请用一句话解释什么是检索增强生成。")
print(response.content)

7.5.3 一个简易的智能体实例

# langchain_simple_agent.py
from langchain.agents import create_agent
from langchain_ollama import ChatOllama

def search_notice(topic: str) -> str:
    """
    查询课程相关通知信息
    根据输入的主题关键词,返回对应的课程通知内容
    Args:
        topic: 要查询的通知主题(如:实验报告、课程答疑)
    Returns:
        对应的通知详情字符串
    """
    notices = {
        "实验报告": "实验报告需在本周五18:00前提交到教学平台。",
        "课程答疑": "课程答疑安排在周三下午 4 点,地点为实验楼302。",
    }
    for keyword, notice in notices.items():
        if keyword in topic:
            return notice
    return notices.get(topic, "暂未查询到相关通知。")

def build_agent():
    model = ChatOllama(
        model="gemma4:e4b",
        temperature=0,
        base_url="http://127.0.0.1:11434",
    )
    return create_agent(
        model=model,
        tools=[search_notice],
        system_prompt="你是一名课程助理,必要时调用工具查询课程通知。",
    )

def main() -> None:
    agent = build_agent()
    result = agent.invoke(
        {"messages": [{"role": "user", "content": "请帮我查询实验报告的提交要求。"}]}
    )
    print(result)

if __name__ == "__main__":
    main()

7.6 LangChain的消息

7.6.3 消息实操

以下示例演示多轮对话中消息的创建与使用,场景模拟了教师与助手的连续提问。具体代码如下:

# langchain_messages_demo.py
from langchain_ollama import ChatOllama
from dotenv import load_dotenv
import os

load_dotenv()

llm = ChatOllama(
    model=os.getenv("OLLAMA_MODEL", "gemma4:e4b"),
    base_url=os.getenv("OLLAMA_BASE_URL", "http://localhost:11434"),
    temperature=0.7
)

# 构建多轮对话消息列表
messages = [
    {"role": "system", "content": "你是一个专业的Python教学助手,回答简洁明了。"},
    {"role": "user", "content": "什么是装饰器?"},
    {"role": "assistant", "content": "装饰器是Python中用于修改函数或类行为的特殊语法。"},
    {"role": "user", "content": "请举例说明"}
]

response = llm.invoke(messages)
print(f"AI响应: {response.content}")

c7.6.4 消息在智能体中的流转

以下示例完整演示四种消息类型在一次工具调用中的流转过程:

# langchain_agent_messages_demo.py
from dotenv import load_dotenv
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage, ToolMessage
from langchain_core.tools import tool
from langchain_ollama import ChatOllama
import os

load_dotenv()

@tool
def calculate(a: float, b: float, operator: str) -> str:
    """
    执行基础数学计算

    参数:
        a: 第一个数字
        b: 第二个数字
        operator: 运算符,支持 "+"、"-"、"*"、"/"
    """
    if operator == "+":
        return str(a + b)
    elif operator == "-":
        return str(a - b)
    elif operator == "*":
        return str(a * b)
    elif operator == "/" and b != 0:
        return str(a / b)
    return "不支持的操作"

llm = ChatOllama(
    model=os.getenv("OLLAMA_MODEL", "gemma4:e4b"),
    base_url=os.getenv("OLLAMA_BASE_URL", "http://localhost:11434"),
    temperature=0.7
)
llm_with_tools = llm.bind_tools([calculate])

# ── 步骤 1:用户输入封装为 HumanMessage ──────────────────────
messages = [
    SystemMessage(content="你是计算助手,使用 calculate 工具完成计算。"),
    HumanMessage(content="计算 15 加 27 等于多少"),
]
print(f"步骤1 消息数: {len(messages)},类型: {[type(m).__name__ for m in messages]}")

# ── 步骤 2:模型判断需要调用工具,返回含 tool_calls 的 AIMessage ──
ai_response = llm_with_tools.invoke(messages)
print(f"\n步骤2 模型回复类型: {type(ai_response).__name__}")
print(f"  工具调用指令: {ai_response.tool_calls}")

# ── 步骤 3:执行工具,结果封装为 ToolMessage ─────────────────
tool_call = ai_response.tool_calls[0]
tool_result = calculate.invoke(tool_call["args"])
tool_message = ToolMessage(content=tool_result, tool_call_id=tool_call["id"])
print(f"\n步骤3 工具执行结果: {tool_message.content}")

# ── 步骤 4:把完整历史交给模型,生成自然语言最终回复 ──────────
messages_full = messages + [ai_response, tool_message]
final_response = llm_with_tools.invoke(messages_full)
print(f"\n步骤4 最终回复: {final_response.content}")
print(f"\n完整消息链类型: {[type(m).__name__ for m in messages_full]}")

7.7 LangChain的提示词

7.7.3 提示词模板

在LangChain中,可以使用 PromptTemplate类创建简单的提示词。提示词模板可以内嵌任意数量的模板参数,然后通过参数值格式化模板内容。下面是一个代码实例:

# langchain_string_prompt.py
from langchain_core.prompts import PromptTemplate
# 定义一个提示模板,包含adjective和content两个模板变量,模板变量使用{}包括起来
prompt_template = PromptTemplate.from_template(
    "给我讲一个关于{content}的{adjective}故事。"
)
# 通过模板参数格式化提示模板
result = prompt_template.format(adjective="童话", content="一千零一夜")
print(result)

下面是一个关于聊天消息提示词模板的具体代码实例:

# langchain_chat_prompt.py
from langchain_core.prompts import ChatPromptTemplate
chat_template = ChatPromptTemplate.from_messages(
    [
        ("system", "你是一位人工智能助手,你的名字是{name}。"),
        ("human", "你能做什么"),
        ("ai", "设计课程,谢谢!"),
        ("human", "{user_input}"),
    ]
)
messages = chat_template.format_messages(name="教学助手",
                                          user_input="你的名字叫什么?")
print(messages)

MessagesPlaceholder提示词模板负责在特定位置添加消息列表。在上面的 ChatPromptTemplate中,我们看到了如何格式化两条消息,每条消息都是一个字符串。如果我们希望用户传入一个消息列表,就可以通过MessagesPlaceholder的方式将其插入到特定位置。下面是一个具体代码实例:

# langchain_messagesplaceholder_prompt.py
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.prompts import MessagesPlaceholder
from langchain_core.messages import HumanMessage
prompt_template = ChatPromptTemplate.from_messages([
    ("system", "You are a helpful assistant"),
    #可以传入一组消息
    MessagesPlaceholder("msgs")
])
result = prompt_template.invoke({"msgs": [HumanMessage(content="您好!"),
                                          HumanMessage(content="langchain!")]})
print(result)

7.7.4 LCEL:把提示词串进链式调用

学完ChatPromptTemplate,可以自然引出 LCEL(LangChain Expression Language),它用管道符“|”把提示词、模型、输出解析器串成一条“流水线”,是LangChain推荐的现代写法。
这里对比两种不同的写法:

# 传统写法:手动分三步
messages = chat_prompt.format_messages(role="Python工程师", question="装饰器")
response = llm.invoke(messages)
result = response.content          # 手动取 .content

# LCEL写法:用管道符“|”串成链,一步完成
from langchain_core.output_parsers import StrOutputParser
chain = chat_prompt | llm | StrOutputParser()
result = chain.invoke({"role": "Python工程师", "question": "装饰器"})

以下示例演示流式输出,在需要“打字机效果”的界面中常用:

# langchain_stream_demo.py
from dotenv import load_dotenv
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_ollama import ChatOllama
import os

load_dotenv()

llm = ChatOllama(
    model=os.getenv("OLLAMA_MODEL", "gemma4:e4b"),
    base_url=os.getenv("OLLAMA_BASE_URL", "http://localhost:11434"),
    temperature=0.7
)

chain = ChatPromptTemplate.from_messages([
    ("system", "你是一个简洁的学习助手,回答不超过100字。"),
    ("human", "{question}")
]) | llm | StrOutputParser()

# 流式输出:逐块打印,不等待全部生成完成
print("助手:", end="", flush=True)
for chunk in chain.stream({"question": "什么是递归?用一句话解释"}):
    print(chunk, end="", flush=True)
print()

7.8 LangChain的工具

7.8.3 自定义工具实操

以下示例演示如何封装一个支持四则运算的计算器工具,并通过invoke方法验证正常与异常两种情况。

# langchain_tool_calculator.py
from langchain_core.tools import tool
from typing import Union

@tool
def calculate(a: Union[int, float], b: Union[int, float], operator: str) -> str:
    """
    执行基础数学计算,支持加减乘除。

    参数:
        a: 第一个数字
        b: 第二个数字
        operator: 运算符,可选值: "+", "-", "*", "/"

    返回:
        计算结果字符串,格式:"结果: {数值}"

    示例:
        calculate(10, 5, "+") → "结果: 15"
    """
    try:
        if operator == "+":
            result = a + b
        elif operator == "-":
            result = a - b
        elif operator == "*":
            result = a * b
        elif operator == "/":
            if b == 0:
                return "错误:除数不能为零"
            result = a / b
        else:
            return f"错误:不支持的运算符 '{operator}'"
        return f"结果: {result}"
    except Exception as e:
        return f"错误:{str(e)}"

print(calculate.invoke({"a": 10, "b": 5, "operator": "+"}))
print(calculate.invoke({"a": 10, "b": 0, "operator": "/"}))

以下示例演示如何封装日期加减运算工具,同时兼容YYYY-MM-DD和YYYY/MM/DD 两种常见日期格式。

# langchain_date_tool.py
from langchain_core.tools import tool
from datetime import datetime, timedelta
from typing import Union

@tool
def calculate_date(date_str: str, days: int) -> str:
    """
    计算指定日期加减天数后的日期。
    参数:
        date_str: 原始日期,格式支持 YYYY-MM-DD 或 YYYY/MM/DD
        days: 要加减的天数(正数加,负数减)
    返回:
        计算后的日期字符串,格式:"YYYY-MM-DD"
    示例:
        calculate_date("2026-01-01", 10) → "2026-01-11"
        calculate_date("2026-01-01", -5) → "2025-12-27"
    """
    formats = ["%Y-%m-%d", "%Y/%m/%d"]
    for fmt in formats:
        try:
            original = datetime.strptime(date_str, fmt)
            target = original + timedelta(days=days)
            return target.strftime("%Y-%m-%d")
        except ValueError:
            continue
    return f"错误:日期格式无效,请使用 YYYY-MM-DD 或 YYYY/MM/DD"

print(calculate_date.invoke({"date_str": "2026-04-18", "days": 7}))
print(calculate_date.invoke({"date_str": "2026/04/18", "days": -30}))

以下示例演示文件写入工具的实现,包括目录自动创建、追加/覆盖两种写入模式,以及权限异常的友好处理:

# langchain_file_tool.py
from langchain_core.tools import tool
from pathlib import Path

@tool
def write_to_file(file_path: str, content: str, mode: str = "append") -> str:
    """
    将文本内容写入本地文件。
    参数:
        file_path: 文件路径(支持相对路径和绝对路径)
        content: 要写入的文本内容
        mode: 写入模式,"append"(追加)或 "overwrite"(覆盖)
    返回:
        操作结果字符串
    示例:
        write_to_file("test.txt", "Hello", "overwrite") → "成功写入test.txt"
    """
    try:
        path = Path(file_path)
        path.parent.mkdir(parents=True, exist_ok=True)  # 自动创建缺失的目录层级

        write_mode = "a" if mode == "append" else "w"
        with open(file_path, write_mode, encoding="utf-8") as f:
            f.write(content + "\n")

        return f"成功:{'追加写入' if mode == 'append' else '覆盖写入'}{file_path}"
    except PermissionError:
        return f"错误:权限不足,无法写入{file_path}"
    except Exception as e:
        return f"错误:{str(e)}"

print(write_to_file.invoke({
    "file_path": "output/test.txt",
    "content": "LangChain工具测试",
    "mode": "overwrite"
}))

7.8.4 工具在智能体中的调用

这里给出一个包含工具调用的最小可运行智能体示例。虽然该智能体只有一个工具,但已经具备“理解问题、决定是否调用工具、返回最终答案”的基本能力。具体代码如下:

# langchain_agent_tool.py
from langchain.agents import create_agent
from langchain.tools import tool
from langchain_ollama import ChatOllama
SYSTEM_PROMPT = """
你是高校《AI编程和智能体开发》课程助教。
请准确回答学生问题;涉及分数计算时必须调用工具。
"""
@tool
def calculate_final_score(attendance: float, lab: float, exam: float) -> str:
    """按照总评 = 平时考勤*0.1 + 实验*0.4 + 期末考试*0.5 计算成绩。"""
    total = attendance * 0.1 + lab * 0.4 + exam * 0.5
return f"根据课程规则,总评成绩为 {total:.2f} 分。"

def build_agent():
    model = ChatOllama(
        model="gemma4:e4b",
        temperature=0,
        base_url="http://127.0.0.1:11434",
    )
    return create_agent(
        model=model,
        tools=[calculate_final_score],
        system_prompt=SYSTEM_PROMPT,
)

agent = build_agent()
response = agent.invoke(
    {
        "messages": [
            {
                "role": "user",
                "content": "我的考勤 95,实验 88,期末 90,请计算总评。"
            }
        ]
    }
)
print(response["messages"][-1].content)

7.8.5 MCP:标准化的工具与上下文接入协议

运行示例前,需要在当前Python环境中安装MCP SDK、LangChain MCP适配器以及本章前面使用过的LangChain和模型接入包,具体安装命令如下:

# 安装依赖示例
pip install mcp langchain-mcp-adapters

构建课程通知MCP服务器的代码如下:

# mcp_course_server.py
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("course-assistant")
COURSE_NOTICES = {
    "实验报告": "实验报告需在本周五22:00前提交,文件名格式为:学号-姓名-实验4.docx。",
    "课程答疑": "本周三19:30在线答疑,主题为LangChain工具调用与RAG案例。",
    "期末复习": "期末复习资料将在第16周发布,重点包括RAG、工具调用和智能体状态管理。"
}

@mcp.tool()
def query_course_notice(topic: str) -> str:
    """
    根据主题查询课程通知。
    参数:
        topic: 通知主题,例如“实验报告”“课程答疑”或“期末复习”
    返回:
        与主题相关的课程通知;若没有命中,则返回可查询主题列表。
    """
    if topic in COURSE_NOTICES:
        return COURSE_NOTICES[topic]
    topics = "、".join(COURSE_NOTICES.keys())
    return f"未找到主题“{topic}”。当前可查询主题包括:{topics}。"

if __name__ == "__main__":
    mcp.run(transport="stdio")

有了MCP服务器以后,LangChain应用可以通过MCP适配器加载工具。下面的示例演示如何连接上面的课程通知服务器,并把MCP工具交给LangChain智能体使用。为了与本章前面的示例保持一致,这里仍以本地Ollama模型为例;如果读者使用其他大模型服务,只需要替换模型初始化部分。具体代码如下:

# langchain_use_mcp_tool.py
import asyncio

from langchain.agents import create_agent
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain_ollama import ChatOllama

async def main():
    client = MultiServerMCPClient({
        "course": {
            "command": "python",
            "args": ["mcp_course_server.py"],
            "transport": "stdio",
        }
    })

    tools = await client.get_tools()
    print("已加载工具:", [tool.name for tool in tools])

    model = ChatOllama(model="gemma4:e4b")
    agent = create_agent(model=model, tools=tools)

    result = await agent.ainvoke({
        "messages": [
            {
                "role": "user",
                "content": "请查询实验报告的提交要求,并用一句话提醒学生。"
            }
        ]
    })
    print(result["messages"][-1].content)

if __name__ == "__main__":
      asyncio.run(main())

7.9 LangChain的状态和持久化

7.9.2 短期状态:让同一会话能够连续对话

下面的示例使用InMemorySaver在内存中保存状态。它适合演示和本地调试,但程序退出后状态会丢失。具体代码如下:

# langchain_short_term_state_demo.py
from dotenv import load_dotenv
from langchain.agents import create_agent
from langchain_ollama import ChatOllama
from langgraph.checkpoint.memory import InMemorySaver
import os

load_dotenv()

llm = ChatOllama(
    model=os.getenv("OLLAMA_MODEL", "gemma4:e4b"),
    base_url=os.getenv("OLLAMA_BASE_URL", "http://localhost:11434"),
    temperature=0.2,
)

# InMemorySaver把状态保存在当前Python进程的内存中。
checkpointer = InMemorySaver()

agent = create_agent(
    model=llm,
    tools=[],
    checkpointer=checkpointer,
    system_prompt="你是一个课程学习助手,回答要简洁准确。",
)

config = {"configurable": {"thread_id": "student-001"}}

result1 = agent.invoke(
    {"messages": [{"role": "user", "content": "我叫小林,正在学习LangChain。"}]},
    config=config,
)
print(result1["messages"][-1].content)

result2 = agent.invoke(
    {"messages": [{"role": "user", "content": "我正在学习什么?"}]},
    config=config,
)
print(result2["messages"][-1].content)

7.9.3 持久化检查点:让程序重启后仍能延续状态

使用SQLite检查点前,需要安装额外依赖。可以在虚拟环境中执行如下命令:

pip install langgraph-checkpoint-sqlite

下面的示例把检查点写入本地SQLite数据库文件。第一次运行程序时,用户告诉助手自己的课程;再次运行程序时,只要使用相同的数据库文件和thread_id,助手就可以继续读取先前保存的会话状态。具体代码如下:

# langchain_persistent_state_demo.py
from dotenv import load_dotenv
from langchain.agents import create_agent
from langchain_ollama import ChatOllama
from langgraph.checkpoint.sqlite import SqliteSaver
import os

load_dotenv()

llm = ChatOllama(
    model=os.getenv("OLLAMA_MODEL", "gemma4:e4b"),
    base_url=os.getenv("OLLAMA_BASE_URL", "http://localhost:11434"),
    temperature=0.2,
)

config = {"configurable": {"thread_id": "student-001"}}

with SqliteSaver.from_conn_string("langchain_state.db") as checkpointer:
    agent = create_agent(
        model=llm,
        tools=[],
        checkpointer=checkpointer,
        system_prompt="你是一个课程学习助手,回答要简洁准确。",
    )

    result = agent.invoke(
        {"messages": [{"role": "user", "content": "我这学期选了人工智能导论。"}]},
        config=config,
    )
    print(result["messages"][-1].content)

    result = agent.invoke(
        {"messages": [{"role": "user", "content": "请提醒我刚才提到的课程名称。"}]},
        config=config,
    )
    print(result["messages"][-1].content)

7.9.4 长期记忆:不要把所有业务信息都放进聊天历史

一种常见做法是:把稳定的用户信息保存在业务存储中,每次调用智能体前只取出与当前任务相关的少量信息,放入系统提示词或运行时上下文。下面用一个简化的字典模拟长期用户信息,说明长期记忆如何与短期会话状态配合使用:

# langchain_profile_context_demo.py
from dotenv import load_dotenv
from langchain.agents import create_agent
from langchain_ollama import ChatOllama
from langgraph.checkpoint.memory import InMemorySaver
import os

load_dotenv()

user_profiles = {
    "student-001": {
        "name": "小林",
        "course": "人工智能导论",
        "level": "大学本科二年级",
    }
}

def build_system_prompt(user_id: str) -> str:
    profile = user_profiles.get(user_id, {})
    return (
        "你是一个课程学习助手。"
        f"当前学生姓名:{profile.get('name', '未知')};"
        f"课程:{profile.get('course', '未知')};"
        f"学习阶段:{profile.get('level', '未知')}。"
        "回答时结合这些背景,但不要编造未提供的信息。"
    )

llm = ChatOllama(
    model=os.getenv("OLLAMA_MODEL", "gemma4:e4b"),
    base_url=os.getenv("OLLAMA_BASE_URL", "http://localhost:11434"),
    temperature=0.2,
)

user_id = "student-001"
agent = create_agent(
    model=llm,
    tools=[],
    checkpointer=InMemorySaver(),
    system_prompt=build_system_prompt(user_id),
)

result = agent.invoke(
    {"messages": [{"role": "user", "content": "请给我一个适合当前课程的复习建议。"}]},
    config={"configurable": {"thread_id": user_id}},
)
print(result["messages"][-1].content)

7.10 案例:课程资料问答助手

文档加载和文本切分的示例代码如下:

# langchain_text_load_split.py
from pathlib import Path
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
data_dir = Path("course_docs")
docs = []
for path in data_dir.glob("*.txt"):
    loader = TextLoader(str(path), encoding="utf-8")
    docs.extend(loader.load())
splitter = RecursiveCharacterTextSplitter(
    chunk_size=350,
    chunk_overlap=80
)
splits = splitter.split_documents(docs)
print(f"原始文档数:{len(docs)}")
print(f"切分后片段数:{len(splits)}")

7.10.4向量索引构建

在本案例中,使用FAISS作为本地向量索引工具。它不依赖额外数据库服务,便于在个人计算机上复现。需要指出的是,向量索引并不直接生成答案,它只负责找出最相关的知识片段。真正的答案仍由大模型在阅读这些片段后生成。索引构建过程如下所示:

# langchain_index_build.py
from langchain_community.vectorstores import FAISS
from langchain_ollama import OllamaEmbeddings
from pathlib import Path
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter

data_dir = Path("course_docs")
docs = []
for path in data_dir.glob("*.txt"):
    loader = TextLoader(str(path), encoding="utf-8")
    docs.extend(loader.load())
splitter = RecursiveCharacterTextSplitter(
    chunk_size=350,
    chunk_overlap=80
)
splits = splitter.split_documents(docs)
print(f"原始文档数:{len(docs)}")
print(f"切分后片段数:{len(splits)}")
embeddings = OllamaEmbeddings(model="nomic-embed-text", base_url="http://127.0.0.1:11434")
vector_store = FAISS.from_documents(
    documents=splits,
    embedding=embeddings
)
retriever = vector_store.as_retriever(search_kwargs={"k": 5})
print(retriever)

7.10.5检索问答链实现

这一实现方式本质上属于2-Step RAG,即检索步骤先于生成步骤且流程固定。它的优点是结构清晰、执行稳定。若未来需要把系统升级为更灵活的知识智能体,还可以把检索器进一步封装为工具,由智能体决定何时检索。检索问答链的核心代码如下:

# langchain_find_answer_chain.py
from langchain_community.vectorstores import FAISS
from langchain_ollama import OllamaEmbeddings
from pathlib import Path
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_ollama import ChatOllama

data_dir = Path("course_docs")
docs = []
for path in data_dir.glob("*.txt"):
    loader = TextLoader(str(path), encoding="utf-8")
    docs.extend(loader.load())
splitter = RecursiveCharacterTextSplitter(
    chunk_size=350,
    chunk_overlap=80
)
splits = splitter.split_documents(docs)
print(f"原始文档数:{len(docs)}")
print(f"切分后片段数:{len(splits)}")
embeddings = OllamaEmbeddings(model="nomic-embed-text", base_url="http://127.0.0.1:11434")
vector_store = FAISS.from_documents(
    documents=splits,
    embedding=embeddings
)
retriever = vector_store.as_retriever(search_kwargs={"k": 5})
print(retriever)

def format_docs(documents):
    return "\n\n".join(doc.page_content for doc in documents)

prompt = ChatPromptTemplate.from_template(
    """你是一名课程助教。请结合给定资料回答问题。
如果资料中没有足够信息,请明确说明“资料中未给出充分信息”。
资料内容:
{context}
用户问题:
{question}
"""
)
model = ChatOllama(model="gemma4:e4b", temperature=0, base_url="http://127.0.0.1:11434")
parser = StrOutputParser()
rag_chain = (
        {
            "context": retriever | format_docs,
            "question": RunnablePassthrough()
        }
        | prompt
        | model
        | parser
)
answer = rag_chain.invoke("MapReduce 在课程资料中被描述为什么?")
print(answer)