langchain基础和rag

langchain基础

1.0 单论对话和多轮对话

from langchain_community.chat_models import QianfanChatEndpoint
from langchain_community.llms import Tongyi
from langchain_community.chat_models.tongyi import ChatTongyi
from langchain.schema import HumanMessage,
import os
# 方法1: 使用通义千问 (阿里百炼)
os.environ["DASHSCOPE_API_KEY"] = os.getenv("DASHSCOPE_API_KEY")

# 初始化通义千问模型
model = Tongyi(
    model_name="qwen-plus",
    temperature=0.7
)

# 测试模型
response = model.invoke("你好,请介绍一下自己")
print(response)

第二种方式

from langchain_community.chat_models import QianfanChatEndpoint
from langchain_community.llms import Tongyi
from langchain_community.chat_models.tongyi import ChatTongyi
from langchain.schema import HumanMessage
from langchain.schema import (
    AIMessage,  # 等价于OpenAI接口中的assistant role
    HumanMessage,  # 等价于OpenAI接口中的user role
    SystemMessage  # 等价于OpenAI接口中的system role
)
import os
# 方法1: 使用通义千问 (阿里百炼)
os.environ["DASHSCOPE_API_KEY"] = os.getenv("DASHSCOPE_API_KEY")


chat_model = ChatTongyi(
    model_name="qwen-plus",
    temperature=0.7
)

messages = [SystemMessage("你是一个代码学习助手"),
            AIMessage(content="你的功能是帮助用户解决代码问题"),
            HumanMessage(content="你好,请介绍一下自己"),
            ]
chat_response = chat_model.invoke(messages)
print("聊天模式回复:", chat_response.content)

如果我们想要实现流式输出:

from langchain_community.chat_models import QianfanChatEndpoint
from langchain_community.llms import Tongyi
from langchain_community.chat_models.tongyi import ChatTongyi
from langchain.schema import HumanMessage
from langchain.schema import (
    AIMessage,  # 等价于OpenAI接口中的assistant role
    HumanMessage,  # 等价于OpenAI接口中的user role
    SystemMessage  # 等价于OpenAI接口中的system role
)
import os
# 方法1: 使用通义千问 (阿里百炼)
os.environ["DASHSCOPE_API_KEY"] = os.getenv("DASHSCOPE_API_KEY")


chat_model = ChatTongyi(
    model_name="qwen-plus",
    temperature=0.7
)

messages = [SystemMessage("你是一个代码学习助手"),
            AIMessage(content="你的功能是帮助用户解决代码问题"),
            HumanMessage(content="你好,请介绍一下自己"),
            ]
for token in  chat_model.stream(messages):
    print(token.content,end="");

1.1 提示词模板

from langchain.prompts import PromptTemplate

template = PromptTemplate.from_template("给我讲个关于{subject}的笑话")
print(template.format(subject='小明'))

实际使用

from langchain.prompts import PromptTemplate
from langchain_community.chat_models.tongyi import ChatTongyi
chat_message=ChatTongyi(
    model="qwen-plus"
)

template = PromptTemplate.from_template("给我讲个关于{subject}的笑话")
print(chat_message.invoke(template.format(subject="dog")).content)

我们可以直接使用openai的兼容接口

from langchain.prompts import (
    ChatPromptTemplate,
    HumanMessagePromptTemplate,
    SystemMessagePromptTemplate,
)
from langchain.chat_models import init_chat_model
import os

# 设置阿里百炼API密钥
os.environ["OPENAI_API_KEY"] =  os.getenv("DASHSCOPE_API_KEY")
os.environ["OPENAI_BASE_URL"] = "https://dashscope.aliyuncs.com/compatible-mode/v1"

# 使用OpenAI兼容接口调用通义千问
llm = init_chat_model(
    "qwen-plus",  # 或者 qwen-turbo, qwen-max
    model_provider="openai"
)

template = ChatPromptTemplate.from_messages(
    [
        SystemMessagePromptTemplate.from_template("你是{product}的客服助手。你的名字叫{name}"),
        HumanMessagePromptTemplate.from_template("{query}")
    ]
)

prompt = template.format_messages(
    product="代码编辑助手",
    name="coder",
    query="你是谁"
)

print(prompt)

ret = llm.invoke(prompt)

print(ret.content)

1.2 实现历史记录加入

