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:
@@ -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 的验证器",
|
||||
}
|
||||
Reference in New Issue
Block a user