[对比学习LangChain和MAF-11]RAG的实现

在大语言模型(LLM)的实际应用中,单靠模型自身的能力会遇到严重的瓶颈。RAG(检索增强生成)技术的出现,正是为了解决原生LLM在商业和技术落地上的五大硬伤:

  • 根治LLM的幻觉 硬伤大语言模型本质上是基于概率的下一个词预测生成器 :
    • 没有RAG:当模型遇到不掌握的知识时,它不会说我不知道 ,而是会一本正经地胡编乱造,产生严重的幻觉(Hallucination)
    • 引入RAG:它强迫LLM进行开卷考试。模型必须基于检索到的确定性参考资料来组织语言,从根本上压制了胡思乱想,确保回答的严谨性;
  • 跨越知识时效性的时间墙 所有LLM在训练完成的那一刻,其内部知识就已经冻结了:
    • 没有RAG:模型无法得知今天早上刚发生的新闻,也无法得知你刚刚更新的产品规格。如果想要让它知道,必须重新花费数周时间、数百万资金去**微调(Fine-tuning)**或重新训练模型;
    • 引入RAG:无需触动模型本身。只需要把最新的新闻、实时的股市数据、或今天的会议记录放入外部知识库,LLM通过即时检索,就能立刻掌握最新动态;
  • 解锁企业私有海量数据 LLM是在公开的互联网数据上训练出来的,对企业内部的机密知识 一无所知:
    • 没有RAG:企业如果直接把自己的财务报表、员工手册、客户服务记录喂给公共LLM去训练,会导致严重的商业机密泄露风险;
    • 引入RAG:企业的数据可以安全地保存在本地或私有云的向量数据库中。只有在用户提问时,系统才会安全地检索相关片段提供给AI临时阅读,完美保护了数据隐私;
  • 实现答案的可追溯性 与透明度原生LLM的输出是一个黑盒,你很难知道它的某句话是从哪里学来的:
    • 没有RAG:AI 给出医疗建议或法律条款时,用户无法判断其真伪,不敢盲目采信;
    • 引入RAG:由于LLM是根据检索到的具体文本片段进行回答的,系统可以轻松地在回答末尾附带来源引用(例如:"参考自《2026年Q1财务报告》第14页"),从而建立了极高的信任度;
  • 极低的管理与算力成本这是RAG能够在企业级应用中迅速普及的商业决定性原因:
    • 对比模型微调(SFT):微调需要昂贵的GPU算力、专业的 AI 科学家、以及繁琐的数据标注,且每次更新数据都要重新来过;
    • RAG的优势:只需像管理网盘一样,往数据库里增删改文档(增量更新),成本几乎为零。普通开发人员只需几行代码即可搭建完成;

以下面这个程序为例。我们通过LangGraph SDK来调用之前构建的Agent服务器,并请求它回答**2026年斯诺克世界赛冠军是谁?**这个问题。由于这个问题涉及到2026年的最新信息,原生的LLM是无法回答的(会产生幻觉),但是由于我们在Agent服务器端引入了RAG技术,Agent可以通过检索最新的知识库来给出正确的答案。

csharp 复制代码
using Azure.AI.Projects;
using dotenv.net;
using Microsoft.Extensions.AI;
using OpenAI;
using OpenAI.Responses;
using System.ClientModel;

DotEnv.Load();

var model = Environment.GetEnvironmentVariable("MODEL")!;
var apiKey = Environment.GetEnvironmentVariable("API_KEY")!;
var endpoint = Environment.GetEnvironmentVariable("OPENAI_URL")!;

var agent = new OpenAIClient(
    credential: new ApiKeyCredential(key: apiKey),
    options: new OpenAIClientOptions { Endpoint = new Uri(endpoint) })
    .GetResponsesClient()
    .AsAIAgent(model: model);
var response = await agent.RunAsync(message:"2026年斯诺克世界赛冠军是谁?");
Console.WriteLine(response.Text);

输出:

markdown 复制代码
我目前**无法确定**2026年斯诺克世界锦标赛的冠军是谁。

原因是:
- 虽然按赛程来看,2026年世锦赛通常在**4月下旬至5月初**结束,但
- 我无法获取**实时或最新比赛结果**,而且不应在没有可靠数据的情况下猜测冠军。

你可以选择下面两种方式之一:
1. **如果你想要的是已产生的正式结果**:我可以告诉你去哪里快速核实(如世界斯诺克官网、BBC Sport、世界斯诺克巡回赛官方微博等)。
2. **如果你想要的是赛前或赛中的预测/分析**:我可以根据当时的世界排名、签表、球员状态,给你一个专业预测。

你是想问**"已经夺冠的是谁"**,还是**"你预测谁会夺冠"**?