from langchain_core.messages import AIMessage, HumanMessage
from langchain.prompts import (
    ChatPromptTemplate,
    HumanMessagePromptTemplate,
    MessagesPlaceholder,
)
from langchain.chat_models import init_chat_model
import os
# 设置阿里百炼API密钥
os.environ["OPENAI_API_KEY"] =  os.getenv("DASHSCOPE_API_KEY")
os.environ["OPENAI_BASE_URL"] = "https://dashscope.aliyuncs.com/compatible-mode/v1"

# 使用OpenAI兼容接口调用通义千问
llm = init_chat_model(
    "qwen-plus",  # 或者 qwen-turbo, qwen-max
    model_provider="openai"
)
human_prompt = "Translate your answer to {language}."
human_message_template = HumanMessagePromptTemplate.from_template(human_prompt)

chat_prompt = ChatPromptTemplate.from_messages(
    [MessagesPlaceholder("history"), human_message_template]
)
human_message = HumanMessage(content="Who is Elon Musk?")
ai_message = AIMessage(
    content="Elon Musk is a billionaire entrepreneur, inventor, and industrial designer"
)

messages = chat_prompt.format_prompt(
    # 对 "history" 和 "language" 赋值
    history=[human_message, ai_message], language="中文"
)

print(messages.to_messages())
result = llm.invoke(messages)
print(result.content)

当前实现了历史会话加入的功能,

在对话提示词模板中

chat_prompt = ChatPromptTemplate.from_messages(
    [MessagesPlaceholder("history"), human_message_template]
)

除了需要一开始用的用户提示词,还家加入了历史提示词占位符,等待后续向其中加入历史记录,具体的补全替换

发生在:

messages = chat_prompt.format_prompt(
    # 对 "history" 和 "language" 赋值
    history=[human_message, ai_message], language="中文"
)

1.3从文件加载提示词

from langchain_core.messages import AIMessage, HumanMessage
from langchain.prompts import PromptTemplate
from langchain.prompts import (
    ChatPromptTemplate,
    HumanMessagePromptTemplate,
    MessagesPlaceholder,
)
from langchain.chat_models import init_chat_model
import os
# 设置阿里百炼API密钥
os.environ["OPENAI_API_KEY"] =  os.getenv("DASHSCOPE_API_KEY")
os.environ["OPENAI_BASE_URL"] = "https://dashscope.aliyuncs.com/compatible-mode/v1"

# 使用OpenAI兼容接口调用通义千问
llm = init_chat_model(
    "qwen-plus",  # 或者 qwen-turbo, qwen-max
    model_provider="openai"
)
template = PromptTemplate.from_file("example_prompt_template.txt",encoding="UTF-8")
print("===Template===")
print(template)
print("===Prompt===")
print(template.format(topic='黑色幽默'))
print(llm.invoke(template.format(topic='黑色幽默')))

1.4 结构化输出

from pydantic import BaseModel, Field
from langchain.chat_models import init_chat_model
from langchain.prompts import PromptTemplate
from langchain_core.output_parsers import JsonOutputParser
import os

# 定义结构化输出模型
class Date(BaseModel):
    year: int = Field(description="Year")
    month: int = Field(description="Month")
    day: int = Field(description="Day")
    era: str = Field(description="BC or AD")

# 设置 API 密钥和接口地址
os.environ["OPENAI_API_KEY"] =  os.getenv("DASHSCOPE_API_KEY")
os.environ["OPENAI_BASE_URL"] = "https://dashscope.aliyuncs.com/compatible-mode/v1"

# 使用OpenAI兼容接口调用通义千问
llm = init_chat_model(
    "qwen-plus",  # 或者 qwen-turbo, qwen-max
    model_provider="openai"
)

# 使用 JsonOutputParser 引导模型输出 JSON 格式
parser = JsonOutputParser(pydantic_object=Date)

# 构造提示词模板,包含格式说明
template = """
提取用户输入中的日期,并以如下 JSON 格式返回:

{format_instructions}

用户输入:
{query}
"""

prompt = PromptTemplate(
    template=template,
    input_variables=["query"],
    partial_variables={"format_instructions": parser.get_format_instructions()}
)

# 用户输入
query = "2023年四月6日天气晴..."

# 构建输入
_input = prompt.format(query=query)

# 调用模型并解析输出
output = llm.invoke(_input)
parsed_output = parser.parse(output.content)

print(parsed_output)

接下来我们来解析一下这个代码

class Date(BaseModel):
    year: int = Field(description="Year")
    month: int = Field(description="Month")
    day: int = Field(description="Day")
    era: str = Field(description="BC or AD")

这是我们结构化输出类,定义了结构化输出 的变量和其描述,每一个属性就是一个json变量,后面有这个json变

量的描述

parser = JsonOutputParser(pydantic_object=Date)

这是langchain提供的一个json解析器,用来输出格式提示词和解析验证大模型输出

prompt = PromptTemplate(
    template=template,
    input_variables=["query"],
    partial_variables={"format_instructions": parser.get_format_instructions()}
)

此时选择显示指明变量, input_variables=["query"]代表提示词中的变量

partial_variables={"format_instructions": parser.get_format_instructions()} 代表提示词中定义

的在提示词模板中被确定的变量,类似于静态常量static final的声明,后面的字典就是定义其值

虽然我们在这可以只写一个template=template也不会报错,但需要显示指明变量时需要用当前这种方式

parsed_output = parser.parse(output.content)

这是解析器解析响应,将其转换成指定格式,如果我们直接查看output.content 内容如下

```json
{
  "year": 2023,
  "month": 4,
  "day": 6,
  "era": "AD"
}
```

发现就是一个json字符串,并不是json格式的数据,通过解析器进行解析,就变成了

{'year': 2023, 'month': 4, 'day': 6, 'era': 'AD'}

这就实现了json格式化输出

我们如果想得到Pydantic格式数据,可以像下面这样写:

from pydantic import BaseModel, Field
from langchain.chat_models import init_chat_model
from langchain.prompts import PromptTemplate
from langchain_core.output_parsers import JsonOutputParser
import os
from langchain_core.output_parsers import PydanticOutputParser
# 定义结构化输出模型
class Date(BaseModel):
    year: int = Field(description="Year")
    month: int = Field(description="Month")
    day: int = Field(description="Day")
    era: str = Field(description="BC or AD")

# 设置 API 密钥和接口地址
os.environ["OPENAI_API_KEY"] =  os.getenv("DASHSCOPE_API_KEY")
os.environ["OPENAI_BASE_URL"] = "https://dashscope.aliyuncs.com/compatible-mode/v1"

# 使用OpenAI兼容接口调用通义千问
llm = init_chat_model(
    "qwen-plus",  # 或者 qwen-turbo, qwen-max
    model_provider="openai"
)

# 使用 JsonOutputParser 引导模型输出 JSON 格式
parser = PydanticOutputParser(pydantic_object=Date)

# 构造提示词模板,包含格式说明
template = """
提取用户输入中的日期,并以如下 pydantic 格式返回:

{format_instructions}

用户输入:
{query}
"""

prompt = PromptTemplate(
    template=template,
    input_variables=["query"],
    partial_variables={"format_instructions": parser.get_format_instructions()}
)

# 用户输入
query = "2023年四月6日天气晴..."

# 构建输入
_input = prompt.format(query=query)

# 调用模型并解析输出
output = llm.invoke(_input)
# print(output.content)
parsed_output = parser.parse(output.content)
print(type(parsed_output))

我们可以利用大模型的纠错功能对结构化输出数据中的错误进行纠正

from pydantic import BaseModel, Field
from langchain.chat_models import init_chat_model
from langchain.prompts import PromptTemplate
from langchain_core.output_parsers import JsonOutputParser
import os
from langchain.output_parsers import OutputFixingParser


# 定义结构化输出模型
class Date(BaseModel):
    year: int = Field(description="Year")
    month: int = Field(description="Month")
    day: int = Field(description="Day")
    era: str = Field(description="BC or AD")

# 设置 API 密钥和接口地址
os.environ["OPENAI_API_KEY"] =  os.getenv("DASHSCOPE_API_KEY")
os.environ["OPENAI_BASE_URL"] = "https://dashscope.aliyuncs.com/compatible-mode/v1"

# 使用OpenAI兼容接口调用通义千问
llm = init_chat_model(
    "qwen-plus",  # 或者 qwen-turbo, qwen-max
    model_provider="openai"
)

# 使用 JsonOutputParser 引导模型输出 JSON 格式
parser = JsonOutputParser(pydantic_object=Date)
new_parser = OutputFixingParser.from_llm(parser=parser, llm=llm)
# 构造提示词模板,包含格式说明
template = """
提取用户输入中的日期,并以如下 JSON 格式返回:

{format_instructions}

用户输入:
{query}
"""

