第 4 节 / 共 8 节

RAG 开发(二):意图识别

🎯 BERT 意图分类器训练与语料准备。
📖 笔记

EduRAG 笔记(day04)

EduRAG项目

【了解】今日内容介绍

  • Langchain框架
  • Models
  • 核心包
  • 核心组件
  • 使用场景

【理解】Langchain

简介

含义:围绕LLMs(大语言模型)建立的一个框架,可以快速开发大模型应用。

作用:LangChain自身并不开发LLMs,它的核心理念是为各种LLMs实现通用的接口,把LLMs相关的组件“链接”在一起

官网:https://docs.langchain.com/oss/python/integrations/providers/overview

核心包

  • langchain-core:聊天模型和其他组件的基础抽象。
  • 集成包(例如 langchain-openai、langchain-anthropic 等):重要的集成被拆分为轻量级的独立包,由 LangChain 团队和集成方共同维护。
  • langchain:包含链(chains)、智能体(agents)和检索策略,这些构成了应用的认知架构。
  • langchain-community:由社区维护的第三方集成。

核心组件

Langchain之6大组件:

  • Models:各种模型,集成各种厂家API和开源模型。支持3种用法,generate、chat、embedding
  • Prompts:提示词模板,内置了一些提示词。 主要功能类似于字符串.format
  • Memory:记忆,短期记忆和长期记忆
  • Indexes:文档加载、文档切块、 向量化(和向量库相关的API)
  • Chains:提示词链,编排简单模型调用工作流
  • Agents:智能体,只支持基于React算法的智能体

1781007750917

Models

目前支持三种模型类型:

  • LLMs:大语言模型接收文本字符作为输入,返回的也是文本字符.
  • 聊天模型:基于LLMs, 不同的是它接收聊天消息(一种特定格式的数据)作为输入,返回的也是聊天消息.
  • 文本嵌入模型:文本嵌入模型接收文本作为输入, 返回的是浮点数列表.
LLMs

接收文本字符作为输入,返回的也是文本字符.

def demo01():
    from langchain_openai import ChatOpenAI
    import os

    # 实例化模型

    # os.environ.get:从环境变量中通过key获取value

    model = ChatOpenAI(
        base_url=os.environ.get('base_url', 'https://dashscope.aliyuncs.com/compatible-mode/v1'),
        model='qwen-plus',
        openai_api_key=os.environ.get('DASHSCOPE_API_KEY', 'sk-XXXXXX'),
        max_tokens=1000,
        temperature=0
    )

    # 获取问答结果
    result = model.invoke("帮我讲个笑话吧")
    print(result.content)

def demo02():
    # from langchain_community.llms import Ollama # Langchain 0.x版本使用
    from langchain_ollama import OllamaLLM

    # 实例化模型
    # model = Ollama(model="qwen2.5:7b") # Langchain 0.x版本使用
    model = OllamaLLM(model="qwen2.5:7b")

    # 获取问答结果
    result = model.invoke("请给我讲个笑话吧")
    print(result)
Chat Models

聊天消息包含下面几种类型,使用时需要按照约定传入合适的值:

  • AIMessage:就是 AI 输出的消息,可以是针对问题的回答.
  • HumanMessage:人类消息就是用户信息,由人给出的信息发送给LLMs的提示信息,比如“实现一个快速排序方法”.
  • SystemMessage:可以用于指定模型具体所处的环境和背景,如角色扮演等。你可以在这里给出具体的指示,比如“作为一个代码专家”,或者“返回json格式”.
  • ChatMessage:Chat 消息可以接受任意角色的参数,但是在大多数时间,我们应该使用上面的三种类型.
Embedding Models

Prompts

支持两种模板:

  1. zero-shot:0个示例
  2. few-shot:少数几个示例

相当于字符串format。

基础提示模板

zero-shot:

def demo01():
    # from langchain import PromptTemplate # Langchain 0.x版本使用
    from langchain_core.prompts import PromptTemplate
    # from langchain_community.llms import Ollama  # Langchain 0.x版本使用
    from langchain_ollama import OllamaLLM

    # model = Ollama(model="qwen2.5:7b") # Langchain 0.x版本使用
    model = OllamaLLM(model="qwen2.5:7b")


    # 定义模板
    template = "我的邻居姓{lastname},他生了个儿子,给他儿子起个名字"

    prompt = PromptTemplate(
        input_variables=["lastname"],
        template=template)

    prompt_text = prompt.format(lastname="王")
    print(prompt_text)
    # result: 我的邻居姓王,他生了个儿子,给他儿子起个名字

    result = model.invoke(prompt_text)
    print(result)

