feat: implement mid-platform core services and APIs [AC-IDMP-01~20, AC-MARH-01~12]
- Add dialogue, messages, sessions, share API endpoints
- Add mid-platform schemas and models (memory, tool_registry, tool_trace)
- Add core services: agent_orchestrator, policy_router, runtime_observer
- Add tool services: kb_search_dynamic, memory_adapter, tool_registry
- Add guardrail services: output_guardrail_executor, high_risk_handler
- Add utility services: timeout_governor, segment_humanizer, metrics_collector
2026-03-05 10:13:34 +00:00
|
|
|
|
"""
|
|
|
|
|
|
Dialogue Controller for Mid Platform.
|
|
|
|
|
|
[AC-MARH-01, AC-MARH-02, AC-MARH-03, AC-MARH-04, AC-MARH-05, AC-MARH-06,
|
|
|
|
|
|
AC-MARH-07, AC-MARH-08, AC-MARH-09, AC-MARH-10, AC-MARH-11, AC-MARH-12]
|
|
|
|
|
|
|
|
|
|
|
|
Core endpoint: POST /mid/dialogue/respond
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
import logging
|
|
|
|
|
|
import time
|
|
|
|
|
|
import uuid
|
|
|
|
|
|
from typing import Annotated, Any
|
|
|
|
|
|
|
|
|
|
|
|
from fastapi import APIRouter, Depends, Request
|
|
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
|
|
|
|
|
|
|
|
from app.core.database import get_session
|
|
|
|
|
|
from app.core.tenant import get_tenant_id
|
|
|
|
|
|
from app.models.mid.schemas import (
|
|
|
|
|
|
DialogueRequest,
|
|
|
|
|
|
DialogueResponse,
|
|
|
|
|
|
ExecutionMode,
|
|
|
|
|
|
Segment,
|
|
|
|
|
|
TraceInfo,
|
|
|
|
|
|
)
|
|
|
|
|
|
from app.services.mid.agent_orchestrator import AgentOrchestrator
|
|
|
|
|
|
from app.services.mid.default_kb_tool_runner import DefaultKbToolRunner
|
|
|
|
|
|
from app.services.mid.feature_flags import FeatureFlagService
|
|
|
|
|
|
from app.services.mid.high_risk_handler import HighRiskHandler
|
|
|
|
|
|
from app.services.mid.interrupt_context_enricher import InterruptContextEnricher
|
|
|
|
|
|
from app.services.mid.kb_search_dynamic_tool import (
|
|
|
|
|
|
KbSearchDynamicConfig,
|
|
|
|
|
|
KbSearchDynamicTool,
|
|
|
|
|
|
)
|
|
|
|
|
|
from app.services.mid.high_risk_check_tool import (
|
|
|
|
|
|
HighRiskCheckConfig,
|
|
|
|
|
|
HighRiskCheckTool,
|
|
|
|
|
|
register_high_risk_check_tool,
|
|
|
|
|
|
)
|
|
|
|
|
|
from app.services.mid.intent_hint_tool import (
|
|
|
|
|
|
IntentHintConfig,
|
|
|
|
|
|
IntentHintTool,
|
|
|
|
|
|
register_intent_hint_tool,
|
|
|
|
|
|
)
|
|
|
|
|
|
from app.services.mid.memory_recall_tool import (
|
|
|
|
|
|
MemoryRecallConfig,
|
|
|
|
|
|
MemoryRecallTool,
|
|
|
|
|
|
register_memory_recall_tool,
|
|
|
|
|
|
)
|
|
|
|
|
|
from app.services.mid.metrics_collector import MetricsCollector
|
|
|
|
|
|
from app.services.mid.output_guardrail_executor import OutputGuardrailExecutor
|
|
|
|
|
|
from app.services.mid.policy_router import IntentMatch, PolicyRouter
|
|
|
|
|
|
from app.services.mid.runtime_observer import RuntimeObserver
|
|
|
|
|
|
from app.services.mid.segment_humanizer import HumanizeConfig, SegmentHumanizer
|
|
|
|
|
|
from app.services.mid.timeout_governor import TimeoutGovernor
|
|
|
|
|
|
from app.services.mid.tool_registry import ToolRegistry
|
|
|
|
|
|
from app.services.mid.trace_logger import TraceLogger
|
2026-03-10 04:06:57 +00:00
|
|
|
|
from app.services.prompt.template_service import PromptTemplateService
|
|
|
|
|
|
from app.services.prompt.variable_resolver import VariableResolver
|
|
|
|
|
|
from app.services.intent.clarification import (
|
|
|
|
|
|
ClarificationEngine,
|
|
|
|
|
|
ClarifyReason,
|
|
|
|
|
|
ClarifySessionManager,
|
|
|
|
|
|
ClarifyState,
|
|
|
|
|
|
HybridIntentResult,
|
|
|
|
|
|
IntentCandidate,
|
|
|
|
|
|
T_HIGH,
|
|
|
|
|
|
T_LOW,
|
|
|
|
|
|
)
|
feat: implement mid-platform core services and APIs [AC-IDMP-01~20, AC-MARH-01~12]
- Add dialogue, messages, sessions, share API endpoints
- Add mid-platform schemas and models (memory, tool_registry, tool_trace)
- Add core services: agent_orchestrator, policy_router, runtime_observer
- Add tool services: kb_search_dynamic, memory_adapter, tool_registry
- Add guardrail services: output_guardrail_executor, high_risk_handler
- Add utility services: timeout_governor, segment_humanizer, metrics_collector
2026-03-05 10:13:34 +00:00
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
router = APIRouter(prefix="/mid", tags=["Mid Platform Dialogue"])
|
|
|
|
|
|
|
|
|
|
|
|
_mid_services: dict[str, Any] = {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_policy_router() -> PolicyRouter:
|
|
|
|
|
|
"""Get or create PolicyRouter instance."""
|
|
|
|
|
|
if "policy_router" not in _mid_services:
|
|
|
|
|
|
_mid_services["policy_router"] = PolicyRouter()
|
|
|
|
|
|
return _mid_services["policy_router"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_high_risk_handler() -> HighRiskHandler:
|
|
|
|
|
|
"""Get or create HighRiskHandler instance."""
|
|
|
|
|
|
if "high_risk_handler" not in _mid_services:
|
|
|
|
|
|
_mid_services["high_risk_handler"] = HighRiskHandler()
|
|
|
|
|
|
return _mid_services["high_risk_handler"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_timeout_governor() -> TimeoutGovernor:
|
|
|
|
|
|
"""Get or create TimeoutGovernor instance."""
|
|
|
|
|
|
if "timeout_governor" not in _mid_services:
|
|
|
|
|
|
_mid_services["timeout_governor"] = TimeoutGovernor()
|
|
|
|
|
|
return _mid_services["timeout_governor"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_feature_flag_service() -> FeatureFlagService:
|
|
|
|
|
|
"""Get or create FeatureFlagService instance."""
|
|
|
|
|
|
if "feature_flag_service" not in _mid_services:
|
|
|
|
|
|
_mid_services["feature_flag_service"] = FeatureFlagService()
|
|
|
|
|
|
return _mid_services["feature_flag_service"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_trace_logger() -> TraceLogger:
|
|
|
|
|
|
"""Get or create TraceLogger instance."""
|
|
|
|
|
|
if "trace_logger" not in _mid_services:
|
|
|
|
|
|
_mid_services["trace_logger"] = TraceLogger()
|
|
|
|
|
|
return _mid_services["trace_logger"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_metrics_collector() -> MetricsCollector:
|
|
|
|
|
|
"""Get or create MetricsCollector instance."""
|
|
|
|
|
|
if "metrics_collector" not in _mid_services:
|
|
|
|
|
|
_mid_services["metrics_collector"] = MetricsCollector()
|
|
|
|
|
|
return _mid_services["metrics_collector"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_tool_registry() -> ToolRegistry:
|
|
|
|
|
|
"""Get or create ToolRegistry instance."""
|
|
|
|
|
|
if "tool_registry" not in _mid_services:
|
|
|
|
|
|
_mid_services["tool_registry"] = ToolRegistry(
|
|
|
|
|
|
timeout_governor=get_timeout_governor()
|
|
|
|
|
|
)
|
|
|
|
|
|
return _mid_services["tool_registry"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_kb_search_dynamic_registered: bool = False
|
|
|
|
|
|
_intent_hint_registered: bool = False
|
|
|
|
|
|
_high_risk_check_registered: bool = False
|
|
|
|
|
|
_memory_recall_registered: bool = False
|
2026-03-10 04:06:57 +00:00
|
|
|
|
_metadata_discovery_registered: bool = False
|
feat: implement mid-platform core services and APIs [AC-IDMP-01~20, AC-MARH-01~12]
- Add dialogue, messages, sessions, share API endpoints
- Add mid-platform schemas and models (memory, tool_registry, tool_trace)
- Add core services: agent_orchestrator, policy_router, runtime_observer
- Add tool services: kb_search_dynamic, memory_adapter, tool_registry
- Add guardrail services: output_guardrail_executor, high_risk_handler
- Add utility services: timeout_governor, segment_humanizer, metrics_collector
2026-03-05 10:13:34 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def ensure_kb_search_dynamic_registered(
|
|
|
|
|
|
registry: ToolRegistry,
|
|
|
|
|
|
session: AsyncSession,
|
|
|
|
|
|
) -> None:
|
|
|
|
|
|
"""[AC-MARH-05] Ensure kb_search_dynamic tool is registered."""
|
|
|
|
|
|
global _kb_search_dynamic_registered
|
|
|
|
|
|
if _kb_search_dynamic_registered:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
from app.services.mid.kb_search_dynamic_tool import register_kb_search_dynamic_tool
|
|
|
|
|
|
|
|
|
|
|
|
config = KbSearchDynamicConfig(
|
|
|
|
|
|
enabled=True,
|
|
|
|
|
|
top_k=5,
|
2026-03-10 04:06:57 +00:00
|
|
|
|
timeout_ms=10000,
|
feat: implement mid-platform core services and APIs [AC-IDMP-01~20, AC-MARH-01~12]
- Add dialogue, messages, sessions, share API endpoints
- Add mid-platform schemas and models (memory, tool_registry, tool_trace)
- Add core services: agent_orchestrator, policy_router, runtime_observer
- Add tool services: kb_search_dynamic, memory_adapter, tool_registry
- Add guardrail services: output_guardrail_executor, high_risk_handler
- Add utility services: timeout_governor, segment_humanizer, metrics_collector
2026-03-05 10:13:34 +00:00
|
|
|
|
min_score_threshold=0.5,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
register_kb_search_dynamic_tool(
|
|
|
|
|
|
registry=registry,
|
|
|
|
|
|
session=session,
|
|
|
|
|
|
timeout_governor=get_timeout_governor(),
|
|
|
|
|
|
config=config,
|
|
|
|
|
|
)
|
|
|
|
|
|
_kb_search_dynamic_registered = True
|
|
|
|
|
|
logger.info("[AC-MARH-05] kb_search_dynamic tool registered to registry")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def ensure_intent_hint_registered(
|
|
|
|
|
|
registry: ToolRegistry,
|
|
|
|
|
|
session: AsyncSession,
|
|
|
|
|
|
) -> None:
|
|
|
|
|
|
"""[AC-IDMP-02, AC-IDMP-16] Ensure intent_hint tool is registered."""
|
|
|
|
|
|
global _intent_hint_registered
|
|
|
|
|
|
if _intent_hint_registered:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
config = IntentHintConfig(
|
|
|
|
|
|
enabled=True,
|
|
|
|
|
|
timeout_ms=500,
|
|
|
|
|
|
top_n=3,
|
|
|
|
|
|
low_confidence_threshold=0.3,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
register_intent_hint_tool(
|
|
|
|
|
|
registry=registry,
|
|
|
|
|
|
session=session,
|
|
|
|
|
|
config=config,
|
|
|
|
|
|
)
|
|
|
|
|
|
_intent_hint_registered = True
|
|
|
|
|
|
logger.info("[AC-IDMP-02] intent_hint tool registered to registry")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def ensure_high_risk_check_registered(
|
|
|
|
|
|
registry: ToolRegistry,
|
|
|
|
|
|
session: AsyncSession,
|
|
|
|
|
|
) -> None:
|
|
|
|
|
|
"""[AC-IDMP-05, AC-IDMP-20] Ensure high_risk_check tool is registered."""
|
|
|
|
|
|
global _high_risk_check_registered
|
|
|
|
|
|
if _high_risk_check_registered:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
config = HighRiskCheckConfig(
|
|
|
|
|
|
enabled=True,
|
|
|
|
|
|
timeout_ms=500,
|
|
|
|
|
|
default_confidence=0.9,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
register_high_risk_check_tool(
|
|
|
|
|
|
registry=registry,
|
|
|
|
|
|
session=session,
|
|
|
|
|
|
config=config,
|
|
|
|
|
|
)
|
|
|
|
|
|
_high_risk_check_registered = True
|
|
|
|
|
|
logger.info("[AC-IDMP-05] high_risk_check tool registered to registry")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def ensure_memory_recall_registered(
|
|
|
|
|
|
registry: ToolRegistry,
|
|
|
|
|
|
session: AsyncSession,
|
|
|
|
|
|
) -> None:
|
|
|
|
|
|
"""[AC-IDMP-13] Ensure memory_recall tool is registered."""
|
|
|
|
|
|
global _memory_recall_registered
|
|
|
|
|
|
if _memory_recall_registered:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
config = MemoryRecallConfig(
|
|
|
|
|
|
enabled=True,
|
|
|
|
|
|
timeout_ms=1000,
|
|
|
|
|
|
max_recent_messages=8,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
register_memory_recall_tool(
|
|
|
|
|
|
registry=registry,
|
|
|
|
|
|
session=session,
|
|
|
|
|
|
timeout_governor=get_timeout_governor(),
|
|
|
|
|
|
config=config,
|
|
|
|
|
|
)
|
|
|
|
|
|
_memory_recall_registered = True
|
|
|
|
|
|
logger.info("[AC-IDMP-13] memory_recall tool registered to registry")
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-10 04:06:57 +00:00
|
|
|
|
def ensure_metadata_discovery_registered(
|
|
|
|
|
|
registry: ToolRegistry,
|
|
|
|
|
|
session: AsyncSession,
|
|
|
|
|
|
) -> None:
|
|
|
|
|
|
"""Ensure metadata_discovery tool is registered."""
|
|
|
|
|
|
global _metadata_discovery_registered
|
|
|
|
|
|
if _metadata_discovery_registered:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
from app.services.mid.metadata_discovery_tool import register_metadata_discovery_tool
|
|
|
|
|
|
|
|
|
|
|
|
register_metadata_discovery_tool(
|
|
|
|
|
|
registry=registry,
|
|
|
|
|
|
session=session,
|
|
|
|
|
|
timeout_governor=get_timeout_governor(),
|
|
|
|
|
|
)
|
|
|
|
|
|
_metadata_discovery_registered = True
|
|
|
|
|
|
logger.info("[MetadataDiscovery] metadata_discovery tool registered to registry")
|
|
|
|
|
|
|
|
|
|
|
|
|
feat: implement mid-platform core services and APIs [AC-IDMP-01~20, AC-MARH-01~12]
- Add dialogue, messages, sessions, share API endpoints
- Add mid-platform schemas and models (memory, tool_registry, tool_trace)
- Add core services: agent_orchestrator, policy_router, runtime_observer
- Add tool services: kb_search_dynamic, memory_adapter, tool_registry
- Add guardrail services: output_guardrail_executor, high_risk_handler
- Add utility services: timeout_governor, segment_humanizer, metrics_collector
2026-03-05 10:13:34 +00:00
|
|
|
|
def get_output_guardrail_executor() -> OutputGuardrailExecutor:
|
|
|
|
|
|
"""Get or create OutputGuardrailExecutor instance."""
|
|
|
|
|
|
if "output_guardrail_executor" not in _mid_services:
|
|
|
|
|
|
_mid_services["output_guardrail_executor"] = OutputGuardrailExecutor()
|
|
|
|
|
|
return _mid_services["output_guardrail_executor"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_interrupt_context_enricher() -> InterruptContextEnricher:
|
|
|
|
|
|
"""Get or create InterruptContextEnricher instance."""
|
|
|
|
|
|
if "interrupt_context_enricher" not in _mid_services:
|
|
|
|
|
|
_mid_services["interrupt_context_enricher"] = InterruptContextEnricher()
|
|
|
|
|
|
return _mid_services["interrupt_context_enricher"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_default_kb_tool_runner() -> DefaultKbToolRunner:
|
|
|
|
|
|
"""Get or create DefaultKbToolRunner instance."""
|
|
|
|
|
|
if "default_kb_tool_runner" not in _mid_services:
|
|
|
|
|
|
_mid_services["default_kb_tool_runner"] = DefaultKbToolRunner(
|
|
|
|
|
|
timeout_governor=get_timeout_governor()
|
|
|
|
|
|
)
|
|
|
|
|
|
return _mid_services["default_kb_tool_runner"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_segment_humanizer() -> SegmentHumanizer:
|
|
|
|
|
|
"""Get or create SegmentHumanizer instance."""
|
|
|
|
|
|
if "segment_humanizer" not in _mid_services:
|
|
|
|
|
|
_mid_services["segment_humanizer"] = SegmentHumanizer()
|
|
|
|
|
|
return _mid_services["segment_humanizer"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_runtime_observer() -> RuntimeObserver:
|
|
|
|
|
|
"""Get or create RuntimeObserver instance."""
|
|
|
|
|
|
if "runtime_observer" not in _mid_services:
|
|
|
|
|
|
_mid_services["runtime_observer"] = RuntimeObserver()
|
|
|
|
|
|
return _mid_services["runtime_observer"]
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-10 04:06:57 +00:00
|
|
|
|
def get_clarification_engine() -> ClarificationEngine:
|
|
|
|
|
|
"""Get or create ClarificationEngine instance."""
|
|
|
|
|
|
if "clarification_engine" not in _mid_services:
|
|
|
|
|
|
_mid_services["clarification_engine"] = ClarificationEngine(
|
|
|
|
|
|
t_high=T_HIGH,
|
|
|
|
|
|
t_low=T_LOW,
|
|
|
|
|
|
)
|
|
|
|
|
|
return _mid_services["clarification_engine"]
|
|
|
|
|
|
|
|
|
|
|
|
|
feat: implement mid-platform core services and APIs [AC-IDMP-01~20, AC-MARH-01~12]
- Add dialogue, messages, sessions, share API endpoints
- Add mid-platform schemas and models (memory, tool_registry, tool_trace)
- Add core services: agent_orchestrator, policy_router, runtime_observer
- Add tool services: kb_search_dynamic, memory_adapter, tool_registry
- Add guardrail services: output_guardrail_executor, high_risk_handler
- Add utility services: timeout_governor, segment_humanizer, metrics_collector
2026-03-05 10:13:34 +00:00
|
|
|
|
@router.post(
|
|
|
|
|
|
"/dialogue/respond",
|
|
|
|
|
|
operation_id="respondDialogue",
|
|
|
|
|
|
summary="Generate mid platform dialogue response",
|
|
|
|
|
|
description="""
|
|
|
|
|
|
[AC-MARH-01~12] Core dialogue response endpoint for mid platform.
|
|
|
|
|
|
|
|
|
|
|
|
Returns segments[] with trace info including:
|
|
|
|
|
|
- guardrail_triggered, guardrail_rule_id
|
|
|
|
|
|
- interrupt_consumed
|
|
|
|
|
|
- kb_tool_called, kb_hit
|
|
|
|
|
|
- timeout_profile, segment_stats
|
|
|
|
|
|
""",
|
|
|
|
|
|
)
|
|
|
|
|
|
async def respond_dialogue(
|
|
|
|
|
|
request: Request,
|
|
|
|
|
|
dialogue_request: DialogueRequest,
|
|
|
|
|
|
session: Annotated[AsyncSession, Depends(get_session)],
|
|
|
|
|
|
policy_router: PolicyRouter = Depends(get_policy_router),
|
|
|
|
|
|
high_risk_handler: HighRiskHandler = Depends(get_high_risk_handler),
|
|
|
|
|
|
timeout_governor: TimeoutGovernor = Depends(get_timeout_governor),
|
|
|
|
|
|
feature_flag_service: FeatureFlagService = Depends(get_feature_flag_service),
|
|
|
|
|
|
trace_logger: TraceLogger = Depends(get_trace_logger),
|
|
|
|
|
|
metrics_collector: MetricsCollector = Depends(get_metrics_collector),
|
|
|
|
|
|
output_guardrail_executor: OutputGuardrailExecutor = Depends(get_output_guardrail_executor),
|
|
|
|
|
|
interrupt_context_enricher: InterruptContextEnricher = Depends(get_interrupt_context_enricher),
|
|
|
|
|
|
default_kb_tool_runner: DefaultKbToolRunner = Depends(get_default_kb_tool_runner),
|
|
|
|
|
|
segment_humanizer: SegmentHumanizer = Depends(get_segment_humanizer),
|
|
|
|
|
|
runtime_observer: RuntimeObserver = Depends(get_runtime_observer),
|
2026-03-10 04:06:57 +00:00
|
|
|
|
clarification_engine: ClarificationEngine = Depends(get_clarification_engine),
|
feat: implement mid-platform core services and APIs [AC-IDMP-01~20, AC-MARH-01~12]
- Add dialogue, messages, sessions, share API endpoints
- Add mid-platform schemas and models (memory, tool_registry, tool_trace)
- Add core services: agent_orchestrator, policy_router, runtime_observer
- Add tool services: kb_search_dynamic, memory_adapter, tool_registry
- Add guardrail services: output_guardrail_executor, high_risk_handler
- Add utility services: timeout_governor, segment_humanizer, metrics_collector
2026-03-05 10:13:34 +00:00
|
|
|
|
) -> DialogueResponse:
|
|
|
|
|
|
"""
|
|
|
|
|
|
[AC-MARH-01~12] Generate dialogue response with segments and trace.
|
|
|
|
|
|
|
|
|
|
|
|
Flow:
|
|
|
|
|
|
1. Validate request and get tenant context
|
|
|
|
|
|
2. Start runtime observation
|
|
|
|
|
|
3. Process interrupted segments (AC-MARH-03/04)
|
|
|
|
|
|
4. Check feature flags for grayscale/rollback
|
|
|
|
|
|
5. Detect high-risk scenarios
|
|
|
|
|
|
6. Route to appropriate execution mode
|
|
|
|
|
|
7. For Agent mode: call KB tool (AC-MARH-05/06)
|
|
|
|
|
|
8. Execute output guardrail (AC-MARH-01/02)
|
|
|
|
|
|
9. Apply segment humanizer (AC-MARH-10/11)
|
|
|
|
|
|
10. Collect trace and return (AC-MARH-12)
|
|
|
|
|
|
"""
|
|
|
|
|
|
start_time = time.time()
|
|
|
|
|
|
tenant_id = get_tenant_id()
|
|
|
|
|
|
|
|
|
|
|
|
if not tenant_id:
|
|
|
|
|
|
from app.core.exceptions import MissingTenantIdException
|
|
|
|
|
|
raise MissingTenantIdException()
|
|
|
|
|
|
|
|
|
|
|
|
request_id = str(uuid.uuid4())
|
|
|
|
|
|
generation_id = str(uuid.uuid4())
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
f"[AC-MARH-01] Dialogue request: tenant={tenant_id}, "
|
2026-03-10 04:06:57 +00:00
|
|
|
|
f"session={dialogue_request.session_id}, request_id={request_id}, "
|
|
|
|
|
|
f"user_message={dialogue_request.user_message[:100] if dialogue_request.user_message else 'None'}..."
|
feat: implement mid-platform core services and APIs [AC-IDMP-01~20, AC-MARH-01~12]
- Add dialogue, messages, sessions, share API endpoints
- Add mid-platform schemas and models (memory, tool_registry, tool_trace)
- Add core services: agent_orchestrator, policy_router, runtime_observer
- Add tool services: kb_search_dynamic, memory_adapter, tool_registry
- Add guardrail services: output_guardrail_executor, high_risk_handler
- Add utility services: timeout_governor, segment_humanizer, metrics_collector
2026-03-05 10:13:34 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
runtime_ctx = runtime_observer.start_observation(
|
|
|
|
|
|
tenant_id=tenant_id,
|
|
|
|
|
|
session_id=dialogue_request.session_id,
|
|
|
|
|
|
request_id=request_id,
|
|
|
|
|
|
generation_id=generation_id,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
trace = trace_logger.start_trace(
|
|
|
|
|
|
tenant_id=tenant_id,
|
|
|
|
|
|
session_id=dialogue_request.session_id,
|
|
|
|
|
|
request_id=request_id,
|
|
|
|
|
|
generation_id=generation_id,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
metrics_collector.start_session(dialogue_request.session_id)
|
|
|
|
|
|
|
|
|
|
|
|
tool_registry = get_tool_registry()
|
|
|
|
|
|
ensure_kb_search_dynamic_registered(tool_registry, session)
|
|
|
|
|
|
ensure_intent_hint_registered(tool_registry, session)
|
|
|
|
|
|
ensure_high_risk_check_registered(tool_registry, session)
|
|
|
|
|
|
ensure_memory_recall_registered(tool_registry, session)
|
2026-03-10 04:06:57 +00:00
|
|
|
|
ensure_metadata_discovery_registered(tool_registry, session)
|
feat: implement mid-platform core services and APIs [AC-IDMP-01~20, AC-MARH-01~12]
- Add dialogue, messages, sessions, share API endpoints
- Add mid-platform schemas and models (memory, tool_registry, tool_trace)
- Add core services: agent_orchestrator, policy_router, runtime_observer
- Add tool services: kb_search_dynamic, memory_adapter, tool_registry
- Add guardrail services: output_guardrail_executor, high_risk_handler
- Add utility services: timeout_governor, segment_humanizer, metrics_collector
2026-03-05 10:13:34 +00:00
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
interrupt_ctx = interrupt_context_enricher.enrich(
|
|
|
|
|
|
dialogue_request.interrupted_segments,
|
|
|
|
|
|
generation_id,
|
|
|
|
|
|
)
|
|
|
|
|
|
runtime_observer.record_interrupt(request_id, interrupt_ctx.consumed)
|
|
|
|
|
|
|
|
|
|
|
|
feature_flags = dialogue_request.feature_flags or feature_flag_service.get_flags(
|
|
|
|
|
|
dialogue_request.session_id
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if feature_flags.rollback_to_legacy:
|
|
|
|
|
|
logger.info(f"[AC-MARH-17] Rollback to legacy for session: {dialogue_request.session_id}")
|
|
|
|
|
|
return await _handle_legacy_response(
|
|
|
|
|
|
tenant_id=tenant_id,
|
|
|
|
|
|
request=dialogue_request,
|
|
|
|
|
|
trace=trace,
|
|
|
|
|
|
trace_logger=trace_logger,
|
|
|
|
|
|
start_time=start_time,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
high_risk_check_tool = HighRiskCheckTool(
|
|
|
|
|
|
session=session,
|
|
|
|
|
|
config=HighRiskCheckConfig(enabled=True, timeout_ms=500),
|
|
|
|
|
|
)
|
|
|
|
|
|
high_risk_result = await high_risk_check_tool.execute(
|
|
|
|
|
|
message=dialogue_request.user_message,
|
|
|
|
|
|
tenant_id=tenant_id,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if high_risk_result.duration_ms > 0:
|
|
|
|
|
|
hr_trace = high_risk_check_tool.create_trace(high_risk_result, tenant_id)
|
|
|
|
|
|
trace_logger.update_trace(
|
|
|
|
|
|
request_id=request_id,
|
|
|
|
|
|
tool_calls=[hr_trace],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
f"[AC-IDMP-05, AC-IDMP-20] High risk check result: "
|
|
|
|
|
|
f"matched={high_risk_result.matched}, scenario={high_risk_result.risk_scenario}, "
|
|
|
|
|
|
f"duration_ms={high_risk_result.duration_ms}"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if high_risk_result.matched and high_risk_result.risk_scenario:
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
f"[AC-IDMP-05] High-risk matched from tool: {high_risk_result.risk_scenario.value}"
|
|
|
|
|
|
)
|
|
|
|
|
|
return await _handle_high_risk_check_response(
|
|
|
|
|
|
tenant_id=tenant_id,
|
|
|
|
|
|
request=dialogue_request,
|
|
|
|
|
|
high_risk_result=high_risk_result,
|
|
|
|
|
|
trace=trace,
|
|
|
|
|
|
trace_logger=trace_logger,
|
|
|
|
|
|
start_time=start_time,
|
|
|
|
|
|
session=session,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
intent_hint_tool = IntentHintTool(
|
|
|
|
|
|
session=session,
|
|
|
|
|
|
config=IntentHintConfig(enabled=True, timeout_ms=500),
|
|
|
|
|
|
)
|
|
|
|
|
|
intent_hint = await intent_hint_tool.execute(
|
|
|
|
|
|
message=dialogue_request.user_message,
|
|
|
|
|
|
tenant_id=tenant_id,
|
|
|
|
|
|
history=[h.model_dump() for h in dialogue_request.history] if dialogue_request.history else None,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if intent_hint.duration_ms > 0:
|
|
|
|
|
|
hint_trace = intent_hint_tool.create_trace(intent_hint)
|
|
|
|
|
|
trace_logger.update_trace(
|
|
|
|
|
|
request_id=request_id,
|
|
|
|
|
|
tool_calls=[hint_trace],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
f"[AC-IDMP-02] Intent hint result: intent={intent_hint.intent}, "
|
|
|
|
|
|
f"confidence={intent_hint.confidence}, suggested_mode={intent_hint.suggested_mode}"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
intent_match = await _match_intent(tenant_id, dialogue_request, session)
|
|
|
|
|
|
|
|
|
|
|
|
router_result = policy_router.route(
|
|
|
|
|
|
user_message=dialogue_request.user_message,
|
|
|
|
|
|
session_mode="BOT_ACTIVE",
|
|
|
|
|
|
feature_flags=feature_flags,
|
|
|
|
|
|
intent_match=intent_match,
|
|
|
|
|
|
intent_hint=intent_hint,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
runtime_observer.update_mode(request_id, router_result.mode, router_result.intent)
|
|
|
|
|
|
runtime_observer.record_timeout_profile(request_id, timeout_governor.profile)
|
|
|
|
|
|
|
|
|
|
|
|
trace_logger.update_trace(
|
|
|
|
|
|
request_id=request_id,
|
|
|
|
|
|
mode=router_result.mode,
|
|
|
|
|
|
intent=router_result.intent,
|
|
|
|
|
|
fallback_reason_code=router_result.fallback_reason_code,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if router_result.mode == ExecutionMode.AGENT:
|
|
|
|
|
|
response = await _execute_agent_mode(
|
|
|
|
|
|
tenant_id=tenant_id,
|
|
|
|
|
|
request=dialogue_request,
|
|
|
|
|
|
request_id=request_id,
|
|
|
|
|
|
trace=trace,
|
|
|
|
|
|
trace_logger=trace_logger,
|
|
|
|
|
|
timeout_governor=timeout_governor,
|
|
|
|
|
|
metrics_collector=metrics_collector,
|
|
|
|
|
|
default_kb_tool_runner=default_kb_tool_runner,
|
|
|
|
|
|
runtime_observer=runtime_observer,
|
|
|
|
|
|
interrupt_ctx=interrupt_ctx,
|
|
|
|
|
|
start_time=start_time,
|
|
|
|
|
|
session=session,
|
|
|
|
|
|
tool_registry=tool_registry,
|
|
|
|
|
|
)
|
|
|
|
|
|
elif router_result.mode == ExecutionMode.MICRO_FLOW:
|
|
|
|
|
|
response = await _execute_micro_flow_mode(
|
|
|
|
|
|
tenant_id=tenant_id,
|
|
|
|
|
|
request=dialogue_request,
|
|
|
|
|
|
router_result=router_result,
|
|
|
|
|
|
trace=trace,
|
|
|
|
|
|
trace_logger=trace_logger,
|
|
|
|
|
|
session=session,
|
|
|
|
|
|
start_time=start_time,
|
|
|
|
|
|
)
|
|
|
|
|
|
elif router_result.mode == ExecutionMode.FIXED:
|
|
|
|
|
|
response = await _execute_fixed_mode(
|
|
|
|
|
|
tenant_id=tenant_id,
|
|
|
|
|
|
request=dialogue_request,
|
|
|
|
|
|
router_result=router_result,
|
|
|
|
|
|
trace=trace,
|
|
|
|
|
|
trace_logger=trace_logger,
|
|
|
|
|
|
start_time=start_time,
|
|
|
|
|
|
)
|
|
|
|
|
|
else:
|
|
|
|
|
|
response = await _execute_transfer_mode(
|
|
|
|
|
|
tenant_id=tenant_id,
|
|
|
|
|
|
request=dialogue_request,
|
|
|
|
|
|
router_result=router_result,
|
|
|
|
|
|
trace=trace,
|
|
|
|
|
|
trace_logger=trace_logger,
|
|
|
|
|
|
start_time=start_time,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
filtered_segments, guardrail_result = await output_guardrail_executor.filter_segments(
|
|
|
|
|
|
response.segments, tenant_id
|
|
|
|
|
|
)
|
|
|
|
|
|
runtime_observer.record_guardrail(
|
|
|
|
|
|
request_id, guardrail_result.triggered, guardrail_result.rule_id
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
humanize_config = None
|
|
|
|
|
|
if dialogue_request.humanize_config:
|
|
|
|
|
|
humanize_config = HumanizeConfig(
|
|
|
|
|
|
enabled=dialogue_request.humanize_config.enabled or True,
|
|
|
|
|
|
min_delay_ms=dialogue_request.humanize_config.min_delay_ms or 50,
|
|
|
|
|
|
max_delay_ms=dialogue_request.humanize_config.max_delay_ms or 500,
|
|
|
|
|
|
length_bucket_strategy=dialogue_request.humanize_config.length_bucket_strategy or "simple",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
final_segments, segment_stats = segment_humanizer.humanize(
|
|
|
|
|
|
"\n\n".join(s.text for s in filtered_segments),
|
|
|
|
|
|
humanize_config,
|
|
|
|
|
|
)
|
|
|
|
|
|
runtime_observer.record_segment_stats(request_id, segment_stats)
|
|
|
|
|
|
|
|
|
|
|
|
final_trace = runtime_observer.end_observation(request_id)
|
|
|
|
|
|
final_trace.segment_stats = segment_stats
|
|
|
|
|
|
final_trace.guardrail_triggered = guardrail_result.triggered
|
|
|
|
|
|
final_trace.guardrail_rule_id = guardrail_result.rule_id
|
|
|
|
|
|
|
|
|
|
|
|
latency_ms = int((time.time() - start_time) * 1000)
|
|
|
|
|
|
|
|
|
|
|
|
metrics_collector.record_turn(
|
|
|
|
|
|
session_id=dialogue_request.session_id,
|
|
|
|
|
|
tenant_id=tenant_id,
|
|
|
|
|
|
latency_ms=latency_ms,
|
|
|
|
|
|
task_completed=True,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
audit = trace_logger.end_trace(
|
|
|
|
|
|
request_id=request_id,
|
|
|
|
|
|
tenant_id=tenant_id,
|
|
|
|
|
|
session_id=dialogue_request.session_id,
|
|
|
|
|
|
latency_ms=latency_ms,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
f"[AC-MARH-12] Audit record: request_id={request_id}, "
|
|
|
|
|
|
f"mode={final_trace.mode.value}, latency_ms={latency_ms}, "
|
|
|
|
|
|
f"guardrail={guardrail_result.triggered}, kb_hit={final_trace.kb_hit}"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return DialogueResponse(
|
|
|
|
|
|
segments=final_segments,
|
|
|
|
|
|
trace=final_trace,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
latency_ms = int((time.time() - start_time) * 1000)
|
2026-03-10 04:06:57 +00:00
|
|
|
|
import traceback
|
|
|
|
|
|
logger.error(
|
|
|
|
|
|
f"[AC-IDMP-06] Dialogue error: {e}\n"
|
|
|
|
|
|
f"Exception type: {type(e).__name__}\n"
|
|
|
|
|
|
f"Request details: session_id={dialogue_request.session_id}, "
|
|
|
|
|
|
f"user_message={dialogue_request.user_message[:200] if dialogue_request.user_message else 'None'}, "
|
|
|
|
|
|
f"scene={dialogue_request.scene}, user_id={dialogue_request.user_id}\n"
|
|
|
|
|
|
f"Traceback:\n{traceback.format_exc()}"
|
|
|
|
|
|
)
|
feat: implement mid-platform core services and APIs [AC-IDMP-01~20, AC-MARH-01~12]
- Add dialogue, messages, sessions, share API endpoints
- Add mid-platform schemas and models (memory, tool_registry, tool_trace)
- Add core services: agent_orchestrator, policy_router, runtime_observer
- Add tool services: kb_search_dynamic, memory_adapter, tool_registry
- Add guardrail services: output_guardrail_executor, high_risk_handler
- Add utility services: timeout_governor, segment_humanizer, metrics_collector
2026-03-05 10:13:34 +00:00
|
|
|
|
|
|
|
|
|
|
trace_logger.update_trace(
|
|
|
|
|
|
request_id=request_id,
|
|
|
|
|
|
mode=ExecutionMode.FIXED,
|
|
|
|
|
|
fallback_reason_code=f"error: {str(e)[:50]}",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
trace_logger.end_trace(
|
|
|
|
|
|
request_id=request_id,
|
|
|
|
|
|
tenant_id=tenant_id,
|
|
|
|
|
|
session_id=dialogue_request.session_id,
|
|
|
|
|
|
latency_ms=latency_ms,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return DialogueResponse(
|
|
|
|
|
|
segments=[Segment(
|
|
|
|
|
|
text="抱歉,服务暂时不可用,请稍后重试或联系人工客服。",
|
|
|
|
|
|
delay_after=0,
|
|
|
|
|
|
)],
|
|
|
|
|
|
trace=TraceInfo(
|
|
|
|
|
|
mode=ExecutionMode.FIXED,
|
|
|
|
|
|
request_id=request_id,
|
|
|
|
|
|
generation_id=generation_id,
|
|
|
|
|
|
fallback_reason_code="service_error",
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def _match_intent(
|
|
|
|
|
|
tenant_id: str,
|
|
|
|
|
|
request: DialogueRequest,
|
|
|
|
|
|
session: AsyncSession,
|
|
|
|
|
|
) -> IntentMatch | None:
|
|
|
|
|
|
"""Match intent from user message."""
|
|
|
|
|
|
try:
|
|
|
|
|
|
from app.services.intent.router import IntentRouter
|
|
|
|
|
|
from app.services.intent.rule_service import IntentRuleService
|
|
|
|
|
|
|
|
|
|
|
|
rule_service = IntentRuleService(session)
|
|
|
|
|
|
rules = await rule_service.get_enabled_rules_for_matching(tenant_id)
|
|
|
|
|
|
|
|
|
|
|
|
if not rules:
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
router = IntentRouter()
|
|
|
|
|
|
result = router.match(request.user_message, rules)
|
|
|
|
|
|
|
|
|
|
|
|
if result:
|
|
|
|
|
|
return IntentMatch(
|
|
|
|
|
|
intent_id=str(result.rule.id),
|
|
|
|
|
|
intent_name=result.rule.name,
|
2026-03-10 04:06:57 +00:00
|
|
|
|
confidence=1.0,
|
feat: implement mid-platform core services and APIs [AC-IDMP-01~20, AC-MARH-01~12]
- Add dialogue, messages, sessions, share API endpoints
- Add mid-platform schemas and models (memory, tool_registry, tool_trace)
- Add core services: agent_orchestrator, policy_router, runtime_observer
- Add tool services: kb_search_dynamic, memory_adapter, tool_registry
- Add guardrail services: output_guardrail_executor, high_risk_handler
- Add utility services: timeout_governor, segment_humanizer, metrics_collector
2026-03-05 10:13:34 +00:00
|
|
|
|
response_type=result.rule.response_type,
|
|
|
|
|
|
target_kb_ids=result.rule.target_kb_ids,
|
|
|
|
|
|
flow_id=str(result.rule.flow_id) if result.rule.flow_id else None,
|
|
|
|
|
|
fixed_reply=result.rule.fixed_reply,
|
|
|
|
|
|
transfer_message=result.rule.transfer_message,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.warning(f"[AC-IDMP-02] Intent match failed: {e}")
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-10 04:06:57 +00:00
|
|
|
|
async def _match_intent_hybrid(
|
|
|
|
|
|
tenant_id: str,
|
|
|
|
|
|
request: DialogueRequest,
|
|
|
|
|
|
session: AsyncSession,
|
|
|
|
|
|
clarification_engine: ClarificationEngine,
|
|
|
|
|
|
) -> HybridIntentResult:
|
|
|
|
|
|
"""
|
|
|
|
|
|
[AC-CLARIFY] Hybrid intent matching with clarification support.
|
|
|
|
|
|
|
|
|
|
|
|
Returns HybridIntentResult with unified confidence and candidates.
|
|
|
|
|
|
"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
from app.services.intent.router import IntentRouter
|
|
|
|
|
|
from app.services.intent.rule_service import IntentRuleService
|
|
|
|
|
|
|
|
|
|
|
|
rule_service = IntentRuleService(session)
|
|
|
|
|
|
rules = await rule_service.get_enabled_rules_for_matching(tenant_id)
|
|
|
|
|
|
|
|
|
|
|
|
if not rules:
|
|
|
|
|
|
return HybridIntentResult(
|
|
|
|
|
|
intent=None,
|
|
|
|
|
|
confidence=0.0,
|
|
|
|
|
|
candidates=[],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
router = IntentRouter()
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
fusion_result = await router.match_hybrid(
|
|
|
|
|
|
message=request.user_message,
|
|
|
|
|
|
rules=rules,
|
|
|
|
|
|
tenant_id=tenant_id,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
hybrid_result = HybridIntentResult.from_fusion_result(fusion_result)
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
f"[AC-CLARIFY] Hybrid intent match: "
|
|
|
|
|
|
f"intent={hybrid_result.intent.intent_name if hybrid_result.intent else None}, "
|
|
|
|
|
|
f"confidence={hybrid_result.confidence:.3f}, "
|
|
|
|
|
|
f"need_clarify={hybrid_result.need_clarify}"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return hybrid_result
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.warning(f"[AC-CLARIFY] Hybrid match failed, fallback to rule: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
result = router.match(request.user_message, rules)
|
|
|
|
|
|
|
|
|
|
|
|
if result:
|
|
|
|
|
|
confidence = clarification_engine.compute_confidence(
|
|
|
|
|
|
rule_score=1.0,
|
|
|
|
|
|
semantic_score=0.0,
|
|
|
|
|
|
llm_score=0.0,
|
|
|
|
|
|
)
|
|
|
|
|
|
candidate = IntentCandidate(
|
|
|
|
|
|
intent_id=str(result.rule.id),
|
|
|
|
|
|
intent_name=result.rule.name,
|
|
|
|
|
|
confidence=confidence,
|
|
|
|
|
|
response_type=result.rule.response_type,
|
|
|
|
|
|
target_kb_ids=result.rule.target_kb_ids,
|
|
|
|
|
|
flow_id=str(result.rule.flow_id) if result.rule.flow_id else None,
|
|
|
|
|
|
fixed_reply=result.rule.fixed_reply,
|
|
|
|
|
|
transfer_message=result.rule.transfer_message,
|
|
|
|
|
|
)
|
|
|
|
|
|
return HybridIntentResult(
|
|
|
|
|
|
intent=candidate,
|
|
|
|
|
|
confidence=confidence,
|
|
|
|
|
|
candidates=[candidate],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return HybridIntentResult(
|
|
|
|
|
|
intent=None,
|
|
|
|
|
|
confidence=0.0,
|
|
|
|
|
|
candidates=[],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.warning(f"[AC-CLARIFY] Intent match failed: {e}")
|
|
|
|
|
|
return HybridIntentResult(
|
|
|
|
|
|
intent=None,
|
|
|
|
|
|
confidence=0.0,
|
|
|
|
|
|
candidates=[],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def _handle_clarification(
|
|
|
|
|
|
tenant_id: str,
|
|
|
|
|
|
request: DialogueRequest,
|
|
|
|
|
|
hybrid_result: HybridIntentResult,
|
|
|
|
|
|
clarification_engine: ClarificationEngine,
|
|
|
|
|
|
trace: TraceInfo,
|
|
|
|
|
|
session: AsyncSession,
|
|
|
|
|
|
required_slots: list[str] | None = None,
|
|
|
|
|
|
filled_slots: dict[str, Any] | None = None,
|
|
|
|
|
|
) -> DialogueResponse | None:
|
|
|
|
|
|
"""
|
|
|
|
|
|
[AC-CLARIFY] Handle clarification logic.
|
|
|
|
|
|
|
|
|
|
|
|
Returns DialogueResponse if clarification is needed, None otherwise.
|
|
|
|
|
|
"""
|
|
|
|
|
|
existing_state = ClarifySessionManager.get_session(request.session_id)
|
|
|
|
|
|
|
|
|
|
|
|
if existing_state and not existing_state.is_max_retry():
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
f"[AC-CLARIFY] Processing clarify response: "
|
|
|
|
|
|
f"session={request.session_id}, retry={existing_state.retry_count}"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
new_result = clarification_engine.process_clarify_response(
|
|
|
|
|
|
user_message=request.user_message,
|
|
|
|
|
|
state=existing_state,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if not new_result.need_clarify:
|
|
|
|
|
|
ClarifySessionManager.clear_session(request.session_id)
|
|
|
|
|
|
|
|
|
|
|
|
if new_result.intent:
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
clarify_prompt = clarification_engine.generate_clarify_prompt(existing_state)
|
|
|
|
|
|
|
|
|
|
|
|
return DialogueResponse(
|
|
|
|
|
|
segments=[Segment(text=clarify_prompt, delay_after=0)],
|
|
|
|
|
|
trace=TraceInfo(
|
|
|
|
|
|
mode=ExecutionMode.FIXED,
|
|
|
|
|
|
request_id=trace.request_id,
|
|
|
|
|
|
generation_id=trace.generation_id,
|
|
|
|
|
|
fallback_reason_code=f"clarify_retry_{existing_state.retry_count}",
|
|
|
|
|
|
intent=existing_state.candidates[0].intent_name if existing_state.candidates else None,
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
should_clarify, clarify_state = clarification_engine.should_trigger_clarify(
|
|
|
|
|
|
result=hybrid_result,
|
|
|
|
|
|
required_slots=required_slots,
|
|
|
|
|
|
filled_slots=filled_slots,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if not should_clarify or not clarify_state:
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
f"[AC-CLARIFY] Clarification triggered: "
|
|
|
|
|
|
f"session={request.session_id}, reason={clarify_state.reason}, "
|
|
|
|
|
|
f"confidence={hybrid_result.confidence:.3f}"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
ClarifySessionManager.set_session(request.session_id, clarify_state)
|
|
|
|
|
|
|
|
|
|
|
|
clarify_prompt = clarification_engine.generate_clarify_prompt(clarify_state)
|
|
|
|
|
|
|
|
|
|
|
|
return DialogueResponse(
|
|
|
|
|
|
segments=[Segment(text=clarify_prompt, delay_after=0)],
|
|
|
|
|
|
trace=TraceInfo(
|
|
|
|
|
|
mode=ExecutionMode.FIXED,
|
|
|
|
|
|
request_id=trace.request_id,
|
|
|
|
|
|
generation_id=trace.generation_id,
|
|
|
|
|
|
fallback_reason_code=f"clarify_{clarify_state.reason.value}",
|
|
|
|
|
|
intent=hybrid_result.intent.intent_name if hybrid_result.intent else None,
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def _check_hard_block(
|
|
|
|
|
|
tenant_id: str,
|
|
|
|
|
|
request: DialogueRequest,
|
|
|
|
|
|
hybrid_result: HybridIntentResult,
|
|
|
|
|
|
clarification_engine: ClarificationEngine,
|
|
|
|
|
|
trace: TraceInfo,
|
|
|
|
|
|
required_slots: list[str] | None = None,
|
|
|
|
|
|
filled_slots: dict[str, Any] | None = None,
|
|
|
|
|
|
) -> DialogueResponse | None:
|
|
|
|
|
|
"""
|
|
|
|
|
|
[AC-CLARIFY] Check hard block conditions.
|
|
|
|
|
|
|
|
|
|
|
|
Hard blocks:
|
|
|
|
|
|
1. confidence < T_high: block entering new flow
|
|
|
|
|
|
2. required_slots missing: block flow progression
|
|
|
|
|
|
|
|
|
|
|
|
Returns DialogueResponse if blocked, None otherwise.
|
|
|
|
|
|
"""
|
|
|
|
|
|
is_blocked, block_reason = clarification_engine.check_hard_block(
|
|
|
|
|
|
result=hybrid_result,
|
|
|
|
|
|
required_slots=required_slots,
|
|
|
|
|
|
filled_slots=filled_slots,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if not is_blocked:
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
f"[AC-CLARIFY] Hard block triggered: "
|
|
|
|
|
|
f"session={request.session_id}, reason={block_reason}, "
|
|
|
|
|
|
f"confidence={hybrid_result.confidence:.3f}"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
clarify_state = ClarifyState(
|
|
|
|
|
|
reason=block_reason,
|
|
|
|
|
|
asked_slot=required_slots[0] if required_slots and block_reason == ClarifyReason.MISSING_SLOT else None,
|
|
|
|
|
|
candidates=hybrid_result.candidates,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
ClarifySessionManager.set_session(request.session_id, clarify_state)
|
|
|
|
|
|
clarification_engine._metrics.record_clarify_trigger()
|
|
|
|
|
|
|
|
|
|
|
|
clarify_prompt = clarification_engine.generate_clarify_prompt(clarify_state)
|
|
|
|
|
|
|
|
|
|
|
|
return DialogueResponse(
|
|
|
|
|
|
segments=[Segment(text=clarify_prompt, delay_after=0)],
|
|
|
|
|
|
trace=TraceInfo(
|
|
|
|
|
|
mode=ExecutionMode.FIXED,
|
|
|
|
|
|
request_id=trace.request_id,
|
|
|
|
|
|
generation_id=trace.generation_id,
|
|
|
|
|
|
fallback_reason_code=f"hard_block_{block_reason.value}",
|
|
|
|
|
|
intent=hybrid_result.intent.intent_name if hybrid_result.intent else None,
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
feat: implement mid-platform core services and APIs [AC-IDMP-01~20, AC-MARH-01~12]
- Add dialogue, messages, sessions, share API endpoints
- Add mid-platform schemas and models (memory, tool_registry, tool_trace)
- Add core services: agent_orchestrator, policy_router, runtime_observer
- Add tool services: kb_search_dynamic, memory_adapter, tool_registry
- Add guardrail services: output_guardrail_executor, high_risk_handler
- Add utility services: timeout_governor, segment_humanizer, metrics_collector
2026-03-05 10:13:34 +00:00
|
|
|
|
async def _handle_legacy_response(
|
|
|
|
|
|
tenant_id: str,
|
|
|
|
|
|
request: DialogueRequest,
|
|
|
|
|
|
trace: TraceInfo,
|
|
|
|
|
|
trace_logger: TraceLogger,
|
|
|
|
|
|
start_time: float,
|
|
|
|
|
|
) -> DialogueResponse:
|
|
|
|
|
|
"""Handle rollback to legacy pipeline."""
|
|
|
|
|
|
latency_ms = int((time.time() - start_time) * 1000)
|
|
|
|
|
|
|
|
|
|
|
|
return DialogueResponse(
|
|
|
|
|
|
segments=[Segment(
|
|
|
|
|
|
text="正在使用传统模式处理您的请求...",
|
|
|
|
|
|
delay_after=0,
|
|
|
|
|
|
)],
|
|
|
|
|
|
trace=TraceInfo(
|
|
|
|
|
|
mode=ExecutionMode.FIXED,
|
|
|
|
|
|
request_id=trace.request_id,
|
|
|
|
|
|
generation_id=trace.generation_id,
|
|
|
|
|
|
fallback_reason_code="rollback_to_legacy",
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def _handle_high_risk_check_response(
|
|
|
|
|
|
tenant_id: str,
|
|
|
|
|
|
request: DialogueRequest,
|
|
|
|
|
|
high_risk_result: Any,
|
|
|
|
|
|
trace: TraceInfo,
|
|
|
|
|
|
trace_logger: TraceLogger,
|
|
|
|
|
|
start_time: float,
|
|
|
|
|
|
session: AsyncSession,
|
|
|
|
|
|
) -> DialogueResponse:
|
|
|
|
|
|
"""
|
|
|
|
|
|
[AC-IDMP-05, AC-IDMP-20] Handle high-risk scenario from high_risk_check tool.
|
|
|
|
|
|
|
|
|
|
|
|
高风险优先于普通意图路由。
|
|
|
|
|
|
"""
|
|
|
|
|
|
from app.models.mid.schemas import HighRiskCheckResult
|
|
|
|
|
|
|
|
|
|
|
|
if not isinstance(high_risk_result, HighRiskCheckResult):
|
|
|
|
|
|
high_risk_result = HighRiskCheckResult(**high_risk_result)
|
|
|
|
|
|
|
|
|
|
|
|
latency_ms = int((time.time() - start_time) * 1000)
|
|
|
|
|
|
|
|
|
|
|
|
recommended_mode = high_risk_result.recommended_mode or ExecutionMode.MICRO_FLOW
|
|
|
|
|
|
risk_scenario = high_risk_result.risk_scenario
|
|
|
|
|
|
|
|
|
|
|
|
trace_logger.update_trace(
|
|
|
|
|
|
request_id=trace.request_id or "",
|
|
|
|
|
|
mode=recommended_mode,
|
|
|
|
|
|
fallback_reason_code=f"high_risk_{risk_scenario.value if risk_scenario else 'unknown'}",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if recommended_mode == ExecutionMode.TRANSFER:
|
|
|
|
|
|
transfer_msg = "正在为您转接人工客服..."
|
|
|
|
|
|
if risk_scenario:
|
|
|
|
|
|
if risk_scenario.value == "complaint_escalation":
|
|
|
|
|
|
transfer_msg = "检测到您可能需要投诉处理,正在为您转接人工客服..."
|
|
|
|
|
|
elif risk_scenario.value == "refund":
|
|
|
|
|
|
transfer_msg = "您的退款请求需要人工处理,正在为您转接..."
|
|
|
|
|
|
|
|
|
|
|
|
return DialogueResponse(
|
|
|
|
|
|
segments=[Segment(
|
|
|
|
|
|
text=transfer_msg,
|
|
|
|
|
|
delay_after=0,
|
|
|
|
|
|
)],
|
|
|
|
|
|
trace=TraceInfo(
|
|
|
|
|
|
mode=ExecutionMode.TRANSFER,
|
|
|
|
|
|
request_id=trace.request_id,
|
|
|
|
|
|
generation_id=trace.generation_id,
|
|
|
|
|
|
high_risk_policy_set=[risk_scenario] if risk_scenario else None,
|
|
|
|
|
|
fallback_reason_code=high_risk_result.rule_id,
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if high_risk_result.rule_id:
|
|
|
|
|
|
try:
|
|
|
|
|
|
from sqlalchemy import select
|
|
|
|
|
|
from app.models.entities import HighRiskPolicy
|
|
|
|
|
|
import uuid
|
|
|
|
|
|
|
|
|
|
|
|
stmt = select(HighRiskPolicy).where(
|
|
|
|
|
|
HighRiskPolicy.id == uuid.UUID(high_risk_result.rule_id)
|
|
|
|
|
|
)
|
|
|
|
|
|
result = await session.execute(stmt)
|
|
|
|
|
|
policy = result.scalar_one_or_none()
|
|
|
|
|
|
|
|
|
|
|
|
if policy and policy.flow_id:
|
|
|
|
|
|
return DialogueResponse(
|
|
|
|
|
|
segments=[Segment(
|
|
|
|
|
|
text="检测到您的请求需要特殊处理,正在为您安排...",
|
|
|
|
|
|
delay_after=0,
|
|
|
|
|
|
)],
|
|
|
|
|
|
trace=TraceInfo(
|
|
|
|
|
|
mode=ExecutionMode.MICRO_FLOW,
|
|
|
|
|
|
request_id=trace.request_id,
|
|
|
|
|
|
generation_id=trace.generation_id,
|
|
|
|
|
|
high_risk_policy_set=[risk_scenario] if risk_scenario else None,
|
|
|
|
|
|
fallback_reason_code=high_risk_result.rule_id,
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.warning(f"[AC-IDMP-05] Failed to load high risk policy: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
return DialogueResponse(
|
|
|
|
|
|
segments=[Segment(
|
|
|
|
|
|
text="检测到您的请求需要特殊处理,正在为您安排...",
|
|
|
|
|
|
delay_after=0,
|
|
|
|
|
|
)],
|
|
|
|
|
|
trace=TraceInfo(
|
|
|
|
|
|
mode=ExecutionMode.MICRO_FLOW,
|
|
|
|
|
|
request_id=trace.request_id,
|
|
|
|
|
|
generation_id=trace.generation_id,
|
|
|
|
|
|
high_risk_policy_set=[risk_scenario] if risk_scenario else None,
|
|
|
|
|
|
fallback_reason_code=high_risk_result.rule_id or f"high_risk_{risk_scenario.value if risk_scenario else 'unknown'}",
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def _handle_high_risk_response(
|
|
|
|
|
|
tenant_id: str,
|
|
|
|
|
|
request: DialogueRequest,
|
|
|
|
|
|
high_risk_match: Any,
|
|
|
|
|
|
high_risk_handler: HighRiskHandler,
|
|
|
|
|
|
trace: TraceInfo,
|
|
|
|
|
|
trace_logger: TraceLogger,
|
|
|
|
|
|
start_time: float,
|
|
|
|
|
|
) -> DialogueResponse:
|
|
|
|
|
|
"""Handle high-risk scenario response."""
|
|
|
|
|
|
router_result = high_risk_handler.handle(high_risk_match)
|
|
|
|
|
|
|
|
|
|
|
|
latency_ms = int((time.time() - start_time) * 1000)
|
|
|
|
|
|
|
|
|
|
|
|
trace_logger.update_trace(
|
|
|
|
|
|
request_id=trace.request_id or "",
|
|
|
|
|
|
mode=router_result.mode,
|
|
|
|
|
|
fallback_reason_code=router_result.fallback_reason_code,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if router_result.mode == ExecutionMode.TRANSFER:
|
|
|
|
|
|
return DialogueResponse(
|
|
|
|
|
|
segments=[Segment(
|
|
|
|
|
|
text=router_result.transfer_message or "正在为您转接人工客服...",
|
|
|
|
|
|
delay_after=0,
|
|
|
|
|
|
)],
|
|
|
|
|
|
trace=TraceInfo(
|
|
|
|
|
|
mode=ExecutionMode.TRANSFER,
|
|
|
|
|
|
request_id=trace.request_id,
|
|
|
|
|
|
generation_id=trace.generation_id,
|
|
|
|
|
|
high_risk_policy_set=[high_risk_match.scenario],
|
|
|
|
|
|
fallback_reason_code=router_result.fallback_reason_code,
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return DialogueResponse(
|
|
|
|
|
|
segments=[Segment(
|
|
|
|
|
|
text="检测到您的请求需要特殊处理,正在为您安排...",
|
|
|
|
|
|
delay_after=0,
|
|
|
|
|
|
)],
|
|
|
|
|
|
trace=TraceInfo(
|
|
|
|
|
|
mode=ExecutionMode.MICRO_FLOW,
|
|
|
|
|
|
request_id=trace.request_id,
|
|
|
|
|
|
generation_id=trace.generation_id,
|
|
|
|
|
|
high_risk_policy_set=[high_risk_match.scenario],
|
|
|
|
|
|
fallback_reason_code=router_result.fallback_reason_code,
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def _execute_agent_mode(
|
|
|
|
|
|
tenant_id: str,
|
|
|
|
|
|
request: DialogueRequest,
|
|
|
|
|
|
request_id: str,
|
|
|
|
|
|
trace: TraceInfo,
|
|
|
|
|
|
trace_logger: TraceLogger,
|
|
|
|
|
|
timeout_governor: TimeoutGovernor,
|
|
|
|
|
|
metrics_collector: MetricsCollector,
|
|
|
|
|
|
default_kb_tool_runner: DefaultKbToolRunner,
|
|
|
|
|
|
runtime_observer: RuntimeObserver,
|
|
|
|
|
|
interrupt_ctx: Any = None,
|
|
|
|
|
|
start_time: float = 0,
|
|
|
|
|
|
session: AsyncSession | None = None,
|
|
|
|
|
|
tool_registry: ToolRegistry | None = None,
|
|
|
|
|
|
) -> DialogueResponse:
|
2026-03-10 04:06:57 +00:00
|
|
|
|
"""
|
|
|
|
|
|
[AC-MARH-05, AC-MARH-06, AC-MARH-07, AC-IDMP-13, AC-MRS-SLOT-META-03]
|
|
|
|
|
|
Execute agent mode with ReAct loop, KB tool, memory recall, and slot state aggregation.
|
|
|
|
|
|
"""
|
feat: implement mid-platform core services and APIs [AC-IDMP-01~20, AC-MARH-01~12]
- Add dialogue, messages, sessions, share API endpoints
- Add mid-platform schemas and models (memory, tool_registry, tool_trace)
- Add core services: agent_orchestrator, policy_router, runtime_observer
- Add tool services: kb_search_dynamic, memory_adapter, tool_registry
- Add guardrail services: output_guardrail_executor, high_risk_handler
- Add utility services: timeout_governor, segment_humanizer, metrics_collector
2026-03-05 10:13:34 +00:00
|
|
|
|
from app.services.llm.factory import get_llm_config_manager
|
2026-03-10 04:06:57 +00:00
|
|
|
|
from app.services.mid.slot_state_aggregator import SlotStateAggregator
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
f"[DEBUG-AGENT] Starting _execute_agent_mode: tenant={tenant_id}, "
|
|
|
|
|
|
f"session={request.session_id}, scene={request.scene}, user_id={request.user_id}, "
|
|
|
|
|
|
f"user_message={request.user_message[:100] if request.user_message else 'None'}..."
|
|
|
|
|
|
)
|
feat: implement mid-platform core services and APIs [AC-IDMP-01~20, AC-MARH-01~12]
- Add dialogue, messages, sessions, share API endpoints
- Add mid-platform schemas and models (memory, tool_registry, tool_trace)
- Add core services: agent_orchestrator, policy_router, runtime_observer
- Add tool services: kb_search_dynamic, memory_adapter, tool_registry
- Add guardrail services: output_guardrail_executor, high_risk_handler
- Add utility services: timeout_governor, segment_humanizer, metrics_collector
2026-03-05 10:13:34 +00:00
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
llm_manager = get_llm_config_manager()
|
|
|
|
|
|
llm_client = llm_manager.get_client()
|
2026-03-10 04:06:57 +00:00
|
|
|
|
logger.info(f"[DEBUG-AGENT] LLM client obtained successfully")
|
feat: implement mid-platform core services and APIs [AC-IDMP-01~20, AC-MARH-01~12]
- Add dialogue, messages, sessions, share API endpoints
- Add mid-platform schemas and models (memory, tool_registry, tool_trace)
- Add core services: agent_orchestrator, policy_router, runtime_observer
- Add tool services: kb_search_dynamic, memory_adapter, tool_registry
- Add guardrail services: output_guardrail_executor, high_risk_handler
- Add utility services: timeout_governor, segment_humanizer, metrics_collector
2026-03-05 10:13:34 +00:00
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.warning(f"[AC-MARH-07] Failed to get LLM client: {e}")
|
|
|
|
|
|
llm_client = None
|
|
|
|
|
|
|
|
|
|
|
|
base_context = {"history": [h.model_dump() for h in request.history]} if request.history else {}
|
|
|
|
|
|
|
2026-03-10 04:06:57 +00:00
|
|
|
|
if request.scene:
|
|
|
|
|
|
base_context["scene"] = request.scene
|
|
|
|
|
|
|
feat: implement mid-platform core services and APIs [AC-IDMP-01~20, AC-MARH-01~12]
- Add dialogue, messages, sessions, share API endpoints
- Add mid-platform schemas and models (memory, tool_registry, tool_trace)
- Add core services: agent_orchestrator, policy_router, runtime_observer
- Add tool services: kb_search_dynamic, memory_adapter, tool_registry
- Add guardrail services: output_guardrail_executor, high_risk_handler
- Add utility services: timeout_governor, segment_humanizer, metrics_collector
2026-03-05 10:13:34 +00:00
|
|
|
|
if interrupt_ctx and interrupt_ctx.consumed:
|
|
|
|
|
|
base_context["interrupted_content"] = interrupt_ctx.interrupted_content
|
|
|
|
|
|
base_context["interrupted_segment_ids"] = interrupt_ctx.interrupted_segment_ids
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
f"[AC-MARH-03] Agent context enriched with interrupt: "
|
|
|
|
|
|
f"{len(interrupt_ctx.interrupted_content or '')} chars"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-03-10 04:06:57 +00:00
|
|
|
|
# [AC-MRS-SLOT-META-03] 初始化槽位状态聚合器
|
|
|
|
|
|
slot_state = None
|
feat: implement mid-platform core services and APIs [AC-IDMP-01~20, AC-MARH-01~12]
- Add dialogue, messages, sessions, share API endpoints
- Add mid-platform schemas and models (memory, tool_registry, tool_trace)
- Add core services: agent_orchestrator, policy_router, runtime_observer
- Add tool services: kb_search_dynamic, memory_adapter, tool_registry
- Add guardrail services: output_guardrail_executor, high_risk_handler
- Add utility services: timeout_governor, segment_humanizer, metrics_collector
2026-03-05 10:13:34 +00:00
|
|
|
|
memory_context = ""
|
|
|
|
|
|
memory_missing_slots: list[str] = []
|
2026-03-10 04:06:57 +00:00
|
|
|
|
scene_slot_context = None
|
|
|
|
|
|
|
|
|
|
|
|
flow_status = None
|
|
|
|
|
|
is_flow_runtime = False
|
|
|
|
|
|
if session:
|
|
|
|
|
|
from app.services.flow.engine import FlowEngine
|
|
|
|
|
|
flow_engine = FlowEngine(session)
|
|
|
|
|
|
flow_status = await flow_engine.get_flow_status(tenant_id, request.session_id)
|
|
|
|
|
|
is_flow_runtime = bool(flow_status and flow_status.get("status") == "active")
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
f"[AC-IDMP-02] 运行轨道判定: session={request.session_id}, "
|
|
|
|
|
|
f"is_flow_runtime={is_flow_runtime}, flow_id={flow_status.get('flow_id') if flow_status else None}, "
|
|
|
|
|
|
f"current_step={flow_status.get('current_step') if flow_status else None}"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# [AC-SCENE-SLOT-02] 加载场景槽位包(仅流程态加载)
|
|
|
|
|
|
if is_flow_runtime and session and request.scene:
|
|
|
|
|
|
from app.services.mid.scene_slot_bundle_loader import SceneSlotBundleLoader
|
|
|
|
|
|
|
|
|
|
|
|
scene_loader = SceneSlotBundleLoader(session)
|
|
|
|
|
|
scene_slot_context = await scene_loader.load_scene_context(
|
|
|
|
|
|
tenant_id=tenant_id,
|
|
|
|
|
|
scene_key=request.scene,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if scene_slot_context:
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
f"[AC-SCENE-SLOT-02] 场景槽位包加载成功: "
|
|
|
|
|
|
f"scene={request.scene}, required={len(scene_slot_context.required_slots)}, "
|
|
|
|
|
|
f"optional={len(scene_slot_context.optional_slots)}, "
|
|
|
|
|
|
f"threshold={scene_slot_context.completion_threshold}"
|
|
|
|
|
|
)
|
|
|
|
|
|
base_context["scene_slot_context"] = {
|
|
|
|
|
|
"scene_key": scene_slot_context.scene_key,
|
|
|
|
|
|
"scene_name": scene_slot_context.scene_name,
|
|
|
|
|
|
"required_slots": scene_slot_context.get_required_slot_keys(),
|
|
|
|
|
|
"optional_slots": scene_slot_context.get_optional_slot_keys(),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat: implement mid-platform core services and APIs [AC-IDMP-01~20, AC-MARH-01~12]
- Add dialogue, messages, sessions, share API endpoints
- Add mid-platform schemas and models (memory, tool_registry, tool_trace)
- Add core services: agent_orchestrator, policy_router, runtime_observer
- Add tool services: kb_search_dynamic, memory_adapter, tool_registry
- Add guardrail services: output_guardrail_executor, high_risk_handler
- Add utility services: timeout_governor, segment_humanizer, metrics_collector
2026-03-05 10:13:34 +00:00
|
|
|
|
if session and request.user_id:
|
|
|
|
|
|
memory_recall_tool = MemoryRecallTool(
|
|
|
|
|
|
session=session,
|
|
|
|
|
|
timeout_governor=timeout_governor,
|
|
|
|
|
|
config=MemoryRecallConfig(
|
|
|
|
|
|
enabled=True,
|
|
|
|
|
|
timeout_ms=1000,
|
|
|
|
|
|
max_recent_messages=8,
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
memory_result = await memory_recall_tool.execute(
|
|
|
|
|
|
tenant_id=tenant_id,
|
|
|
|
|
|
user_id=request.user_id,
|
|
|
|
|
|
session_id=request.session_id,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if memory_result.duration_ms > 0:
|
|
|
|
|
|
memory_trace = memory_recall_tool.create_trace(memory_result, tenant_id)
|
|
|
|
|
|
trace_logger.update_trace(
|
|
|
|
|
|
request_id=request_id,
|
|
|
|
|
|
tool_calls=[memory_trace],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
memory_context = memory_result.get_context_for_prompt()
|
|
|
|
|
|
memory_missing_slots = memory_result.missing_slots
|
|
|
|
|
|
|
|
|
|
|
|
if memory_context:
|
|
|
|
|
|
base_context["memory_context"] = memory_context
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
f"[AC-IDMP-13] Memory recall succeeded: "
|
|
|
|
|
|
f"profile={len(memory_result.profile)}, facts={len(memory_result.facts)}, "
|
|
|
|
|
|
f"slots={len(memory_result.slots)}, missing_slots={len(memory_missing_slots)}, "
|
|
|
|
|
|
f"duration_ms={memory_result.duration_ms}"
|
|
|
|
|
|
)
|
|
|
|
|
|
elif memory_result.fallback_reason_code:
|
|
|
|
|
|
logger.warning(
|
|
|
|
|
|
f"[AC-IDMP-13] Memory recall fallback: reason={memory_result.fallback_reason_code}"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-03-10 04:06:57 +00:00
|
|
|
|
# [AC-MRS-SLOT-META-03] 仅流程态执行槽位聚合/提取
|
|
|
|
|
|
if is_flow_runtime:
|
|
|
|
|
|
slot_aggregator = SlotStateAggregator(
|
|
|
|
|
|
session=session,
|
|
|
|
|
|
tenant_id=tenant_id,
|
|
|
|
|
|
session_id=request.session_id,
|
|
|
|
|
|
)
|
|
|
|
|
|
slot_state = await slot_aggregator.aggregate(
|
|
|
|
|
|
memory_slots=memory_result.slots,
|
|
|
|
|
|
current_input_slots=None, # 可从 request 中解析
|
|
|
|
|
|
context=base_context,
|
|
|
|
|
|
scene_slot_context=scene_slot_context, # [AC-SCENE-SLOT-02] 传入场景槽位上下文
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
f"[AC-MRS-SLOT-META-03] 流程态槽位聚合完成: "
|
|
|
|
|
|
f"filled={len(slot_state.filled_slots)}, "
|
|
|
|
|
|
f"missing={len(slot_state.missing_required_slots)}, "
|
|
|
|
|
|
f"mappings={slot_state.slot_to_field_map}"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# [AC-MRS-SLOT-EXTRACT-01] 自动提取槽位
|
|
|
|
|
|
if slot_state and slot_state.missing_required_slots:
|
|
|
|
|
|
from app.services.mid.slot_extraction_integration import SlotExtractionIntegration
|
|
|
|
|
|
|
|
|
|
|
|
extraction_integration = SlotExtractionIntegration(
|
|
|
|
|
|
session=session,
|
|
|
|
|
|
tenant_id=tenant_id,
|
|
|
|
|
|
session_id=request.session_id,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
extraction_result = await extraction_integration.extract_and_fill(
|
|
|
|
|
|
user_input=request.user_message,
|
|
|
|
|
|
slot_state=slot_state,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if extraction_result.extracted_slots:
|
|
|
|
|
|
slot_state = await slot_aggregator.aggregate(
|
|
|
|
|
|
memory_slots=memory_result.slots,
|
|
|
|
|
|
current_input_slots=extraction_result.extracted_slots,
|
|
|
|
|
|
context=base_context,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
f"[AC-MRS-SLOT-EXTRACT-01] 流程态自动提槽完成: "
|
|
|
|
|
|
f"extracted={list(extraction_result.extracted_slots.keys())}, "
|
|
|
|
|
|
f"time_ms={extraction_result.total_execution_time_ms:.2f}"
|
|
|
|
|
|
)
|
|
|
|
|
|
else:
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
f"[AC-MRS-SLOT-META-03] 当前为通用问答轨道,跳过槽位聚合与缺槽追问: "
|
|
|
|
|
|
f"session={request.session_id}"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
feat: implement mid-platform core services and APIs [AC-IDMP-01~20, AC-MARH-01~12]
- Add dialogue, messages, sessions, share API endpoints
- Add mid-platform schemas and models (memory, tool_registry, tool_trace)
- Add core services: agent_orchestrator, policy_router, runtime_observer
- Add tool services: kb_search_dynamic, memory_adapter, tool_registry
- Add guardrail services: output_guardrail_executor, high_risk_handler
- Add utility services: timeout_governor, segment_humanizer, metrics_collector
2026-03-05 10:13:34 +00:00
|
|
|
|
kb_hits = []
|
|
|
|
|
|
kb_success = False
|
|
|
|
|
|
kb_fallback_reason = None
|
|
|
|
|
|
kb_applied_filter = {}
|
|
|
|
|
|
kb_missing_slots = []
|
2026-03-10 04:06:57 +00:00
|
|
|
|
kb_dynamic_result = None
|
|
|
|
|
|
step_kb_binding_trace: dict[str, Any] | None = None
|
|
|
|
|
|
|
|
|
|
|
|
# [Step-KB-Binding] 获取当前流程步骤的 KB 配置(仅流程态)
|
|
|
|
|
|
step_kb_config = None
|
|
|
|
|
|
if is_flow_runtime and session and flow_status:
|
|
|
|
|
|
current_step_no = flow_status.get("current_step")
|
|
|
|
|
|
flow_id = flow_status.get("flow_id")
|
|
|
|
|
|
|
|
|
|
|
|
if flow_id and current_step_no:
|
|
|
|
|
|
from app.models.entities import ScriptFlow
|
|
|
|
|
|
from sqlalchemy import select
|
|
|
|
|
|
|
|
|
|
|
|
stmt = select(ScriptFlow).where(ScriptFlow.id == flow_id)
|
|
|
|
|
|
result = await session.execute(stmt)
|
|
|
|
|
|
flow = result.scalar_one_or_none()
|
|
|
|
|
|
|
|
|
|
|
|
if flow and flow.steps:
|
|
|
|
|
|
step_idx = current_step_no - 1
|
|
|
|
|
|
if 0 <= step_idx < len(flow.steps):
|
|
|
|
|
|
current_step = flow.steps[step_idx]
|
|
|
|
|
|
|
|
|
|
|
|
# 构建 StepKbConfig
|
|
|
|
|
|
from app.services.mid.kb_search_dynamic_tool import StepKbConfig
|
|
|
|
|
|
step_kb_config = StepKbConfig(
|
|
|
|
|
|
allowed_kb_ids=current_step.get("allowed_kb_ids"),
|
|
|
|
|
|
preferred_kb_ids=current_step.get("preferred_kb_ids"),
|
|
|
|
|
|
kb_query_hint=current_step.get("kb_query_hint"),
|
|
|
|
|
|
max_kb_calls=current_step.get("max_kb_calls_per_step", 1),
|
|
|
|
|
|
step_id=f"{flow_id}_step_{current_step_no}",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
step_kb_binding_trace = {
|
|
|
|
|
|
"flow_id": str(flow_id),
|
|
|
|
|
|
"flow_name": flow_status.get("flow_name"),
|
|
|
|
|
|
"current_step": current_step_no,
|
|
|
|
|
|
"step_id": step_kb_config.step_id,
|
|
|
|
|
|
"allowed_kb_ids": step_kb_config.allowed_kb_ids,
|
|
|
|
|
|
"preferred_kb_ids": step_kb_config.preferred_kb_ids,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
f"[Step-KB-Binding] 步骤知识库配置加载成功: "
|
|
|
|
|
|
f"flow={flow_status.get('flow_name')}, step={current_step_no}, "
|
|
|
|
|
|
f"allowed_kb_ids={step_kb_config.allowed_kb_ids}"
|
|
|
|
|
|
)
|
feat: implement mid-platform core services and APIs [AC-IDMP-01~20, AC-MARH-01~12]
- Add dialogue, messages, sessions, share API endpoints
- Add mid-platform schemas and models (memory, tool_registry, tool_trace)
- Add core services: agent_orchestrator, policy_router, runtime_observer
- Add tool services: kb_search_dynamic, memory_adapter, tool_registry
- Add guardrail services: output_guardrail_executor, high_risk_handler
- Add utility services: timeout_governor, segment_humanizer, metrics_collector
2026-03-05 10:13:34 +00:00
|
|
|
|
|
|
|
|
|
|
if session and tool_registry:
|
|
|
|
|
|
kb_tool = KbSearchDynamicTool(
|
|
|
|
|
|
session=session,
|
|
|
|
|
|
timeout_governor=timeout_governor,
|
|
|
|
|
|
config=KbSearchDynamicConfig(
|
|
|
|
|
|
enabled=True,
|
|
|
|
|
|
top_k=5,
|
2026-03-10 04:06:57 +00:00
|
|
|
|
timeout_ms=10000,
|
feat: implement mid-platform core services and APIs [AC-IDMP-01~20, AC-MARH-01~12]
- Add dialogue, messages, sessions, share API endpoints
- Add mid-platform schemas and models (memory, tool_registry, tool_trace)
- Add core services: agent_orchestrator, policy_router, runtime_observer
- Add tool services: kb_search_dynamic, memory_adapter, tool_registry
- Add guardrail services: output_guardrail_executor, high_risk_handler
- Add utility services: timeout_governor, segment_humanizer, metrics_collector
2026-03-05 10:13:34 +00:00
|
|
|
|
min_score_threshold=0.5,
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-03-10 04:06:57 +00:00
|
|
|
|
# [AC-MRS-SLOT-META-03] 传入 slot_state 进行 KB 检索
|
|
|
|
|
|
# [Step-KB-Binding] 传入 step_kb_config 进行步骤级别的 KB 约束
|
feat: implement mid-platform core services and APIs [AC-IDMP-01~20, AC-MARH-01~12]
- Add dialogue, messages, sessions, share API endpoints
- Add mid-platform schemas and models (memory, tool_registry, tool_trace)
- Add core services: agent_orchestrator, policy_router, runtime_observer
- Add tool services: kb_search_dynamic, memory_adapter, tool_registry
- Add guardrail services: output_guardrail_executor, high_risk_handler
- Add utility services: timeout_governor, segment_humanizer, metrics_collector
2026-03-05 10:13:34 +00:00
|
|
|
|
kb_dynamic_result = await kb_tool.execute(
|
|
|
|
|
|
query=request.user_message,
|
|
|
|
|
|
tenant_id=tenant_id,
|
|
|
|
|
|
top_k=5,
|
|
|
|
|
|
context=base_context,
|
2026-03-10 04:06:57 +00:00
|
|
|
|
slot_state=slot_state,
|
|
|
|
|
|
step_kb_config=step_kb_config,
|
|
|
|
|
|
slot_policy="flow_strict" if is_flow_runtime else "agent_relaxed",
|
feat: implement mid-platform core services and APIs [AC-IDMP-01~20, AC-MARH-01~12]
- Add dialogue, messages, sessions, share API endpoints
- Add mid-platform schemas and models (memory, tool_registry, tool_trace)
- Add core services: agent_orchestrator, policy_router, runtime_observer
- Add tool services: kb_search_dynamic, memory_adapter, tool_registry
- Add guardrail services: output_guardrail_executor, high_risk_handler
- Add utility services: timeout_governor, segment_humanizer, metrics_collector
2026-03-05 10:13:34 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
kb_success = kb_dynamic_result.success
|
|
|
|
|
|
kb_hits = kb_dynamic_result.hits
|
|
|
|
|
|
kb_fallback_reason = kb_dynamic_result.fallback_reason_code
|
|
|
|
|
|
kb_applied_filter = kb_dynamic_result.applied_filter
|
|
|
|
|
|
kb_missing_slots = kb_dynamic_result.missing_required_slots
|
|
|
|
|
|
|
|
|
|
|
|
if kb_dynamic_result.tool_trace:
|
|
|
|
|
|
trace_logger.update_trace(
|
|
|
|
|
|
request_id=request_id,
|
|
|
|
|
|
tool_calls=[kb_dynamic_result.tool_trace],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(
|
2026-03-10 04:06:57 +00:00
|
|
|
|
f"[AC-MARH-05] KB动态检索完成: success={kb_success}, "
|
feat: implement mid-platform core services and APIs [AC-IDMP-01~20, AC-MARH-01~12]
- Add dialogue, messages, sessions, share API endpoints
- Add mid-platform schemas and models (memory, tool_registry, tool_trace)
- Add core services: agent_orchestrator, policy_router, runtime_observer
- Add tool services: kb_search_dynamic, memory_adapter, tool_registry
- Add guardrail services: output_guardrail_executor, high_risk_handler
- Add utility services: timeout_governor, segment_humanizer, metrics_collector
2026-03-05 10:13:34 +00:00
|
|
|
|
f"hits={len(kb_hits)}, filter={kb_applied_filter}, "
|
2026-03-10 04:06:57 +00:00
|
|
|
|
f"missing_slots={kb_missing_slots}, track={'flow' if is_flow_runtime else 'agent'}"
|
feat: implement mid-platform core services and APIs [AC-IDMP-01~20, AC-MARH-01~12]
- Add dialogue, messages, sessions, share API endpoints
- Add mid-platform schemas and models (memory, tool_registry, tool_trace)
- Add core services: agent_orchestrator, policy_router, runtime_observer
- Add tool services: kb_search_dynamic, memory_adapter, tool_registry
- Add guardrail services: output_guardrail_executor, high_risk_handler
- Add utility services: timeout_governor, segment_humanizer, metrics_collector
2026-03-05 10:13:34 +00:00
|
|
|
|
)
|
2026-03-10 04:06:57 +00:00
|
|
|
|
|
|
|
|
|
|
# [AC-MRS-SLOT-META-03] 处理缺失必填槽位 -> 追问闭环(仅流程态)
|
|
|
|
|
|
if is_flow_runtime and kb_fallback_reason == "MISSING_REQUIRED_SLOTS" and kb_missing_slots:
|
|
|
|
|
|
ask_back_text = await _generate_ask_back_for_missing_slots(
|
|
|
|
|
|
slot_state=slot_state,
|
|
|
|
|
|
missing_slots=kb_missing_slots,
|
|
|
|
|
|
session=session,
|
|
|
|
|
|
tenant_id=tenant_id,
|
|
|
|
|
|
session_id=request.session_id,
|
|
|
|
|
|
scene_slot_context=scene_slot_context, # [AC-SCENE-SLOT-02] 传入场景槽位上下文
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
f"[AC-MRS-SLOT-META-03] 流程态缺槽追问文案已生成: "
|
|
|
|
|
|
f"{ask_back_text[:50]}..."
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return DialogueResponse(
|
|
|
|
|
|
segments=[Segment(text=ask_back_text, delay_after=0)],
|
|
|
|
|
|
trace=TraceInfo(
|
|
|
|
|
|
mode=ExecutionMode.AGENT,
|
|
|
|
|
|
request_id=trace.request_id,
|
|
|
|
|
|
generation_id=trace.generation_id,
|
|
|
|
|
|
fallback_reason_code="missing_required_slots",
|
|
|
|
|
|
kb_tool_called=True,
|
|
|
|
|
|
kb_hit=False,
|
|
|
|
|
|
# [AC-SCENE-SLOT-02] 场景槽位追踪
|
|
|
|
|
|
scene=request.scene,
|
|
|
|
|
|
scene_slot_context=base_context.get("scene_slot_context"),
|
|
|
|
|
|
missing_slots=[s.get("slot_key") for s in kb_missing_slots] if kb_missing_slots else None,
|
|
|
|
|
|
ask_back_triggered=True,
|
|
|
|
|
|
slot_sources=slot_state.slot_sources if slot_state else None,
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
feat: implement mid-platform core services and APIs [AC-IDMP-01~20, AC-MARH-01~12]
- Add dialogue, messages, sessions, share API endpoints
- Add mid-platform schemas and models (memory, tool_registry, tool_trace)
- Add core services: agent_orchestrator, policy_router, runtime_observer
- Add tool services: kb_search_dynamic, memory_adapter, tool_registry
- Add guardrail services: output_guardrail_executor, high_risk_handler
- Add utility services: timeout_governor, segment_humanizer, metrics_collector
2026-03-05 10:13:34 +00:00
|
|
|
|
else:
|
|
|
|
|
|
kb_result = await default_kb_tool_runner.execute(
|
|
|
|
|
|
tenant_id=tenant_id,
|
|
|
|
|
|
query=request.user_message,
|
|
|
|
|
|
)
|
|
|
|
|
|
kb_success = kb_result.success
|
|
|
|
|
|
kb_hits = kb_result.hits
|
|
|
|
|
|
kb_fallback_reason = kb_result.fallback_reason_code
|
|
|
|
|
|
|
|
|
|
|
|
runtime_observer.record_kb(
|
|
|
|
|
|
request_id,
|
|
|
|
|
|
tool_called=True,
|
|
|
|
|
|
hit=kb_success and len(kb_hits) > 0,
|
|
|
|
|
|
fallback_reason=kb_fallback_reason,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if kb_success and kb_hits:
|
|
|
|
|
|
kb_context = "\n".join([
|
|
|
|
|
|
f"[知识库] {hit.get('content', '')[:200]}"
|
|
|
|
|
|
for hit in kb_hits[:3]
|
|
|
|
|
|
])
|
|
|
|
|
|
base_context["kb_context"] = kb_context
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
f"[AC-MARH-05] KB retrieval succeeded: hits={len(kb_hits)}"
|
|
|
|
|
|
)
|
|
|
|
|
|
elif kb_fallback_reason:
|
|
|
|
|
|
logger.warning(
|
|
|
|
|
|
f"[AC-MARH-06] KB retrieval fallback: reason={kb_fallback_reason}"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
orchestrator = AgentOrchestrator(
|
|
|
|
|
|
max_iterations=5,
|
|
|
|
|
|
timeout_governor=timeout_governor,
|
|
|
|
|
|
llm_client=llm_client,
|
|
|
|
|
|
tool_registry=tool_registry,
|
2026-03-10 04:06:57 +00:00
|
|
|
|
template_service=PromptTemplateService,
|
|
|
|
|
|
variable_resolver=VariableResolver(),
|
2026-03-05 17:10:24 +00:00
|
|
|
|
tenant_id=tenant_id,
|
2026-03-10 04:06:57 +00:00
|
|
|
|
user_id=request.user_id,
|
|
|
|
|
|
session_id=request.session_id,
|
|
|
|
|
|
scene=request.scene,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
f"[DEBUG-AGENT] Calling orchestrator.execute with: "
|
|
|
|
|
|
f"user_message={request.user_message[:100] if request.user_message else 'None'}, "
|
|
|
|
|
|
f"context_keys={list(base_context.keys())}, llm_client={llm_client is not None}"
|
feat: implement mid-platform core services and APIs [AC-IDMP-01~20, AC-MARH-01~12]
- Add dialogue, messages, sessions, share API endpoints
- Add mid-platform schemas and models (memory, tool_registry, tool_trace)
- Add core services: agent_orchestrator, policy_router, runtime_observer
- Add tool services: kb_search_dynamic, memory_adapter, tool_registry
- Add guardrail services: output_guardrail_executor, high_risk_handler
- Add utility services: timeout_governor, segment_humanizer, metrics_collector
2026-03-05 10:13:34 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
final_answer, react_ctx, agent_trace = await orchestrator.execute(
|
|
|
|
|
|
user_message=request.user_message,
|
|
|
|
|
|
context=base_context,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-03-10 04:06:57 +00:00
|
|
|
|
logger.info(
|
|
|
|
|
|
f"[DEBUG-AGENT] orchestrator.execute completed: "
|
|
|
|
|
|
f"final_answer={final_answer[:200] if final_answer else 'None'}, "
|
|
|
|
|
|
f"iterations={react_ctx.iteration}, tool_calls={len(react_ctx.tool_calls) if react_ctx.tool_calls else 0}"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
feat: implement mid-platform core services and APIs [AC-IDMP-01~20, AC-MARH-01~12]
- Add dialogue, messages, sessions, share API endpoints
- Add mid-platform schemas and models (memory, tool_registry, tool_trace)
- Add core services: agent_orchestrator, policy_router, runtime_observer
- Add tool services: kb_search_dynamic, memory_adapter, tool_registry
- Add guardrail services: output_guardrail_executor, high_risk_handler
- Add utility services: timeout_governor, segment_humanizer, metrics_collector
2026-03-05 10:13:34 +00:00
|
|
|
|
runtime_observer.record_react(request_id, react_ctx.iteration, react_ctx.tool_calls)
|
|
|
|
|
|
|
|
|
|
|
|
trace_logger.update_trace(
|
|
|
|
|
|
request_id=request_id,
|
|
|
|
|
|
react_iterations=react_ctx.iteration,
|
|
|
|
|
|
tool_calls=react_ctx.tool_calls,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
segments = _text_to_segments(final_answer)
|
|
|
|
|
|
|
|
|
|
|
|
return DialogueResponse(
|
|
|
|
|
|
segments=segments,
|
|
|
|
|
|
trace=TraceInfo(
|
|
|
|
|
|
mode=ExecutionMode.AGENT,
|
|
|
|
|
|
request_id=trace.request_id,
|
|
|
|
|
|
generation_id=trace.generation_id,
|
|
|
|
|
|
react_iterations=react_ctx.iteration,
|
|
|
|
|
|
tools_used=[tc.tool_name for tc in react_ctx.tool_calls] if react_ctx.tool_calls else None,
|
|
|
|
|
|
tool_calls=react_ctx.tool_calls,
|
|
|
|
|
|
timeout_profile=timeout_governor.profile,
|
|
|
|
|
|
kb_tool_called=True,
|
|
|
|
|
|
kb_hit=kb_success and len(kb_hits) > 0,
|
|
|
|
|
|
fallback_reason_code=kb_fallback_reason,
|
2026-03-10 04:06:57 +00:00
|
|
|
|
# [AC-SCENE-SLOT-02] 场景槽位追踪
|
|
|
|
|
|
scene=request.scene,
|
|
|
|
|
|
scene_slot_context=base_context.get("scene_slot_context"),
|
|
|
|
|
|
missing_slots=[s.get("slot_key") for s in kb_missing_slots] if kb_missing_slots else None,
|
|
|
|
|
|
ask_back_triggered=False,
|
|
|
|
|
|
slot_sources=slot_state.slot_sources if slot_state else None,
|
|
|
|
|
|
kb_filter_sources=kb_dynamic_result.filter_sources if kb_dynamic_result else None,
|
|
|
|
|
|
# [Step-KB-Binding] 步骤知识库绑定追踪
|
|
|
|
|
|
step_kb_binding=kb_dynamic_result.step_kb_binding if kb_dynamic_result else step_kb_binding_trace,
|
feat: implement mid-platform core services and APIs [AC-IDMP-01~20, AC-MARH-01~12]
- Add dialogue, messages, sessions, share API endpoints
- Add mid-platform schemas and models (memory, tool_registry, tool_trace)
- Add core services: agent_orchestrator, policy_router, runtime_observer
- Add tool services: kb_search_dynamic, memory_adapter, tool_registry
- Add guardrail services: output_guardrail_executor, high_risk_handler
- Add utility services: timeout_governor, segment_humanizer, metrics_collector
2026-03-05 10:13:34 +00:00
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-10 04:06:57 +00:00
|
|
|
|
async def _generate_ask_back_for_missing_slots(
|
|
|
|
|
|
slot_state: Any,
|
|
|
|
|
|
missing_slots: list[dict[str, str]],
|
|
|
|
|
|
session: AsyncSession,
|
|
|
|
|
|
tenant_id: str,
|
|
|
|
|
|
session_id: str | None = None,
|
|
|
|
|
|
max_ask_back_slots: int = 2,
|
|
|
|
|
|
scene_slot_context: Any = None, # [AC-SCENE-SLOT-02] 场景槽位上下文
|
|
|
|
|
|
) -> str:
|
|
|
|
|
|
"""
|
|
|
|
|
|
[AC-MRS-SLOT-META-03, AC-MRS-SLOT-ASKBACK-01] 为缺失的必填槽位生成追问响应
|
|
|
|
|
|
[AC-SCENE-SLOT-02] 支持场景槽位包配置的追问策略
|
|
|
|
|
|
|
|
|
|
|
|
支持批量追问多个缺失槽位,优先追问必填槽位
|
|
|
|
|
|
"""
|
|
|
|
|
|
if not missing_slots:
|
|
|
|
|
|
return "请提供更多信息以便我更好地帮助您。"
|
|
|
|
|
|
|
|
|
|
|
|
# [AC-SCENE-SLOT-02] 如果有场景槽位上下文,使用场景配置的追问策略
|
|
|
|
|
|
if scene_slot_context:
|
|
|
|
|
|
ask_back_order = getattr(scene_slot_context, 'ask_back_order', 'priority')
|
|
|
|
|
|
|
|
|
|
|
|
if ask_back_order == "parallel":
|
|
|
|
|
|
prompts = []
|
|
|
|
|
|
for missing in missing_slots[:max_ask_back_slots]:
|
|
|
|
|
|
ask_back_prompt = missing.get("ask_back_prompt")
|
|
|
|
|
|
if ask_back_prompt:
|
|
|
|
|
|
prompts.append(ask_back_prompt)
|
|
|
|
|
|
else:
|
|
|
|
|
|
slot_key = missing.get("slot_key", "相关信息")
|
|
|
|
|
|
prompts.append(f"您的{slot_key}")
|
|
|
|
|
|
|
|
|
|
|
|
if len(prompts) == 1:
|
|
|
|
|
|
return prompts[0]
|
|
|
|
|
|
elif len(prompts) == 2:
|
|
|
|
|
|
return f"为了更好地为您服务,请告诉我{prompts[0]}和{prompts[1]}。"
|
|
|
|
|
|
else:
|
|
|
|
|
|
all_but_last = "、".join(prompts[:-1])
|
|
|
|
|
|
return f"为了更好地为您服务,请告诉我{all_but_last},以及{prompts[-1]}。"
|
|
|
|
|
|
|
|
|
|
|
|
if len(missing_slots) == 1:
|
|
|
|
|
|
first_missing = missing_slots[0]
|
|
|
|
|
|
ask_back_prompt = first_missing.get("ask_back_prompt")
|
|
|
|
|
|
if ask_back_prompt:
|
|
|
|
|
|
return ask_back_prompt
|
|
|
|
|
|
slot_key = first_missing.get("slot_key", "相关信息")
|
|
|
|
|
|
label = first_missing.get("label", slot_key)
|
|
|
|
|
|
return f"为了更好地为您提供帮助,请告诉我您的{label}。"
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
from app.services.mid.batch_ask_back_service import (
|
|
|
|
|
|
BatchAskBackConfig,
|
|
|
|
|
|
BatchAskBackService,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
config = BatchAskBackConfig(
|
|
|
|
|
|
max_ask_back_slots_per_turn=max_ask_back_slots,
|
|
|
|
|
|
prefer_required=True,
|
|
|
|
|
|
merge_prompts=True,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
ask_back_service = BatchAskBackService(
|
|
|
|
|
|
session=session,
|
|
|
|
|
|
tenant_id=tenant_id,
|
|
|
|
|
|
session_id=session_id or "",
|
|
|
|
|
|
config=config,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
result = await ask_back_service.generate_batch_ask_back(
|
|
|
|
|
|
missing_slots=missing_slots,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if result.has_ask_back():
|
|
|
|
|
|
return result.get_prompt()
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.warning(f"[AC-MRS-SLOT-ASKBACK-01] Batch ask-back failed, fallback to single: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
prompts = []
|
|
|
|
|
|
for missing in missing_slots[:max_ask_back_slots]:
|
|
|
|
|
|
ask_back_prompt = missing.get("ask_back_prompt")
|
|
|
|
|
|
if ask_back_prompt:
|
|
|
|
|
|
prompts.append(ask_back_prompt)
|
|
|
|
|
|
else:
|
|
|
|
|
|
label = missing.get("label", missing.get("slot_key", "相关信息"))
|
|
|
|
|
|
prompts.append(f"您的{label}")
|
|
|
|
|
|
|
|
|
|
|
|
if len(prompts) == 1:
|
|
|
|
|
|
return prompts[0]
|
|
|
|
|
|
elif len(prompts) == 2:
|
|
|
|
|
|
return f"为了更好地为您服务,请告诉我{prompts[0]}和{prompts[1]}。"
|
|
|
|
|
|
else:
|
|
|
|
|
|
all_but_last = "、".join(prompts[:-1])
|
|
|
|
|
|
return f"为了更好地为您服务,请告诉我{all_but_last},以及{prompts[-1]}。"
|
|
|
|
|
|
|
|
|
|
|
|
|
feat: implement mid-platform core services and APIs [AC-IDMP-01~20, AC-MARH-01~12]
- Add dialogue, messages, sessions, share API endpoints
- Add mid-platform schemas and models (memory, tool_registry, tool_trace)
- Add core services: agent_orchestrator, policy_router, runtime_observer
- Add tool services: kb_search_dynamic, memory_adapter, tool_registry
- Add guardrail services: output_guardrail_executor, high_risk_handler
- Add utility services: timeout_governor, segment_humanizer, metrics_collector
2026-03-05 10:13:34 +00:00
|
|
|
|
async def _execute_micro_flow_mode(
|
|
|
|
|
|
tenant_id: str,
|
|
|
|
|
|
request: DialogueRequest,
|
|
|
|
|
|
router_result: Any,
|
|
|
|
|
|
trace: TraceInfo,
|
|
|
|
|
|
trace_logger: TraceLogger,
|
|
|
|
|
|
session: AsyncSession,
|
|
|
|
|
|
start_time: float,
|
|
|
|
|
|
) -> DialogueResponse:
|
|
|
|
|
|
"""Execute micro flow mode."""
|
|
|
|
|
|
if router_result.target_flow_id:
|
|
|
|
|
|
try:
|
|
|
|
|
|
from app.services.flow.engine import FlowEngine
|
|
|
|
|
|
|
|
|
|
|
|
flow_engine = FlowEngine(session)
|
|
|
|
|
|
instance, first_step = await flow_engine.start(
|
|
|
|
|
|
tenant_id=tenant_id,
|
|
|
|
|
|
session_id=request.session_id,
|
|
|
|
|
|
flow_id=router_result.target_flow_id,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if first_step:
|
|
|
|
|
|
return DialogueResponse(
|
|
|
|
|
|
segments=_text_to_segments(first_step),
|
|
|
|
|
|
trace=TraceInfo(
|
|
|
|
|
|
mode=ExecutionMode.MICRO_FLOW,
|
|
|
|
|
|
request_id=trace.request_id,
|
|
|
|
|
|
generation_id=trace.generation_id,
|
|
|
|
|
|
intent=router_result.intent,
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.warning(f"[AC-IDMP-05] Micro flow start failed: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
return DialogueResponse(
|
|
|
|
|
|
segments=[Segment(
|
|
|
|
|
|
text="正在为您处理,请稍候...",
|
|
|
|
|
|
delay_after=0,
|
|
|
|
|
|
)],
|
|
|
|
|
|
trace=TraceInfo(
|
|
|
|
|
|
mode=ExecutionMode.MICRO_FLOW,
|
|
|
|
|
|
request_id=trace.request_id,
|
|
|
|
|
|
generation_id=trace.generation_id,
|
|
|
|
|
|
intent=router_result.intent,
|
|
|
|
|
|
fallback_reason_code=router_result.fallback_reason_code,
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def _execute_fixed_mode(
|
|
|
|
|
|
tenant_id: str,
|
|
|
|
|
|
request: DialogueRequest,
|
|
|
|
|
|
router_result: Any,
|
|
|
|
|
|
trace: TraceInfo,
|
|
|
|
|
|
trace_logger: TraceLogger,
|
|
|
|
|
|
start_time: float,
|
|
|
|
|
|
) -> DialogueResponse:
|
|
|
|
|
|
"""Execute fixed reply mode."""
|
|
|
|
|
|
text = router_result.fixed_reply or "收到您的消息,我们会尽快处理。"
|
|
|
|
|
|
|
|
|
|
|
|
return DialogueResponse(
|
|
|
|
|
|
segments=_text_to_segments(text),
|
|
|
|
|
|
trace=TraceInfo(
|
|
|
|
|
|
mode=ExecutionMode.FIXED,
|
|
|
|
|
|
request_id=trace.request_id,
|
|
|
|
|
|
generation_id=trace.generation_id,
|
|
|
|
|
|
intent=router_result.intent,
|
|
|
|
|
|
fallback_reason_code=router_result.fallback_reason_code,
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def _execute_transfer_mode(
|
|
|
|
|
|
tenant_id: str,
|
|
|
|
|
|
request: DialogueRequest,
|
|
|
|
|
|
router_result: Any,
|
|
|
|
|
|
trace: TraceInfo,
|
|
|
|
|
|
trace_logger: TraceLogger,
|
|
|
|
|
|
start_time: float,
|
|
|
|
|
|
) -> DialogueResponse:
|
|
|
|
|
|
"""Execute transfer to human mode."""
|
|
|
|
|
|
text = router_result.transfer_message or "正在为您转接人工客服,请稍候..."
|
|
|
|
|
|
|
|
|
|
|
|
return DialogueResponse(
|
|
|
|
|
|
segments=_text_to_segments(text),
|
|
|
|
|
|
trace=TraceInfo(
|
|
|
|
|
|
mode=ExecutionMode.TRANSFER,
|
|
|
|
|
|
request_id=trace.request_id,
|
|
|
|
|
|
generation_id=trace.generation_id,
|
|
|
|
|
|
intent=router_result.intent,
|
|
|
|
|
|
fallback_reason_code=router_result.fallback_reason_code,
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _text_to_segments(text: str) -> list[Segment]:
|
|
|
|
|
|
"""Convert text to segments."""
|
|
|
|
|
|
paragraphs = [p.strip() for p in text.split("\n\n") if p.strip()]
|
|
|
|
|
|
|
|
|
|
|
|
if not paragraphs:
|
|
|
|
|
|
paragraphs = [text]
|
|
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
|
Segment(text=p, delay_after=100 if i < len(paragraphs) - 1 else 0)
|
|
|
|
|
|
for i, p in enumerate(paragraphs)
|
|
|
|
|
|
]
|