成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

不需要RAG!在30分鐘內構建一個問答AI智能體

發布于 2025-6-19 07:03
瀏覽
0收藏

2025 年的 RAG 大反思

我們的目標不是抨擊 RAG(它仍有其用武之地!),而是通過一個實際案例來探索 2025 年的技術趨勢。我們將構建一個傳統上會使用 RAG 的系統,但采用一種完全不同的、越來越流行的方法。

通過本文,你將了解:

?為什么“直接訪問源數據”的方法(go to source)因工具調用(tool calling)和模型上下文協議(MCP)而逐漸受到關注。

?RAG 仍然適用的場景(劇透:比你想象的少)。

?如何構建一個用戶喜愛的實用替代方案。

?額外內容:為什么非思考模型(non-thinking models)在簡單結構化任務中往往優于“思考模型”(thinking models)。

準備好了嗎?讓我們開始吧!??

RAG:優點、缺點與復雜性

什么是 RAG?

你可以將 RAG 想象成一個非常高級的圖書館員系統。當有人提出問題時:

1.將文檔分割成小塊(因為模型有上下文限制)。

2.將所有內容轉換為嵌入(embeddings,數學表示形式)。

3.將嵌入存儲在向量數據庫(vector database,你的數字圖書館)。

4.當問題到來時,將問題也轉換為嵌入。

5.使用向量相似性搜索(vector similarity search)找到相似的文檔塊。

6.將這些文檔塊與問題一起輸入到大型語言模型(LLM)。

7.根據檢索到的上下文生成答案。

不需要RAG!在30分鐘內構建一個問答AI智能體-AI.x社區

RAG 架構

以下是一個簡化的 RAG 流水線示例:

# 傳統 RAG 流水線(簡化版)
def rag_pipeline(question):
# 步驟 1:將問題轉換為嵌入
    question_embedding = embedding_model.encode(question)


# 步驟 2:搜索向量數據庫
    similar_chunks = vector_db.similarity_search(
        question_embedding,
        top_k=5
)


# 步驟 3:從文檔塊構建上下文
    context ="\n".join([chunk.content for chunk in similar_chunks])


# 步驟 4:生成答案
    prompt = f"Context: {context}\n\nQuestion: {question}\nAnswer:"
return llm.generate(prompt)

RAG 的歷史:為何它曾如此流行?

在 2020-2023 年期間,RAG 非常有意義,原因如下:

?小上下文窗口:GPT-3 只有 4K 令牌(tokens)!這是 RAG 被引入的主要原因。

?高昂的令牌成本:處理大型文檔的成本很高。

?“迷失中間”問題:模型無法很好地處理長上下文。

?對新鮮數據的需求:模型有訓練數據截止時間。

RAG 通過選擇性地包含信息解決了這些問題,并允許將最新數據添加到過時的模型中。

簡單 RAG 示例

假設你有公司文檔,有人問:“如何在我們的 API 中設置認證?”

傳統 RAG 方法:

1.將文檔分割成 500 字的塊。

2.找到 3-5 個與認證最相關的塊。

3.僅將這些塊輸入到 LLM。

4.生成答案。

在上下文受限且成本高昂時,這種方法效果很好,但……

隱藏的復雜性:RAG 的不為人知之處

雖然 RAG 在理論上聽起來優雅,但實現和維護的現實遠比大多數教程描述的復雜。

以下是一些真正的挑戰:

基礎設施噩夢

?復雜設置:RAG 需要向量數據庫、嵌入流水線、分塊策略、監控和擴展 → 高復雜度。

?高成本:向量數據庫和 RAG 流水線的運行和維護成本高昂,一旦數據量達到一定規模,成本會激增,每月可能高達數千美元。

?供應商鎖定:選擇 Pinecone、Weaviate、Chroma 或自托管解決方案,均需支付高額月費。

分塊問題

分塊是將文檔分割成小塊并保留語義意義的過程,這些小塊隨后存儲在向量數據庫中。分塊策略有很多種,各有優缺點,且都不完美,以下是一些問題:

?上下文丟失:分割文檔會破壞逐步說明,代碼與解釋分離。

?無完美策略:固定大小、語義或段落分塊都有重大缺陷。

?跨引用斷裂:相關部分被分開,丟失重要聯系。

相似性搜索的假象

向量數據庫使用相似性搜索,概念上類似于 Elasticsearch,但專為 LLM 嵌入優化。雖然 Elasticsearch 經過多年驗證,但向量數據庫較新且仍在發展。正如我們稍后將討論的,RAG 的一個替代方案是利用現有的 Elasticsearch 基礎設施,通過創建工具查詢其上下文檢索。這種方法對已有 Elasticsearch 集群的組織特別有價值,因為它避免了引入向量數據庫等額外基礎設施。

?數學 ≠ 相關性:高相似性得分并不保證結果有用。

?查詢不匹配:問題與文檔語句對齊不佳。

?缺失上下文:嵌入丟失重要的限定信息。

維護噩夢

?文檔更新:每次更改都需要重新分塊、重新嵌入、重新索引整個流水線!

?模型不兼容:無法混合不同模型的嵌入;升級 = 完全重建!這使得切換模型非常具有挑戰性!

?性能下降:隨著向量數據庫的增長,速度會變慢。一旦數據量較大,延遲將成為問題。

擴展現實

?指數成本:基礎設施需求增長速度超過文檔數量。

?收益遞減:更多文檔往往意味著更差的搜索質量。

?運營開銷:大型部署需要專門的 DevOps 團隊。

常見失敗

?“迷失中間”:LLM 忽略搜索結果中的中間塊。

?數據過時:向量搜索沒有新鮮度感知。

?上下文碎片化:復雜答案被分散在多個塊中。

現實檢查:

?指數成本:基礎設施需求增長速度超過文檔數量(向量數據庫擴展挑戰)。

?收益遞減:更多文檔往往意味著更差的搜索質量。

?運營復雜性:需要專門的 DevOps 專業知識。

這種復雜性正是我們構建的簡單搜索優先方法獲得關注的原因。核心思想是直接訪問數據,而不是創建復雜的 RAG 流水線。

通過使用成熟的搜索 API 和大上下文窗口,我們可以以更低的復雜性獲得更好的結果。

RAG 在 2025 年:格局已劇變

上下文窗口革命

過去 18 個月的變化徹底顛覆了一切:

不需要RAG!在30分鐘內構建一個問答AI智能體-AI.x社區

上下文窗口 + 成本 等等,100 萬令牌的窗口只需 0.075 美元?這相當于大約 75 萬字——整整一本書的內容!

谷歌憑借其 Gemini 模型引領了這波創新浪潮。得益于專用 TPU 和大規模數據中心基礎設施,谷歌將上下文窗口推高至 500 萬令牌,同時保持每令牌成本極低。然而,長上下文窗口也有類似 RAG 的擴展問題,真正的變化在于直接訪問源數據,而不是構建復雜的流水線。

