feat: LangGraph工作流核心 — Agent状态/节点/图 + 验证服务 + 知识库

agent/
  state.py: AgentState TypedDict(20字段含意图/压缩/会话/撤销)
  nodes.py: 17个节点函数(生成/修改/验证/纠错/意图分类/压缩/撤销/重置)
  graph.py: 17节点状态图,8意图路由分发

验证服务 validation_service/
  main.py: FastAPI服务,lxml XSD验证 + 结构化检查(字段引用/SQL/尺寸)

数据 data/
  sample_templates/: 4个JRXML示例模板
  corrections/: 3个错误修正案例

脚本 scripts/
  init_kb.py: Chroma知识库初始化
This commit is contained in:
2026-05-14 23:21:10 +08:00
parent 21a5fdf930
commit 4b43c5d3e4
14 changed files with 1375 additions and 0 deletions
+129
View File
@@ -0,0 +1,129 @@
"""JRXML 文件验证服务(FastAPI)。
使用 lxml XML Schema 验证作为 JasperReports 7.0.6 编译验证的第一阶段后备方案。
要进行完整的编译验证,需要基于 Java 的验证器以及 JasperReports 7.0.6 + JDK 21。
启动: uvicorn validation_service.main:app --port 8001
"""
import re
import xml.etree.ElementTree as ET
from pathlib import Path
from fastapi import FastAPI
from lxml import etree
from pydantic import BaseModel
app = FastAPI(title="JRXML 验证服务")
SCHEMA_DIR = Path(__file__).parent / "schemas"
SCHEMA_FILE = SCHEMA_DIR / "jasperreport_7_0_6.xsd"
class ValidationRequest(BaseModel):
jrxml: str
class ValidationResponse(BaseModel):
valid: bool
error: str
def _check_structural_issues(jrxml: str) -> list[str]:
"""检查 JRXML 中常见的结构性问题。"""
issues = []
root = None
try:
root = ET.fromstring(jrxml)
except ET.ParseError as e:
issues.append(f"XML 解析错误:{e}")
return issues
# 同时处理带命名空间和不带命名空间的元素名
ns = "http://jasperreports.sourceforge.net/jasperreports"
declared_fields = set()
for elem in root.iter():
tag = elem.tag.split("}")[-1] if "}" in elem.tag else elem.tag
if tag == "field":
name = elem.get("name")
if name:
declared_fields.add(name)
field_expr_pattern = re.compile(r'\$F\{(\w+)\}')
for m in field_expr_pattern.finditer(jrxml):
field_name = m.group(1)
if field_name not in declared_fields:
issues.append(
f"字段 '{field_name}' 在表达式中使用但未在 <field> 部分声明"
)
query = None
for elem in root.iter():
tag = elem.tag.split("}")[-1] if "}" in elem.tag else elem.tag
if tag == "queryString":
query = elem
break
if query is not None:
query_text = "".join(query.itertext()).strip()
if not query_text:
issues.append("<queryString> 为空 - 请在 CDATA 中添加 SQL 查询")
elif not any(kw in query_text.upper() for kw in ["SELECT"]):
issues.append("<queryString> 似乎不包含 SQL SELECT 查询")
if not root.get("pageWidth"):
issues.append("缺少 <jasperReport> 上的 pageWidth 属性")
if not root.get("pageHeight"):
issues.append("缺少 <jasperReport> 上的 pageHeight 属性")
if not root.get("name"):
issues.append("缺少 <jasperReport> 上的 'name' 属性")
return issues
def _validate_xsd(jrxml: str) -> tuple[bool, str]:
"""根据 JasperReports XSD schema 验证 JRXML。"""
if not SCHEMA_FILE.exists():
return True, ""
try:
schema_doc = etree.parse(str(SCHEMA_FILE))
xmlschema = etree.XMLSchema(schema_doc)
doc = etree.fromstring(jrxml.encode("utf-8"))
xmlschema.assertValid(doc)
return True, ""
except etree.DocumentInvalid as e:
return False, str(e)
except etree.XMLSchemaError as e:
return False, f"Schema 错误:{e}"
except Exception as e:
return False, f"XML 验证错误:{e}"
@app.post("/validate", response_model=ValidationResponse)
async def validate_jrxml(req: ValidationRequest):
jrxml = req.jrxml.strip()
if not jrxml:
return ValidationResponse(valid=False, error="JRXML 内容为空")
structural_issues = _check_structural_issues(jrxml)
if structural_issues:
return ValidationResponse(valid=False, error="; ".join(structural_issues))
valid, xsd_error = _validate_xsd(jrxml)
if not valid:
return ValidationResponse(valid=False, error=xsd_error)
return ValidationResponse(valid=True, error="")
@app.get("/health")
async def health():
schema_available = SCHEMA_FILE.exists()
return {
"status": "ok",
"schema_available": schema_available,
"validation_type": "XSD" if schema_available else "仅结构检查",
"note": "如需完整的 JasperReports 7.0.6 编译验证,请使用基于 Java 的验证器",
}
+6
View File
@@ -0,0 +1,6 @@
@echo off
echo 正在启动 JRXML 验证服务...
echo.
cd /d "%~dp0"
python -m uvicorn validation_service.main:app --host 0.0.0.0 --port 8001 --reload
pause