Few-Shot

def demo02():
    # from langchain import PromptTemplate, FewShotPromptTemplate # Langchain 0.x版本使用
    from langchain_core.prompts import PromptTemplate, FewShotPromptTemplate
    from langchain_ollama import OllamaLLM

    model = OllamaLLM(model="qwen2.5:7b")

    examples = [
        {"word": "开心", "antonym": "难过"},
        {"word": "高", "antonym": "矮"}
    ]

    example_template = """
    单词: {word}
    反义词: {antonym}
    """

    example_prompt = PromptTemplate(
        input_variables=["word", "antonym"],
        template=example_template,
    )

    few_shot_prompt = FewShotPromptTemplate(
        # 前缀
        prefix="给出每个单词的反义词",
        # 例子
        examples=examples,
        example_prompt=example_prompt,
        # 后缀
        suffix="单词: {input} 反义词:",
        input_variables=["input"],
        example_separator="\n",
    )

    prompt_text = few_shot_prompt.format(input="粗")

    print(prompt_text)
Chat提示词模板

zero-shot:

def demo04():
    from langchain_core.prompts import ChatPromptTemplate, HumanMessagePromptTemplate
    from langchain_core.messages import SystemMessage
    from langchain_ollama import OllamaLLM

    # 实例化模型
    model = OllamaLLM(model="qwen2.5:7b")

    # 系统信息
    system_prompt = SystemMessage("你是取名专家。")
    # 用户信息模版
    human_str = "我的邻居姓{lastname},他生了个儿子,给他儿子起个名字。"
    human_template = HumanMessagePromptTemplate.from_template(human_str)
    # 组装
    chat_template = ChatPromptTemplate.from_messages([system_prompt, human_template])
    # 生成最终的提示词
    prompt = chat_template.format_messages(lastname="王")
    print(f'prompt-->{prompt}')

    # 调用模型
    result = model.invoke(prompt)  # 返回结果出了content外,还有元数据信息
    print(f'result-->{result}')

few-shot:

def demo05():
    from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
    from langchain_ollama import OllamaLLM

    # 实例化模型
    model = OllamaLLM(model="qwen2.5:7b")

    # 创建 prompt 模版
    prompt_template = ChatPromptTemplate.from_messages(
        [
            ("system", "给出每个单词的反义词"),
            # 占位符
            MessagesPlaceholder("history"),
            ("human", "{question}")
        ]
    )
    # 创建 few-shot prompt
    # history = [
    #     HumanMessage(content="开心"),
    #     AIMessage(content="难过"),
    #     HumanMessage(content="高"),
    #     AIMessage(content="矮")
    # ]
    history = [("human", "开心"), ("ai", "难过"), ("human", "高"), ("ai", "矮")]
    prompt = prompt_template.format_messages(history=history, question="富有")
    print(f"prompt-->{prompt}")

    # 调用模型
    result = model.invoke(prompt)
    print(f'result-->{result}')

Chains(链)

def demo01():
    from langchain_core.prompts import PromptTemplate
    from langchain_ollama import OllamaLLM

    # 定义模板
    template = "我的邻居姓{lastname},他生了个儿子,给他儿子起个名字"

    prompt = PromptTemplate(
        input_variables=["lastname"],
        template=template)

    # 实例化模型
    llm = OllamaLLM(model="qwen2.5:7b")

    chain = prompt | llm
    # 执行链
    print(chain.invoke("王"))

def demo02():
    from langchain_core.prompts import PromptTemplate
    from langchain_ollama import OllamaLLM
    # 创建第一条链
    template = "我的邻居姓{lastname},他生了个儿子,给他儿子起个名字"

    first_prompt = PromptTemplate(
        input_variables=["lastname"],
        template=template, )

    # 实例化模型
    llm = OllamaLLM(model="qwen2.5:7b")

    first_chain = first_prompt | llm

    # 创建第二条链
    second_prompt = PromptTemplate(
        input_variables=["child_name"],
        template="邻居的儿子名字叫{child_name},给他起一个小名")

    second_chain = second_prompt | llm

    # 链接两条链
    overall_chain = first_chain | second_chain

    print(overall_chain)
    print('*' * 80)
    # 执行链,只需要传入第一个参数
    catchphrase = overall_chain.invoke("王")

    print(catchphrase)


output_parsers

使用流程:

# 1.实例化模型

# 2.创建提示词模板: 提示词模板中除了用户输入的业务逻辑以外,最后需要追加解析器对应的format_instruction

# 3.实例化输出解析器:需要实例化对应格式的解析器(json -> JsonOutputParser....),如果系统内置的无法满足业务需求,可以自定义输出解析器

