sdk-python icon indicating copy to clipboard operation
sdk-python copied to clipboard

[Bug] Langfuse Tracing Not Working with Temporal OpenAI Agents Plugin

Open databill86 opened this issue 4 months ago • 5 comments

Context: This issue was separated from a previous bug report. @tconley1428 confirmed that the plugin approach is the only way to run agents [edit: Tim - Agents SDK agents] in Temporal workflows, but the tracing/observability issue remains unresolved and I think it deserves a separate issue.

What are you really trying to do?

I'm building a multi-tenant application using Temporal workflows with OpenAI agents and need observability/tracing to work properly with Langfuse (though logfire, as explained here). I want to trace agent execution, tool calls, and model interactions through Langfuse and logfire.

Describe the bug

When using Temporal workflows with OpenAI agents and the required OpenAIAgentsPlugin, the Langfuse tracing instrumentation fails to work properly. The tracing setup doesn't capture agent execution traces in Langfuse.

Expected Behavior:

  • Agent execution should be traced and visible in Langfuse.
Image

Current Behavior:

  • No traces appear in Langfuse when using the plugin approach
  • Agent execution happens but is not observable through Langfuse
  • Tracing works fine when running agents outside of Temporal workflows

Minimal Reproduction

1. Tracing Setup (that works outside workflows):

from agents import set_trace_processors
import logfire
import nest_asyncio
import os

def init_tracing():
    """Initialize tracing and observability."""
    # Set Langfuse env vars from settings
    os.environ.setdefault("LANGFUSE_PUBLIC_KEY", LANGFUSE_PUBLIC_KEY)
    os.environ.setdefault("LANGFUSE_SECRET_KEY", LANGFUSE_SECRET_KEY)
    os.environ.setdefault("LANGFUSE_HOST", LANGFUSE_HOST)

    set_trace_processors([])  # only disable OpenAI tracing

    # Instrument OpenAI Agents SDK via pydantic-ai logfire
    try:
        nest_asyncio.apply()
        logfire.configure(service_name="temporal-demo", send_to_logfire=False)
        # This method automatically patches the OpenAI Agents SDK to send logs via OTLP to Langfuse.
        logfire.instrument_openai_agents()
    except Exception as exc:
        logger.error(f"Logfire instrumentation not available: {exc}")

2. Worker Setup with Plugin (required for Temporal workflows):

from temporalio.worker import Worker
from temporalio.contrib.openai_agents import OpenAIAgentsPlugin, ModelActivityParameters
from datetime import timedelta

async def main():
    # Initialize tracing (conflicts with plugins)
    init_tracing()  # Sets up logfire → OTLP → Langfuse
    
    # Create Temporal client with plugins (required for agents)
    plugins = [
        OpenAIAgentsPlugin(
            model_params=ModelActivityParameters(
                start_to_close_timeout=timedelta(seconds=30)
            ),
            model_provider=CustomLitellmProvider(
                base_url=PROXY_BASE_URL,
                api_key=PROXY_API_KEY,
            ),
        ),
    ]
    
    client = await create_temporal_client(include_plugins=True)
    
    # Run the worker
    with concurrent.futures.ThreadPoolExecutor(max_workers=10) as activity_executor:
        worker = Worker(
            client,
            task_queue="demo-task-queue",
            workflows=[MyWorkflow],
            activities=[simple_tool_activity],
            activity_executor=activity_executor,
        )
        await worker.run()

3. Workflow with Agent:

from temporalio import workflow
from agents import Agent

@workflow.defn
class MyWorkflow:
    @workflow.run
    async def run(self) -> str:
        # Create agent with string model (required with plugins)
        agent = Agent(
            name="Triage Agent",
            instructions="Your instructions here",
            model="gpt-4o-mini",  # String model name required with plugins
            tools=tools,
        )
        
        # This executes but doesn't appear in Langfuse traces
        result = await agent.run("Your message here")
        return result

4. Custom Model Provider (for proxy configuration):

from agents.extensions.models.litellm_model import LitellmModel
from agents.models.interface import Model, ModelProvider

class CustomLitellmProvider(ModelProvider):
    """Custom ModelProvider that uses LiteLLM with configurable base_url and api_key."""

    def __init__(self, base_url: str | None = None, api_key: str | None = None):
        self.base_url = base_url
        self.api_key = api_key

    @property
    def model_class(self) -> type[Model]:
        return LitellmModel

    @property
    def provider_name(self) -> str:
        return "CustomLitellmProvider"

    def get_model(self, model_name: str) -> Model:
        return LitellmModel(
            model=model_name,
            base_url=self.base_url,
            api_key=self.api_key,
        )

Environment/Versions

  • OS and processor: Linux
  • Temporal Version: temporalio==1.18.0
  • OpenAI SDK: openai==1.109.0
  • OpenAI Agents: openai-agents==0.3.2
  • Python: 3.11
  • Langfuse: Latest version
  • logfire: Latest version
  • Are you using Docker or Kubernetes or building Temporal from source? Using Docker