prompt = PromptTemplate(
    template=template,
    input_variables=["query"],
    partial_variables={"format_instructions": parser.get_format_instructions()}
)

# 用户输入
query = "2023年四月6日天气晴..."

# 构建输入
_input = prompt.format(query=query)

# 调用模型并解析输出
output = llm.invoke(_input)
new_output=output.content.replace("4","四")
print(new_output)
parsed_output = new_parser.parse(new_output)

print(parsed_output)

用到的是OutputFixingParser.from_llm(parser=parser, llm=llm)

输出结果为:

```json
{
  "year": 2023,
  "month": 四,
  "day": 6,
  "era": "AD"
}
```
{'year': 2023, 'month': 4, 'day': 6, 'era': 'AD'}

可以看见我们人为添加的错误被修复

这里提供第二种结构化输出方式,这里利用模型原生的结构化输出功能 准确性更高,不需要解析

from langchain.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from pydantic import BaseModel, Field
from typing import Optional
from enum import Enum
import json
import os
from langchain.chat_models import init_chat_model

# 枚举和模型定义(保持不变)
class SortEnum(str, Enum):
    data = 'data'
    price = 'price'

class OrderingEnum(str, Enum):
    ascend = 'ascend'
    descend = 'descend'

class Semantics(BaseModel):
    name: Optional[str] = Field(description="流量包名称", default=None)
    price_lower: Optional[int] = Field(description="价格下限", default=None)
    price_upper: Optional[int] = Field(description="价格上限", default=None)
    data_lower: Optional[int] = Field(description="流量下限", default=None)
    data_upper: Optional[int] = Field(description="流量上限", default=None)
    sort_by: Optional[SortEnum] = Field(description="按价格或流量排序", default=None)
    ordering: Optional[OrderingEnum] = Field(description="升序或降序排列", default=None)

# Prompt 模板
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个语义解析器。你的任务是将用户的输入解析成JSON表示。不要回答用户的问题。"),
    ("human", "{text}"),
])

# 模型配置
os.environ["OPENAI_API_KEY"] = os.getenv("DASHSCOPE_API_KEY")
os.environ["OPENAI_BASE_URL"] = "https://dashscope.aliyuncs.com/compatible-mode/v1"

llm = init_chat_model("qwen-plus", model_provider="openai")
structured_llm = llm.with_structured_output(Semantics)

# LCEL 表达式
runnable = ({"text": RunnablePassthrough()} | prompt | structured_llm)

# 运行示例
ret = runnable.invoke("不超过100元的流量大的套餐有哪些")
print(json.dumps(ret.model_dump(), indent=4, ensure_ascii=False))

1.6 function calling

from langchain_core.messages import AIMessage, HumanMessage
from pydantic import BaseModel, Field
from langchain.chat_models import init_chat_model
from langchain.prompts import PromptTemplate
from langchain_core.output_parsers import JsonOutputParser
import os
from langchain.output_parsers import OutputFixingParser
from langchain_core.tools import tool
import json


# 设置 API 密钥和接口地址
os.environ["OPENAI_API_KEY"] =  os.getenv("DASHSCOPE_API_KEY")
os.environ["OPENAI_BASE_URL"] = "https://dashscope.aliyuncs.com/compatible-mode/v1"

# 使用OpenAI兼容接口调用通义千问
llm = init_chat_model(
    "qwen-plus",  # 或者 qwen-turbo, qwen-max
    model_provider="openai"
)
@tool
def add(a:int ,b:int )->int:
    """Add two integers

    Args:
        a : First number
        b : Second number
    Returns
        sum : The sum of two args
    """
    return a+b
llm_with_tools=llm.bind_tools([add])
query="3+5 equal?"
messages=[HumanMessage(query)]
output=llm_with_tools.invoke(messages)
messages.append(output)

available_tools = {"add": add}

for tool_call in output.tool_calls:
    print(tool_call)
    selected_tool = available_tools[tool_call["name"].lower()]
    tool_msg = selected_tool.invoke(tool_call)
    print(tool_msg)
    messages.append(tool_msg)

new_output = llm_with_tools.invoke(messages)
for message in messages:
    print(json.dumps(message.model_dump(), indent=4, ensure_ascii=False))
print(new_output.content)
llm_with_tools=llm.bind_tools([add])

该操作将大模型和工具进行绑定