# 4.使用链组合: 模板|模型|解析器
StrOutputParser

以字符串的格式输出结果

def demo01():
    from langchain_core.output_parsers import StrOutputParser
    from langchain_core.prompts import ChatPromptTemplate
    from langchain_ollama import OllamaLLM

    # 实例化模型
    model = OllamaLLM(model="qwen2.5:7b")

    # 创建简单链
    prompt = ChatPromptTemplate.from_template("解释{topic}是什么?回答控制20字以内")
    chain1 = prompt | model
    result1 = chain1.invoke({"topic": "ai"})
    print(f"result1-->{result1}")

    # 添加字符串解析器
    parser = StrOutputParser()
    chain2 = prompt | model | parser
    result2 = chain2.invoke({"topic": "ai"})
    print(f"result2-->{result2}")

CommaSeparatedListParser

逗号分割,转成list[str]格式输出

def demo02():
    from langchain_core.output_parsers import CommaSeparatedListOutputParser
    from langchain_core.prompts import ChatPromptTemplate
    from langchain_ollama import OllamaLLM

    # 实例化模型
    model = OllamaLLM(model="qwen2.5:7b")

    # 创建列表解析器
    parser = CommaSeparatedListOutputParser()

    # 创建带格式说明的提示模板
    # format_instructions = parser.get_format_instructions()
    # print(format_instructions)

    prompt = ChatPromptTemplate.from_template(
        "用中文列出{topic}的五个最重要特点。\n{format_instructions}"
    )

    # 组合组件
    chain = prompt | model | parser

    # 调用链
    result = chain.invoke({
        "topic": "大模型",
        "format_instructions": "你的回答应该是一个用逗号分隔的值的列表,例如:foo, bar, baz或 foo,bar,baz"
    })
    print(result)
JsonOutputParser

把json结构的数据转成dict类型进行输出

def demo03():
    from langchain_core.output_parsers import JsonOutputParser
    from langchain_core.prompts import ChatPromptTemplate
    from langchain_ollama import OllamaLLM

    # 实例化模型
    model = OllamaLLM(model="qwen2.5:7b")

    # 创建JSON解析器
    json_parser = JsonOutputParser()

    # 创建带格式说明的提示模板
    json_format_instructions = json_parser.get_format_instructions()
    print(json_format_instructions)

    json_prompt = ChatPromptTemplate.from_template(
        "生成一个包含{person}基本信息的JSON。应包括姓名、职业、年龄和技能列表, 不要包含任何注释或额外说明。\n{format_instructions}"
    )

    # 组合组件
    json_chain = json_prompt | model | json_parser

    # 调用链
    result = json_chain.invoke({
        "person": "雷军",
        "format_instructions": "返回一个Json对象"
    })

    print(result)

PydanticOutputParser
def demo04():
    from langchain_core.prompts import ChatPromptTemplate
    from langchain_core.output_parsers import PydanticOutputParser
    from langchain_ollama import OllamaLLM
    # pydantic
    from pydantic import BaseModel, Field
    from typing import List


    # 实例化模型
    model = OllamaLLM(model="qwen2.5:7b")

    # 定义Pydantic模型
    class Movie(BaseModel):
        title: str = Field(description="电影标题")
        director: str = Field(description="导演姓名")
        year: int = Field(description="上映年份")
        # todo : 单标签多分类 (每个电影只能有个类型)、 多标签多分类(一个电影可以有多个类型)
        genre: List[str] = Field(description="电影类型")
        rating: float = Field(description="评分(1-10)")

    # 创建Pydantic解析器
    pydantic_parser = PydanticOutputParser(pydantic_object=Movie)

    # 创建带格式说明的提示模板
    format_instructions = pydantic_parser.get_format_instructions()
    print(format_instructions)

    pydantic_prompt = ChatPromptTemplate.from_template(
        "生成一部{genre}电影的信息。\n{format_instructions}"
    )

    # 组合组件
    pydantic_chain = pydantic_prompt | model | pydantic_parser

    # 调用链
    movie_data = pydantic_chain.invoke({
        "genre": "科幻",
        "format_instructions": format_instructions
    })
    print(movie_data)

提示词模板:

The output should be formatted as a JSON instance that conforms to the JSON schema below.

As an example, for the schema {"properties": {"foo": {"title": "Foo", "description": "a list of strings", "type": "array", "items": {"type": "string"}}}, "required": ["foo"]}
the object {"foo": ["bar", "baz"]} is a well-formatted instance of the schema. The object {"properties": {"foo": ["bar", "baz"]}} is not well-formatted.

Here is the output schema:
```
{"properties": {"title": {"description": "电影标题", "title": "Title", "type": "string"}, "director": {"description": "导演姓名", "title": "Director", "type": "string"}, "year": {"description": "上映年份", "title": "Year", "type": "integer"}, "genre": {"description": "电影类型", "items": {"type": "string"}, "title": "Genre", "type": "array"}, "rating": {"description": "评分(1-10)", "title": "Rating", "type": "number"}}, "required": ["title", "director", "year", "genre", "rating"]}
```
自定义解析器

作用:用于输出用户想要的自定义格式, 需要集成BaseOutputParser, 实现parse(解析大模型输出的字符串,转化成自定义格式)和get_format_instruction(提供提示词模板,作为提示词的一部分,用于规定大模型输出的格式)

def demo05():
    from langchain_core.output_parsers import BaseOutputParser
    from typing import Dict, Any
    from langchain_core.prompts import ChatPromptTemplate
    from langchain_ollama import OllamaLLM

    # 实例化模型
    model = OllamaLLM(model="qwen2.5:7b")

    # 泛型:用来规定代码中的数据返回的格式的参数
    class CustomKeyValueParser(BaseOutputParser[Dict[str, Any]]):
        """解析形如'key: value'的文本"""

        # 接受模型输出的文本作为输入: text:模型的输出的问题本
        def parse(self, text: str) -> Dict[str, Any]:
            """从文本中解析键值对"""
            result = {}
            lines = text.strip().split('\n')

            for line in lines:
                if ':' in line:
                    key, value = line.split(':', 1)
                    result[key.strip()] = value.strip()

            return result

        def get_format_instructions(self) -> str:
            """提供格式指导给模型"""
            return """请以'键:值'的格式返回信息,每行一个键值对。
                    例如:
                    名称:爱因斯坦
                    职业:物理学家
                    贡献:相对论"""

    # 使用自定义解析器
    custom_parser = CustomKeyValueParser()
    custom_prompt = ChatPromptTemplate.from_template(
        "提供关于{person}的基本信息。\n{format_instructions}"
    )

    # 组合组件
    custom_chain = custom_prompt | model | custom_parser

    # 调用模型
    result = custom_chain.invoke({
        "person": "屠呦呦",
        "format_instructions": custom_parser.get_format_instructions()
    })
    print(result)

Memory

大模型本身不具备上下文的概念,它并不保存上次交互的内容,ChatGPT之所以能够和人正常沟通对话,因为它进行了一层封装,将历史记录回传给了模型。

两种:

  • 短期记忆:Memory【业界的叫法,需要和langchain的memory组件进行区分】
  • 长期记忆:History

最常用的长期记忆的对象:ChatMessageHistory

  • message_to_dict: 把长期记忆进行序列化,转为基本类型,方便保存
  • message_from_dict:把序列化后的长期记忆进行反序列化,进行加载,转成ChatMessageHistory对象
def demo01():
    from langchain_community.chat_message_histories import ChatMessageHistory
    from langchain_core.messages import messages_to_dict, messages_from_dict
    import json

    # 1.创建一个ChatMessageHistory对象,用来存储对话信息
    history = ChatMessageHistory()
    # 添加用户消息
    history.add_user_message("在吗?")
    # 添加大模型消息
    history.add_ai_message("在")
    # 打印所有消息
    print(f'history.messages-->{history.messages}')

    # 2.可以将history.messages中的信息保存到字典中,然后保存到数据库或者文件中,方便后续读取
    # 2.1 messages_to_dict()方法将history.messages中的信息转换成字典
    dicts = messages_to_dict(history.messages)
    print(f'dicts-->{dicts}')
    # 2.2 这里将dicts保存到文件中
    with open('history.json', 'w', encoding='utf-8') as f:
        f.write(json.dumps(dicts, indent=2, ensure_ascii=False))

    # 3.从文件中读取出字典,然后将字典转换成消息
    # 3.1 读取文件
    messages = json.load(open('history.json', 'r', encoding='utf-8'))
    # 3.2 然后将字典转换成消息
    chat_messages = messages_from_dict(messages)
    print(f'chat_messages-->{chat_messages}')

Indexes