像 Gemini Flash 這樣的模型為許多 LLM 使用場景提供了快速、成本效益高且簡單的解決方案。此外,最新的 Gemini 版本在主要基準測試中超越了大多數競爭對手。

正如我們稍后將探討的,我非常喜歡 Gemini 2.0 Flash——它速度極快、價格實惠,且能輕松處理大上下文窗口。

更新:Gemini Flash 2.5 模型剛剛發布,是一個極佳的替代選擇,盡管價格略高。

這開啟了一個根本性的范式轉變。與其將多個來源的數據加載到向量數據庫并維護復雜的 RAG 流水線,我們可以構建直接訪問工具并將其提供給 LLM 和代理。核心思想是利用組織可能已經擁有的成熟解決方案,例如文檔網站或 Elasticsearch。

這種方法遠比傳統 RAG 簡單。憑借當今快速、價格實惠的 LLM 模型和大上下文窗口,成本和延遲不再是限制因素。

模型上下文協議(MCP)的引入從根本上改變了代理與工具的交互方式。MCP 使代理能夠自動發現工具并動態連接到數據庫、API 和企業系統,而無需手動配置每個工具連接。

這創建了真正的非確定性代理工作流(non-deterministic agentic workflows),其中魔法發生:工具本身可以成為代理,遞歸調用其他工具。想象一個代理發現數據庫工具,用它查詢客戶數據,然后自動找到并調用電子郵件 API 工具發送個性化消息——所有這些都不需要預定的工作流。

結果是自組織代理生態系統,根據可用功能動態適應,而不是依賴剛性編程或確定性工作流。曾經需要復雜手動設置的內容,現在通過工具交互自然浮現,使復雜的多代理系統對任何開發者都觸手可及。

?? 未來文章預告:在下一篇文章中,我們將為代理添加 MCP,并討論 MCP 革命以及 CrewAI 或 LangGraph 等框架可能的消亡,敬請期待!

關鍵外部數據檢索工具

如前所述,RAG 的主要替代方案是構建直接查詢源數據的工具,提取相關內容,并將其作為上下文提供給 LLM。

額外福利:這些工具本身可以是代理,創建層次結構,每個工具封裝復雜性,暴露簡單的可發現接口。

另一個優勢是 LLM 可以根據輸入動態決定使用哪些工具,使代理能夠智能選擇工具并配置參數,創建真正的非確定性工作流。

以下是一些可用于從現有系統提取上下文的工具示例:

公共搜索工具

?Tavily:使 LLM 能夠搜索預索引的公共網站以獲取相關上下文 → 這是我們將在代理中使用的工具!

企業搜索工具

?Elasticsearch 等平臺:直接連接到現有搜索索引以檢索查詢的相關數據。

?利用現有基礎設施:無需重建已有的內容。

?搜索引擎 vs 向量數據庫:像 Elasticsearch 這樣的傳統搜索引擎已使用多年,在檢索相關信息方面通常優于向量數據庫。

數據庫集成工具

?SQL 生成:使用 LLM 生成 SQL 查詢并直接從數據庫檢索結構化數據的工具。這更復雜,但可行。

?? 未來文章預告:對數據庫集成方法感興趣?請告訴我——我計劃撰寫一篇關于 LLM 到 SQL 工具和技術的專門文章!

得益于 MCP,代理可用的工具數量激增,你可以在這里找到精選列表,也可以使用 Awesome MCP Servers 發現更多。你甚至可以直接從代理連接到遠程服務器。我將在下一篇文章中詳細討論 MCP!

為什么 RAG 替代方案表現更好

更簡單的架構 —— 更少的活動部件

更低的維護成本 —— 利用現有搜索基礎設施

始終新鮮的數據 —— 無需擔心過時嵌入

成本效益高 —— 現代 LLM 使這在經濟上可行

更快開發 —— 基于成熟的搜索技術

?? 核心洞察:當搜索引擎已經有效解決了檢索問題時,不要重新發明輪子。

RAG 替代方案的優勢

搜索優先更便宜,比傳統 RAG 維護成本低得多

??搜索優先始終新鮮—— 無需更新過時索引或重新生成嵌入

?無“迷失中間”問題—— 搜索優先返回最相關內容

?更好的上下文相關性—— 搜索算法優化查詢相關性

?更快迭代—— 文檔更改時無需重新生成嵌入

?更簡單調試 —— 容易查看檢索到的內容及原因

然而,RAG 在以下場景仍有用:

?超大數據集(100GB+)

?細粒度文檔塊訪問控制?離線/隔離環境?超高流量場景(>10萬查詢/天)

?需要復雜關系的文檔

結論:RAG 不應是首選,僅在隱私敏感或數據難以即時訪問的特定情況下考慮。

動手實踐:構建搜索優先的問答代理

好了,理論講夠了!讓我們構建一個能證明搜索優先優于 RAG 的系統。

?? 獲取代碼:關注本公眾號并回復0615 獲取代碼下載地址。

我們要構建什么?

我們將創建一個特定領域的問答代理,它:

?僅搜索批準的組織文檔網站 → 設置護欄(guardrails)

?使用搜索 API 而非向量數據庫

?在需要時回退到全面的網頁抓取

?提供透明的來源歸因

?可選地,總結搜索結果以降低成本和延遲

?成本僅為傳統 RAG 系統的一小部分

將其視為“無檢索的 RAG”——我們用新鮮、全面的搜索結果增強生成,而不是預處理的分塊。

傳統上,當我們受限于 4K 令牌窗口時,開發者會將所有文檔加載到向量數據庫并使用 RAG 提取相關上下文。如前所述,這相當復雜且難以維護。以下是傳統解決方案的樣子:

不需要RAG!在30分鐘內構建一個問答AI智能體-AI.x社區

復雜 RAG 解決方案

這種方法非常復雜,我們的方法則不同:直接使用工具訪問源數據,充分利用已證明其數據提取效果的現有搜索引擎。

不需要RAG!在30分鐘內構建一個問答AI智能體-AI.x社區

更簡單的解決方案

重要說明:此解決方案專門適用于搜索引擎索引的公開網站。如果您處理的是內部文檔或私有數據,我很樂意展示如何使用 Elasticsearch 進行內部上下文檢索,或使用 SQL 即時檢索結構化數據。

特性:為什么這優于傳統 RAG

不需要RAG!在30分鐘內構建一個問答AI智能體-AI.x社區

架構:實際工作原理

不需要RAG!在30分鐘內構建一個問答AI智能體-AI.x社區

1.初始化階段:代理啟動時,讀取包含以下內容的 CSV 文件:?要搜索的網站 URL?領域或類別(主題/學科領域,非網站域名)?幫助 LLM 理解何時使用每個網站的描述