output=llm_with_tools.invoke(messages)

第一次调用大模型,将我们的问题发送给大模型,因为大模型之前绑定了我们的工具,通过分析用户意图识别出了

需要进行工具调用,所以此时生成的是工具调用需要的参数,类型是一个AIMessage 值如下:

AIMessage(
    content="",  # 通常为空,因为需要调用工具
    tool_calls=[  # 工具调用列表
        {
            "name": "add",           # 工具名称
            "args": {                # 工具参数
                "a": 3,
                "b": 5
            },
            "id": "call_xxxxxxxx"    # 调用ID(用于跟踪)
        }
    ]
)

我们按照标注难度Function calling来调用

# 标准流程需要记录每一步:
用户 → AI工具调用 → 工具执行 → AI最终回答

所以我们需要将我们生成的工具调用信息加入到消息列表中

messages.append(output)

接下来是枚举我们的所有工具,看看哪些工具需要实际调用

available_tools = {"add": add}

for tool_call in output.tool_calls:
    selected_tool = available_tools[tool_call["name"].lower()]
    tool_msg = selected_tool.invoke(tool_call)
    messages.append(tool_msg)

上面的available_tools是我们的可选工具列表

其中的tool_calls AIMessage的一个属性,是一个存储了工具调用信息的列表,每个工具的调用信息都是一个字

典,存储了参数,id,工具调用id等,下面是一个例子

{'name': 'add', 'args': {'a': 3, 'b': 5}, 'id': 'call_2e55d57fdb094ec58132c5', 'type': 'tool_call'}

我们通过遍历整个返回的工具调用信息,找到需要调用的工具,然后调用该工具

具体的调用语句为

selected_tool.invoke(tool_call)

我们这样使用invoke方法是因为我们上面声明的工具使用了tools装饰器,当你使用 @tool 装饰器时,它会将普通

的 Python 函数转换为一个 Tool 对象,这个 Tool 对象继承自 LangChain 的 Runnable 基类。

在 LangChain 中,所有可执行的对象都实现了一个标准的 Runnable 接口,这个接口包含:

  • .invoke() - 同步执行单个输入
  • .ainvoke() - 异步执行单个输入
  • .batch() - 批量执行多个输入
  • .astream() - 异步流式执行等

我们通过将tool_call传入这个工具,实现了工具调用,最后将调用的加过加入大模型中

messages.append(tool_msg)

最后我们再调一遍大模型,将我们的messages传入就可以得到答案了

new_output = llm_with_tools.invoke(messages)
print(new_output.content)

1.7 利用langchain 构建rag

from langchain_community.document_loaders import PyMuPDFLoader
import os
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_community.vectorstores import FAISS
from langchain_community.document_loaders import PyMuPDFLoader

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=512,
    chunk_overlap=200,
    length_function=len,
    add_start_index=True,
)
loader = PyMuPDFLoader("./data/deepseek-v3-1-4.pdf")
pages = loader.load_and_split()
texts = text_splitter.create_documents(
    [page.page_content for page in pages]
)

embeddings = DashScopeEmbeddings(
    model="text-embedding-v3", dashscope_api_key=os.getenv("DASHSCOPE_API_KEY")
)
index = FAISS.from_documents(texts, embeddings)
index.save_local("./faiss_index")
# 检索 top-5 结果
retriever = index.as_retriever(search_kwargs={"k": 5})

docs = retriever.invoke("deepseek v3有多少参数")

for doc in docs:
    print(doc.page_content)
    print("----")

这是构建一个本地知识库,然后召回top 5,相比llamaindex langchain的召回结果关联性不是很强

1.8 LCEL 和Chain

下面举一个例子:

from langchain.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough, RunnableLambda
from pydantic import BaseModel, Field
from typing import List, Dict, Optional
from enum import Enum
from langchain_core.output_parsers import JsonOutputParser
import json
import os
from langchain.chat_models import init_chat_model

# 输出结构
class SortEnum(str, Enum):
    data = 'data'
    price = 'price'


class OrderingEnum(str, Enum):
    ascend = 'ascend'
    descend = 'descend'


class Semantics(BaseModel):
    name: Optional[str] = Field(description="流量包名称", default=None)
    price_lower: Optional[int] = Field(description="价格下限", default=None)
    price_upper: Optional[int] = Field(description="价格上限", default=None)
    data_lower: Optional[int] = Field(description="流量下限", default=None)
    data_upper: Optional[int] = Field(description="流量上限", default=None)
    sort_by: Optional[SortEnum] = Field(description="按价格或流量排序", default=None)
    ordering: Optional[OrderingEnum] = Field(
description="升序或降序排列", default=None)


