|
8 | 8 |
|
9 | 9 | from ddtrace.internal import core |
10 | 10 | from ddtrace.internal.logger import get_logger |
| 11 | +from ddtrace.internal.utils import get_argument_value |
11 | 12 | from ddtrace.internal.utils.formats import format_trace_id |
| 13 | +from ddtrace.llmobs._constants import AGENT_MANIFEST |
12 | 14 | from ddtrace.llmobs._constants import DISPATCH_ON_LLM_TOOL_CHOICE |
13 | 15 | from ddtrace.llmobs._constants import DISPATCH_ON_TOOL_CALL |
14 | 16 | from ddtrace.llmobs._constants import DISPATCH_ON_TOOL_CALL_OUTPUT_USED |
|
31 | 33 | from ddtrace.llmobs._integrations.utils import OaiTraceAdapter |
32 | 34 | from ddtrace.llmobs._utils import _get_nearest_llmobs_ancestor |
33 | 35 | from ddtrace.llmobs._utils import _get_span_name |
| 36 | +from ddtrace.llmobs._utils import load_data_value |
34 | 37 | from ddtrace.trace import Pin |
35 | 38 | from ddtrace.trace import Span |
36 | 39 |
|
@@ -296,3 +299,145 @@ def _llmobs_get_trace_info( |
296 | 299 | def clear_state(self) -> None: |
297 | 300 | self.oai_to_llmobs_span.clear() |
298 | 301 | self.llmobs_traces.clear() |
| 302 | + |
| 303 | + def tag_agent_manifest(self, span: Span, args: List[Any], kwargs: Dict[str, Any], agent_index: int) -> None: |
| 304 | + agent = get_argument_value(args, kwargs, agent_index, "agent", True) |
| 305 | + if not agent or not self.llmobs_enabled: |
| 306 | + return |
| 307 | + |
| 308 | + manifest = {} |
| 309 | + manifest["framework"] = "OpenAI" |
| 310 | + if hasattr(agent, "name"): |
| 311 | + manifest["name"] = agent.name |
| 312 | + if hasattr(agent, "instructions"): |
| 313 | + manifest["instructions"] = agent.instructions |
| 314 | + if hasattr(agent, "handoff_description"): |
| 315 | + manifest["handoff_description"] = agent.handoff_description |
| 316 | + if hasattr(agent, "model"): |
| 317 | + model = agent.model |
| 318 | + manifest["model"] = model if isinstance(model, str) else getattr(model, "model", "") |
| 319 | + |
| 320 | + model_settings = self._extract_model_settings_from_agent(agent) |
| 321 | + if model_settings: |
| 322 | + manifest["model_settings"] = model_settings |
| 323 | + |
| 324 | + tools = self._extract_tools_from_agent(agent) |
| 325 | + if tools: |
| 326 | + manifest["tools"] = tools |
| 327 | + |
| 328 | + handoffs = self._extract_handoffs_from_agent(agent) |
| 329 | + if handoffs: |
| 330 | + manifest["handoffs"] = handoffs |
| 331 | + |
| 332 | + guardrails = self._extract_guardrails_from_agent(agent) |
| 333 | + if guardrails: |
| 334 | + manifest["guardrails"] = guardrails |
| 335 | + |
| 336 | + span._set_ctx_item(AGENT_MANIFEST, manifest) |
| 337 | + |
| 338 | + def _extract_model_settings_from_agent(self, agent): |
| 339 | + if not hasattr(agent, "model_settings"): |
| 340 | + return None |
| 341 | + |
| 342 | + # convert model_settings to dict if it's not already |
| 343 | + model_settings = agent.model_settings |
| 344 | + if type(model_settings) != dict: |
| 345 | + model_settings = getattr(model_settings, "__dict__", None) |
| 346 | + |
| 347 | + return load_data_value(model_settings) |
| 348 | + |
| 349 | + def _extract_tools_from_agent(self, agent): |
| 350 | + if not hasattr(agent, "tools") or not agent.tools: |
| 351 | + return None |
| 352 | + |
| 353 | + tools = [] |
| 354 | + for tool in agent.tools: |
| 355 | + tool_dict = {} |
| 356 | + tool_name = getattr(tool, "name", None) |
| 357 | + if tool_name: |
| 358 | + tool_dict["name"] = tool_name |
| 359 | + if tool_name == "web_search_preview": |
| 360 | + if hasattr(tool, "user_location"): |
| 361 | + tool_dict["user_location"] = tool.user_location |
| 362 | + if hasattr(tool, "search_context_size"): |
| 363 | + tool_dict["search_context_size"] = tool.search_context_size |
| 364 | + elif tool_name == "file_search": |
| 365 | + if hasattr(tool, "vector_store_ids"): |
| 366 | + tool_dict["vector_store_ids"] = tool.vector_store_ids |
| 367 | + if hasattr(tool, "max_num_results"): |
| 368 | + tool_dict["max_num_results"] = tool.max_num_results |
| 369 | + if hasattr(tool, "include_search_results"): |
| 370 | + tool_dict["include_search_results"] = tool.include_search_results |
| 371 | + if hasattr(tool, "ranking_options"): |
| 372 | + tool_dict["ranking_options"] = tool.ranking_options |
| 373 | + if hasattr(tool, "filters"): |
| 374 | + tool_dict["filters"] = tool.filters |
| 375 | + elif tool_name == "computer_use_preview": |
| 376 | + if hasattr(tool, "computer"): |
| 377 | + tool_dict["computer"] = tool.computer |
| 378 | + if hasattr(tool, "on_safety_check"): |
| 379 | + tool_dict["on_safety_check"] = tool.on_safety_check |
| 380 | + elif tool_name == "code_interpreter": |
| 381 | + if hasattr(tool, "tool_config"): |
| 382 | + tool_dict["tool_config"] = tool.tool_config |
| 383 | + elif tool_name == "hosted_mcp": |
| 384 | + if hasattr(tool, "tool_config"): |
| 385 | + tool_dict["tool_config"] = tool.tool_config |
| 386 | + if hasattr(tool, "on_approval_request"): |
| 387 | + tool_dict["on_approval_request"] = tool.on_approval_request |
| 388 | + elif tool_name == "image_generation": |
| 389 | + if hasattr(tool, "tool_config"): |
| 390 | + tool_dict["tool_config"] = tool.tool_config |
| 391 | + elif tool_name == "local_shell": |
| 392 | + if hasattr(tool, "executor"): |
| 393 | + tool_dict["executor"] = tool.executor |
| 394 | + else: |
| 395 | + if hasattr(tool, "description"): |
| 396 | + tool_dict["description"] = tool.description |
| 397 | + if hasattr(tool, "strict_json_schema"): |
| 398 | + tool_dict["strict_json_schema"] = tool.strict_json_schema |
| 399 | + if hasattr(tool, "params_json_schema"): |
| 400 | + parameter_schema = tool.params_json_schema |
| 401 | + required_params = {param: True for param in parameter_schema.get("required", [])} |
| 402 | + parameters = {} |
| 403 | + for param, schema in parameter_schema.get("properties", {}).items(): |
| 404 | + param_dict = {} |
| 405 | + if "type" in schema: |
| 406 | + param_dict["type"] = schema["type"] |
| 407 | + if "title" in schema: |
| 408 | + param_dict["title"] = schema["title"] |
| 409 | + if param in required_params: |
| 410 | + param_dict["required"] = True |
| 411 | + parameters[param] = param_dict |
| 412 | + tool_dict["parameters"] = parameters |
| 413 | + tools.append(tool_dict) |
| 414 | + |
| 415 | + return tools |
| 416 | + |
| 417 | + def _extract_handoffs_from_agent(self, agent): |
| 418 | + if not hasattr(agent, "handoffs") or not agent.handoffs: |
| 419 | + return None |
| 420 | + |
| 421 | + handoffs = [] |
| 422 | + for handoff in agent.handoffs: |
| 423 | + handoff_dict = {} |
| 424 | + if hasattr(handoff, "handoff_description") or hasattr(handoff, "tool_description"): |
| 425 | + handoff_dict["handoff_description"] = getattr(handoff, "handoff_description", None) or getattr( |
| 426 | + handoff, "tool_description", None |
| 427 | + ) |
| 428 | + if hasattr(handoff, "name") or hasattr(handoff, "agent_name"): |
| 429 | + handoff_dict["agent_name"] = getattr(handoff, "name", None) or getattr(handoff, "agent_name", None) |
| 430 | + if hasattr(handoff, "tool_name"): |
| 431 | + handoff_dict["tool_name"] = handoff.tool_name |
| 432 | + if handoff_dict: |
| 433 | + handoffs.append(handoff_dict) |
| 434 | + |
| 435 | + return handoffs |
| 436 | + |
| 437 | + def _extract_guardrails_from_agent(self, agent): |
| 438 | + guardrails = [] |
| 439 | + if hasattr(agent, "input_guardrails"): |
| 440 | + guardrails.extend([getattr(guardrail, "name", "") for guardrail in agent.input_guardrails]) |
| 441 | + if hasattr(agent, "output_guardrails"): |
| 442 | + guardrails.extend([getattr(guardrail, "name", "") for guardrail in agent.output_guardrails]) |
| 443 | + return guardrails |
0 commit comments