Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

LLAgent

面向长篇中文小说(~500 万字)的检索增强问答工具。包含四个部分:多跳检索 Agent(loop)、单轮 RAG 流水线(fast)、前后端 Web 产品(system)、评测体系(evaluate)。

解决的问题很直接:模型单靠训练数据回答长文本里的问题,会记错、会编造;全文又超出上下文窗口。方案是检索增强加多步推理——先检索再回答,复杂问题拆成子问题逐个查证,把确认的结论沉淀成笔记供后续使用。

tech/
├── loop/      多跳检索 Agent(CLI,核心算法)
├── fast/      单轮 Top-K RAG 流水线(速度优先)
├── system/    前后端 Web 产品(复用 loop,生产部署栈)
└── evaluate/  评测体系(消融实验 + AnonyRAG 基准)

架构

算法层只有两条线:

LLM 工具调用 + 检索工具(BM25 / 向量 / 章节 / 窗口 / 笔记)
   ├── loop:多跳 Agent —— 规划 → 检索 → 综合,25 轮上限,笔记跨会话记忆
   └── fast:单轮 Top-K —— 问题拆分 → 双路召回 → RRF 融合 → 重排 → 受证据生成
本地向量检索:BAAI/bge-base-zh-v1.5(FAISS)+ rank-bm25(jieba)+ bge-reranker

Web 版(system)在上面叠加了产品层:

浏览器 → nginx(:80) → Django(WSGI 控制面: auth/会话/文件)
                     → SSE 网关(FastAPI/uvicorn: Redis Streams 事件流)
    → PostgreSQL / Redis / MinIO / Prometheus
Celery worker(宿主机,需 CUDA embedding)异步执行 loop 多跳问答

评测层(evaluate)用消融实验量化 loop 各特性的贡献,另外跑了 AnonyRAG 基准:

消融实验:C0 完整 Loop vs C1 无向量 vs C2 无规划 vs C3 单次检索 vs C4 单轮 RAG
   → 34 题,LLM 事实覆盖判分 → results/report.md 失败案例分析
AnonyRAG:匿名实体推理基准 → 验证检索-推理还原真实身份的能力

loop:多跳检索 Agent

核心设计是防幻觉加多跳推理:模型必须先检索再回答,多要素问题拆成子问题逐个逼近,每步结论沉淀为笔记,下次提问按语义召回相关笔记注入上下文。

