Files
agent_jrxml/ARCHITECTURE.md
T

342 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 XSD286KB
├── 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 上展示错误信息。
## 核心组件
### AgentStateagent/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 |