由于模型训练的时间早于2026年,所以它无法知道2026年斯诺克世界锦标赛的冠军是谁。既然模型不均有相关的知识,我们就根据用户的查询将相关的信息检索出来,作为上下文提供给LLM来生成回答。接下来我们通过简单例子看看在LangChain和MAF中如何利用RAG来解决这个问题。

1. LangChain

传统的RAG基本建立在向量数据库基于自然语言的检索上。简单起见,下面的程序程序将检索逻辑硬编码在工具函数search_context中。在这个模拟基于用户查询检索的工具函数中,如果用户的查询中同时包含2026斯诺克世锦赛 这几个关键词,我们就返回一段相关的新闻稿。除了提供文本内容之外,我们还附带了一个SourceLinkSourceName来说明这个信息的来源。

python 复制代码
from langchain.agents import create_agent
from langchain.tools import tool
from dotenv import load_dotenv

load_dotenv()

@tool
def search_context(query:str) -> str:
    """根据用户的查询检索相关的上下文信息"""
    if "2026" in query and "斯诺克" in query and "世锦赛" in query:
        news = """\
北京时间2026年5月5日,英国谢菲尔德克鲁斯堡剧院,决胜局最后一颗黑球落袋后,00后中国球员吴宜泽挥拳庆祝。18:17,吴宜泽击败肖恩·墨菲,拿下2026年斯诺克世锦赛冠军。
社交平台上,"吴宜泽夺冠"迅速登上热搜,不少球迷将这场胜利形容为"中国斯诺克新的接力时刻"。这是继2025年赵心童夺冠之后,中国选手再次问鼎这项赛事最高荣誉。这也是在经历2023年前后相关禁赛与争议事件后,中国球员重新回到世界顶级竞争序列的重要节点。
相比十多年前,丁俊晖在斯诺克领域的单点突破,中国近几年开始稳定涌现世界级斯诺克选手。从个体突破到群体崛起,这项运动在中国已经进入新的发展阶段。
""";
        return f"""\
SourceLink: https://baijiahao.baidu.com/s?id=1864605028122769594
SourceName: 每日经济新闻,
Text: {news}
"""
    return "No relevant context found."

agent = create_agent(
    model= "gpt-5.2-chat",
    tools=[search_context],
    system_prompt="你拥有一个根据用户查询检索相关上下文信息的工具`search_context`。请根据用户的查询调用工具获取相关的上下文信息,并结合这些信息给出最终的回答。"
)

result = agent.invoke(input={
    "messages":[{"role":"user", "content": "2026年斯诺克世锦赛的冠军是谁?"}]
})

for message in result["messages"]:
    message.pretty_print()

我们在系统提示词中明确告诉模型它拥有一个名为search_context的工具,这个工具可以根据用户的查询来检索相关的上下文信息。模型需要先调用这个工具来获取相关的信息,然后才能结合这些信息来生成最终的回答。程序输出结果如下:

markdown 复制代码
================================ Human Message =================================

2026年斯诺克世锦赛的冠军是谁?
================================== Ai Message ==================================
Tool Calls:
  search_context (call_fEqvdSEywsm9aCrJAeJJdQaO)
 Call ID: call_fEqvdSEywsm9aCrJAeJJdQaO
  Args:
    query: 2026年 斯诺克 世锦赛 冠军
================================= Tool Message =================================
Name: search_context

SourceLink: https://baijiahao.baidu.com/s?id=1864605028122769594
SourceName: 每日经济新闻,
Text: 北京时间2026年5月5日,英国谢菲尔德克鲁斯堡剧院,决胜局最后一颗黑球落袋后,00后中国球员吴宜泽挥拳庆祝。18:17,吴宜泽击败肖恩·墨菲,拿下2026年斯诺克世锦赛冠军。
社交平台上,"吴宜泽夺冠"迅速登上热搜,不少球迷将这场胜利形容为"中国斯诺克新的接力时刻"。这是继2025年赵心童夺冠之后,中国选手再次问鼎这项赛事最高荣誉。这也是在经历2023年前后相关禁赛与争议事件后,中国球员重新回到世界顶级竞争序列的重要节点。
相比十多年前,丁俊晖在斯诺克领域的单点突破,中国近几年开始稳定涌现世界级斯诺克选手。从个体突破到群体崛起,这项运动在中国已经进入新的发展阶段。


================================== Ai Message ==================================

2026年斯诺克世锦赛的冠军是**吴宜泽**。

他在北京时间 **2026年5月5日** 于英国谢菲尔德克鲁斯堡剧院举行的决赛中,击败了**肖恩·墨菲**,成功夺冠。这也是继2025年赵心童夺冠之后,中国选手连续第二年获得斯诺克世锦赛冠军。