特性 说明
强制检索铁律 System Prompt 明确"即使认为知道答案也必须用工具查证",对抗训练数据幻觉
规划引导 用户问题后注入"先规划再检索",多跳问题第一轮就给出方案而不是乱试
多步分解 多要素问题(人物/时间/事件/次数)先拆子问题逐个检索再综合,工具上限 25 轮
检索后汇报发现 每步检索后用一两句话说明查到什么,中间结论可见,留在上下文供后续推理
进展即记笔记 子问题查清立即 take_notes 沉淀;跨会话按问题向量召回相关笔记(最多 10 条)
Debug 状态机 3 个暂停点(请求工具 / 工具执行完 / 返回文本),Enter 继续 / R 重试 / Esc 退出,全局开关一键关闭
retry 快照回退 暂停点 R 回到本轮开始前快照重演,失败不污染上下文
审查模块 二次独立 LLM 核对回答事实(Cited/Unsupported),当前待用(ENABLE_REVIEW

工具集:

工具 作用
bm25_search(keyword, top_k) 关键词定位(jieba + BM25),返回临时表
vector_search(description, top_k) 语义定位(bge 向量 + FAISS)
get_chapter(chapter_number) 取整章全部块
window_search(table_id, center, before, after) 在检索结果表上展开前后窗口
take_notes(notes) 沉淀笔记到 SQLite,跨会话记忆

fast:单轮 Top-K RAG 流水线

无 agent 循环,速度更快,实现更简。

  • 按"第 N 章"切分文本,建 FAISS 向量索引 + BM25 关键词索引(索引可复用)。
  • 历史问答拆分问题(支持指代/省略/复合),双路召回后 RRF 融合,bge-reranker 重排。
  • 第一轮直接检索并审查,失败才进入 HyDE / 查询改写 / 增强检索。
  • LLM 用 DeepSeek 官方 deepseek-v4-flash,Embedding 与 reranker 强制 CUDA。

system:Web 产品与生产部署栈

前后端分离的 Web 版,复用 loop 的多跳问答能力,加用户体系、文件检索与流式交互。提供本地生产部署栈(Docker Compose 一键起全套)。

技术栈:

  • 后端:Django 5.2 + DRF + JWT(simplejwt),PostgreSQL + Redis + MinIO(Docker),Celery 异步任务
  • SSE 网关:独立 FastAPI 服务(system/gateway/),从 Redis Streams 按游标推送事件
  • 入口:nginx(唯一对外入口 :80),SSE 路由关闭缓冲、调大超时
  • 前端:Vite + React 19 + TypeScript,react-markdown 渲染回答

两种交互模式:

模式 触发条件 行为
普通 AI 对话(chat) 会话未绑定文件 无工具、无检索,直接 LLM 对话
文件检索问答(rag) 会话绑定文件 Loop 多跳 + 检索绑定文件的索引回答

后端能力:

能力 说明
用户认证 注册 / 登录 / me / 登出,JWT Bearer token
API Key 加密存储 OpenRouter key 以 AES-GCM 密文存 UserProfile,前端只见掩码,运行时解密、不随请求传明文
会话与消息 多会话,Loop 问答完成后自动写入会话(刷新后可恢复)
会话绑定文件 ChatSession.files ManyToMany,PATCH /api/sessions/{id} 绑定(仅当前用户已解析完成的文件),create_run 按绑定文件分流 rag/chat 模式
文件自动建索引 上传后 Celery 异步建索引(BM25 + FAISS),进度经 Redis 推送,not_started → indexing → ready/error,每文件隔离索引目录
Loop 问答 API POST /api/runs 创建任务 → Celery 异步执行 loop 多跳引擎
SSE 流式 独立 FastAPI 网关 GET /api/runs/{id}/stream;Redis Streams 事件总线;id: 字段让浏览器断线自动带 Last-Event-ID 续传(不重放)

前端能力:

能力 说明
登录 / 注册 JWT 持久化到 localStorage,刷新自动恢复会话
多会话侧边栏 新建 / 切换 / 重命名 / 删除
文件管理 上传 txt(自动解析,进度轮询)、预览(增量加载)、重命名、删除
绑定文件 输入框左侧 + 按钮 → 菜单勾选已解析文件;绑定文件显示在输入框上方
Loop 对话 提问 → SSE 流式回答;工具阶段以 > 引用块显示,最终答案 markdown 渲染
API Key 设置 掩码显示已存 key,支持替换 / 清空
主题 浅色 / 深色切换

evaluate:评测体系

消融实验

量化 loop 各特性的贡献,以及 loop 与普通 RAG 的差距。

配置 隔离变量
C0_full 完整 Loop(规划 + 5 工具 + 笔记)
C1_no_vector 禁用 vector_search
C2_no_plan 不注入规划引导
C3_one_search 只允许一次定位检索
C4_fast 普通单轮 Top-K RAG

结果(34 题,答案覆盖 reference 全部核心事实点即通过):

难度 C0 完整 C1 无vector C2 无规划 C3 单次检索 C4 fast
简单 (20) 100% 100% 95% 95% 55%
普通 (10) 90% 100% 80% 50% 40%
困难 (4) 100% 25% 50% 50% 25%

几条结论:

  1. 多跳 Agent 明显强于单轮 RAG:简单 95–100% vs 55%,普通 80–100% vs 40%,困难 50–100% vs 25%。
  2. 多跳检索对普通题很关键:单次检索(C3)普通题掉到 50%。
  3. vector_search 的价值主要在困难题:完整 Loop 100% vs 25%,语义检索帮模型拼齐散落的多事实证据。

AnonyRAG

匿名实体推理基准,验证模型在给定匿名实体 ID 时能否还原真实身份。数据/索引/测试集在 evaluate/anonyrag_data/,分标准档(anonyrag_testset.json)和最难档(anonyrag_hard50.json,实体数最多的 50 题)。复用 system 的 run_loop_query 无头调用,用实体映射精确匹配打分(golden「实体ID=名字」)。

hard50 结果:

指标 结果
整题通过率 18/50 = 36%
实体级命中率 328/429 = 76%
未通过题分布 部分命中 28 题 / 完全答错 4 题

通过率不高,但拆开看,问题不在"不会",在完整性:多数未通过题是答对了大部分实体、漏了一两个,而 Anonymity Reversion 要求全部命中才判通过。实体级 76% 对整题 36% 的差距说明瓶颈在枚举的收尾,和消融实验里"枚举/聚合是已知边界"的结论一致。标准档结果见 anonyrag_data/anonyrag_results.json,hard50 评分版见 anonyrag_hard50_scored.json


高并发改造

在 system 基础上做的生产化改造,不推翻原有选型,只在边界补健壮性。核心矛盾:LLM 调用是瓶颈,Web 层要做到不占线程、不雪崩、可横向扩展。

能力 实现 解决什么
SSE 异步网关 独立 FastAPI 服务(system/gateway/)+ Redis Streams 事件流 长连接不占 WSGI 线程;Streams 游标 + Last-Event-ID 断线续传,修掉重连重放 bug
Celery worker 并行化 多 solo worker 进程(LLAGENT_WORKERS Windows 无 prefork,多进程实现并行;并发问答从 1 → N
任务健壮性 acks_late=True, max_retries=3 + 幂等(run 已 COMPLETED 跳过、消息去重) 任务失败自动重试不丢回答
每用户并发上限 Redis 原子 INCR/DECR 计数,create_run 超限返回 429 单用户连发 N 个问答不打爆上游 LLM
队列限流 broker 积压 ≥ 阈值返回 503 突发流量被挡在入口
Redis 职责分离 缓存 / broker / 事件流分 DB(生产规划) 高并发下三者互不干扰
DB 连接池 + 分区 CONN_MAX_AGE / PgBouncer + 按月分区(生产规划) 防连接堆积、表膨胀
nginx 统一入口 静态 + 路由 + SSE 缓冲关 + 超时调大 连接承载、负载均衡、TLS

几个实现细节(完整方案见 .docs/高并发改造方案.md):

  • 事件流用 Redis Streams:tasks 写 XADD,网关 XRANGE 按游标轮询(xread(block=) 在本环境会挂起,弃用)。
  • SSE 断线续传:网关每条事件带 id:,浏览器原生 EventSource 重连自动带 Last-Event-ID 从断点继续,不重放、不漏。
  • 限流完整生命周期:acquire → 超限回滚 429 → worker 完成释放 → 计数归零。

压测

逐环节压测生产栈,全程不调 GPU、不调真实 API key(全链路用本地 mock LLM + 假 key)。详见 system/loadtest/README.md

脚本 压什么 方式
loadtest.py --nginx nginx 静态/health 吞吐 Python 并发 httpx
loadtest.py --crud Django CRUD(登录/会话/文件) 并发 httpx + mock 账号 admin
loadtest.py --sse SSE 网关并发长连接承载 asyncio + 预置假事件流
fullchain.py 全链路(nginx→Django→Celery→mock LLM→Redis→网关→SSE) mock_llm + 假 key 用户

结果:

环节 压测内容 结果
A. nginx GET /health / 静态页 115 / 117 req/s,0 错误
B. Django CRUD 会话 / 文件列表 85 / 83 req/s,0 错误
C. SSE 网关 100 并发长连接 100/100 挂住(3.2s)
D. 全链路 并发 POST /api/runs(20 并发 × 60) 429×58 / 201×2(限流生效)
E. 全链路 SSE 消费抽查 completed=True
扩展性 1 worker → 2 worker 成功创建 1 → 3

几点结论:

  1. Web 层不是瓶颈(nginx/Django 吞吐充足、零错误)。
  2. 异步网关的价值被实测证明:100 并发 SSE 长连接不占线程,同步 WSGI 的话早把 worker 耗尽了。
  3. 限流确实拦住过载:429 是设计行为——每用户 in-flight 上限保护上游 LLM,worker 完成后计数归零。
  4. 扩容杠杆是 worker:加 worker 吞吐线性提升,瓶颈在 LLM 调用耗时。

压测过程还发现并修了 3 个 bug(mock 延迟误解析、SSE 测试脚本卡死、in-flight 计数泄漏),见 loadtest/README 第四节。


快速开始

环境要求:Python 3.11(tech/.venv,uv 管理)、支持 CUDA 的 NVIDIA GPU(loop/fast 的 embedding 强制 CUDA)、Docker Desktop(system 生产栈)、OpenRouter API Key(loop/system 的 LLM)。

:: loop(CLI 多跳 Agent)
cd tech\loop
..\.venv\Scripts\python.exe main.py

:: fast(单轮 RAG)
cd tech
.venv\Scripts\python.exe fast\main.py

:: system(Web 生产栈)
system\backend\run_system.bat   :: compose up + 本机 worker
:: 访问 http://localhost,登录 admin / 123456 → 设置填 OpenRouter API Key
:: → 上传小说(等解析完成)→ 绑定文件 → 提问

评测和压测:

:: 评测
cd tech\evaluate
..\.venv\Scripts\python.exe generate_testset.py
..\.venv\Scripts\python.exe runner.py          :: C0–C3 消融
..\.venv\Scripts\python.exe run_fast.py        :: C4
..\.venv\Scripts\python.exe judge.py --reuse   :: 打分 → report.md

:: 压测(全链路需 mock_llm + mock worker)
cd tech\system\loadtest
..\..\.venv\Scripts\python.exe loadtest.py --all --concurrency 100 --requests 1000 --sse-concurrency 100
..\..\.venv\Scripts\python.exe fullchain.py --concurrency 20 --runs 60

目录结构

tech/
├── loop/                    多跳检索 Agent(CLI,核心算法)
│   ├── main.py              CLI 入口
│   └── utils/               loop.py(对话循环) / utils.py(工具/检索/索引/SQLite)
├── fast/                    单轮 Top-K RAG 流水线
│   ├── main.py              CLI + 模块入口
│   └── utils/               qsplit / hyde / qrewrite / recall / rerank / reason
├── system/                  Web 产品 + 生产部署栈
│   ├── backend/             Django + DRF + Celery(apps: accounts/chat/documents/rag/loop)
│   ├── gateway/             独立 FastAPI SSE 网关(Redis Streams 游标续传)
│   ├── frontend/            Vite + React 19 + TS(useSSE/useLoopChat hooks)
│   ├── nginx/               nginx 入口配置(SSE 缓冲关 + 超时调大)
│   ├── prometheus/          Prometheus 配置
│   ├── loadtest/            压测:mock_llm / loadtest(三层) / fullchain(全链路) / README
│   └── docker-compose.yml   生产栈编排(nginx/Django/gateway/pg/redis/minio/prometheus)
├── evaluate/                评测体系
│   ├── runner.py            消融跑批(C0–C3)
│   ├── run_fast.py          C4 fast
│   ├── judge.py             LLM 事实覆盖评分
│   ├── anonyrag_*.py        AnonyRAG 基准
│   └── results/             消融结果 + report.md
├── .dockerignore
└── README.MD                本文件

技术栈

技术
LLM DeepSeek v4-flash(OpenRouter,可通过 LLAGENT_LLM_BASE_URL 覆盖为本地 mock / 本地推理)
Embedding / Rerank BAAI/bge-base-zh-v1.5、bge-reranker-base(本地 CUDA)
向量 / 关键词检索 FAISS + rank-bm25(jieba) + RRF 融合
Web 后端 Django 5.2 + DRF + JWT(simplejwt)+ Celery
SSE 网关 FastAPI + uvicorn + Redis Streams
存储 PostgreSQL + Redis + MinIO
入口 / 部署 nginx + Docker Compose
前端 Vite + React 19 + TypeScript + react-markdown
可观测 Prometheus + Grafana
评测 LLM-as-judge 事实覆盖 + AnonyRAG 精确匹配

About

Agent for Ultra Long Content Reading

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages