Skip to content

Conversation

@Dwij1704
Copy link
Member

We've been facing issues with duplicate LLM instrumentation when multiple instrumentors (openai, crewai etc.) try to wrap the same LLM calls. This leads to redundant spans and potential conflicts in tracing.
Refactoring instrumentation module to be independent of each other is eating me up for the past week :)

The Solution

Me, @the-praxs and @fenilfaldu were discussing approaches to fix tightly coupled behavior and make it independent of each other. Thanks to @the-praxs (🐐) who came up with this solution from V3, we've implemented a lazy loading approach for LLM libraries. Instead of eagerly importing and instrumenting all possible LLM providers and agentic frameworks, we now check sys.modules for direct imports and prioritize Agentic Libararis over providers to eliminate redundant spans.

Why This is Better

  • Prevents duplicate instrumentation of the same LLM calls
  • Reduces overhead by only instrumenting what's actually used
  • More reliable than depending on other instrumentors' LLM call spans
  • Cleaner traces with single spans per LLM call

Required Changes to Agentic Libraries

With this change, we need to update our agentic library integrations to record their own LLM spans:

  • CrewAI: Need to modify to record spans for its internal LLM calls
  • AutoGen: Update to track LLM interactions within agent conversations
  • Agents SDK: Add span recording for direct LLM calls

This ensures that even when these frameworks make LLM calls internally, we'll have proper visibility into their operations without duplicate instrumentation.

Current Limitations

There's one known limitation: if a provider/agentic framework is imported in a separate file and then imported into the main file, we miss it. For example:

# llm_caller.py (helper file)
from anthropic import Anthropic  # This import might be missed

def call_anthropic():
    client = Anthropic()
    return client.messages.create(...)

# main.py
from llm_caller import call_anthropic  # We detect this import but miss the underlying Anthropic import

Possible fix is to scan imported modules' source files for additional imports

Testing

I've done some testing with this approach and found it to be more reliable than our previous method. It successfully prevents duplicate spans but we still need to update crewai, autogen and agents instrumentations.

…sion checks. Introduced PROVIDERS and AGENTIC_LIBRARIES dictionaries for managing instrumentors, and implemented get_active_libraries function to identify currently used libraries. Updated InstrumentorLoader to validate library versions before activation. Refactored instrument_all function to prioritize agentic libraries before standard providers.
@Dwij1704 Dwij1704 requested a review from areibman May 19, 2025 06:33
@codecov
Copy link

codecov bot commented May 19, 2025

Codecov Report

Attention: Patch coverage is 79.06977% with 18 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
agentops/instrumentation/__init__.py 79.06% 18 Missing ⚠️

📢 Thoughts on this report? Let us know!

…or improved package monitoring and dynamic instrumentation. Added methods for managing active instrumentors, handling conflicts between agentic libraries and providers, and monitoring imports. Updated configuration for supported libraries and combined target packages for streamlined instrumentation.
@Dwij1704
Copy link
Member Author

Dwij1704 commented May 19, 2025

Import Hooking Magic:
Hook into Python's import system, when someone imports an LLM provider or agentic library, we automatically detect it and set up the instrumentation.

We handle the tricky case of provider vs agentic library conflicts. For example, if someone's using CrewAI (which uses litellm, which uses OpenAI under the hood), we automatically uninstrument the direct OpenAI provider to avoid double-counting.

The implementation uses a singleton InstrumentationManager that:

  • Monitors Python imports using a custom import hook
  • Maintains a list of active instrumentors
  • Handles the provider vs agentic library dance

@the-praxs @tcdent @areibman Thoughts? checkout the PR, I have made changes with this approach and it works!

@dot-agi
Copy link
Member

dot-agi commented May 20, 2025

The dance of instrumentation is a tedious one.

But I will review thy PR. Time for some Easter eggs in the codebase :)

@areibman areibman requested a review from tcdent May 20, 2025 15:53
@tcdent
Copy link
Contributor

tcdent commented May 20, 2025

Does this now incorporate a fix for modules imported in sub-modules? Super common use case to have a project span multiple modules.

Copy link
Contributor

@tcdent tcdent left a comment

Choose a reason for hiding this comment

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

Just left a couple comments on code style.

@Dwij1704
Copy link
Member Author

Does this now incorporate a fix for modules imported in sub-modules? Super common use case to have a project span multiple modules.

Yes it does, at first it checks using sys.modules to instrument existing direct imports, then relies on import hook to instrument indirect imports

@Dwij1704
Copy link
Member Author

Dwij1704 commented May 20, 2025

@tcdent Try this out

#Example.py

from dotenv import load_dotenv
load_dotenv()

import os
from openai import OpenAI
import agentops

agentops.init(os.getenv("AGENTOPS_API_KEY"))
from llm_caller import generate_content
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

print("Hello, world!")
res1 = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "Hello, world!"}]
)
print(res1.choices[0].message.content)


response = generate_content("Hello, world!")
print(response)

#llm_caller.py

from anthropic import Anthropic
from dotenv import load_dotenv
import os
load_dotenv()
client = Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))
def generate_content(prompt):
    # Make a completion request - AgentOps will track it automatically
    message = client.messages.create(
        model="claude-3-opus-20240229",
        max_tokens=1000,
        messages=[
            {"role": "user", "content": prompt}
        ]
    )

    return message

Dwij1704 and others added 6 commits May 21, 2025 00:07
…tor configurations, improving type safety for PROVIDERS and AGENTIC_LIBRARIES dictionaries. This change facilitates better structure and clarity in managing instrumentor settings.
…implement module-level state management for active instrumentors. Introduced helper functions for package instrumentation, monitoring imports, and uninstrumenting providers, enhancing clarity and maintainability of the code. Updated instrumentation logic to handle conflicts between agentic libraries and providers more effectively.
…menting processes. Removed internal helper functions for starting and stopping monitoring, integrating their logic directly into the `instrument_all` and `uninstrument_all` functions for improved clarity and maintainability.
… existing imports directly into the `instrument_all` function. This change removes the now-unnecessary `_check_existing_imports` helper function, enhancing code clarity and maintainability while preserving the functionality of monitoring already imported packages.
@Dwij1704 Dwij1704 requested review from dot-agi and tcdent May 21, 2025 06:43
@bboynton97 bboynton97 self-requested a review May 21, 2025 15:45
@dot-agi dot-agi merged commit f92a115 into main May 21, 2025
10 checks passed
@dot-agi dot-agi deleted the lazy-loading-enhancement branch May 21, 2025 17:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants