6262
6363logger = logging .getLogger (__name__ )
6464
65+ _MISSING = object ()
66+
6567
6668@contextmanager
6769def _harness_metrics_lifecycle (should_trace : bool ):
@@ -401,6 +403,12 @@ def _strip_blocked_modules(
401403 "current_method" , default = None
402404)
403405_current_llm_var : contextvars .ContextVar [Any ] = contextvars .ContextVar ("current_llm" , default = None )
406+ _current_llm_model_name_var : contextvars .ContextVar [str | None ] = contextvars .ContextVar (
407+ "current_llm_model_name" , default = None
408+ )
409+ _current_llm_selection_source_var : contextvars .ContextVar [str | None ] = contextvars .ContextVar (
410+ "current_llm_selection_source" , default = None
411+ )
404412
405413# Context variable for the resolved truncation config for the current generation call.
406414# Set by _execute_with_generation() so that method-level @strategy(truncation=...)
@@ -1953,6 +1961,14 @@ async def execute_nested(
19531961 if hasattr (strategy , "prefill" ) and getattr (strategy , "prefill" ) is not None : # noqa: B009
19541962 strategy_kwargs ["has_prefill" ] = True
19551963
1964+ llm_model_name = _current_llm_model_name_var .get ()
1965+ llm_selection_source = _current_llm_selection_source_var .get ()
1966+ llm_kwargs = {}
1967+ if llm_model_name is not None :
1968+ llm_kwargs ["llm.model_name" ] = llm_model_name
1969+ if llm_selection_source is not None :
1970+ llm_kwargs ["llm.selection_source" ] = llm_selection_source
1971+
19561972 # Call generation hooks (skip for non-traceable strategies like TemplateStrategy)
19571973 should_trace = strategy .traceable
19581974 hook_context = None
@@ -1965,6 +1981,7 @@ async def execute_nested(
19651981 generation_id = generation_id ,
19661982 parent_generation_id = parent_generation_id ,
19671983 agent_call_id = self ._agent_call_id ,
1984+ ** llm_kwargs ,
19681985 ** strategy_kwargs , # Add strategy config parameters
19691986 )
19701987
@@ -2530,13 +2547,18 @@ async def _execute_with_generation(
25302547 method_name : str ,
25312548 ) -> Any :
25322549 """Execute a method that needs LLM generation."""
2550+ base_method = getattr (method , "__func__" , method )
2551+ try :
2552+ has_user_llm_param = "llm" in inspect .signature (method ).parameters
2553+ except (TypeError , ValueError ):
2554+ has_user_llm_param = False
2555+
25332556 # Extract framework parameters (don't pass to generated method)
25342557 call_strategy = kwargs .pop ("_strategy" , None )
2535- call_llm = kwargs .pop ("llm" , None )
2558+ call_llm = kwargs .pop ("llm" , _MISSING ) if not has_user_llm_param else _MISSING
25362559 call_session_locals = kwargs .pop ("_session_locals" , None )
25372560
25382561 # Get strategy with priority: call-level > decorator > default
2539- base_method = getattr (method , "__func__" , method )
25402562 decorator_strategy = getattr (base_method , "_plan_strategy" , None )
25412563
25422564 # DEBUG: Log strategy retrieval for nested method debugging
@@ -2561,17 +2583,21 @@ async def _execute_with_generation(
25612583 # A @strategy(llm=...) value may be a callable resolved against the agent
25622584 # instance; only invoke it when it would actually be used, so a call-level
25632585 # override doesn't trigger someone else's resolver side effects.
2564- if call_llm is not None :
2586+ plan_llm = getattr (base_method , "_plan_llm" , None )
2587+ if call_llm is not _MISSING and call_llm is not None :
25652588 llm_client = call_llm
2566- else :
2567- plan_llm = getattr (base_method , "_plan_llm" , None )
2568- if plan_llm is not None :
2569- from nooa .method_llm import resolve_method_llm
2589+ llm_selection_source = "call_site"
2590+ elif plan_llm is not None :
2591+ from nooa .method_llm import resolve_method_llm
25702592
2571- plan_llm = resolve_method_llm (plan_llm , self .agent , method_name )
2572- llm_client = plan_llm or getattr (self .agent , "_llm" , None )
2593+ llm_client = resolve_method_llm (plan_llm , self .agent , method_name )
2594+ llm_selection_source = "decorator"
2595+ else :
2596+ llm_client = getattr (self .agent , "_llm" , None )
2597+ llm_selection_source = "agent_default"
25732598 if llm_client is None :
25742599 raise RuntimeError (f"No LLM client available for { method_name } " )
2600+ llm_model_name = getattr (llm_client , "model" , "" ) or ""
25752601
25762602 # Resolve truncation config: method-level @strategy(truncation=...) > agent-level
25772603 method_truncation = getattr (base_method , "_strategy_truncation" , None )
@@ -2615,6 +2641,10 @@ async def _execute_with_generation(
26152641 generation_id = generation_id ,
26162642 parent_generation_id = parent_generation_id ,
26172643 agent_call_id = self ._agent_call_id ,
2644+ ** {
2645+ "llm.model_name" : llm_model_name ,
2646+ "llm.selection_source" : llm_selection_source ,
2647+ },
26182648 ** strategy_kwargs , # Add strategy config parameters
26192649 )
26202650
@@ -2709,6 +2739,10 @@ async def _execute_with_generation(
27092739 call_token = _current_call_var .set (call )
27102740 method_token = _current_method_var .set (method )
27112741 llm_token = _current_llm_var .set (llm_client )
2742+ llm_model_name_token = _current_llm_model_name_var .set (llm_model_name )
2743+ llm_selection_source_token = _current_llm_selection_source_var .set (
2744+ llm_selection_source
2745+ )
27122746 truncation_token = _current_truncation_config_var .set (resolved_truncation )
27132747 event_format_token = _current_event_format_var .set (
27142748 resolved_truncation .event_format .model_dump ()
@@ -2749,6 +2783,8 @@ async def _execute_with_generation(
27492783 _current_call_var .reset (call_token )
27502784 _current_method_var .reset (method_token )
27512785 _current_llm_var .reset (llm_token )
2786+ _current_llm_model_name_var .reset (llm_model_name_token )
2787+ _current_llm_selection_source_var .reset (llm_selection_source_token )
27522788 _current_truncation_config_var .reset (truncation_token )
27532789 _current_event_format_var .reset (event_format_token )
27542790 _decorator_context_var .reset (decorator_ctx_token )
0 commit comments