Current Behavior: No traces appear in Langfuse at all when using the plugin approach.

Thanks 🙏

databill86 avatar Sep 30 '25 15:09 databill86

Can you clarify, the link you provided doesn't mention logfire at all.

tconley1428 avatar Sep 30 '25 15:09 tconley1428

Sorry about that, I actually had the link saved in a readme file and copy pasted without checking. The tutorial has been updated, and it no longer uses logfire instrument.

Instead of logfire.instrument_openai_agents(), they now recommend:

from openinference.instrumentation.openai_agents import OpenAIAgentsInstrumentor
OpenAIAgentsInstrumentor().instrument()

I tested this, and it worked! I can see the traces in Langfuse (screenshot attached).

Image

However, I have the following errors in the logs:

ERROR:opentelemetry.context:Failed to detach context
Traceback (most recent call last):
  File "/lib/python3.11/site-packages/opentelemetry/context/__init__.py", line 155, in detach
    _RUNTIME_CONTEXT.detach(token)
  File "/lib/python3.11/site-packages/opentelemetry/context/contextvars_context.py", line 53, in detach
    self._current_context.reset(token)
ValueError: <Token var=<ContextVar name='current_context' default={} at 0x79c8f92d40e0> at 0x79c8c81a70c0> was created in a different Context

2025-09-30 17:53:03.498 | INFO     | activities.simple_tool_activity:tool_func:27 - ✅ Tool activity completed: Tool processed: 'Hello, this is a test message for the demo!' - This is a simple response from the tool activity!

ERROR:opentelemetry.context:Failed to detach context
Traceback (most recent call last):
  File "/lib/python3.11/site-packages/opentelemetry/context/__init__.py", line 155, in detach
    _RUNTIME_CONTEXT.detach(token)
  File "/lib/python3.11/site-packages/opentelemetry/context/contextvars_context.py", line 53, in detach
    self._current_context.reset(token)
ValueError: <Token var=<ContextVar name='current_context' default={} at 0x79c8f92d40e0> at 0x79c8b81d9b40> was created in a different Context
ERROR:opentelemetry.context:Failed to detach context
Traceback (most recent call last):
  File "/lib/python3.11/site-packages/opentelemetry/context/__init__.py", line 155, in detach
    _RUNTIME_CONTEXT.detach(token)
  File "/lib/python3.11/site-packages/opentelemetry/context/contextvars_context.py", line 53, in detach
    self._current_context.reset(token)
ValueError: <Token var=<ContextVar name='current_context' default={} at 0x79c8f92d40e0> at 0x79c8c81a47c0> was created in a different Context

Should I be concerned about these context detachment errors?

databill86 avatar Sep 30 '25 16:09 databill86

We have an open report of that which has been around for a while. https://github.com/temporalio/sdk-python/issues/441 If you don't see other issues with the run, it is probably okay to carry on, but it is something that is bubbling up for us to prioritize investigating.

tconley1428 avatar Sep 30 '25 16:09 tconley1428

Thanks for the quick response.

Tracing is absolutely critical for us, we can't avoid it. The specific tracing tool doesn't matter as long as it works reliably with Temporal. Langfuse is probably the most widely used, so if there's something that works well with that, it would be cool.

To summarize, I've done a lot of testing with different tracing approaches and here's what I found:

  1. Using logfire with logfire.instrument_openai_agents()

    • Doesn't work with Temporal (when using plugins in the client definition) - nothing gets traced, no errors
    • Works without Temporal plugins - traces perfectly when agent runs in a regular activity outside workflow
  2. Using logfire with logfire.instrument_openai() (global OpenAI client instrumentation)

    • ⚠️ Partially works with Temporal (when using plugins in the client definition) - traces metadata but input/output are null, also we have multiple observations in different langfuse traces for the same agent run, without any errors.
  3. Using openinference OpenAIAgentsInstrumentor().instrument() from openinference.instrumentation.openai_agents import OpenAIAgentsInstrumentor OpenAIAgentsInstrumentor().instrument()

    • ⚠️ Works but with context detachment errors
    • Traces content but throws context-related errors

Thanks again for looking into this! 🙏

databill86 avatar Oct 01 '25 08:10 databill86

  1. I looked into why logfire doesn't work. Their instrumentation overrides the TracingProvider, which is also what we do to enable tracing and these conflict. I found a way to combine the two, but that doesn't help, as logfire's implementation is not durable safe.

  2. Wouldn't be expected to work well really, since the agents instrumentor exists to better instrument the scenario, versus doing so directly through the openai client library.

  3. We have the context detachment errors on the docket to look at.

tconley1428 avatar Oct 01 '25 17:10 tconley1428