# Prompt 模板
prompt = ChatPromptTemplate.from_messages(
    [
        ("system", "你是一个语义解析器。你的任务是将用户的输入解析成JSON表示。格式为:{json}不要回答用户的问题。"),
        ("human", "{text}"),
    ]
)

os.environ["OPENAI_API_KEY"] =  os.getenv("DASHSCOPE_API_KEY")
os.environ["OPENAI_BASE_URL"] = "https://dashscope.aliyuncs.com/compatible-mode/v1"
llm = init_chat_model(
    "qwen-plus",
    model_provider="openai",
)
parser = JsonOutputParser(pydantic_object=Semantics)

# 创建一个返回格式说明的可运行组件
def get_format_instructions(input):
    return parser.get_format_instructions()

format_instructions_runnable = RunnableLambda(get_format_instructions)

# LCEL 表达式
runnable = (
    {"text": RunnablePassthrough(), "json": format_instructions_runnable} | prompt | llm | parser
)

# 直接运行
ret = runnable.invoke("我需要一个名为超级套餐的流量包,价格在50元到100元之间,流量不少于30GB但不超过100GB,按流量从高到低排序")

print(ret)
class SortEnum(str, Enum):
    data = 'data'
    price = 'price'

这是在定义字符串类型的枚举对象

runnable = (
    {"text": RunnablePassthrough(), "json": format_instructions_runnable} | prompt | llm | parser
)

这是一个典型的链式调用,其中RunnablePassthrough()是获得直接的输入

我们第一个使用的{} 使用的是 RunnableParalle语法,看似是字典的键,实际上是一个可运行的组件

(Runnable),我们正常调用parser.get_format_instructions() 获得输出格式描述,但由于不是Runnabl

e类型,需要封装一下,所以有了

def get_format_instructions(input):
    return parser.get_format_instructions()

format_instructions_runnable = RunnableLambda(get_format_instructions)

下面给一个利用链式调用构建rag的例子:

import os
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain.chains import RetrievalQA
from langchain_community.document_loaders import PyMuPDFLoader
from langchain_community.embeddings.dashscope import DashScopeEmbeddings
from langchain.schema.output_parser import StrOutputParser
from langchain.schema.runnable import RunnablePassthrough
from langchain.prompts import ChatPromptTemplate
from langchain.chat_models import init_chat_model

loader = PyMuPDFLoader("./data/deepseek-v3-1-4.pdf")
pages = loader.load_and_split()

os.environ["OPENAI_API_KEY"] =  os.getenv("DASHSCOPE_API_KEY")
os.environ["OPENAI_BASE_URL"] = "https://dashscope.aliyuncs.com/compatible-mode/v1"
llm = init_chat_model(
    "qwen-plus",
    model_provider="openai",
)
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=512,
    chunk_overlap=200,
    length_function=len,
    add_start_index=True,
)

texts = text_splitter.create_documents(
    [page.page_content for page in pages]
)

# 灌库
embeddings = DashScopeEmbeddings(
    model="text-embedding-v3", dashscope_api_key=os.getenv("DASHSCOPE_API_KEY")
)
index = FAISS.from_documents(texts, embeddings)
index.save_local("./faiss_index")
# 检索 top-5 结果
retriever = index.as_retriever(search_kwargs={"k": 5})
docs = retriever.invoke("deepseek v3有多少参数")

# Prompt模板
template = """Answer the question based only on the following context:
{context}

Question: {question}
"""
prompt = ChatPromptTemplate.from_template(template)

rag_chain = (
    {"question": RunnablePassthrough(), "context": retriever}
    | prompt
    | llm
    | StrOutputParser()
)

output=rag_chain.invoke("deepseek V3有多少参数")
print(output)

1.9 基础的native rag

import os
from PyPDF2 import PdfReader
from langchain_community.callbacks import get_openai_callback
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_community.vectorstores import FAISS
from langchain_openai import ChatOpenAI
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser


def extract_text(pdf: PdfReader) -> str:
    text = ""
    for page in pdf.pages:
        page_text = page.extract_text()
        if page_text:
            text += page_text
    return text


