342 lines
16 KiB
Markdown
342 lines
16 KiB
Markdown
# JRXML 生成代理 — 架构文档
|
||
|
||
## 概览
|
||
|
||
一个三层架构的桌面应用,通过自然语言多轮对话帮助非技术用户创建 JasperReports 模板(JRXML)。核心流程:用户输入 → 意图识别 → 模板检索 → LLM 生成/修改 → 自动验证修正 → 输出可编译的 JRXML。
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────────────┐
|
||
│ Vue 3 + Vite 前端 (:5173) │
|
||
│ frontend/ (聊天界面 + SSE 流式) │
|
||
│ 聊天界面 / 会话管理 / JRXML 预览 / 下载 / 快捷操作 │
|
||
└─────────────────────┬────────────────────────────────────────┘
|
||
│ HTTP + SSE (/api/*)
|
||
▼
|
||
┌──────────────────────────────────────────────────────────────┐
|
||
│ FastAPI SSE 后端 (:8000) │
|
||
│ api_server.py │
|
||
│ REST: /api/sessions, /api/upload, /api/.../download/latest │
|
||
│ SSE: /api/sessions/{id}/chat (流式推送) │
|
||
│ 事件: node_start | node_complete | stream_token │
|
||
│ agent_complete | agent_error │
|
||
└─────────────────────┬────────────────────────────────────────┘
|
||
│ run_agent(user_input)
|
||
▼
|
||
┌──────────────────────────────────────────────────────────────┐
|
||
│ LangGraph 状态机 (agent/) │
|
||
│ │
|
||
│ load_session → process_input → manage_context │
|
||
│ → save_state_snapshot → classify_intent │
|
||
│ │ │ │ │ │ │
|
||
│ ▼ ▼ ▼ ▼ ▼ │
|
||
│ retrieve modify_jrxml preview consult undo/reset │
|
||
│ │ │ /export │
|
||
│ ▼ ▼ │
|
||
│ generate save_session │
|
||
│ │ │ │
|
||
│ └────┬─────┘ │
|
||
│ ▼ │
|
||
│ (jrxml_reorder 自动规范化元素顺序) │
|
||
│ ▼ │
|
||
│ validate ──(fail)──► explain_error ──► correct_jrxml │
|
||
│ │ ▲ │ │
|
||
│ (pass) └──(retry<N)───┘ │
|
||
│ ▼ │
|
||
│ finalize (失败版本 → jrxml_versions, 提示下载) │
|
||
└──────────┬──────────────┬─────────────────────┬──────────────┘
|
||
│ │ │
|
||
▼ ▼ ▼
|
||
┌──────────────┐ ┌──────────────┐ ┌──────────────────────────┐
|
||
│ LLM 后端 │ │ 向量知识库 │ │ 验证服务 (:8001) │
|
||
│ backend/llm │ │ ChromaDB + │ │ FastAPI │
|
||
│ │ │ RAGSearcher │ │ 结构检查 + 严格 XSD 校验 │
|
||
│ Anthropic SDK│ │ │ │ │
|
||
│ OpenAI SDK │ │ Sentence- │ │ /validate │
|
||
│ Ollama │ │ Transformer │ │ /health │
|
||
└──────────────┘ └──────────────┘ └──────────────────────────┘
|
||
```
|
||
|
||
## 目录结构
|
||
|
||
```
|
||
agent_jrxml/
|
||
├── api_server.py # FastAPI SSE 后端(REST + 流式推送)
|
||
│
|
||
├── frontend/ # Vue 3 + Vite 前端
|
||
│ └── src/
|
||
│ ├── api/client.ts # SSE 客户端 + fetch 封装
|
||
│ ├── stores/ # Pinia 状态管理(chat + session)
|
||
│ └── components/ # 聊天界面组件
|
||
│
|
||
├── agent/ # LangGraph 工作流层
|
||
│ ├── __init__.py
|
||
│ ├── state.py # AgentState TypedDict 定义(~28 字段)
|
||
│ ├── nodes.py # 18 个工作流节点(生成/修改/验证/修正/意图识别...)
|
||
│ └── graph.py # 状态图编译 + 路由逻辑 + 初始状态工厂
|
||
│
|
||
├── backend/ # 基础设施层
|
||
│ ├── __init__.py
|
||
│ ├── llm.py # LLM 工厂:Anthropic(MiniMax) / OpenAI / Ollama
|
||
│ ├── embeddings.py # 嵌入模型工厂:HuggingFace / OpenAI
|
||
│ ├── validation.py # 验证服务 HTTP 客户端
|
||
│ ├── session.py # 会话持久化(JSON CRUD + flush/fsync)
|
||
│ ├── jrxml_reorder.py # JRXML 元素自动排序(匹配 XSD sequence)
|
||
│ └── rag_adapter.py # RAG 适配层:连接 ChromaDB 做语义搜索
|
||
│
|
||
├── validation_service/ # 独立验证微服务
|
||
│ ├── main.py # FastAPI 服务:结构检查 + 严格 XSD 校验
|
||
│ └── schemas/
|
||
│ └── jasperreport_7_0_6.xsd # JasperReports 7.0.6 XSD(286KB)
|
||
│
|
||
├── scripts/
|
||
│ └── init_kb.py # 知识库初始化(预下载嵌入模型)
|
||
│
|
||
├── tests/
|
||
│ ├── __init__.py
|
||
│ ├── test_validation.py # 验证服务单元测试
|
||
│ └── test_agent.py # 代理集成测试
|
||
│
|
||
├── data/ # 数据目录
|
||
│ ├── sample_templates/ # 示例 JRXML 模板
|
||
│ └── corrections/ # 错误修正案例
|
||
│
|
||
├── db/chroma/ # ChromaDB 持久化存储
|
||
├── sessions/ # 会话 JSON 文件存储
|
||
├── jrxml_versions/ # 失败版本归档存储
|
||
├── rag/ # RAG 子模块(独立管线)
|
||
├── requirements.txt # Python 依赖
|
||
├── start_all.bat # 一键启动全部服务
|
||
├── start.bat # 启动脚本
|
||
├── stop.bat # 一键停止全部服务
|
||
├── .env.example # 环境变量模板
|
||
└── README.md # 使用说明
|
||
```
|
||
|
||
## 数据流详解
|
||
|
||
### 1. 请求生命周期
|
||
|
||
```
|
||
用户输入 "创建员工名册,包含 id、name、department"
|
||
│
|
||
├─ load_session 从 sessions/{id}.json 恢复历史状态
|
||
├─ process_input 记录用户消息到 conversation_history
|
||
├─ manage_context 检查 token 数,超阈值则 LLM 压缩早期对话
|
||
├─ save_state_snapshot 保存当前状态快照(用于撤销)
|
||
├─ classify_intent LLM 分类 → initial_generation
|
||
├─ retrieve RAGSearcher.search_as_context() → 注入 prompt
|
||
├─ generate LLM 生成初始 JRXML
|
||
├─ save_session 持久化到磁盘
|
||
├─ validate 调用 FastAPI 验证服务
|
||
│ ├─ pass → finalize
|
||
│ └─ fail → explain_error → correct_jrxml → validate (最多 5 次)
|
||
└─ finalize 保存最终 JRXML,UI 展示结果
|
||
```
|
||
|
||
### 2. 意图路由(8 种意图)
|
||
|
||
| 意图 | 条件 | 路由目标 |
|
||
|------|------|---------|
|
||
| `initial_generation` | 无现有报表 | retrieve → generate |
|
||
| `modify_report` | 有现有报表 | modify_jrxml |
|
||
| `preview_report` | — | 直接展示 current_jrxml |
|
||
| `export_jrxml` | — | 触发下载 |
|
||
| `export_pdf` | — | 触发下载 |
|
||
| `consult_question` | — | handle_consult(独立回答) |
|
||
| `undo_modification` | history_states 非空 | 恢复上一个快照 |
|
||
| `reset_session` | — | 清空所有报表状态 |
|
||
|
||
### 3. 自动修正循环
|
||
|
||
```
|
||
validate ──fail──► explain_error ──► correct_jrxml ──► validate
|
||
▲ │
|
||
└──────────── retry_count < MAX_RETRY (5) ──────────────┘
|
||
```
|
||
|
||
每次修正都会递增 `retry_count`,达到上限后直接 `finalize`(即使仍有错误),在 UI 上展示错误信息。
|
||
|
||
## 核心组件
|
||
|
||
### AgentState(agent/state.py)
|
||
|
||
```python
|
||
class AgentState(TypedDict, total=False):
|
||
# 工作流核心
|
||
conversation_history: List[dict] # 当前上下文的对话(可能被压缩裁剪)
|
||
current_jrxml: str # 当前 JRXML 文本
|
||
user_input: str # 本轮用户输入
|
||
status: str # "pass" | "fail"
|
||
error_msg: str # 验证错误信息
|
||
natural_explanation: str # 错误的人类可读解释
|
||
retry_count: int # 当前修正尝试次数
|
||
user_modification_request: str # 修改请求文本
|
||
final_jrxml: str # 最终验证通过的 JRXML
|
||
stage: str # 当前阶段标识
|
||
retrieved_context: str # RAG 检索到的模板上下文
|
||
|
||
# 上下文压缩
|
||
full_conversation_history: List[dict] # 完整对话(含时间戳)
|
||
compressed_history: str # 早期对话的压缩摘要
|
||
current_token_count: int # 当前估算 token 数
|
||
|
||
# 会话持久化
|
||
session_id: str
|
||
session_name: str
|
||
created_at: str
|
||
updated_at: str
|
||
|
||
# 意图识别 + 撤销
|
||
intent: str # 8 种意图之一
|
||
history_states: List[dict] # 状态快照栈(最多 10 个)
|
||
```
|
||
|
||
### 工作流节点(agent/nodes.py)
|
||
|
||
| 节点 | 职责 | 调用外部 |
|
||
|------|------|---------|
|
||
| `load_session_node` | 从磁盘恢复会话状态 | `backend.session.load_session` |
|
||
| `process_input` | 记录用户输入到对话历史 | — |
|
||
| `manage_context` | token 超阈值时 LLM 压缩早期对话 | `get_llm()` |
|
||
| `save_state_snapshot` | 保存快照到 history_states | — |
|
||
| `classify_intent` | LLM 分类用户意图(8 类) | `get_llm()` |
|
||
| `retrieve` | 从 ChromaDB 搜索相关模板 | `backend.rag_adapter.search_chunks` |
|
||
| `generate` | 首次生成 JRXML | `get_llm()` |
|
||
| `modify_jrxml` | 根据用户需求修改现有 JRXML | `get_llm()` |
|
||
| `validate` | 调用验证服务检查 JRXML | `backend.validation.validate_jrxml` |
|
||
| `explain_error` | LLM 将编译错误翻译为人话 | `get_llm()` |
|
||
| `correct_jrxml` | LLM 自动修正验证失败 | `get_llm()` |
|
||
| `finalize` | 保存最终 JRXML,标记完成 | — |
|
||
| `handle_consult` | 回答 JasperReports 咨询 | `get_llm()` |
|
||
| `handle_undo` | 从 history_states 恢复上一状态 | — |
|
||
| `handle_reset` | 清空报表,重置会话 | — |
|
||
| `save_session_node` | 持久化当前状态到磁盘 | `backend.session.save_session` |
|
||
|
||
### LLM 工厂(backend/llm.py)
|
||
|
||
```
|
||
get_llm()
|
||
├─ LLM_BACKEND=local → langchain_ollama.ChatOllama
|
||
└─ LLM_BACKEND=cloud
|
||
├─ LLM_PROVIDER=anthropic → raw anthropic.Anthropic SDK
|
||
│ 适配 MiniMax Anthropic 兼容 API
|
||
│ 包装为 MiniMaxLLM(提供 .invoke() 接口)
|
||
└─ LLM_PROVIDER=openai → langchain_openai.ChatOpenAI
|
||
```
|
||
|
||
**MiniMaxLLM 适配器**:将 Anthropic SDK 的 `client.messages.create()` 包装成与 LangChain 兼容的 `.invoke(prompt) → Response.content` 接口,供所有节点统一调用。
|
||
|
||
### RAG 适配层(backend/rag_adapter.py)
|
||
|
||
```
|
||
search_chunks(query, k=5)
|
||
└─ RAGSearcher(单例)
|
||
├─ 懒加载 SentenceTransformer 模型
|
||
├─ 懒连接 ChromaDB PersistentClient
|
||
├─ query → 向量编码 → collection.query() → top-k 结果
|
||
└─ search_as_context() → 拼接带元数据标签的上下文字符串
|
||
```
|
||
|
||
### 验证服务(validation_service/main.py)
|
||
|
||
独立的 FastAPI 进程(端口 8001),提供两级验证:
|
||
|
||
1. **结构检查**(始终执行):
|
||
- XML 语法正确性
|
||
- `$F{field}` 引用一致性(表达式 vs `<field>` 声明)
|
||
- `<queryString>` 是否含有效 SQL SELECT
|
||
- `<jasperReport>` 必需属性(pageWidth, pageHeight, name)
|
||
|
||
2. **XSD Schema 校验**(可选):
|
||
- 需要 `validation_service/schemas/jasperreport_7_0_6.xsd` 文件
|
||
- 使用 `lxml.etree.XMLSchema` 进行完整 schema 校验
|
||
|
||
### 会话持久化(backend/session.py)
|
||
|
||
```
|
||
sessions/{session_id}.json
|
||
{
|
||
"session_id": "abc123def456",
|
||
"session_name": "员工名册报表",
|
||
"created_at": "2026-05-19T09:00:00+00:00",
|
||
"updated_at": "2026-05-19T09:30:00+00:00",
|
||
"agent_state": { ... } // 完整的 AgentState 字段
|
||
}
|
||
```
|
||
|
||
## 关键 Prompt 设计
|
||
|
||
| Prompt | 用途 | 输出约束 |
|
||
|--------|------|---------|
|
||
| `INTENT_CLASSIFY_PROMPT` | 8 分类意图识别 | 只输出意图名称 |
|
||
| `INITIAL_GENERATION_PROMPT` | 首次生成 JRXML | 只输出 JRXML,无 markdown |
|
||
| `MODIFICATION_PROMPT` | 修改现有 JRXML | 只输出完整 JRXML |
|
||
| `CORRECTION_PROMPT` | 自动修正错误 | 只输出修复后 JRXML |
|
||
| `EXPLAIN_PROMPT` | 错误转人话 | 2-3 句话 |
|
||
| `COMPRESSION_PROMPT` | 对话压缩 | ≤200 字摘要 |
|
||
| `CONSULT_PROMPT` | 咨询解答 | 简洁中文 |
|
||
|
||
## 配置参数(.env)
|
||
|
||
| 参数 | 默认值 | 说明 |
|
||
|------|--------|------|
|
||
| `LLM_BACKEND` | cloud | cloud / local |
|
||
| `LLM_PROVIDER` | openai | openai / anthropic |
|
||
| `OPENAI_API_KEY` | — | API 密钥 |
|
||
| `OPENAI_BASE_URL` | https://api.openai.com/v1 | API 端点 |
|
||
| `LLM_MODEL` | gpt-4o | 模型名称 |
|
||
| `LOCAL_LLM_MODEL` | qwen2.5-coder:7b | Ollama 模型 |
|
||
| `EMBED_BACKEND` | local | local / cloud |
|
||
| `LOCAL_EMBED_MODEL` | Qwen/Qwen3-Embedding-0.6B | 本地嵌入模型 |
|
||
| `VALIDATION_SERVICE_URL` | http://localhost:8001/validate | 验证端点 |
|
||
| `CHROMA_PERSIST_DIR` | ./db/chroma | ChromaDB 路径 |
|
||
| `MAX_RETRY` | 5 | 自动修正最大尝试次数 |
|
||
| `CONTEXT_MAX_TOKENS` | 6000 | 触发压缩的 token 阈值 |
|
||
| `CONTEXT_KEEP_RECENT` | 4 | 保留最近 N 轮完整对话 |
|
||
| `SESSIONS_DIR` | ./sessions | 会话 JSON 存储目录 |
|
||
| `HISTORY_MAX_SNAPSHOTS` | 10 | 撤销快照保留数量 |
|
||
|
||
## 启动流程
|
||
|
||
```bash
|
||
# 1. 安装依赖
|
||
pip install -r requirements.txt
|
||
|
||
# 2. 配置环境
|
||
cp .env.example .env
|
||
# 编辑 .env 填入 API 密钥
|
||
|
||
# 3. 初始化知识库(预下载嵌入模型)
|
||
python scripts/init_kb.py --download-model
|
||
|
||
# 4. 启动验证服务(终端 1)
|
||
python -m uvicorn validation_service.main:app --port 8001 --host 0.0.0.0
|
||
|
||
# 5. 启动 Streamlit 界面(终端 2)
|
||
STREAMLIT_SERVER_HEADLESS=true streamlit run app.py --server.port 8501
|
||
|
||
# 6. 访问 http://localhost:8501
|
||
```
|
||
|
||
## 测试
|
||
|
||
```bash
|
||
pytest tests/test_validation.py -v # 验证服务单元测试
|
||
pytest tests/test_agent.py -v # 代理集成测试
|
||
pytest tests/ -v # 全部测试
|
||
```
|
||
|
||
## 技术栈
|
||
|
||
| 层 | 技术 |
|
||
|----|------|
|
||
| UI | Streamlit 1.57 |
|
||
| 工作流引擎 | LangGraph 1.2 |
|
||
| LLM 接入 | Anthropic SDK / LangChain-OpenAI / LangChain-Ollama |
|
||
| 向量数据库 | ChromaDB 1.5 |
|
||
| 嵌入模型 | Sentence-Transformers (HuggingFace) |
|
||
| 验证服务 | FastAPI + lxml XMLSchema |
|
||
| HTTP 客户端 | httpx |
|
||
| Token 计算 | tiktoken |
|
||
| 持久化 | JSON 文件 + ChromaDB PersistentClient |
|