文档加载器
def demo01():
    from langchain_community.document_loaders import UnstructuredFileLoader
    loader = UnstructuredFileLoader('衣服属性.txt', encoding='utf8')
    docs = loader.load()
    print(docs)
    print(len(docs))
    first_01 = docs[0].page_content[:4]
    print(first_01)
    print('*' * 80)
    from langchain_community.document_loaders import TextLoader
    loader = TextLoader('衣服属性.txt', encoding='utf8')
    docs = loader.load()
    print(docs)
    print(len(docs))
    first_01 = docs[0].page_content[:4]
    print(first_01)
    # 打印结果:
    '''
    [Document(page_content='身高:160-170cm, 体重:90-115斤,建议尺码M。\n身高:165-175cm, 体重:115-135斤,建议尺码L。\n身高:170-178cm, 体重:130-150斤,建议尺码XL。\n身高:175-182cm, 体重:145-165斤,建议尺码2XL。\n身高:178-185cm, 体重:160-180斤,建议尺码3XL。\n身高:180-190cm, 体重:180-210斤,建议尺码4XL。\n面料分类:其他\n图案:纯色\n领型:翻领\n衣门襟:单排扣\n颜色:黑色 卡其色 粉色 杏色\n袖型:收口袖\n适用季节:冬季\n袖长:长袖\n厚薄:厚款\n适用场景:其他休闲\n衣长:常规款\n版型:宽松型\n款式细节:假两件\n工艺处理:免烫处理\n适用对象:青年\n面料功能:保暖\n穿搭方式:外穿\n销售渠道类型:纯电商(只在线上销售)\n材质成分:棉100%', metadata={'source': '衣服属性.txt'})]
    1
    身高:1
    ********************************************************************************
    [Document(page_content='身高:160-170cm, 体重:90-115斤,建议尺码M。\n\n身高:165-175cm, 体重:115-135斤,建议尺码L。\n\n身高:170-178cm, 体重:130-150斤,建议尺码XL。\n\n身高:175-182cm, 体重:145-165斤,建议尺码2XL。\n\n身高:178-185cm, 体重:160-180斤,建议尺码3XL。\n\n身高:180-190cm, 体重:180-210斤,建议尺码4XL。\n\n面料分类:其他\n\n图案:纯色\n\n领型:翻领\n\n衣门襟:单排扣\n\n颜色:黑色 卡其色 粉色 杏色\n\n袖型:收口袖\n\n适用季节:冬季\n\n袖长:长袖\n\n厚薄:厚款\n\n适用场景:其他休闲\n\n衣长:常规款\n\n版型:宽松型\n\n款式细节:假两件\n\n工艺处理:免烫处理\n\n适用对象:青年\n\n面料功能:保暖\n\n穿搭方式:外穿\n\n销售渠道类型:纯电商(只在线上销售)\n\n材质成分:棉100%', metadata={'source': '衣服属性.txt'})]
    1
    身高:1
    '''
文档分割器
CharacterTextSplitter

作用:通过字符长度进行分割, 需要3个参数:分割符号、块大小、块重叠字符数

def demo02():
    from langchain_core.documents import Document
    from langchain_text_splitters import CharacterTextSplitter

    # 创建分词器 separator参数指的是分割的分隔符,chunk_size指的是分割出来的每个块的大小,chunk_overlap指的是每个块之间重叠的大小
    text_splitter = CharacterTextSplitter(separator=" ", chunk_size=5, chunk_overlap=1)

    # 一句话分割
    result1 = text_splitter.split_text("a b c d e f")
    print(f'result1--->{result1}')

    # 多句话分割
    result2 = text_splitter.create_documents(["a b c d e f", "e f g h"])
    print(f'result2--->{result2}')

    # 多句话分割
    result3 = text_splitter.split_documents([Document(page_content="a b c d e f", metadata={"id": "1"})])
    print(f'result3--->{result3}')
RecursiveCharacterTextSplitter

作用:按照指定的分隔符顺序递归分割文本,直到满足文本块大小为止。如果分割到最后一个分隔符未能满足块大小,直接截断。

需要传入4个参数: 块大小、块重叠、长度统计方式(需要把函数作为参数传入,默认用len)、分隔符(列表格式,严格按照内部顺序执行)

def demo03():
    from langchain_text_splitters import RecursiveCharacterTextSplitter

    # 4个参数:
    # 1. 块大小 2.重叠字符数 3. 长度衡量方式:传入一个函数进来,调用函数判断长度
    # 4. 分割符号: 按照列表中的分割符号,从左到右依次进行分割,直到满足块大小限制。 如果分割到最后一个符号未满足块大小,直接截断

    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=30,  # 每个块最多 100 个字符
        chunk_overlap=6,  # 相邻分块会共享 20 个字符
        # 使用len,中文和英文没有区别;byte
        length_function=len,  # 用字符数来衡量长度
        separators=["\n\n", "\n", " ", ""]  # 会优先尝试按 \n\n 分段,如果太长,再按 \n → 空格 → 逐字符切分。
    )

    text = """
    人工智能正在快速发展,尤其是大语言模型的应用,正在改变人类的工作方式。
    它们可以帮助人们进行写作、代码生成、甚至是科研探索。
    相比之下,新能源的发展同样重要。
    电动车和太阳能正在逐渐替代传统能源,减少碳排放,对全球环境保护至关重要。
    """
    docs = text_splitter.split_text(text)
    print(docs)

