16 KiB
16 KiB
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)
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),提供两级验证:
-
结构检查(始终执行):
- XML 语法正确性
$F{field}引用一致性(表达式 vs<field>声明)<queryString>是否含有效 SQL SELECT<jasperReport>必需属性(pageWidth, pageHeight, name)
-
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 | 撤销快照保留数量 |
启动流程
# 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
测试
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 |