Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions python/packages/core/agent_framework/_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,7 @@ class WeatherArgs(BaseModel):
"""

INJECTABLE: ClassVar[set[str]] = {"func"}
DEFAULT_EXCLUDE: ClassVar[set[str]] = {"input_model", "_invocation_duration_histogram"}
DEFAULT_EXCLUDE: ClassVar[set[str]] = {"input_model", "_invocation_duration_histogram", "_cached_parameters"}

def __init__(
self,
Expand Down Expand Up @@ -615,6 +615,7 @@ def __init__(
self.func = func
self._instance = None # Store the instance for bound methods
self.input_model = self._resolve_input_model(input_model)
self._cached_parameters: dict[str, Any] | None = None # Cache for model_json_schema()
self.approval_mode = approval_mode or "never_require"
if max_invocations is not None and max_invocations < 1:
raise ValueError("max_invocations must be at least 1 or None.")
Expand Down Expand Up @@ -802,8 +803,11 @@ def parameters(self) -> dict[str, Any]:

Returns:
A dictionary containing the JSON schema for the function's parameters.
The result is cached after the first call for performance.
"""
return self.input_model.model_json_schema()
if self._cached_parameters is None:
self._cached_parameters = self.input_model.model_json_schema()
return self._cached_parameters

def to_json_schema_spec(self) -> dict[str, Any]:
"""Convert a AIFunction to the JSON Schema function specification format.
Expand All @@ -825,7 +829,7 @@ def to_dict(self, *, exclude: set[str] | None = None, exclude_none: bool = True)
as_dict = super().to_dict(exclude=exclude, exclude_none=exclude_none)
if (exclude and "input_model" in exclude) or not self.input_model:
return as_dict
as_dict["input_model"] = self.input_model.model_json_schema()
as_dict["input_model"] = self.parameters() # Use cached parameters()
return as_dict


Expand Down
21 changes: 11 additions & 10 deletions python/packages/core/agent_framework/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -2248,20 +2248,21 @@ def _process_update(
if update.message_id:
message.message_id = update.message_id
for content in update.contents:
if (
isinstance(content, FunctionCallContent)
and len(message.contents) > 0
and isinstance(message.contents[-1], FunctionCallContent)
):
try:
message.contents[-1] += content
except AdditionItemMismatch:
# Use type attribute check first (fast string comparison) before isinstance (slower)
content_type = getattr(content, "type", None)
if content_type == "function_call":
if len(message.contents) > 0 and getattr(message.contents[-1], "type", None) == "function_call":
try:
message.contents[-1] += content
except AdditionItemMismatch:
message.contents.append(content)
else:
message.contents.append(content)
elif isinstance(content, UsageContent):
elif content_type == "usage":
if response.usage_details is None:
response.usage_details = UsageDetails()
response.usage_details += content.details
elif isinstance(content, (dict, MutableMapping)):
elif content_type is None and isinstance(content, (dict, MutableMapping)):
try:
cont = _parse_content(content)
message.contents.append(cont)
Expand Down
11 changes: 5 additions & 6 deletions python/packages/core/agent_framework/observability.py
Original file line number Diff line number Diff line change
Expand Up @@ -1680,13 +1680,12 @@ def _capture_messages(
prepped = prepare_messages(messages, system_instructions=system_instructions)
otel_messages: list[dict[str, Any]] = []
for index, message in enumerate(prepped):
otel_messages.append(_to_otel_message(message))
try:
message_data = message.to_dict(exclude_none=True)
except Exception:
message_data = {"role": message.role.value, "contents": message.contents}
# Reuse the otel message representation for logging instead of calling to_dict()
# to avoid expensive Pydantic serialization overhead
otel_message = _to_otel_message(message)
otel_messages.append(otel_message)
logger.info(
message_data,
otel_message,
extra={
OtelAttr.EVENT_NAME: OtelAttr.CHOICE if output else ROLE_EVENT_MAP.get(message.role.value),
OtelAttr.PROVIDER_NAME: provider_name,
Expand Down
Loading