分割过程示意图:

递归文本分割器的工作过程

SemanticChunker

作用: 把文本基于分隔符分割成多个段落,每个段落进行向量化,计算相似度,再基于分割点阈值进行切分 。 一共需要5个参数:嵌入模型、分割点阈值类型、分割点阈值(控制分割后的文本个数)、分隔符(正则表达式)、分割后每个片段的最小字符数。

优点:切分的效果比较好,尤其是在对语义要求比较高的场景

缺点:速度慢,成本高(要调用大模型)

def demo04():
    from langchain_experimental.text_splitter import SemanticChunker
    from langchain_ollama import OllamaEmbeddings

    embed = OllamaEmbeddings(model="mxbai-embed-large")

    text_splitter = SemanticChunker(
        embeddings=embed,
        breakpoint_threshold_type='percentile',
        # 更低的百分位会更“积极”地切分(数值越小 => 切得越多)
        breakpoint_threshold_amount=40.0,
        # 句子拆分正则为同时识别中/英文终结符
        sentence_split_regex=r'(?<=[。!?.!?])\s*',
        # 每个切分后片段(chunk)至少要有多少个字符
        min_chunk_size=10
    )

    text = """
    人工智能正在快速发展,尤其是大语言模型的应用,正在改变人类的工作方式。
    它们可以帮助人们进行写作、代码生成、甚至是科研探索。
    相比之下,新能源的发展同样重要。
    电动车和太阳能正在逐渐替代传统能源,减少碳排放,对全球环境保护至关重要。
    """

    docs = text_splitter.split_text(text)
    for i, d in enumerate(docs):
        print(f"------ Chunk {i + 1} ------")
        print(d.strip())
        print()

VectorStores

作用:Langchain集成市面上常见的向量库api , 用于和向量库进行交互。 支持chroma、elasticsearch、milvus、faiss、pinecone等常见向量库。

def demo01():
    from langchain_text_splitters import CharacterTextSplitter
    from langchain_chroma import Chroma
    from langchain_community.document_loaders import TextLoader
    from langchain_ollama import OllamaEmbeddings

    # 1.加载文档
    # 创建 TextLoader 对象
    loader = TextLoader('./data/pku.txt', encoding='utf-8')
    # 加载文档
    docs = loader.load()
    # print(f'docs-->{docs}')

    # 2.将文档进行分块
    # 创建 CharacterTextSplitter 对象
    text_splitter = CharacterTextSplitter(separator="\n\n", chunk_size=200, chunk_overlap=30)
    # 分块
    split_docs = text_splitter.split_documents(docs)
    print(f'split_docs-->{split_docs}')

    # 3.将分割后的文档存储到向量数据库中
    # 加载embedding模型
    embedding = OllamaEmbeddings(model="mxbai-embed-large")
    # 创建向量数据库,需要指定 存储的文档和向量模型名称以及持久化目录
    chromadaDB = Chroma.from_documents(documents=split_docs,
                                       embedding=embedding,
                                       persist_directory='./chroma_db')

    # 假如你的向量数据库已经存在,那么可以直接加载
    # chromadaDB = Chroma(persist_directory='./chroma_db', embedding_function=embedding)

    # 4.使用向量数据库进行查询
    query = "1937年北京大学发生了什么?"
    result = chromadaDB.similarity_search(query, k=2)
    print(f'result-->{result}')


if __name__ == '__main__':
    demo01()

常见的向量存储引擎:

image-20260116164000587

检索器

作用:用于检索文档库的数据,将用户的query和文档库中的文档进行相似度匹配。

def get_relevant_texts_demo():

    loader = TextLoader('./pku.txt')
    documents = loader.load()
    text_splitter = CharacterTextSplitter(chunk_size=100, chunk_overlap=0)
    texts = text_splitter.split_documents(documents)

    embeddings = OllamaEmbeddings(model="mxbai-embed-large")

    db = FAISS.from_documents(texts, embeddings)
    retriever = db.as_retriever(search_kwargs={'k': 1})
    docs = retriever.get_relevant_documents("北京大学什么时候成立的")
    print(docs)

Agents

作用:根据用户的需求,来访问一些第三方工具(比如:搜索引擎或者数据库),进而来解决相关需求问题。

