1212# See the License for the specific language governing permissions and
1313# limitations under the License.
1414
15+ import importlib
16+ import logging
1517from typing import Any , Collection
1618
1719from wrapt import wrap_function_wrapper
3032_AGENT = "agno.agent"
3133_MODULE = "agno.models.base"
3234_TOOLKIT = "agno.tools.function"
35+ _logger = logging .getLogger (__name__ )
3336__all__ = ["AgnoInstrumentor" ]
3437
38+ _INNER_MODEL_WRAPPERS = (
39+ ("Model._process_model_response" , "process_model_response" ),
40+ ("Model._aprocess_model_response" , "aprocess_model_response" ),
41+ ("Model.process_response_stream" , "process_response_stream" ),
42+ ("Model.aprocess_response_stream" , "aprocess_response_stream" ),
43+ ("Model.run_function_calls" , "run_function_calls" ),
44+ ("Model.arun_function_calls" , "arun_function_calls" ),
45+ )
46+
47+ _FALLBACK_MODEL_WRAPPERS = (
48+ ("Model.response" , "response" ),
49+ ("Model.aresponse" , "aresponse" ),
50+ ("Model.response_stream" , "response_stream" ),
51+ ("Model.aresponse_stream" , "aresponse_stream" ),
52+ )
53+
54+
55+ def _has_wrap_target (module_name : str , name : str ) -> bool :
56+ try :
57+ target = importlib .import_module (module_name )
58+ for part in name .split ("." ):
59+ target = getattr (target , part )
60+ except Exception :
61+ return False
62+ return True
63+
64+
65+ def _wrap_model_methods (model_wrapper : AgnoModelWrapper ) -> None :
66+ if all (
67+ _has_wrap_target (_MODULE , name )
68+ for name , _wrapper_name in _INNER_MODEL_WRAPPERS
69+ ):
70+ for name , wrapper_name in _INNER_MODEL_WRAPPERS :
71+ wrap_function_wrapper (
72+ module = _MODULE ,
73+ name = name ,
74+ wrapper = getattr (model_wrapper , wrapper_name ),
75+ )
76+ return
77+
78+ _logger .warning (
79+ "Agno inner model hooks are unavailable; falling back to outer "
80+ "Model.response wrappers."
81+ )
82+ for name , wrapper_name in _FALLBACK_MODEL_WRAPPERS :
83+ wrap_function_wrapper (
84+ module = _MODULE ,
85+ name = name ,
86+ wrapper = getattr (model_wrapper , wrapper_name ),
87+ )
88+
89+
90+ def _unwrap_if_present (target : Any , name : str ) -> None :
91+ if hasattr (target , name ):
92+ unwrap (target , name )
93+
3594
3695class AgnoInstrumentor (BaseInstrumentor ): # type: ignore
3796 """
@@ -91,27 +150,9 @@ def _instrument(self, **kwargs: Any) -> None:
91150 wrapper = function_call_wrapper .aexecute ,
92151 )
93152
94- # Wrap the model
95- wrap_function_wrapper (
96- module = _MODULE ,
97- name = "Model.response" ,
98- wrapper = model_wrapper .response ,
99- )
100- wrap_function_wrapper (
101- module = _MODULE ,
102- name = "Model.aresponse" ,
103- wrapper = model_wrapper .aresponse ,
104- )
105- wrap_function_wrapper (
106- module = _MODULE ,
107- name = "Model.response_stream" ,
108- wrapper = model_wrapper .response_stream ,
109- )
110- wrap_function_wrapper (
111- module = _MODULE ,
112- name = "Model.aresponse_stream" ,
113- wrapper = model_wrapper .aresponse_stream ,
114- )
153+ # Wrap the model. Prefer Agno's per-request internals so a tool-call
154+ # loop emits one LLM span per provider call.
155+ _wrap_model_methods (model_wrapper )
115156
116157 def _uninstrument (self , ** kwargs : Any ) -> None :
117158 # Unwrap the agent call function
@@ -129,8 +170,9 @@ def _uninstrument(self, **kwargs: Any) -> None:
129170 # Unwrap the model
130171 import agno .models .base # noqa: PLC0415
131172
132- unwrap (agno .models .base .Model , "response" )
133- unwrap (agno .models .base .Model , "aresponse" )
134- unwrap (agno .models .base .Model , "response_stream" )
135- unwrap (agno .models .base .Model , "aresponse_stream" )
173+ for name , _wrapper_name in (
174+ * _INNER_MODEL_WRAPPERS ,
175+ * _FALLBACK_MODEL_WRAPPERS ,
176+ ):
177+ _unwrap_if_present (agno .models .base .Model , name .split ("." , 1 )[1 ])
136178 self ._handler = None
0 commit comments