def create_rag_chain(retriever, llm):
    prompt = ChatPromptTemplate.from_template("""
    请基于以下上下文回答问题:

    上下文: {context}

    问题: {question}

    回答:
    """)

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

    chain = (
            {"context": retriever | format_docs, "question": RunnablePassthrough()}
            | prompt
            | llm
            | StrOutputParser()
    )
    return chain


def process_text_with_splitter(text: str, save_path: str) -> FAISS:
    text_splitter = RecursiveCharacterTextSplitter(
        separators=["\n\n", "\n", ".", ",", ""],
        chunk_size=512,
        chunk_overlap=128,
        length_function=len
    )
    chunks = text_splitter.split_text(text)
    embeddings = DashScopeEmbeddings(
        model="text-embedding-v2"
    )
    knowledge_base = FAISS.from_texts(chunks, embeddings)
    knowledge_base.save_local(save_path)
    return knowledge_base


if __name__ == "__main__":
    pdf_reader = PdfReader("浦发上海浦东发展银行西安分行个金客户经理考核办法.pdf")
    text = extract_text(pdf_reader)
    save_dir = "./vector_db"
    knowledgeBase = process_text_with_splitter(text, save_dir)
    retriever = knowledgeBase.as_retriever(search_kwargs={"k": 4})
    chatLLM = ChatOpenAI(
        api_key=os.getenv("DASHSCOPE_API_KEY"),
        base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
        model="deepseek-v3"
    )
    rag_chain = create_rag_chain(retriever, chatLLM)
    query = "工作质量考核是什么"

    if query:
        with get_openai_callback() as cost:
            response = rag_chain.invoke(query)
            docs = retriever.invoke(query)
            print(f"查询已处理。成本: {cost}")
            print(f"回答: {response}")
            print("来源:")

        for doc in docs:
            print(doc.page_content)

2.0 多查询召回

为了提高召回多样性,大模型将用户查询改写为多个语义相近的查询

import os
from langchain.retrievers import MultiQueryRetriever
from langchain_community.vectorstores import FAISS
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_community.llms import Tongyi


··

embeddings = DashScopeEmbeddings(model="text-embedding-v3")

# 加载向量数据库
vectorstore = FAISS.load_local("./vector_db", embeddings, allow_dangerous_deserialization=True)

# 创建MultiQueryRetriever
retriever = MultiQueryRetriever.from_llm(
    retriever=vectorstore.as_retriever(),
    llm=llm
)

# 示例查询
query = "客户经理的考核标准是什么?"
results = retriever.invoke(query)

# 打印结果
print(f"查询: {query}")
print(f"找到 {len(results)} 个相关文档:")
for i, doc in enumerate(results):
    print(f"\n文档 {i+1}:")
    print(doc.page_content[:200] + "..." if len(doc.page_content) > 200 else doc.page_content)

2.1基于qwen-agent 构建

我们可以选用现有agent框架去建立,这里选用qwen-agent可以帮我们完成
文档解析
智能分块
关键词提取
token优化

还具备多模态功能,相比我们手动构建知识库,建立native rag 用这种方式相对要简单一点

import logging
import io
from qwen_agent.agents import Assistant
import os


# 创建一个自定义的日志处理器来捕获日志输出
class LogCapture:
    def __init__(self):
        self.log_capture_string = io.StringIO()
        self.log_handler = logging.StreamHandler(self.log_capture_string)
        self.log_handler.setLevel(logging.INFO)
        self.log_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        self.log_handler.setFormatter(self.log_formatter)

        # 获取 qwen_agent 的日志记录器
        self.logger = logging.getLogger('qwen_agent')  # 更准确的日志记录器名称
        self.logger.setLevel(logging.INFO)
        self.logger.addHandler(self.log_handler)

    def get_log(self):
        return self.log_capture_string.getvalue()

    def clear_log(self):
        self.log_capture_string.truncate(0)
        self.log_capture_string.seek(0)

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        # 清理资源
        self.logger.removeHandler(self.log_handler)
        self.log_handler.close()


# 步骤 1:配置您所使用的 LLM
llm_cfg = {
    'model': 'qwen-max',
    'model_server': 'dashscope',
    'api_key': os.getenv("DASHSCOPE_API_KEY"),
    'generate_cfg': {
        'top_p': 0.8,
        'temperature': 0.7  # 添加温度参数以提高回答质量
    }
}