回忆:React的实现

REACT

循环执行思考 -> 行动 -> 观察,指导得到最终答案。(或触发结束循环的条件,比如次数)

"""
你是一个使用ReAct范式的智能代理,必须按以下格式输出:
Thought: <你的思考>
Action: <要执行的动作,从 [{', '.join(TOOLS.keys())}] 中选择,或 Final Answer>
Action Input: <动作输入>

当前上下文:
{context}

问题:{question}
"""

Langchain的Agent的内部架构:

image-20260306175110152

agent的实现:

def demo02():
    from langchain_core.messages import HumanMessage
    from langgraph.checkpoint.memory import MemorySaver
    from langchain.agents import create_agent
    from langchain_community.agent_toolkits.load_tools import load_tools

    from langchain_ollama import OllamaLLM
    import time

    # 初始化大模型,这里使用的是 Qwen 的聊天模型
    import os

    # 实例化模型
    # TODO: 这里使用的模型必须支持function call,模型需要具备思考使用什么模型的能力
    #  {
    #   "tool_calls": [
    #     {
    #       "function": {
    #         "name": "get_weather",
    #         "arguments": "{\"location\": \"北京\", \"unit\": \"celsius\"}"
    #       },
    #       "id": "call_abc123",
    #       "type": "function"
    #     }
    #   ]
    # }

    model = ChatOllama(model="qwen2.5:7b")
    #
    # model = ChatOpenAI(
    #     base_url=os.environ.get('base_url', 'https://dashscope.aliyuncs.com/compatible-mode/v1'),
    #     model='qwen-plus',
    #     openai_api_key=os.environ.get('DASHSCOPE_API_KEY', 'sk-XXXXXXXX'),
    #     max_tokens=1000,
    #     temperature=0
    # )

    # Create the agent
    memory = MemorySaver()
    tools = load_tools(['wikipedia', 'llm-math'], llm=model)
    agent_executor = create_agent(model, tools, checkpointer=memory)

    # Use the agent
    config = {"configurable": {"thread_id": "abc123"}}

    # 1. 发送:计算一下300的25%是多少? 问题给到qwen-plus
    for chunk in agent_executor.stream(
            {"messages": [HumanMessage(content="计算一下300的25%是多少?")]}, config
    ):
        print(chunk)
        print("----")

    # for chunk in agent_executor.stream(
    #     {"messages": [HumanMessage(content="大熊猫是哪个国家的国宝?")]}, config
    # ):
    #     print(chunk)
    #     print("----")

案例代码三次调用大模型:

第一次:

{
  "model": "qwen2.5:7b",
  "stream": true,
  "options": {},
  "messages": [
    {
      "role": "user",
      "content": "计算一下300的25%是多少?"
    }
  ],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "wikipedia",
        "description": "A wrapper around Wikipedia. Useful for when you need to answer general questions about people, places, companies, facts, historical events, or other subjects. Input should be a search query.",
        "parameters": {
          "type": "object",
          "required": [
            "query"
          ],
          "properties": {
            "query": {
              "type": "string",
              "description": "query to look up on wikipedia"
            }
          }
        }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "Calculator",
        "description": "Useful for when you need to answer questions about math.",
        "parameters": {
          "type": "object",
          "required": [
            "__arg1"
          ],
          "properties": {
            "__arg1": {
              "type": "string"
            }
          }
        }
      }
    }
  ]
}

返回值:
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:13.74034Z","message":{"role":"assistant","content":"","tool_calls":[{"id":"call_jq4bhh1h","function":{"index":0,"name":"Calculator","arguments":{"__arg1":"300*25%"}}}]},"done":false}

第二次调用:

{
  "model": "qwen2.5:7b",
  "stream": true,
  "options": {
    "stop": [
      "```output"
    ]
  },
  "messages": [
    {
      "role": "user",
      "content": "Translate a math problem into a expression that can be executed using Python's numexpr library. Use the output of running this code to answer the question.\n\nQuestion: ${Question with math problem.}\n```text\n${single line mathematical expression that solves the problem}\n```\n...numexpr.evaluate(text)...\n```output\n${Output of running the code}\n```\nAnswer: ${Answer}\n\nBegin.\n\nQuestion: What is 37593 * 67?\n```text\n37593 * 67\n```\n...numexpr.evaluate(\"37593 * 67\")...\n```output\n2518731\n```\nAnswer: 2518731\n\nQuestion: 37593^(1/5)\n```text\n37593**(1/5)\n```\n...numexpr.evaluate(\"37593**(1/5)\")...\n```output\n8.222831614237718\n```\nAnswer: 8.222831614237718\n\nQuestion: 300*25%\n"
    }
  ],
  "tools": []
}

