|
39 | 39 |
|
40 | 40 | load_dotenv() |
41 | 41 |
|
| 42 | +# Ensure operations register before we request LangChain tools |
| 43 | +import operations # noqa: F401 |
| 44 | + |
42 | 45 | # Local - Import operations registry for automatic tool discovery |
43 | 46 | from api_decorators import get_langchain_tools |
| 47 | + |
44 | 48 | # Third-party - FastMCP client for external MCP servers |
45 | 49 | from fastmcp import Client as MCPClient |
46 | 50 | from langchain_core.tools import StructuredTool |
| 51 | + |
47 | 52 | # Third-party - LangChain and LangGraph |
48 | 53 | from langchain_openai import AzureChatOpenAI |
49 | 54 | from langgraph.prebuilt import create_react_agent |
| 55 | + |
50 | 56 | # Third-party - Pydantic for validation |
51 | 57 | from pydantic import BaseModel, Field, create_model, field_validator |
52 | 58 |
|
@@ -193,12 +199,31 @@ def _mcp_tool_to_langchain(mcp_client: MCPClient, tool: Any) -> StructuredTool: |
193 | 199 |
|
194 | 200 | # Create async wrapper that calls MCP server |
195 | 201 | async def call_mcp_tool(**kwargs) -> str: |
| 202 | + import json as _json |
| 203 | + print(f"\n{'='*60}") |
| 204 | + print(f"🔧 MCP TOOL CALL: {tool_name}") |
| 205 | + print(f"{'='*60}") |
| 206 | + print(f"📤 REQUEST:") |
| 207 | + print(f" Tool: {tool_name}") |
| 208 | + print(f" Args: {_json.dumps(kwargs, indent=6, default=str)}") |
| 209 | + |
196 | 210 | result = await mcp_client.call_tool(tool_name, kwargs) |
| 211 | + |
| 212 | + print(f"\n📥 RESPONSE:") |
197 | 213 | # Extract text from MCP response |
198 | 214 | if hasattr(result, 'content') and result.content: |
199 | 215 | texts = [c.text for c in result.content if hasattr(c, 'text')] |
200 | | - return "\n".join(texts) if texts else str(result) |
201 | | - return str(result) |
| 216 | + response_text = "\n".join(texts) if texts else str(result) |
| 217 | + # Truncate for display if too long |
| 218 | + display_text = response_text[:500] + "..." if len(response_text) > 500 else response_text |
| 219 | + print(f" Content items: {len(result.content)}") |
| 220 | + print(f" Text preview: {display_text}") |
| 221 | + else: |
| 222 | + response_text = str(result) |
| 223 | + print(f" Raw: {response_text[:500]}") |
| 224 | + |
| 225 | + print(f"{'='*60}\n") |
| 226 | + return response_text |
202 | 227 |
|
203 | 228 | # Build Pydantic model from input schema |
204 | 229 | args_model = _schema_to_pydantic(tool_name, input_schema) |
@@ -282,11 +307,16 @@ async def _ensure_ticket_mcp_connection(self): |
282 | 307 |
|
283 | 308 | # Fetch and convert ticket MCP tools |
284 | 309 | mcp_tools = await client.list_tools() |
| 310 | + print(f"\n{'='*60}") |
| 311 | + print(f"🎫 TICKET MCP SERVER CONNECTED") |
| 312 | + print(f"{'='*60}") |
| 313 | + print(f" URL: {TICKET_MCP_SERVER_URL}") |
| 314 | + print(f" Tools available: {len(mcp_tools)}") |
285 | 315 | for tool in mcp_tools: |
286 | 316 | lc_tool = _mcp_tool_to_langchain(client, tool) |
287 | 317 | self.tools.append(lc_tool) |
288 | | - |
289 | | - print(f"DEBUG: Loaded {len(mcp_tools)} ticket tools from MCP server {TICKET_MCP_SERVER_URL}") |
| 318 | + print(f" ✓ {tool.name}: {(tool.description or '')[:60]}...") |
| 319 | + print(f"{'='*60}\n") |
290 | 320 | self._ticket_mcp_tools_loaded = True |
291 | 321 |
|
292 | 322 | except Exception as e: |
@@ -336,28 +366,58 @@ async def run_agent(self, request: AgentRequest) -> AgentResponse: |
336 | 366 |
|
337 | 367 | # System message to guide the agent's behavior |
338 | 368 | system_msg = ( |
339 | | - "You are a helpful task management assistant. " |
340 | | - "You MUST use the available tools to perform all actions. " |
341 | | - "NEVER pretend or simulate creating, updating, or listing tasks - you MUST call the actual tools. " |
342 | | - "Available tools: create_task, update_task, delete_task, list_tasks, get_task, get_task_stats. " |
343 | | - "When asked to create tasks, call create_task for each one. " |
344 | | - "When asked to list tasks, call list_tasks. " |
345 | | - "Always use tools and confirm what you've done based on the tool results." |
| 369 | + "You are a support ticket management assistant. " |
| 370 | + "Your primary role is to help users manage, search, and evaluate support tickets. " |
| 371 | + "You MUST use the available tools to perform all actions - NEVER simulate or pretend. " |
| 372 | + "\n\n" |
| 373 | + "TICKET CAPABILITIES:\n" |
| 374 | + "- Search and list tickets by status, priority, city, service\n" |
| 375 | + "- Get detailed ticket information and work logs\n" |
| 376 | + "- Analyze ticket statistics and trends\n" |
| 377 | + "- Request modifications to tickets (status, priority, assignee, etc.)\n" |
| 378 | + "- Review and approve/reject modification requests\n" |
| 379 | + "\n" |
| 380 | + "TASK CAPABILITIES:\n" |
| 381 | + "- Create, update, delete, and list tasks\n" |
| 382 | + "- Get task statistics\n" |
| 383 | + "\n" |
| 384 | + "When users ask about tickets, use the ticket tools (list_tickets, get_ticket, search_tickets, etc.). " |
| 385 | + "When users ask about tasks, use task tools (create_task, list_tasks, etc.). " |
| 386 | + "Always confirm actions based on actual tool results." |
346 | 387 | ) |
347 | 388 |
|
348 | 389 | # Execute agent with user prompt |
349 | | - print(f"DEBUG: Running agent with {len(self.tools)} tools") |
350 | | - print(f"DEBUG: Tools: {[t.name if hasattr(t, 'name') else str(t) for t in self.tools]}") |
| 390 | + print(f"\n{'='*60}") |
| 391 | + print(f"🤖 AGENT EXECUTION START") |
| 392 | + print(f"{'='*60}") |
| 393 | + print(f" Prompt: {request.prompt[:100]}{'...' if len(request.prompt) > 100 else ''}") |
| 394 | + print(f" Agent type: {request.agent_type}") |
| 395 | + print(f" Available tools ({len(self.tools)}):") |
| 396 | + for t in self.tools: |
| 397 | + name = t.name if hasattr(t, 'name') else str(t) |
| 398 | + print(f" • {name}") |
| 399 | + print(f"{'='*60}\n") |
351 | 400 |
|
352 | 401 | result = await agent.ainvoke( |
353 | 402 | {"messages": [("system", system_msg), ("user", request.prompt)]} |
354 | 403 | ) |
355 | 404 |
|
356 | | - print(f"DEBUG: Agent result messages: {len(result['messages'])}") |
| 405 | + print(f"\n{'='*60}") |
| 406 | + print(f"📋 AGENT EXECUTION COMPLETE") |
| 407 | + print(f"{'='*60}") |
| 408 | + print(f" Total messages: {len(result['messages'])}") |
357 | 409 | for i, msg in enumerate(result["messages"]): |
358 | | - print(f"DEBUG: Message {i}: type={type(msg).__name__}, has_tool_calls={hasattr(msg, 'tool_calls')}") |
359 | | - if hasattr(msg, 'tool_calls') and msg.tool_calls: |
360 | | - print(f"DEBUG: Tool calls: {msg.tool_calls}") |
| 410 | + msg_type = type(msg).__name__ |
| 411 | + has_tool_calls = hasattr(msg, 'tool_calls') and msg.tool_calls |
| 412 | + content_preview = "" |
| 413 | + if hasattr(msg, 'content') and msg.content: |
| 414 | + content_preview = str(msg.content)[:80] + "..." if len(str(msg.content)) > 80 else str(msg.content) |
| 415 | + print(f" [{i}] {msg_type}: {content_preview}") |
| 416 | + if has_tool_calls: |
| 417 | + for tc in msg.tool_calls: |
| 418 | + tc_name = tc.get('name', tc) if isinstance(tc, dict) else str(tc) |
| 419 | + print(f" 🔧 Tool call: {tc_name}") |
| 420 | + print(f"{'='*60}\n") |
361 | 421 |
|
362 | 422 | # Extract the agent's final response |
363 | 423 | final_message = result["messages"][-1] |
|
0 commit comments