# 验证API密钥
if not llm_cfg['api_key']:
    raise ValueError("DASHSCOPE_API_KEY 环境变量未设置")

# 步骤 2:创建一个智能体
system_instruction = '''你是一个专业的银行业务助手,擅长分析银行内部文档和政策。
请基于提供的文档内容准确回答问题,如果文档中没有相关信息,请明确说明。'''

tools = []
files = ['./浦发上海浦东发展银行西安分行个金客户经理考核办法.pdf']


def analyze_query_response(query: str):
    """分析查询和响应的函数"""
    # 使用上下文管理器确保资源正确释放
    with LogCapture() as log_capture:
        try:
            # 创建智能体
            bot = Assistant(
                llm=llm_cfg,
                system_message=system_instruction,
                function_list=tools,
                files=files
            )

            # 步骤 3:处理查询
            messages = [{'role': 'user', 'content': query}]
            response_parts = []

            print(f"查询: {query}")
            print("回答: ", end='', flush=True)

            # 流式处理响应
            for response in bot.run(messages=messages):
                current_response = response[0]['content'][len(''.join(response_parts)):]
                response_parts.append(current_response)
                print(current_response, end='', flush=True)

            print("\n")  # 换行

            # 分析日志
            log_content = log_capture.get_log()
            print("\n===== 检索分析 =====")

            # 分类日志信息
            log_categories = {
                'retrieval': ['retriev', 'search', 'chunk', 'document', 'ref'],
                'content': ['content:', 'text:', 'page'],
                'processing': ['process', 'parse', 'extract'],
                'keywords': ['keyword', 'query', 'term']
            }

            for category, keywords in log_categories.items():
                category_logs = [
                    line for line in log_content.split('\n')
                    if any(keyword in line.lower() for keyword in keywords) and line.strip()
                ]

                if category_logs:
                    print(f"\n{category.upper()}:")
                    for log_line in category_logs[:5]:  # 限制显示行数
                        print(f"  {log_line}")

            print("===================\n")

            return ''.join(response_parts)

        except Exception as e:
            print(f"处理查询时出错: {e}")
            return None


# 主执行部分
if __name__ == "__main__":
    query = "客户经理被客户投诉一次,扣多少分?扣分规则是什么"
    result = analyze_query_response(query)

    if result:
        print(f"查询完成,响应长度: {len(result)} 字符")

2.2 父文档检索器

传统的文档切割检索有一定局限性,我们可以使用父文档检索器技术

import os
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_community.llms import Tongyi
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.retrievers import ParentDocumentRetriever
from langchain.storage import InMemoryStore
from langchain_chroma import Chroma
from langchain_community.document_loaders import PyPDFLoader
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser

# 加载文档
docs = PyPDFLoader("./浦发上海浦东发展银行西安分行个金客户经理考核办法.pdf").load()

# 简化的模型初始化
llm = Tongyi(model="qwen-max")
embeddings = DashScopeEmbeddings(model="text-embedding-v3")

# 创建分割器(优化chunk大小)
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)
child_splitter = RecursiveCharacterTextSplitter(chunk_size=400, chunk_overlap=50)

# 添加持久化向量数据库
vectorstore = Chroma(
    collection_name="split_parents",
    embedding_function=embeddings,
    persist_directory="./chroma_db"  # 添加持久化存储
)

store = InMemoryStore()

# 优化检索参数
retriever = ParentDocumentRetriever(
    vectorstore=vectorstore,
    docstore=store,
    child_splitter=child_splitter,
    parent_splitter=parent_splitter,
    search_kwargs={"k": 3}  # 增加检索数量
)

retriever.add_documents(docs)

# 提示模板
template = """你是一个专业的银行业务助手,擅长分析银行内部文档和政策。
基于以下检索到的文档内容回答问题。如果文档中没有相关信息,请明确说明。

问题: {question}

相关文档内容:
{context}

请提供准确、简洁的回答:
"""

prompt = ChatPromptTemplate.from_template(template)

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

chain = (
    {
        "context": retriever | format_docs,
        "question": RunnablePassthrough()
    }
    | prompt
    | llm
    | StrOutputParser()
)

if __name__ == "__main__":
    question = "客户经理被客户投诉一次,扣多少分?"
    result = chain.invoke(question)
    print(f"问题: {question}")
    print(f"回答: {result}")
posted @ 2025-07-26 00:46  折翼的小鸟先生  阅读(79)  评论(0)    收藏  举报