-----------
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:14.449323Z","message":{"role":"assistant","content":"```text"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:14.46917Z","message":{"role":"assistant","content":"\n"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:14.489589Z","message":{"role":"assistant","content":"3"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:14.509714Z","message":{"role":"assistant","content":"0"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:14.530932Z","message":{"role":"assistant","content":"0"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:14.553936Z","message":{"role":"assistant","content":" *"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:14.573914Z","message":{"role":"assistant","content":" "},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:14.594292Z","message":{"role":"assistant","content":"0"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:14.614514Z","message":{"role":"assistant","content":"."},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:14.634429Z","message":{"role":"assistant","content":"2"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:14.654519Z","message":{"role":"assistant","content":"5"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:14.674535Z","message":{"role":"assistant","content":"\n"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:14.717898Z","message":{"role":"assistant","content":"```\n"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:14.738706Z","message":{"role":"assistant","content":"..."},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:14.761338Z","message":{"role":"assistant","content":"num"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:14.781529Z","message":{"role":"assistant","content":"expr"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:14.803925Z","message":{"role":"assistant","content":".evaluate"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:14.826211Z","message":{"role":"assistant","content":"(\""},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:14.846939Z","message":{"role":"assistant","content":"3"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:14.868359Z","message":{"role":"assistant","content":"0"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:14.892578Z","message":{"role":"assistant","content":"0"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:14.914759Z","message":{"role":"assistant","content":" *"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:14.937478Z","message":{"role":"assistant","content":" "},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:14.959496Z","message":{"role":"assistant","content":"0"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:14.981581Z","message":{"role":"assistant","content":"."},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:15.001706Z","message":{"role":"assistant","content":"2"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:15.024193Z","message":{"role":"assistant","content":"5"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:15.044315Z","message":{"role":"assistant","content":"\")"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:15.066256Z","message":{"role":"assistant","content":"...\n"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:15.108846Z","message":{"role":"assistant","content":""},"done":true,"done_reason":"stop","total_duration":1312374083,"load_duration":66089916,"prompt_eval_count":277,"prompt_eval_duration":563196209,"eval_count":33,"eval_duration":668055375}


第三次调用:

{
  "model": "qwen2.5:7b",
  "stream": true,
  "options": {},
  "messages": [
    {
      "role": "user",
      "content": "计算一下300的25%是多少?"
    },
    {
      "role": "assistant",
      "tool_calls": [
        {
          "function": {
            "name": "Calculator",
            "arguments": {
              "__arg1": "300*25%"
            }
          }
        }
      ]
    },
    {
      "role": "tool",
      "content": "Answer: 75.0"
    }
  ],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "wikipedia",
        "description": "A wrapper around Wikipedia. Useful for when you need to answer general questions about people, places, companies, facts, historical events, or other subjects. Input should be a search query.",
        "parameters": {
          "type": "object",
          "required": [
            "query"
          ],
          "properties": {
            "query": {
              "type": "string",
              "description": "query to look up on wikipedia"
            }
          }
        }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "Calculator",
        "description": "Useful for when you need to answer questions about math.",
        "parameters": {
          "type": "object",
          "required": [
            "__arg1"
          ],
          "properties": {
            "__arg1": {
              "type": "string"
            }
          }
        }
      }
    }
  ]
}

----------------------
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:15.832853Z","message":{"role":"assistant","content":"3"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:15.853891Z","message":{"role":"assistant","content":"0"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:15.874864Z","message":{"role":"assistant","content":"0"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:15.899847Z","message":{"role":"assistant","content":"的"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:15.922601Z","message":{"role":"assistant","content":"2"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:15.942874Z","message":{"role":"assistant","content":"5"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:15.965128Z","message":{"role":"assistant","content":"%"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:15.98762Z","message":{"role":"assistant","content":"是"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:16.008236Z","message":{"role":"assistant","content":"7"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:16.031402Z","message":{"role":"assistant","content":"5"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:16.053328Z","message":{"role":"assistant","content":"。"},"done":false}
{"model":"qwen2.5:7b","created_at":"2026-03-06T10:12:16.074034Z","message":{"role":"assistant","content":""},"done":true,"done_reason":"stop","total_duration":925874792,"load_duration":89372459,"prompt_eval_count":292,"prompt_eval_duration":591315500,"eval_count":12,"eval_duration":235936335}

使用场景

  • 个人助手
  • 基于文档的问答系统
  • 聊天机器人
  • Tabular数据查询
  • API交互
  • 信息提取
  • 文档总结
📎 资料

命名实体语料