TASK 1 — Profile Schema (core/profile/schema.py) 已存在:EvidenceThresholds / ModelRouting / WorkflowStep / ApprovalRule / Permissions / CachePolicy / Profile,全部 frozen=True,含 tool-reference 验证器。 TASK 2 — 三个行业 SOP Profile YAML - profiles/presales/profile.yaml 企业预售方案生成 - profiles/security_analysis/profile.yaml 安全事件分析(host_isolation 需审批) - profiles/knowledge_qa/profile.yaml 知识问答(严格模式,阈值全 0.90) TASK 3 — loader / registry / 测试 - core/profile/loader.py + registry.py(线程安全单例 + 热更新) - tests/unit/test_profiles.py(19 个用例) - tests/unit/test_scorer.py(12 个用例,覆盖 EvidenceScorer 全路径) TASK 4 — 端到端集成测试 + 成本追踪 - tests/integration/test_full_pipeline.py 7 个 mock-based 测试 - tests/integration/test_cost_tracking.py 3 个成本测试 - scripts/smoke_test.sh 更新为与最终门禁一致的 ruff 规则 Bug fixes(使全部测试通过): - LangGraph: ask_node 显式初始化 error/pending_approval,确保 ainvoke() 结果 dict 中始终包含这两个字段 - GraphInterrupt 不再被 act_node 的 except Exception 吞掉 - Citation.model_dump(mode="json") 修复 Decimal JSON 序列化问题 - act_node 直接调用 audit_logger.write_tool_call(集成测试 mock executor 时仍可审计) - 修复 mypy 错误:config.py 重复字段、encryptor __init__ 返回值、 registry 属性声明、sanitizer.py 类型注解 - 修复 47 处 ruff lint(F401/F841/E501/I001) - 添加 pytest-timeout>=2.3.0 依赖 最终门禁全部通过: ruff E,F,I 0 errors | mypy 0 errors 143 unit tests, 80.38% coverage | 11 integration tests Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
110 lines
3.0 KiB
Python
110 lines
3.0 KiB
Python
"""
|
|
LangGraph state graph construction and compilation.
|
|
|
|
Builds the agent execution flow with conditional edges:
|
|
- ask → plan (or END if clarification/error)
|
|
- plan → act (or END if error)
|
|
- act → verify (or END if error/approval pending)
|
|
- verify → report (or END if error)
|
|
- report → END
|
|
|
|
Uses PostgresSaver for production, MemorySaver for tests.
|
|
"""
|
|
|
|
import logging
|
|
from typing import Any
|
|
|
|
from langgraph.graph import END, StateGraph
|
|
|
|
from core.engine.nodes import act_node, ask_node, plan_node, report_node, verify_node
|
|
from core.engine.state import AgentState
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def build_agent_graph(checkpointer: Any = None) -> Any:
|
|
"""
|
|
Build and compile the agent state graph.
|
|
|
|
Args:
|
|
checkpointer: Optional checkpointer (PostgresSaver or MemorySaver)
|
|
|
|
Returns:
|
|
Compiled StateGraph
|
|
"""
|
|
graph = StateGraph(AgentState)
|
|
|
|
# Add nodes
|
|
graph.add_node("ask", ask_node)
|
|
graph.add_node("plan", plan_node)
|
|
graph.add_node("act", act_node)
|
|
graph.add_node("verify", verify_node)
|
|
graph.add_node("report", report_node)
|
|
|
|
# Set entry point
|
|
graph.set_entry_point("ask")
|
|
|
|
# Conditional edges
|
|
# ask → plan or END (clarification/error)
|
|
graph.add_conditional_edges(
|
|
"ask",
|
|
lambda s: END if s.is_clarification_needed() or s.has_error() else "plan",
|
|
)
|
|
|
|
# plan → act or END (error)
|
|
graph.add_conditional_edges(
|
|
"plan",
|
|
lambda s: END if s.has_error() else "act",
|
|
)
|
|
|
|
# act → verify or END (error)
|
|
# Note: approval pending is handled by interrupt_after, not routing
|
|
graph.add_conditional_edges(
|
|
"act",
|
|
lambda s: END if s.has_error() else "verify",
|
|
)
|
|
|
|
# verify → report or END (error)
|
|
graph.add_conditional_edges(
|
|
"verify",
|
|
lambda s: END if s.has_error() else "report",
|
|
)
|
|
|
|
# report → END
|
|
graph.add_edge("report", END)
|
|
|
|
# Compile with checkpointer; approval interrupts are driven by interrupt()
|
|
# inside act_node rather than the interrupt_after mechanism.
|
|
return graph.compile(checkpointer=checkpointer)
|
|
|
|
|
|
def create_agent_graph_with_postgres(database_url: str) -> Any:
|
|
"""
|
|
Create agent graph with PostgreSQL checkpointer for production.
|
|
|
|
Args:
|
|
database_url: PostgreSQL connection string
|
|
|
|
Returns:
|
|
Compiled StateGraph with PostgresSaver
|
|
"""
|
|
from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver
|
|
|
|
checkpointer = AsyncPostgresSaver.from_conn_string(database_url)
|
|
logger.info("Created agent graph with PostgresSaver")
|
|
return build_agent_graph(checkpointer)
|
|
|
|
|
|
def create_agent_graph_for_tests() -> Any:
|
|
"""
|
|
Create agent graph with in-memory checkpointer for tests.
|
|
|
|
Returns:
|
|
Compiled StateGraph with MemorySaver
|
|
"""
|
|
from langgraph.checkpoint.memory import MemorySaver
|
|
|
|
checkpointer = MemorySaver()
|
|
logger.info("Created agent graph with MemorySaver (test mode)")
|
|
return build_agent_graph(checkpointer)
|