2.查詢處理:當用戶提交查詢時,LLM 分析查詢并智能選擇最可能提供答案的網站。所有查詢均異步處理。

3.搜索執行:選定的網站被傳遞給搜索工具,使用 Tavily 在這些預批準的域名內執行快速、針對性的搜索。

4.動態決策:根據搜索結果,LLM 評估是否足以提供完整答案:?如果是:立即返回響應?如果否:可能調用網頁抓取工具以從特定頁面收集更多細節

此解決方案展示了真正代理化設計的美妙之處——簡單卻異常強大。我們不遵循僵化的預編程路徑,而是讓代理根據每個查詢的特定上下文智能決定采取哪些行動和使用哪些工具。為此,我們使用簡單的非思考模型,遵循著名的 ReACT 框架,讓代理在循環中思考并決定下一步行動。這是使非思考模型“思考”的簡單方法。

我們使用雙層策略

?快速搜索(90% 的查詢):使用 Tavily 在批準的域名內快速搜索并總結結果。

?深度抓取(10% 的查詢):當搜索不足以提供答案時,抓取整個頁面。

ReACT vs 思考模型:為什么我們選擇速度而非“智能”

這是一個有爭議的觀點:我們故意選擇 Gemini 2.0 Flash 而非“思考”模型(如 OpenAI 的 o3 或 Gemini 2.5 Pro)。為什么?

不需要RAG!在30分鐘內構建一個問答AI智能體-AI.x社區

?Gemini Flash 2.0 便宜且快速?ReAct 框架以 1/200 的成本提供結構化思考

我更喜歡使用帶 ReACT 的非思考模型的主要原因是可擴展性。在許多情況下,查詢生成簡單響應,不需要廣泛推理,思考模型顯得過于復雜。非思考模型為這些場景提供快速且成本效益高的解決方案。對于復雜問題,ReACT 通過循環模擬思考模型的推理能力。

ReAct 循環

對于搜索和回答工作流,這比內部鏈式推理(chain-of-thought)更高效。根據我的經驗,當使用多個工具且每個工具有多個輸入時,這種解決方案比使用鏈式推理 LLM 更有效。

代理遵循 ReACT 提示,每個工具的描述詳細說明了輸入和輸出。運行應用程序時,你會看到代理可能通過鏈式思考循環進行多次搜索,或決定使用網頁抓取,所有這些都由代理自行決定,無需人工干預!

ReACT 提示在長上下文窗口中也能有效防止幻覺(hallucinations)!

模型選擇

對于問答代理,gemini-2.0-flash 提供了出色的平衡,具備驚人的速度、成本效益以及輕松處理大上下文窗口的能力。其免費層限制相當寬松,提供每分鐘 15 次請求(RPM)、每分鐘 100 萬令牌(TPM)和每天 1500 次請求(RPD),非常適合廣泛的應用場景,在考慮付費層之前。這是我在倉庫中使用的模型。

當您的問答代理需要更強的推理能力和更“智能”的響應時,gemini-2.5-flash-preview-05-20 是更好的選擇。該模型性能更佳,分析能力更強,適合復雜查詢。然而,其免費層限制更嚴格(例如,10 RPM、25 萬 TPM、500 RPD),且價格略高。如果您有付費賬戶且需要高準確性,請使用此模型。

另一方面,如果您的主要關注點是高吞吐量、成本效益和最大化免費層使用量以處理簡單、高流量的問答,gemini-2.0-flash-lite 是最合適的替代選擇。雖然其復雜推理能力略遜,但其顯著更高的速率限制(例如,30 RPM、100 萬 TPM、1500 RPD)和價格允許更大的交互量。

實現:讓我們看看代碼

現在是激動人心的部分!我們將逐步講解實際實現。即使您對 Python 或 LangChain 不熟悉,我也會一步步解釋。

我們使用 LangChain,因為它提供了統一的、模型無關的接口,簡化了復雜 LLM 應用的開發。它擅長通過“鏈”(Chains)和“代理”(Agents)編排多步驟工作流,使 LLM 能夠使用“工具”(Tools),并輕松管理對話記憶。這種抽象層減少了樣板代碼,加速開發,增強應用靈活性,未來-proof,并通過 LangSmith 等集成提供關鍵的調試功能。

Pydantic AI 是 LangChain 的一個更易用的替代方案。

架構概覽:文件如何協同工作

倉庫遵循簡單的分層架構,非常簡潔:

??項目結構
├── main.py           # ?? FastAPI 網絡服務器(入口)
├── qa_agent.py       # ?? 核心代理邏輯(編排者)
├── search_tool.py    # ?? 搜索功能
├── scraping_tool.py  # ??? 網頁抓取功能
└── sites_data.csv    # ?? 域名配置

流程

?main.py → 接收用戶 HTTP 請求

?qa_agent.py → 決定使用哪些工具并編排響應

?search_tool.py / scraping_tool.py → 執行查找信息的實際工作

?返回 qa_agent.py → 合并結果并生成最終答案

?返回 main.py → 將響應返回給用戶

將其想象成一家餐廳:main.py 是服務員,qa_agent.py 是決定烹飪什么的廚師,工具是專門的廚房設備。

1. 搜索工具(search_tool.py)

讓我們從搜索功能開始。此工具連接到 Tavily 搜索 API 以查找相關信息。

"""
特定領域的Tavily搜索工具
"""
import logging
from typing importList,Optional,Type,Any
from pydantic importBaseModel,Field,ConfigDict
from langchain.tools importBaseTool
from langchain_google_genai importChatGoogleGenerativeAI
from tavily importTavilyClient


logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


classTavilySearchInput(BaseModel):
    query: str =Field(descriptinotallow="包含相關關鍵詞的搜索查詢")
    sites:List[str]=Field(
        descriptinotallow="要搜索的網站域名(例如,['docs.langchain.com'])"
)
    max_results:Optional[int]=Field(
default=None, descriptinotallow="返回的最大結果數"
)
    depth:Optional[str]=Field(
default=None, descriptinotallow="搜索深度:'basic' 或 'advanced'"
)


classTavilyDomainSearchTool(BaseTool):
"""使用 Tavily 搜索特定域名"""


    name: str ="search_documentation"
    description: str ="""使用 Tavily 網絡搜索特定文檔網站。


必需參數:
- query (字符串):包含相關關鍵詞的搜索查詢-你想查找的內容
- sites (列表):要在其中搜索的網站域名(例如,['docs.langchain.com','fastapi.tiangolo.com'])


可選參數:
- max_results (整數):返回的最大搜索結果數(默認:10)
- depth (字符串):搜索深度-'basic'用于快速搜索,'advanced'用于全面搜索(默認:'basic')


使用指南:
1.從用戶問題中創建富含關鍵詞的搜索查詢
2.根據提到的技術選擇相關網站域名
3.使用'basic'深度進行快速回答,'advanced'進行深入研究
4.根據答案的全面性需求調整 max_results


示例:
-快速搜索:query="LangChain 自定義工具", sites=["docs.langchain.com"], depth="basic", max_results=5
-全面搜索:query="FastAPI 認證中間件", sites=["fastapi.tiangolo.com"], depth="advanced", max_results=15


最佳實踐:
-在查詢中包含技術術語和框架名稱
-根據問題上下文選擇合適的域名
-優先選擇官方文檔網站而非第三方來源
-使用具體查詢而非寬泛術語以獲得更好結果
"""
    args_schema:Type[BaseModel]=TavilySearchInput


    tavily_client:Any=Field(default=None, exclude=True)
    api_key: str =Field(exclude=True)
    default_max_results:int=Field(default=10, exclude=True)
    default_depth: str =Field(default="basic", exclude=True)
    max_content_size:int=Field(default=10000, exclude=True)
    enable_summarization:bool=Field(default=False, exclude=True)
    summarizer_llm:Any=Field(default=None, exclude=True)


    model_config =ConfigDict(arbitrary_types_allowed=True)


def __init__(
self,
        api_key: str,
        max_results:int=10,
        depth: str ="basic",
        max_content_size:int=10000,
        enable_summarization:bool=False,
        google_api_key:Optional[str]=None,
):
super().__init__(
            api_key=api_key,
            default_max_results=max_results,
            default_depth=depth,
            max_content_size=max_content_size,
            enable_summarizatinotallow=enable_summarization,
            args_schema=TavilySearchInput,
)


ifnot api_key:
raiseValueError("TAVILY_API_KEY 是必需的")


object.__setattr__(self,"tavily_client",TavilyClient(api_key=api_key))


if enable_summarization and google_api_key:
            summarizer = create_summarizer_llm(google_api_key)
object.__setattr__(self,"summarizer_llm", summarizer)
            logger.info("?? 使用 Gemini Flash-Lite 啟用搜索結果摘要")
elif enable_summarization:
            logger.warning("?? 摘要功能已禁用:未提供 google_api_key")
object.__setattr__(self,"enable_summarization",False)


        logger.info(
            f"Tavily 搜索工具已初始化(摘要功能:{'啟用' if self.enable_summarization else '禁用'})"
)


    async def _search_async(self, query: str, sites:List[str], max_results:int=None, depth: str =None)-> str:
"""異步執行給定參數的搜索"""
try:
            final_max_results = max_results orself.default_max_results
            final_depth = depth orself.default_depth


            logger.info(f"?? 搜索:'{query}' 在網站:{sites}")
            logger.info(
                f"?? 參數:max_results={final_max_results}, depth={final_depth}"
)


# 注意:TavilyClient 尚不支持異步方法,因此在線程中運行
            search_results = await asyncio.to_thread(
self.tavily_client.search,
                query=query,
                max_results=final_max_results,
                search_depth=final_depth,
                include_domains=sites,
)


            logger.info(f"?? 收到 {len(search_results.get('results', []))} 個結果")


ifnot search_results.get("results"):
                logger.warning("?? 未返回搜索結果")
return"未找到結果。請嘗試不同的搜索查詢或檢查域名是否可訪問。"


            formatted_results = format_search_results(
                search_results["results"][:final_max_results],self.max_content_size
)
            final_result ="\n".join(formatted_results)


            logger.info(
                f"? 處理了 {len(search_results['results'])} 個結果,返回 {len(final_result)} 個字符"
)


ifself.enable_summarization andself.summarizer_llm:
try:
                    logger.info("?? 正在摘要結果...")
                    summarized_result = await self._summarize_results_async(final_result, query)
                    reduction = round(
(1- len(summarized_result)/ len(final_result))*100
)
                    logger.info(
                        f"?? 摘要:{len(final_result)} → {len(summarized_result)} 字符(減少 {reduction}%)"
)
return summarized_result
exceptExceptionas e:
                    logger.error(
                        f"? 摘要失敗:{e}。返回原始結果。"
)


return final_result


exceptExceptionas e:
            error_msg = f"? 搜索錯誤:{str(e)}"
            logger.error(error_msg)
return error_msg


    async def _summarize_results_async(self, search_results: str, original_query: str)-> str:
"""使用 LLM 異步摘要搜索結果"""
try:
            prompt = create_summary_prompt(search_results, original_query)
            response = await asyncio.to_thread(self.summarizer_llm.invoke, prompt)
return response.content
exceptExceptionas e:
            logger.error(f"LLM 摘要失敗:{e}")
return search_results


def _run(
self, query: str, sites:List[str], max_results:int=None, depth: str =None
)-> str:
"""執行給定參數的搜索"""
return asyncio.run(self._search_async(query, sites, max_results, depth))


    async def _arun(
self, query: str, sites:List[str], max_results:int=None, depth: str =None
)-> str:
"""異步搜索版本"""
return await self._search_async(query, sites, max_results, depth)

這里發生了什么

?LangChain BaseTool:我們繼承這個類來創建代理可以使用的工具,在下一篇文章中我們會將其轉換為 MCP 服務器。

?工具描述:非常重要,告訴代理何時以及如何使用工具。確保描述精確。

?Pydantic 模型:用于工具輸入的類型驗證——確保代理傳遞正確的參數。

?域名限制:通過 ??include_domains?? 參數實現護欄,確保僅搜索批準的網站。

?智能摘要:可選的 AI 驅動結果壓縮,使用 Gemini Flash-Lite 降低令牌成本。

?異步支持:使用 ??_arun?? 方法支持異步執行。

?錯誤處理:全面的日志記錄和優雅的失敗恢復。

?靈活配置:支持不同的搜索深度和結果限制。

2. 網頁抓取工具(scraping_tool.py):可選

當搜索結果不足時,此工具抓取整個網頁以獲取全面信息。

"""
使用Chromium進行動態內容提取的網頁抓取工具
"""


import logging
import asyncio
from typing importList,Type


from pydantic importBaseModel,Field,ConfigDict
from langchain.tools importBaseTool
from langchain_community.document_loaders importAsyncChromiumLoader
from langchain_community.document_transformers importBeautifulSoupTransformer


logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


def get_default_tags()->List[str]:
"""獲取用于網頁抓取的默認 HTML 標簽"""
return["p","li","div","a","span","h1","h2","h3","h4","h5","h6"]


classWebScrapingInput(BaseModel):
    url: str =Field(descriptinotallow="要抓取的 URL")
    tags_to_extract:List[str]=Field(
        default_factory=get_default_tags, descriptinotallow="要提取的 HTML 標簽"
)


classWebScrapingTool(BaseTool):
"""當搜索結果不足時抓取網站內容"""


    name: str ="scrape_website"
    description: str ="""使用 Chromium 瀏覽器抓取完整網站內容以進行全面頁面提取。


必需參數:
- url (字符串):要抓取的完整 URL(必須包含 https:// 或 http://)


可選參數:
- tags_to_extract (列表):要提取內容的 HTML 標簽
默認:["p","li","div","a","span","h1","h2","h3","h4","h5","h6"]
自定義示例:["pre","code"]用于代碼示例,["table","tr","td"]用于表格


何時使用:
-搜索結果不完整或不足
-需要包括代碼示例的完整頁面內容
-頁面有搜索未捕捉的動態JavaScript內容
-需要搜索未捕捉的特定格式或結構


示例:
-基本抓取:url="https://docs.langchain.com/docs/modules/agents"
-代碼聚焦抓取:url="https://fastapi.tiangolo.com/tutorial/", tags_to_extract=["pre","code","p"]
-表格提取:url="https://docs.python.org/3/library/", tags_to_extract=["table","tr","td","th"]


最佳實踐:
-僅在 search_documentation 提供的信息不足時使用
-優先選擇來自先前搜索結果的 URL 以確保相關性
-使用特定標簽提取以獲得目標內容(處理更快)
-注意:比搜索慢約3-10倍,為性能起見謹慎使用


限制:
-內容會在配置的限制處截斷以防止過多令牌使用
-某些網站可能阻止自動化抓取
-比搜索慢-僅在搜索不足時使用
"""
    args_schema:Type[BaseModel]=WebScrapingInput


    max_content_length:int=Field(default=10000, exclude=True)
    model_config =ConfigDict(arbitrary_types_allowed=True)


def __init__(self, max_content_length:int=10000):
super().__init__(
            max_content_length=max_content_length, args_schema=WebScrapingInput
)


    async def _process_scraping(
self, url: str, tags_to_extract:List[str]=None, is_async:bool=True
)-> str:
"""同步和異步抓取的通用邏輯"""
try:
if tags_to_extract isNone:
                tags_to_extract = get_default_tags()


            loader =AsyncChromiumLoader([url])


if is_async:
                html_docs = await asyncio.to_thread(loader.load)
else:
                html_docs = loader.load()


ifnot html_docs:
return f"無法從 {url} 加載內容"


            bs_transformer =BeautifulSoupTransformer()


if is_async:
                docs_transformed = await asyncio.to_thread(
                    bs_transformer.transform_documents,
                    html_docs,
                    tags_to_extract=tags_to_extract,
)
else:
                docs_transformed = bs_transformer.transform_documents(
                    html_docs,
                    tags_to_extract=tags_to_extract,
)


ifnot docs_transformed:
return f"無法從 {url} 提取內容"


            content = docs_transformed[0].page_content


if len(content)>self.max_content_length:
                content =(
                    content[:self.max_content_length]+"\n\n... (內容已截斷)"
)


return f"""
**抓取的網站:**{url}
**提取的內容:**


{content}


**注意:**完整的網站內容用于全面分析。
"""


exceptExceptionas e:
return f"網頁抓取錯誤 {url}:{str(e)}"


def _run(self, url: str, tags_to_extract:List[str]=None)-> str:
"""抓取網站內容"""
return asyncio.run(
self._process_scraping(url, tags_to_extract, is_async=False)
)


    async def _arun(self, url: str, tags_to_extract:List[str]=None)-> str:
"""異步抓取版本"""
return await self._process_scraping(url, tags_to_extract, is_async=True)

??? 這里發生了什么

?AsyncChromiumLoader:使用真實的 Chromium 瀏覽器處理 JavaScript 密集型網站。

?BeautifulSoupTransformer:從 HTML 中智能提取干凈的文本。

?選擇性標簽提取:僅提取有意義的標簽內容,忽略導航和廣告。

?內容限制:自動截斷長頁面以保持令牌成本合理。注意:這里也可以使用摘要!

?錯誤恢復:優雅處理網絡故障、JavaScript 錯誤和解析問題。

?異步支持:內置異步方法以實現生產可擴展性。

3. 問答代理(qa_agent.py) - 大腦

這里是魔法發生的地方。代理決定使用哪些工具并編排整個對話。

"""
具有特定領域網絡搜索能力的問答代理
"""


import logging
import pandas as pd
from typing importList,Dict,Any,Optional


from langchain.agents importAgentExecutor, create_structured_chat_agent
from langchain_google_genai importChatGoogleGenerativeAI
from langchain.prompts importChatPromptTemplate,MessagesPlaceholder
from langchain.schema importBaseMessage,HumanMessage,AIMessage


from search_tool importTavilyDomainSearchTool
from scraping_tool importWebScrapingTool


logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


def create_system_prompt(knowledge_sources_md: str, domains:List[str])-> str:
"""創建包含知識來源的系統提示"""
return f"""你是一個專門搜索特定文檔網站的問答代理。


可用知識來源按類別/領域/主題劃分,包含每個類別的網站和描述:
{knowledge_sources_md}


指令:
1.對于任何問題,始終首先使用 search_documentation 工具
2.分析用戶問題以確定相關的領域/主題/類別
3.根據提到的技術/主題選擇合適的網站
4.如果搜索結果不足以完全回答問題,則在搜索結果中最相關的 URL 上使用 scrape_website 工具
5.你只能回答關于可用知識來源的問題:{domains}
6.如果問題超出可用知識來源,不要回答問題,并建議可以回答的主題


工具使用策略:
-首先:使用 search_documentation 快速查找相關信息
-其次:如果搜索結果不完整、不清晰或信息不足以回答問題,則在搜索結果中最有希望的 URL 上使用 scrape_website
-為效率起見,始終優先搜索而非抓取,但在搜索結果無相關信息時始終使用抓取


規則:
-保持幫助性和全面性
-盡可能引用來源
-僅在搜索結果無法提供答案時使用抓取
-抓取時,選擇來自先前搜索結果的最相關 URL


你可以使用以下工具:


{{tools}}


通過提供 action 鍵(工具名稱)和 action_input 鍵(工具輸入)使用 JSON blob 指定工具。


有效“action”值:“FinalAnswer”或{{tool_names}}


每次 $JSON_BLOB 僅提供一個動作,如下所示:

{{{{ "action": "$TOOL_NAME", "action_input": "$INPUT" }}}}

遵循以下格式:


問題:要回答的輸入問題
思考:考慮前后的步驟
動作:

$JSON_BLOB

觀察:動作結果
...(重復思考/動作/觀察 N 次)
思考:我知道如何回應
動作:

{{{{ "action": "Final Answer", "action_input": "response" }}}}

開始!提醒:始終以單個動作的有效 JSON blob 響應。如有需要使用工具。如果合適直接響應,如果不明確則要求澄清。格式為動作:```$JSON_BLOB```然后觀察
"""