上面这段输出体现包含整个推理流程产生的四个消息:

  • Human Message:用户的输入消息,即"2026年斯诺克世锦赛的冠军是谁?"
  • Ai Message:模型生成的消息,其中包含了模型调用工具的相关信息(工具名称、调用ID、调用参数等)。
  • Tool Message:工具函数search_context的输出结果,其中包含了检索到的相关上下文信息(新闻内容、来源链接和来源名称)。
  • Ai Message:模型根据工具函数的输出生成的最终回答,即"2026年斯诺克世锦赛的冠军是吴宜泽"。

关于RAG涉及的内容还有很多,比如各种文档的加载、文本的分割、向量数据库、重排序甚至图数据库(实现GraphRAG)。如果希望了解关于RAG的更多内容,可以参考我们之前的博客文章系列RAG在LangChain中的实现(共8篇)

2. MAF

顾名思义,RAG的本质上属于AIAgent输入增强的范畴。在ChatClientAgent的管道中,具有专门用于输入和响应增强的组件,那就是AIContextProvider。MAF的常规额RAG结解决方案由TextSearchProvider实现,它就派生于AIContextProvider

这是上面演示实例针对MAF的实现版本。我们首先创建了一个TextSearchProvider,调用其构造函数提供的第一个参数是一个指向SearchAsync函数的委托,TextSearchProvider正是使用它来完成上下文的检索。接下来我们创建了一个针对OpenAIClientChatClientAgent,在调用AsAIAgent扩展方法时,我们提供了一个ChatClientAgentOptions对象,并将之前创建的TextSearchProvider添加到AIContextProviders属性中。

csharp 复制代码
using Azure.AI.Projects;
using dotenv.net;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using OpenAI;
using OpenAI.Responses;
using System.ClientModel;
using static Microsoft.Agents.AI.TextSearchProvider;

DotEnv.Load();

var model = Environment.GetEnvironmentVariable("MODEL")!;
var apiKey = Environment.GetEnvironmentVariable("API_KEY")!;
var endpoint = Environment.GetEnvironmentVariable("OPENAI_URL")!;

var textSearchProvider = new TextSearchProvider(searchAsync: SearchAsync);
var agent = new OpenAIClient(
    credential: new ApiKeyCredential(key: apiKey),
    options: new OpenAIClientOptions { Endpoint = new Uri(endpoint) })
    .GetChatClient(model:model)
    .AsIChatClient()
    .AsAIAgent(options: new ChatClientAgentOptions {  AIContextProviders = [textSearchProvider] });


var response = await agent.RunAsync(message:"2026年斯诺克世界赛冠军是谁?");
Console.WriteLine(response.Text);

static Task<IEnumerable<TextSearchResult>> SearchAsync(string query, CancellationToken cancellationToken)
{
    if (query.Contains("2026") && query.Contains("斯诺克") && query.Contains("世界赛"))
    {
        var news = """
北京时间2026年5月5日,英国谢菲尔德克鲁斯堡剧院,决胜局最后一颗黑球落袋后,00后中国球员吴宜泽挥拳庆祝。18:17,吴宜泽击败肖恩·墨菲,拿下2026年斯诺克世锦赛冠军。
社交平台上,"吴宜泽夺冠"迅速登上热搜,不少球迷将这场胜利形容为"中国斯诺克新的接力时刻"。这是继2025年赵心童夺冠之后,中国选手再次问鼎这项赛事最高荣誉。这也是在经历2023年前后相关禁赛与争议事件后,中国球员重新回到世界顶级竞争序列的重要节点。
相比十多年前,丁俊晖在斯诺克领域的单点突破,中国近几年开始稳定涌现世界级斯诺克选手。从个体突破到群体崛起,这项运动在中国已经进入新的发展阶段。
""";
        var result = new TextSearchResult
        {
            RawRepresentation = news,
            SourceLink = "https://baijiahao.baidu.com/s?id=1864605028122769594",
            SourceName = "每日经济新闻",
            Text = news
        };
        return Task.FromResult<IEnumerable<TextSearchResult>>([result]);
    }
    else
    {
        return Task.FromResult<IEnumerable<TextSearchResult>>([]);
    }
}

我们最终调用RunAsync方法来请求Agent回答**2026年斯诺克世界赛冠军是谁?**这个问题。程序输出结果如下:

markdown 复制代码
2026年斯诺克世界锦标赛冠军是**吴宜泽**。

据《每日经济新闻》报道,北京时间2026年5月5日,在英国谢菲尔德克鲁斯堡剧院举行的决赛中,吴宜泽在决胜局击败肖恩·墨菲,成功夺得2026年斯诺克世锦赛冠军。这也是继2025年赵心童夺冠之后,中国选手再次问鼎这项斯诺克最高荣誉赛事。
来源:每日经济新闻
链接:https://baijiahao.baidu.com/s?id=1864605028122769594

关于TextSearchProvider的设计和实现原理,可以参考我们之前的博客文章TextSearchProvider:在MAF中实现RAG