File size: 4,645 Bytes
2f235a0
 
 
 
 
 
 
 
 
 
 
 
 
 
c16e1c9
2f235a0
 
c16e1c9
 
2f235a0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c509b44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# =============================================================
# File: backend/api/routes/agent.py
# =============================================================

from fastapi import APIRouter
from pydantic import BaseModel
import os
import sys
from pathlib import Path

# Add backend to path for imports
backend_dir = Path(__file__).parent.parent.parent
sys.path.insert(0, str(backend_dir))

from api.services.agent_orchestrator import AgentOrchestrator
from api.models.agent import AgentRequest, AgentResponse


router = APIRouter()


orchestrator = AgentOrchestrator(
    rag_mcp_url=os.getenv("RAG_MCP_URL", "http://localhost:8001"),
    web_mcp_url=os.getenv("WEB_MCP_URL", "http://localhost:8002"),
    admin_mcp_url=os.getenv("ADMIN_MCP_URL", "http://localhost:8003"),
    llm_backend=os.getenv("LLM_BACKEND", "ollama")
)


class ChatRequest(BaseModel):
    tenant_id: str
    user_id: str | None = None
    message: str
    conversation_history: list[dict] = []
    temperature: float = 0.0


@router.post("/message", response_model=AgentResponse)
async def agent_chat(req: ChatRequest):
    agent_req = AgentRequest(
        tenant_id=req.tenant_id,
        user_id=req.user_id,
        message=req.message,
        conversation_history=req.conversation_history,
        temperature=req.temperature
    )
    return await orchestrator.handle(agent_req)


@router.post("/debug")
async def agent_debug(req: ChatRequest):
    """
    Returns detailed debugging information about agent reasoning.
    Includes intent classification, tool selection, reasoning trace, and tool traces.
    """
    agent_req = AgentRequest(
        tenant_id=req.tenant_id,
        user_id=req.user_id,
        message=req.message,
        conversation_history=req.conversation_history,
        temperature=req.temperature
    )
    response = await orchestrator.handle(agent_req)
    
    return {
        "request": {
            "tenant_id": req.tenant_id,
            "user_id": req.user_id,
            "message": req.message[:200],
            "temperature": req.temperature
        },
        "response": {
            "text": response.text[:500] + "..." if len(response.text) > 500 else response.text,
            "decision": response.decision.dict() if response.decision else None,
            "tool_traces": response.tool_traces,
            "reasoning_trace": response.reasoning_trace
        },
        "debug_info": {
            "intent": response.reasoning_trace[1].get("intent") if len(response.reasoning_trace) > 1 else None,
            "tool_selection": next((t for t in response.reasoning_trace if t.get("step") == "tool_selection"), None),
            "tool_scores": next((t for t in response.reasoning_trace if t.get("step") == "tool_scoring"), None),
            "redflag_check": next((t for t in response.reasoning_trace if t.get("step") == "redflag_check"), None),
            "total_steps": len(response.reasoning_trace)
        }
    }


@router.post("/plan")
async def agent_plan(req: ChatRequest):
    """
    Returns only the agent's planning output (tool selection decision).
    Useful for understanding what tools the agent would use without executing them.
    """
    from ..services.intent_classifier import IntentClassifier
    from ..services.tool_selector import ToolSelector
    from ..services.tool_scoring import ToolScoringService
    import os
    
    # Create minimal orchestrator components for planning only
    llm = orchestrator.llm
    intent_classifier = IntentClassifier(llm_client=llm)
    tool_selector = ToolSelector(llm_client=llm)
    tool_scorer = ToolScoringService()
    
    # Classify intent
    intent = await intent_classifier.classify(req.message)
    
    # Pre-fetch RAG for context (optional)
    rag_results = []
    try:
        rag_prefetch = await orchestrator.mcp.call_rag(req.tenant_id, req.message)
        if isinstance(rag_prefetch, dict):
            rag_results = rag_prefetch.get("results") or rag_prefetch.get("hits") or []
    except Exception:
        pass
    
    # Score tools
    tool_scores = tool_scorer.score(req.message, intent, rag_results)
    
    # Select tools
    ctx = {
        "tenant_id": req.tenant_id,
        "rag_results": rag_results,
        "tool_scores": tool_scores
    }
    decision = await tool_selector.select(intent, req.message, ctx)
    
    return {
        "tenant_id": req.tenant_id,
        "message": req.message,
        "intent": intent,
        "tool_scores": tool_scores,
        "plan": decision.dict(),
        "steps": decision.tool_input.get("steps", []) if decision.tool_input else [],
        "reason": decision.reason
    }