Skip to content

Conversation

@eavanvalkenburg
Copy link
Member

@eavanvalkenburg eavanvalkenburg commented Dec 22, 2025

Motivation and Context

Used a profiling script and cProfile to figure out the biggest time losses in AF, led to a few improvements

  • caching json schema creation from the pydantic input model
  • using checks on type fields instead of isinstance
  • logging using the same object as what is sent to otel, to eliminate a additional to_dict call

Description

Benchmark Comparison: Old vs New (Optimized) Code

Benchmark Old (ms) New (ms) Change Improvement
Imports (steady-state)
import_agent_framework 0.004 0.004 ~0 -
import_azure_openai_chat_client 0.006 0.006 ~0 -
import_openai_chat_client 0.005 0.005 ~0 -
import_chat_agent 0.006 0.005 -0.001 ~17% faster
import_ai_function 0.005 0.005 ~0 -
Chat Client (no observability)
chat_client_streaming=False_function_call=False 0.052 0.052 ~0 -
chat_client_streaming=True_function_call=False 0.108 0.107 -0.001 ~1% faster
chat_client_streaming=False_function_call=True 0.054 0.052 -0.002 ~4% faster
chat_client_streaming=True_function_call=True 0.161 0.143 -0.018 ~11% faster
Chat Client (with observability)
chat_client_streaming=False_function_call=False 0.107 0.082 -0.025 ~23% faster
chat_client_streaming=True_function_call=False 0.204 0.186 -0.018 ~9% faster
chat_client_streaming=False_function_call=True 0.111 0.079 -0.032 ~29% faster
chat_client_streaming=True_function_call=True 0.222 0.182 -0.040 ~18% faster
Agent (no observability)
agent_streaming=False_function_call=False 0.107 0.104 -0.003 ~3% faster
agent_streaming=True_function_call=False 0.195 0.262 +0.067 ~34% slower*
agent_streaming=False_function_call=True 1.79 1.55 -0.24 ~13% faster
agent_streaming=True_function_call=True 0.78 0.66 -0.12 ~15% faster
Agent (with observability)
agent_streaming=False_function_call=False 0.21 0.16 -0.05 ~24% faster
agent_streaming=True_function_call=False 0.38 0.33 -0.05 ~13% faster
agent_streaming=False_function_call=True 2.49 1.70 -0.79 ~32% faster
agent_streaming=True_function_call=True 1.55 0.93 -0.62 ~40% faster

Key Findings

🎯 Biggest Wins (with function calls + observability):

  • Agent streaming + function_call + observability: 40% faster (1.55ms → 0.93ms)
  • Agent non-streaming + function_call + observability: 32% faster (2.49ms → 1.70ms)
  • Chat client + function_call + observability: 29% faster (0.11ms → 0.079ms)

📊 Observability Overhead Reduced:

  • Old: Observability added ~0.05-0.09ms overhead
  • New: Observability overhead reduced significantly, especially for function call scenarios

⚡ Summary:

  • Observability-enabled paths show 13-40% improvement
  • Function call scenarios show 13-32% improvement
  • Base operations (no observability, no function calls) remain similar
  • One outlier: agent_streaming=True_function_call=False is slightly slower (likely measurement variance)

The optimizations primarily benefit the hot paths: function calling and observability tracing - which are the most common real-world usage patterns!

Contribution Checklist

  • The code builds clean without any errors or warnings
  • The PR follows the Contribution Guidelines
  • All unit tests pass, and I have added new tests where possible
  • Is this a breaking change? If yes, add "[BREAKING]" prefix to the title of the PR.

Copilot AI review requested due to automatic review settings December 22, 2025 16:11
@markwallace-microsoft
Copy link
Member

markwallace-microsoft commented Dec 22, 2025

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
packages/core/agent_framework
   _tools.py7116790%224, 270, 321, 323, 486, 518–519, 621, 623, 643, 661, 675, 687, 692, 694, 701, 734, 788–790, 831, 854, 856–865, 874–880, 916, 926, 1110, 1524–1528, 1649, 1718, 1811, 1817, 1860–1861, 1874–1875, 2004, 2045–2046, 2074–2076, 2118–2119, 2184–2185, 2192–2193
   _types.py96310189%130–131, 149–150, 287, 289, 296, 315, 355, 401–402, 438, 588, 702–703, 705, 730, 737, 754–756, 841, 846–847, 849, 856–857, 859, 886, 895, 898–900, 905–906, 913, 917–919, 1075, 1164–1167, 1175–1176, 1267, 1448, 1454, 1698–1700, 1706–1707, 2009, 2014, 2018, 2022, 2200–2202, 2214, 2266–2270, 2280, 2285, 2744, 2830–2832, 2905, 2916–2917, 3091, 3095, 3107–3109, 3210–3212, 3214–3216, 3219, 3223, 3226, 3231, 3276–3277, 3284–3285, 3319–3321, 3352, 3380, 3387
   observability.py64215476%244, 312–317, 319, 321–322, 324, 326–328, 331–333, 338–339, 345–346, 352–353, 360, 362–364, 367–369, 374–375, 381–382, 388–389, 396, 433, 436, 439–441, 444, 447–448, 451–453, 455–457, 460, 547, 549, 631, 649–650, 652, 655, 663–664, 667–670, 672, 675–677, 680–681, 694–700, 702–711, 714–718, 721–724, 726–729, 732–733, 741, 842, 844, 869–871, 993, 995, 999–1004, 1006, 1009–1013, 1015, 1285, 1365–1367, 1439–1441, 1614, 1622, 1626, 1630, 1636, 1638, 1640, 1648, 1658, 1699–1702, 1716, 1718, 1725, 1741, 1744, 1804, 1820, 1824, 1958, 1960
TOTAL16846262184% 

Python Unit Test Overview

Tests Skipped Failures Errors Time
2554 148 💤 0 ❌ 0 🔥 58.823s ⏱️

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces performance improvements to the Python Agent Framework based on profiling analysis. The changes focus on reducing computational overhead through strategic optimizations.

Key Changes

  • Caching of JSON schema generation from Pydantic models to avoid repeated expensive serialization
  • Replacing isinstance() checks with faster string attribute comparisons for content type identification
  • Reusing OpenTelemetry message representations for logging to eliminate redundant to_dict() calls

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
python/packages/core/agent_framework/observability.py Optimizes message logging by reusing the OTEL message representation instead of calling to_dict() separately
python/packages/core/agent_framework/_types.py Improves content type checking performance by using type attribute comparison before falling back to isinstance()
python/packages/core/agent_framework/_tools.py Implements caching for model_json_schema() results and updates exclusion list for proper serialization

@eavanvalkenburg eavanvalkenburg marked this pull request as draft December 22, 2025 16:16
except AdditionItemMismatch:
# Use type attribute check first (fast string comparison) before isinstance (slower)
content_type = getattr(content, "type", None)
if content_type == "function_call":
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use enums at least?

Copy link
Contributor

@TaoChenOSU TaoChenOSU left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: please also share the profiling results

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants