Files
2026-07-13 12:32:03 +08:00

317 lines
11 KiB
Python

"""
Agentic RAG MVP 示例
实现一个最小可用的 Agentic RAG 系统,演示如何通过工具组合实现"先粗后细"的证据收集策略
"""
from typing import List, Dict
import json
from dataclasses import dataclass
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
@dataclass
class FileChunk:
"""文件片段"""
file_id: int
chunk_index: int
content: str
@dataclass
class FileInfo:
"""文件信息"""
id: int
filename: str
chunk_count: int
status: str = "done"
class MockKnowledgeBaseController:
"""模拟知识库控制器 - 内存版本,用于演示"""
def __init__(self):
# 模拟一些文档数据
self.files = [
FileInfo(1, "rag_introduction.md", 5),
FileInfo(2, "llm_fundamentals.md", 4),
FileInfo(3, "vector_search.md", 3),
FileInfo(4, "prompt_engineering.md", 4),
]
# 模拟文档内容片段
self.chunks = {
(1, 0): FileChunk(
1,
0,
"RAG (Retrieval-Augmented Generation) 是一种结合检索和生成的技术,通过从外部知识源检索相关信息来增强大语言模型的生成能力。",
),
(1, 1): FileChunk(
1,
1,
"RAG 的优点包括:1) 能够访问最新信息,2) 减少模型幻觉,3) 提供可追溯的信息来源,4) 无需重新训练模型即可更新知识。",
),
(1, 2): FileChunk(
1,
2,
"RAG 的缺点包括:1) 检索质量直接影响生成效果,2) 增加了系统复杂度,3) 对向量数据库的依赖,4) 可能存在检索延迟。",
),
(1, 3): FileChunk(
1,
3,
"传统 RAG 系统通常采用固定的检索-生成流程,无法根据问题复杂度动态调整策略。",
),
(1, 4): FileChunk(
1,
4,
"Agentic RAG 通过引入智能体,使系统能够自主决策何时检索、如何检索以及检索多少内容,从而提升复杂问题的处理能力。",
),
(2, 0): FileChunk(
2,
0,
"大语言模型 (LLM) 是基于 Transformer 架构的深度学习模型,通过预训练学习语言的统计规律。",
),
(2, 1): FileChunk(
2, 1, "LLM 的核心能力包括自然语言理解、生成、推理和少样本学习等。"
),
(2, 2): FileChunk(
2, 2, "LLM 的局限性包括知识截止时间、可能产生幻觉、计算资源消耗大等。"
),
(2, 3): FileChunk(
2,
3,
"工具调用是 LLM 的重要扩展能力,使模型能够与外部系统交互,执行复杂任务。",
),
(3, 0): FileChunk(
3,
0,
"向量搜索是 RAG 系统的核心组件,通过将文本转换为向量表示来实现语义相似度匹配。",
),
(3, 1): FileChunk(
3,
1,
"常见的向量搜索算法包括 FAISS、Chroma、Pinecone 等,各有不同的性能特点。",
),
(3, 2): FileChunk(
3,
2,
"向量搜索的效果很大程度上依赖于embedding模型的质量和索引构建策略。",
),
(4, 0): FileChunk(
4,
0,
"提示工程是优化大模型表现的重要技术,包括设计有效的提示模板、上下文管理等。",
),
(4, 1): FileChunk(
4, 1, "良好的提示设计原则包括:清晰明确、提供示例、结构化输出格式等。"
),
(4, 2): FileChunk(
4, 2, "Agent 系统的提示设计需要考虑工具调用的策略指导和错误处理机制。"
),
(4, 3): FileChunk(
4, 3, "系统提示词应该明确定义 Agent 的角色、能力边界和行为规范。"
),
}
def search(self, kb_id: int, query: str) -> List[Dict]:
"""模拟语义搜索 - 基于关键词匹配"""
query_lower = query.lower()
results = []
for (file_id, chunk_idx), chunk in self.chunks.items():
content_lower = chunk.content.lower()
# 简单的关键词匹配评分
score = 0
keywords = [
"rag",
"agentic",
"优缺点",
"优点",
"缺点",
"llm",
"检索",
"生成",
"向量",
"搜索",
]
for keyword in keywords:
if keyword in query_lower and keyword in content_lower:
score += 1
if score > 0 or any(word in content_lower for word in query_lower.split()):
file_info = next(f for f in self.files if f.id == file_id)
results.append(
{
"file_id": file_id,
"chunk_index": chunk_idx,
"filename": file_info.filename,
"score": score + 0.5, # 基础分
"preview": chunk.content[:100] + "..."
if len(chunk.content) > 100
else chunk.content,
}
)
# 按分数排序并返回前5个
results.sort(key=lambda x: x["score"], reverse=True)
return results[:5]
def getFilesMeta(self, kb_id: int, file_ids: List[int]) -> List[Dict]:
"""获取文件元信息"""
result = []
for file_id in file_ids:
file_info = next((f for f in self.files if f.id == file_id), None)
if file_info:
result.append(
{
"id": file_info.id,
"filename": file_info.filename,
"chunk_count": file_info.chunk_count,
"status": file_info.status,
}
)
return result
def readFileChunks(self, kb_id: int, chunks: List[Dict[str, int]]) -> List[Dict]:
"""读取具体的文件片段"""
result = []
for chunk_spec in chunks:
file_id = chunk_spec.get("fileId")
chunk_index = chunk_spec.get("chunkIndex")
chunk = self.chunks.get((file_id, chunk_index))
if chunk:
result.append(
{
"file_id": file_id,
"chunk_index": chunk_index,
"content": chunk.content,
"filename": next(
f.filename for f in self.files if f.id == file_id
),
}
)
return result
def listFilesPaginated(self, kb_id: int, page: int, page_size: int) -> List[Dict]:
"""分页列出文件"""
start = page * page_size
end = start + page_size
files_slice = self.files[start:end]
return [
{
"id": f.id,
"filename": f.filename,
"chunk_count": f.chunk_count,
"status": f.status,
}
for f in files_slice
]
# 初始化模拟的知识库控制器
kb_controller = MockKnowledgeBaseController()
knowledge_base_id = 1 # 模拟的知识库ID
# 定义四个核心工具
@tool("query_knowledge_base")
def query_knowledge_base(query: str) -> str:
"""Query a knowledge base with semantic search"""
results = kb_controller.search(knowledge_base_id, query)
return json.dumps(results, ensure_ascii=False, indent=2)
@tool("get_files_meta")
def get_files_meta(fileIds: List[int]) -> str:
"""Get metadata for files in the current knowledge base."""
if not fileIds:
return "请提供文件ID数组"
results = kb_controller.getFilesMeta(knowledge_base_id, fileIds)
return json.dumps(results, ensure_ascii=False, indent=2)
@tool("read_file_chunks")
def read_file_chunks(chunks: List[Dict[str, int]]) -> str:
"""Read content chunks from specified files in the current knowledge base."""
if not chunks:
return "请提供要读取的chunk信息数组"
results = kb_controller.readFileChunks(knowledge_base_id, chunks)
return json.dumps(results, ensure_ascii=False, indent=2)
@tool("list_files")
def list_files(page: int = 0, pageSize: int = 10) -> str:
"""List all files in the current knowledge base. Returns file ID, filename, and chunk count."""
results = kb_controller.listFilesPaginated(knowledge_base_id, page, pageSize)
return json.dumps(results, ensure_ascii=False, indent=2)
def create_agentic_rag_system():
"""创建 Agentic RAG 系统"""
# 工具清单
tools = [query_knowledge_base, get_files_meta, read_file_chunks, list_files]
# 行为策略(系统提示)
SYSTEM_PROMPT = """你是一个 Agentic RAG 助手。请遵循以下策略逐步收集证据后回答:
1. 先用 query_knowledge_base 搜索相关内容,获得候选文件和片段线索
2. 根据搜索结果,选择最相关的文件,可选择性使用 get_files_meta 查看详细文件信息
3. 使用 read_file_chunks 精读最相关的2-3个片段内容作为证据
4. 基于读取的具体片段内容组织答案
5. 回答末尾用"引用:"格式列出实际读取的fileId和chunkIndex
重要原则:
- 不要编造信息,只基于实际读取的片段内容回答
- 若证据不足,请说明并建议进一步搜索的方向
- 优先选择评分高的搜索结果进行深入阅读
"""
# 模型与 Agent
llm = ChatOpenAI(
# model="gpt-3.5-turbo", # 使用 OpenAI 默认模型便于测试
temperature=0,
max_retries=3,
# 如需使用其他API,可配置 base_url
base_url="https://api.siliconflow.cn/v1",
model="THUDM/glm-4-9b-chat",
)
agent = create_react_agent(llm, tools, prompt=SYSTEM_PROMPT)
return agent
def main():
"""主函数 - 演示 Agentic RAG 的工作流程"""
print("🚀 初始化 Agentic RAG 系统...")
agent = create_agentic_rag_system()
print("\n📚 模拟知识库包含以下文件:")
for file in kb_controller.files:
print(f" - {file.filename} ({file.chunk_count} chunks)")
print("\n" + "=" * 80)
print("💬 开始问答演示")
print("=" * 80)
# 测试问题
question = "请基于知识库,概述 RAG 的优缺点,并给出引用。"
print(f"\n❓ 问题: {question}")
print("\n🤔 Agent 思考与行动过程:")
print("-" * 50)
# 调用 Agent
result = agent.invoke({"messages": [("user", question)]})
print("======")
final_answer = result["messages"][-1].content
print(result)
if __name__ == "__main__":
main()