classDomainQAAgent:
"""根據用戶查詢搜索特定域的問答代理"""


def __init__(
self,
        csv_file_path: str ="sites_data.csv",
        config:Optional[Dict[str,Any]]=None,
):
if config isNone:
raiseValueError("配置是必需的")


self.config = config
self.sites_df = load_sites_data(csv_file_path)
self.llm = create_llm(config)
self.search_tool = create_search_tool(config)
self.scraping_tool = create_scraping_tool(config)
self.chat_history:List[BaseMessage]=[]
self.agent_executor =self._create_agent()


        logger.info(f"代理已初始化,包含 {len(self.sites_df)} 個網站")


def _create_agent(self)->AgentExecutor:
"""創建帶工具和提示的結構化聊天代理"""
        knowledge_sources_md, domains = build_knowledge_sources_text(self.sites_df)
        system_message = create_system_prompt(knowledge_sources_md, domains)


        prompt =ChatPromptTemplate.from_messages(
[
("system", system_message),
MessagesPlaceholder(variable_name="chat_history", optinotallow=True),
(
"human",
"{input}\n\n{agent_scratchpad}(提醒:無論如何都以 JSON blob 響應)"
"\n 重要:調用工具時保持 JSON blob 的相同格式,使用 action/action_input 字段,并在 action_input 字段中傳遞函數參數",
),
]
)


        agent = create_structured_chat_agent(
            llm=self.llm, tools=[self.search_tool,self.scraping_tool], prompt=prompt
)


returnAgentExecutor(
            agent=agent,
            tools=[self.search_tool,self.scraping_tool],
            verbose=True,
            max_iteratinotallow=10,# 限制迭代以防止無限循環
            return_intermediate_steps=True,
            handle_parsing_errors=True,# 優雅處理解析錯誤
)


    async def achat(self, user_input: str)-> str:
"""異步處理用戶輸入"""
try:
            logger.info(f"處理:{user_input}")


            agent_input ={
"input": user_input,
"chat_history":(
self.chat_history[-5:]ifself.chat_history else[]
),# 限制上下文窗口為 5
}


# 異步調用代理執行器
            response = await self.agent_executor.ainvoke(agent_input)
            answer = response.get("output","無法處理您的請求。")


# 更新對話歷史
self.chat_history.extend(
[HumanMessage(cnotallow=user_input),AIMessage(cnotallow=answer)]
)


return answer


exceptExceptionas e:
            error_msg = f"錯誤:{str(e)}"
            logger.error(error_msg)
return error_msg


def reset_memory(self):
"""重置對話記憶"""
self.chat_history.clear()
        logger.info("記憶已重置")

LangChain 使代理代碼非常簡單,大部分代碼是提示,可以在未來提取到單獨文件中。

這里發生了什么

?結構化聊天代理:我們使用比基本 ReAct 代理更可靠的方法來處理復雜工具使用。我發現 LangChain 的 ??create_structured_chat_agent?? 方法 + ReACT 使用非思考模型是需要人類交互的大多數代理的最佳組合。

?提示:我們使用 ReACT 框架加上特定指令和護欄,僅回答特定類別/領域的問題。我們解析 CSV 內容并將其傳遞到系統提示中。

?ChatGoogleGenerativeAI:為 Gemini 優化的包裝器,具有適當的錯誤處理。

?動態提示生成:系統提示根據 CSV 中的可用知識來源動態適應。?MessagesPlaceholder:啟用對話記憶和上下文感知。

?智能工具策略:明確指令何時搜索、何時抓取。

?生產安全性:迭代限制、錯誤處理和全面日志記錄。

?異步支持:為高性能網絡應用而構建。

4. FastAPI 服務器(main.py) - 前門

這創建了一個生產就緒的 Web API,用戶可以通過 HTTP 請求與之交互。

"""
特定領域的問答代理FastAPI應用程序


它從.env 文件讀取環境變量并使用它們初始化問答代理。
它有一個聊天端點,允許你與代理聊天。
"""


import logging
import os
import uuid
from contextlib import asynccontextmanager
from typing importDict,Any


from fastapi importFastAPI,HTTPException,Cookie,Response
from pydantic importBaseModel
import uvicorn
from dotenv import load_dotenv


from qa_agent importDomainQAAgent


load_dotenv()
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


def get_int_env(key: str,default:int)->int:
"""從環境變量解析整數,提供回退"""
try:
returnint(os.getenv(key,default))
exceptValueError:
        logger.warning(f"無效的 {key},使用默認值:{default}")
returndefault


def get_float_env(key: str,default:float)->float:
"""從環境變量解析浮點數,提供回退"""
try:
returnfloat(os.getenv(key,default))
exceptValueError:
        logger.warning(f"無效的 {key},使用默認值:{default}")
returndefault


def validate_api_keys():
"""驗證所需的 API 密鑰是否存在"""
    google_api_key = os.getenv("GOOGLE_API_KEY")
    tavily_api_key = os.getenv("TAVILY_API_KEY")


ifnot google_api_key or google_api_key =="your_google_api_key_here":
raiseValueError("GOOGLE_API_KEY 環境變量是必需的")


ifnot tavily_api_key or tavily_api_key =="your_tavily_api_key_here":
raiseValueError("TAVILY_API_KEY 環境變量是必需的")


return google_api_key, tavily_api_key


def build_config()->Dict[str,Any]:
"""從環境變量構建配置"""
    google_api_key, tavily_api_key = validate_api_keys()


    search_depth = os.getenv("SEARCH_DEPTH","basic")
if search_depth notin["basic","advanced"]:
        logger.warning(f"無效的 SEARCH_DEPTH '{search_depth}',使用默認值:basic")
        search_depth ="basic"


return{
"google_api_key": google_api_key,
"tavily_api_key": tavily_api_key,
"max_results": get_int_env("MAX_RESULTS",10),
"search_depth": search_depth,
"max_content_size": get_int_env("MAX_CONTENT_SIZE",10000),
"max_scrape_length": get_int_env("MAX_SCRAPE_LENGTH",10000),
"enable_search_summarization": os.getenv(
"ENABLE_SEARCH_SUMMARIZATION","false"
).lower()
=="true",
"llm_temperature": get_float_env("LLM_TEMPERATURE",0.1),
"llm_max_tokens": get_int_env("LLM_MAX_TOKENS",3000),
"request_timeout": get_int_env("REQUEST_TIMEOUT",30),
"llm_timeout": get_int_env("LLM_TIMEOUT",60),
}


def log_config(config:Dict[str,Any]):
"""美化打印配置(排除 API 密鑰)"""
    safe_config ={k: v for k, v in config.items()ifnot k.endswith("_api_key")}
    logger.info("配置已加載:")
for key, value in safe_config.items():
        logger.info(f"  {key}: {value}")


def create_config()->Dict[str,Any]:
"""創建并驗證完整配置"""
try:
        config = build_config()
        log_config(config)
        logger.info("環境驗證完成")
return config
exceptExceptionas e:
        logger.error(f"環境驗證失敗:{str(e)}")
raise


@asynccontextmanager
async def lifespan(app:FastAPI):
"""初始化和清理問答代理"""
try:
        logger.info("初始化問答代理...")
        config = create_config()
# 初始化空的會話存儲而不是單個代理
        app.state.user_sessions ={}
        app.state.config = config
        logger.info("會話存儲初始化成功")
exceptExceptionas e:
        logger.error(f"無法初始化會話存儲:{str(e)}")
raise


yield


    logger.info("關閉會話存儲...")
# 清理所有代理實例
if hasattr(app.state,'user_sessions'):
for session_id in list(app.state.user_sessions.keys()):
            logger.info(f"清理會話 {session_id}")
        app.state.user_sessions.clear()


app =FastAPI(
    title="特定領域問答代理 API",
    descriptinotallow="使用 Tavily 和 LangChain 搜索特定域的問答代理",
    versinotallow="1.0.0",
    lifespan=lifespan,
)


def get_or_create_agent(session_id: str)->DomainQAAgent:
"""獲取現有代理實例或為會話創建新實例"""
ifnot hasattr(app.state,"user_sessions"):
raiseHTTPException(status_code=500, detail="會話存儲未初始化")


if session_id notin app.state.user_sessions:
        logger.info(f"為會話 {session_id} 創建新代理實例")
        app.state.user_sessions[session_id]=DomainQAAgent(cnotallow=app.state.config)


return app.state.user_sessions[session_id]


classChatRequest(BaseModel):
    message: str
    reset_memory:bool=False


classChatResponse(BaseModel):
    response: str
    status: str ="success"
    session_id: str


@app.get("/health")
async def health_check():
"""帶會話存儲狀態的健康檢查"""
return{
"message":"特定領域問答代理 API 正在運行",
"status":"healthy",
"version":"1.0.0",
"active_sessions": len(app.state.user_sessions)if hasattr(app.state,"user_sessions")else0,
}


@app.post("/chat", response_model=ChatResponse, summary="與問答代理聊天")
async def chat(
    request:ChatRequest,
    response:Response,
    session_id: str =Cookie(None)
):
"""通過問答代理處理用戶問題"""
# 如果不存在會話 ID,則生成新的
ifnot session_id:
        session_id = str(uuid.uuid4())
        response.set_cookie(
            key="session_id",
            value=session_id,
            httpnotallow=True,
            secure=True,
            samesite="lax",
            max_age=3600# 1 小時會話
)


    logger.info(f"處理會話 {session_id} 的聊天請求")


# 獲取或為此會話創建代理實例
    agent = get_or_create_agent(session_id)


if request.reset_memory:
        agent.reset_memory()
        logger.info(f"會話 {session_id} 請求重置記憶")


    response_text = await agent.achat(request.message)
    logger.info(f"成功處理會話 {session_id} 的聊天請求")


returnChatResponse(
        respnotallow=response_text,
        status="success",
        session_id=session_id
)


@app.post("/reset", summary="重置對話記憶")
async def reset_memory(session_id: str =Cookie(None)):
"""重置當前會話的對話記憶"""
ifnot session_id:
raiseHTTPException(status_code=400, detail="無活躍會話")


    agent = get_or_create_agent(session_id)
    agent.reset_memory()
    logger.info(f"通過端點為會話 {session_id} 重置記憶")
return{"message":"對話記憶已重置","status":"success"}


if __name__ =="__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True, log_level="info")

這里發生了什么

?FastAPI:現代 Python Web 框架,帶自動 OpenAPI 文檔。

?Pydantic 模型:類型安全的請求/響應驗證和序列化。

?依賴注入:干凈的架構,適當的錯誤處理。

?生命周期管理:代理在啟動時初始化并在請求間持續存在。

?環境驗證:所有配置參數的全面驗證。

?Cookie 支持:API 使用安全的 HTTP Cookie 為每個用戶維護單獨的對話記憶。首次請求時,會自動生成唯一的會話 ID(UUID)并存儲在安全 Cookie 中。每個會話 ID 創建自己的代理實例,擁有隔離的記憶,因此您的對話歷史不會與其他用戶混淆——即使他們同時使用 API。

?生產功能:健康檢查、適當的日志記錄、錯誤處理和監控。

?異步支持:完全異步架構以實現高負載下的性能。

整體工作流程:完整流程

以下是用戶提問時發生的事情:

1.HTTP 請求 → main.py 接收到 POST 到 /chat 的消息

2.驗證 → Pydantic 驗證請求格式和代理可用性

3.代理處理 → qa_agent.py 接收問題及對話歷史

4.結構化思考 → 代理使用系統提示分析問題并決定策略

5.工具選擇 → 通常首先使用 search_tool.py 獲取快速結果

6.域名限制搜索 → 通過 Tavily API 僅搜索批準的域名

7.結果評估 → 代理分析搜索質量并決定是否足夠

8.可選抓取 → 如有需要,在最相關的 URL 上使用 scraping_tool.py

9.響應合成 → 代理將所有信息整合成連貫的答案

10.記憶更新 → 更新對話歷史以便在未來問題中提供上下文

11.HTTP 響應 → 格式化的響應發送給客戶端

這種架構展示了非思考模型如 Gemini Flash 如何在結構化任務中超越昂貴的“思考”模型。ReAct 框架以極低的成本提供系統化推理,而基于工具的架構確保可靠、可追溯的結果。非常適合使用公共網站的生產問答系統!??

這如何擴展?

你可以不斷添加更多工具:SQL 生成與執行、Elasticsearch、S3 訪問等等,可能性無窮無盡,只需構建更多工具并通過 MCP 暴露它們!但首先檢查 MCP 倉庫,很可能已經有人構建了集成。

核心思想是直接訪問數據,而不是擁有復雜的數據流水線,LLM 可以很好地處理噪聲或不干凈的數據!

讓我們玩吧!

是時候看到代理的實際行動了!以下是如何運行它:

設置知識來源

要配置代理可以搜索的網站,你需要編輯 sites_data.csv 文件。此 CSV 包含三列,告訴代理有哪些資源可用:

CSV 結構

?列 1(領域/類別):主題領域或話題(例如,“python”、“web-development”、“machine-learning”)

?列 2(網站 URL):實際網站域名(例如,“??python.langchain.com???”、“??docs.python.org??”)

?列 3(描述):清楚說明網站包含的內容及何時使用

示例條目

python,python.langchain.com,官方LangChainPython文檔,包括指南、教程和構建 LLM 應用的 API 參考
web-development,developer.mozilla.org,涵蓋 HTML、CSS、JavaScript和Web API 的全面Web開發文檔

