mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: Tool definitions otel (#936)
* added tool_definitions * removed json dump * improved logic * updated for pydantic
This commit is contained in:
committed by
GitHub
Unverified
parent
54ad135914
commit
40f5b6d8fe
@@ -545,6 +545,47 @@ class AIFunction(BaseTool, Generic[ArgsT, ReturnT]):
|
||||
}
|
||||
|
||||
|
||||
def _tools_to_dict(
|
||||
tools: (
|
||||
ToolProtocol
|
||||
| Callable[..., Any]
|
||||
| MutableMapping[str, Any]
|
||||
| Sequence[ToolProtocol | Callable[..., Any] | MutableMapping[str, Any]]
|
||||
| None
|
||||
),
|
||||
) -> list[str | dict[str, Any]] | None:
|
||||
"""Parse the tools to a dict."""
|
||||
if not tools:
|
||||
return None
|
||||
if not isinstance(tools, list):
|
||||
if isinstance(tools, AIFunction):
|
||||
return [tools.to_json_schema_spec()]
|
||||
if isinstance(tools, SerializationMixin):
|
||||
return [tools.to_dict()]
|
||||
if isinstance(tools, dict):
|
||||
return [tools]
|
||||
if callable(tools):
|
||||
return [ai_function(tools).to_json_schema_spec()]
|
||||
logger.warning("Can't parse tool.")
|
||||
return None
|
||||
results: list[str | dict[str, Any]] = []
|
||||
for tool in tools:
|
||||
if isinstance(tool, AIFunction):
|
||||
results.append(tool.to_json_schema_spec())
|
||||
continue
|
||||
if isinstance(tool, SerializationMixin):
|
||||
results.append(tool.to_dict())
|
||||
continue
|
||||
if isinstance(tool, dict):
|
||||
results.append(tool)
|
||||
continue
|
||||
if callable(tool):
|
||||
results.append(ai_function(tool).to_json_schema_spec())
|
||||
continue
|
||||
logger.warning("Can't parse tool.")
|
||||
return results
|
||||
|
||||
|
||||
# region AI Function Decorator
|
||||
|
||||
|
||||
|
||||
@@ -145,6 +145,7 @@ class OtelAttr(str, Enum):
|
||||
TOOL_DESCRIPTION = "gen_ai.tool.description"
|
||||
TOOL_NAME = "gen_ai.tool.name"
|
||||
TOOL_TYPE = "gen_ai.tool.type"
|
||||
TOOL_DEFINITIONS = "gen_ai.tool.definitions"
|
||||
TOOL_ARGUMENTS = "gen_ai.tool.call.arguments"
|
||||
TOOL_RESULT = "gen_ai.tool.call.result"
|
||||
# Agent attributes
|
||||
@@ -993,7 +994,6 @@ def _trace_agent_run(
|
||||
if not OBSERVABILITY_SETTINGS.ENABLED:
|
||||
# If model diagnostics are not enabled, just return the completion
|
||||
return await run_func(self, messages=messages, thread=thread, **kwargs)
|
||||
|
||||
attributes = _get_span_attributes(
|
||||
operation_name=OtelAttr.AGENT_INVOKE_OPERATION,
|
||||
provider_name=provider_name,
|
||||
@@ -1001,6 +1001,7 @@ def _trace_agent_run(
|
||||
agent_name=self.display_name,
|
||||
agent_description=self.description,
|
||||
thread_id=thread.service_thread_id if thread else None,
|
||||
chat_options=getattr(self, "chat_options", None),
|
||||
**kwargs,
|
||||
)
|
||||
with _get_span(attributes=attributes, span_name_attribute=OtelAttr.AGENT_NAME) as span:
|
||||
@@ -1070,6 +1071,7 @@ def _trace_agent_run_stream(
|
||||
agent_name=self.display_name,
|
||||
agent_description=self.description,
|
||||
thread_id=thread.service_thread_id if thread else None,
|
||||
chat_options=getattr(self, "chat_options", None),
|
||||
**kwargs,
|
||||
)
|
||||
with _get_span(attributes=attributes, span_name_attribute=OtelAttr.AGENT_NAME) as span:
|
||||
@@ -1182,13 +1184,17 @@ def _get_span(
|
||||
|
||||
def _get_span_attributes(**kwargs: Any) -> dict[str, Any]:
|
||||
"""Get the span attributes from a kwargs dictionary."""
|
||||
from ._tools import _tools_to_dict
|
||||
from ._types import ChatOptions
|
||||
|
||||
attributes: dict[str, Any] = {}
|
||||
chat_options: ChatOptions | None = kwargs.get("chat_options")
|
||||
if chat_options is None:
|
||||
chat_options = ChatOptions()
|
||||
if operation_name := kwargs.get("operation_name"):
|
||||
attributes[OtelAttr.OPERATION] = operation_name
|
||||
if choice_count := kwargs.get("choice_count", 1):
|
||||
attributes[OtelAttr.CHOICE_COUNT] = choice_count
|
||||
if operation_name := kwargs.get("operation_name"):
|
||||
attributes[OtelAttr.OPERATION] = operation_name
|
||||
if system_name := kwargs.get("system_name"):
|
||||
attributes[SpanAttributes.LLM_SYSTEM] = system_name
|
||||
if provider_name := kwargs.get("provider_name"):
|
||||
@@ -1196,21 +1202,21 @@ def _get_span_attributes(**kwargs: Any) -> dict[str, Any]:
|
||||
attributes[SpanAttributes.LLM_REQUEST_MODEL] = kwargs.get("model_id", "unknown")
|
||||
if service_url := kwargs.get("service_url"):
|
||||
attributes[OtelAttr.ADDRESS] = service_url
|
||||
if conversation_id := kwargs.get("conversation_id"):
|
||||
if conversation_id := kwargs.get("conversation_id", chat_options.conversation_id):
|
||||
attributes[OtelAttr.CONVERSATION_ID] = conversation_id
|
||||
if seed := kwargs.get("seed"):
|
||||
if seed := kwargs.get("seed", chat_options.seed):
|
||||
attributes[OtelAttr.SEED] = seed
|
||||
if frequency_penalty := kwargs.get("frequency_penalty"):
|
||||
if frequency_penalty := kwargs.get("frequency_penalty", chat_options.frequency_penalty):
|
||||
attributes[OtelAttr.FREQUENCY_PENALTY] = frequency_penalty
|
||||
if max_tokens := kwargs.get("max_tokens"):
|
||||
if max_tokens := kwargs.get("max_tokens", chat_options.max_tokens):
|
||||
attributes[SpanAttributes.LLM_REQUEST_MAX_TOKENS] = max_tokens
|
||||
if stop := kwargs.get("stop"):
|
||||
if stop := kwargs.get("stop", chat_options.stop):
|
||||
attributes[OtelAttr.STOP_SEQUENCES] = stop
|
||||
if temperature := kwargs.get("temperature"):
|
||||
if temperature := kwargs.get("temperature", chat_options.temperature):
|
||||
attributes[SpanAttributes.LLM_REQUEST_TEMPERATURE] = temperature
|
||||
if top_p := kwargs.get("top_p"):
|
||||
if top_p := kwargs.get("top_p", chat_options.top_p):
|
||||
attributes[SpanAttributes.LLM_REQUEST_TOP_P] = top_p
|
||||
if presence_penalty := kwargs.get("presence_penalty"):
|
||||
if presence_penalty := kwargs.get("presence_penalty", chat_options.presence_penalty):
|
||||
attributes[OtelAttr.PRESENCE_PENALTY] = presence_penalty
|
||||
if top_k := kwargs.get("top_k"):
|
||||
attributes[OtelAttr.TOP_K] = top_k
|
||||
@@ -1218,6 +1224,10 @@ def _get_span_attributes(**kwargs: Any) -> dict[str, Any]:
|
||||
attributes[OtelAttr.ENCODING_FORMATS] = json.dumps(
|
||||
encoding_formats if isinstance(encoding_formats, list) else [encoding_formats]
|
||||
)
|
||||
if tools := kwargs.get("tools", chat_options.tools):
|
||||
tools_as_json_list = _tools_to_dict(tools)
|
||||
if tools_as_json_list:
|
||||
attributes[OtelAttr.TOOL_DEFINITIONS] = json.dumps(tools_as_json_list)
|
||||
if error := kwargs.get("error"):
|
||||
attributes[OtelAttr.ERROR_TYPE] = type(error).__name__
|
||||
# agent attributes
|
||||
|
||||
@@ -88,6 +88,17 @@ setup_observability(exporters=[exporter])
|
||||
|
||||
> Using this method implicitly enables telemetry, so you do not need to set the `ENABLE_OTEL` environment variable. You can still set `ENABLE_SENSITIVE_DATA` to control whether sensitive data is included in the telemetry, or call the `setup_observability()` function with the `enable_sensitive_data` parameter set to `True`.
|
||||
|
||||
#### Logging
|
||||
You can control at what level logging happens and thus what logs get exported, you can do this, by adding this:
|
||||
|
||||
```python
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.NOTSET)
|
||||
```
|
||||
This gets the root logger and sets the level of that, automatically other loggers inherit from that one, and you will get detailed logs in your telemetry.
|
||||
|
||||
## Samples
|
||||
|
||||
This folder contains different samples demonstrating how to use telemetry in various scenarios.
|
||||
|
||||
Reference in New Issue
Block a user