99The host project injects tools and LLM configuration at startup.
1010"""
1111
12+ import logging
1213import os
1314from datetime import datetime
1415from pathlib import Path
16+ from time import perf_counter
1517from typing import Any , Optional
1618
1719from sqlmodel import Session , select
3335)
3436from .tool_registry import ToolRegistry
3537
38+ logger = logging .getLogger (__name__ )
39+
40+
41+ # ============================================================================
42+ # TOOL CALL LOGGING CALLBACK
43+ # ============================================================================
44+
45+ def _make_tool_logging_callback () -> Any :
46+ """Create a callback handler that logs tool invocations with latency."""
47+ from langchain_core .callbacks import BaseCallbackHandler
48+
49+ class ToolCallLoggingCallback (BaseCallbackHandler ):
50+ def __init__ (self ) -> None :
51+ super ().__init__ ()
52+ self ._start_times : dict [Any , float ] = {}
53+
54+ def on_tool_start (self , serialized : dict [str , Any ], input_str : str , * , run_id : Any , ** kwargs : Any ) -> None :
55+ self ._start_times [run_id ] = perf_counter ()
56+ name = serialized .get ("name" , "?" )
57+ preview = input_str [:200 ] if isinstance (input_str , str ) else str (input_str )[:200 ]
58+ logger .info ("🔧 Tool START name=%s run_id=%s input=%s" , name , run_id , preview )
59+
60+ def on_tool_end (self , output : str , * , run_id : Any , ** kwargs : Any ) -> None :
61+ started = self ._start_times .pop (run_id , None )
62+ ms = int ((perf_counter () - started ) * 1000 ) if started is not None else None
63+ preview = output [:300 ] if isinstance (output , str ) else str (output )[:300 ]
64+ logger .info ("✅ Tool END run_id=%s duration_ms=%s output=%s" , run_id , ms , preview )
65+
66+ def on_tool_error (self , error : BaseException , * , run_id : Any , ** kwargs : Any ) -> None :
67+ started = self ._start_times .pop (run_id , None )
68+ ms = int ((perf_counter () - started ) * 1000 ) if started is not None else None
69+ logger .error ("❌ Tool ERROR run_id=%s duration_ms=%s error=%s" , run_id , ms , error )
70+
71+ return ToolCallLoggingCallback ()
72+
3673# ============================================================================
3774# LLM HELPER - isolated so it stays optional at import time
3875# ============================================================================
@@ -381,11 +418,20 @@ async def run_agent(
381418 runtime_system_prompt = _append_markdown_output_instruction (agent_def .system_prompt )
382419 react = _build_react_agent (self .llm , tools , runtime_system_prompt )
383420
421+ logger .info ("▶️ Agent run_id=%s agent=%s tools=%s prompt=%s" ,
422+ run_id , agent_id , validated_tool_names , user_message [:120 ])
423+ t0 = perf_counter ()
424+
384425 result = await react .ainvoke (
385426 {"messages" : [("user" , user_message )]},
386- config = {"recursion_limit" : self ._recursion_limit },
427+ config = {
428+ "recursion_limit" : self ._recursion_limit ,
429+ "callbacks" : [_make_tool_logging_callback ()],
430+ },
387431 )
388432
433+ total_ms = int ((perf_counter () - t0 ) * 1000 )
434+
389435 final_msg = result ["messages" ][- 1 ]
390436 output = final_msg .content if hasattr (final_msg , "content" ) else str (final_msg )
391437
@@ -398,6 +444,9 @@ async def run_agent(
398444 if name :
399445 tools_used .append (name )
400446
447+ logger .info ("⏹️ Agent done run_id=%s total_ms=%s tools_used=%s messages=%d" ,
448+ run_id , total_ms , tools_used , len (result ["messages" ]))
449+
401450 # -- Persist completion --
402451 with Session (self ._engine ) as session :
403452 db_run = session .get (AgentRun , run_id )
0 commit comments