專業提示:描述至關重要——代理使用它來決定某個網站是否對回答用戶問題有幫助。明確說明每個網站涵蓋的主題和信息類型。

你可以根據需要添加任意數量的網站。代理將根據用戶查詢和你的描述智能選擇要搜索的網站。

獲取憑據

你需要前往 Tavily 和 Google 獲取 API 密鑰。

獲取 Tavily API 密鑰

訪問 tavily.com[1] 并注冊免費賬戶。登錄后,導航到儀表板或 API 部分,你會找到你的 API 密鑰。Tavily 通常提供慷慨的免費層,包含每月數千次搜索,足以用于測試和小項目。

獲取 Gemini API 密鑰

訪問 ai.google.dev[2](Google AI Studio)并使用 Google 賬戶登錄。在界面中,尋找“獲取 API 密鑰”按鈕或導航到 API 密鑰部分。如有需要,創建新項目,然后生成 API 密鑰。Google 的 Gemini API 也提供大量免費層請求,足以用于開發和小型生產使用。

獲得兩個密鑰后,你需要將它們添加到環境變量或配置文件中。大多數項目使用 .env 文件,你可以在其中添加:

TAVILY_API_KEY=your_tavily_key_here
GEMINI_API_KEY=your_gemini_key_here

確保這些密鑰安全,切勿提交到公共倉庫。兩項服務都提供出色的免費層,因此你可以無前期成本開始構建和測試。免費配額通常足以用于開發和小型生產用途。

現在,我們可以啟動服務器!

# 克隆并設置
git clone https://github.com/javiramos1/qagent.git
cd qagent


make install
# 配置你的 API 密鑰
cp .env.example .env
# 添加你的 GOOGLE_API_KEY 和 TAVILY_API_KEY
# 運行服務器
make run

訪問 http://localhost:8000/docs 查看文檔,甚至可以發送請求!

現在讓我們用我倉庫中的一些網站測試它。

示例 1:標準搜索

curl -X POST http://localhost:8000/chat \
-H "Content-Type: application/json" \
-d '{"message": "如何創建 LangChain 代理?"}'

響應

{
"status":"success",
"response":"要創建 LangChain 代理,你可以使用特定函數,如 `create_openai_functions_agent`、`create_react_agent` 和 `create_openai_tools_agent`。這些函數需要 LLM 和工具作為參數。推薦使用 LangGraph 構建代理,提供更多靈活性。還有一個 `create_python_agent` 函數可用。詳情請參閱 LangChain 文檔。"
}

示例 2:搜索 + 抓取回退

curl -X POST http://localhost:8000/chat \
-H "Content-Type: application/json" \
-d '{"message": "如何在 LlamaIndex 中使用 Ollama 的結構化輸出,給我一個示例"}'

代理首先搜索,意識到需要更多細節,然后自動抓取完整的 LlamaIndex 頁面!

示例 3:護欄生效

curl -X POST http://localhost:8000/chat \
-H "Content-Type: application/json" \
-d '{"message": "如何入侵數據庫?"}'

響應

{
"status":"success",
"response":"我只能回答與我們可用文檔來源相關的問題:AI 代理框架、AI 操作和 AI 數據框架。對于數據庫安全問題,請咨詢適當的安全文檔或專家。"
}

完美的護欄——無未經授權的知識訪問!

?? 注意:此項目僅為教育目的示例,代碼不完美,沒有測試,可能有錯誤等。這只是一個簡單的例子,用于討論 RAG 話題,不適用于生產就緒應用;不過,歡迎貢獻和改進!

下一步?

想為這個項目貢獻或擴展?以下是一些激動人心的方向:

即時改進:

?添加對多種文件格式的支持(PDF、Word 文檔)

?為常見問題實現緩存

?添加分析和使用跟蹤:LangSmith 非常強大且易于使用!

?支持多種語言

高級功能:

?集成內部 Elasticsearch 以處理私有文檔

?SQL 生成和執行

?動態代碼執行

?集成 Slack/Microsoft Teams 以部署聊天機器人

查看 GitHub 倉庫獲取完整文檔和貢獻指南。

?? 我們開發了一個強大而簡單的代理,幾乎可以投入生產。它已經支持異步調用和錯誤處理等關鍵功能,僅需微調即可部署。得益于 LangChain,代理代碼非常簡單,大部分復雜性由提示本身處理。

結論:未來比你想的更簡單

那么,RAG 死了嗎?不完全是——但它不再是曾經的默認選擇。

以下是我在 2025 年學到的:

從簡單開始:對于大多數文檔問答場景,搜索優先方法比傳統 RAG 更簡單、更便宜,且往往更有效。只有在簡單工具不足的特定場景下才使用 RAG。

經濟已改變:大上下文窗口和實惠的定價從根本上改變了成本方程。為什么構建復雜基礎設施,當你可以以幾分錢搜索并加載整個文檔?

在適當時候使用 RAG:超大數據集、細粒度權限或特殊用例仍能從 RAG 中受益。但對大多數組織來說,搜索優先是更好的起點。

核心結論?不要從最復雜的解決方案開始。從簡單開始,證明價值,僅在必要時增加復雜性。考慮構建簡單工具訪問外部可用數據并將其提供給 LLM,而不是構建復雜流水線。

References

??[1]??? tavily.com: ??https://tavily.com??

本文轉載自??PyTorch研習社??,作者:PyTorch研習社

已于2025-6-19 09:45:47修改
收藏
回復
舉報
回復
相關推薦
主站蜘蛛池模板: 免费黄色在线 | 午夜在线精品 | 精品久久精品 | 黑人巨大精品欧美一区二区免费 | 超碰综合 | 999免费视频 | 丁香婷婷综合激情五月色 | 国产精品欧美精品日韩精品 | 成人av一区 | 91最新在线视频 | 亚洲综合在线播放 | 日韩视频精品在线 | 日韩精品极品视频在线观看免费 | 黄色一级大片在线免费看产 | 久久国产精品久久久久久久久久 | 国产精品久久久久久久久久免费看 | 成人av在线大片 | 青娱乐国产 | 日韩一区中文字幕 | 国产视频一区二区 | 午夜亚洲 | 精品久久香蕉国产线看观看亚洲 | 91精品国产一区二区在线观看 | 欧美精品中文字幕久久二区 | www.久草| 国产精选一区 | 一区二区三区视频在线观看 | 一级在线| 99爱视频 | 欧美精品一区二区免费视频 | 免费的av网站 | 自拍 亚洲 欧美 老师 丝袜 | www.色.com| 久久这里只有精品首页 | 国产成人精品久久二区二区91 | 91欧美 | 国产美女黄色片 | 日本精品免费 | 视频在线一区二区 | 男女视频在线观看 | 亚洲国产激情 |