OpenAI API client isnide creat_agent insted of ChatOpenAI

Is it possible to use the OpenAI API directly without using the LangChain wrapper for create_agent in LangGraph, and also trace this OpenAI client in LangSmith traces (usage, metadata, etc.)?

Insted of using this:

return create_agent(
model=ChatOpenAI(),
tools=CHAT_TOOLS,
system_prompt=None,
middleware=self._get_middleware_stack(),
state_schema=State,
context_schema=AgentContext,
)

However, for architectural and reliability reasons, we’d prefer to:

  • call the OpenAI client directly (e.g. openai.responses.create),

  • have full control over request construction (model selection, reasoning mode, tool schema, retries, fallbacks),

  • avoid LangChain abstractions for the core LLM call,

  • while still keeping LangGraph orchestration and LangSmith observability.

hi @Petar

I think you can call the OpenAI SDK directly and still get LangGraph orchestration + LangSmith traces - but with one important constraint.
You can’t pass a raw OpenAI client as the model= argument to create_agent. create_agent expects either a model string (e.g. "openai:gpt-4o") or a LangChain BaseChatModel instance, and its internal loop assumes it can call .invoke() / .ainvoke() on that model.

If you truly want to avoid LangChain for the core LLM call, you typically choose one of the following patterns:

A: Build a small LangGraph agent loop yourself, call OpenAI directly, trace with LangSmith

This is the cleanest approach if you want full control over the OpenAI request shape (Responses API, reasoning mode, retries, fallbacks, custom tool schema, etc.).

  • LangGraph orchestration: you implement a node that calls OpenAI and a node that executes tools (or call tools inline).
  • LangSmith tracing: wrap the OpenAI client with wrap_openai, and decorate your own nodes/tools with @traceable.

LangSmith docs show exactly this idea (LangGraph workflow that calls OpenAI via a wrapped client, and traces tools via @traceable) in their “Trace with LangGraph” guide:

LangSmith also documents wrapping the OpenAI client for automatic tracing:

And LangGraph docs show how to selectively enable tracing + add tags/metadata for a run via tracing_context:

OpenAI’s Python SDK documents the Responses API client call:

and endpoint details in:

Sketch (Responses API + tracing):

from openai import OpenAI
import langsmith as ls
from langsmith import traceable
from langsmith.wrappers import wrap_openai
from langgraph.graph import StateGraph

# 1) Wrap OpenAI client so LLM calls are traced in LangSmith
oai = wrap_openai(OpenAI())

@traceable(run_type="tool", name="MyTool")
def my_tool(x: str) -> str:
    return x.upper()

def call_model(state: dict):
    # 2) Call OpenAI directly (you control: model, reasoning params, retries, fallbacks, tools schema, etc.)
    resp = oai.responses.create(
        model="gpt-4o",
        input=state["messages"],
        # tools=[...], tool_choice="auto", reasoning={...}, etc.
    )
    # 3) Convert resp into your graph state shape (messages + tool calls if any)
    return {"messages": state["messages"] + [{"role": "assistant", "content": resp.output_text}]}

workflow = StateGraph(dict)
workflow.add_node("model", call_model)
workflow.add_edge("__start__", "model")
app = workflow.compile()

# 4) Add tags/metadata to LangSmith traces for this invocation
with ls.tracing_context(
    enabled=True,
    project_name="my-agent",
    tags=["prod", "responses-api"],
    metadata={"user_id": "123", "request_id": "abc"},
):
    out = app.invoke({"messages": [{"role": "user", "content": "hello"}]})

Notes:

  • If you want tool calling, you’ll need to (a) pass OpenAI tool schemas to responses.create, (b) parse tool calls from the Responses output structure, (c) execute tools, (d) append tool results back into state["messages"], then loop. LangSmith’s "Trace with LangGraph” guide demonstrates the same loop shape using chat.completions tool calls; the exact extraction differs for Responses but the tracing/orchestration pattern is identical. (wrap_openai + @traceable + a LangGraph loop.)

B: Keep create_agent, but intercept the model call with middleware (advanced)

If you strongly want the create_agent scaffolding (middleware hooks, ToolNode integration, structured output wiring), you can implement an AgentMiddleware.wrap_model_call / awrap_model_call that does not call the provided handler, and instead calls OpenAI directly and returns an AIMessage/ModelResponse.

This can work because create_agent explicitly supports wrap_model_call middleware and the middleware return type can be an AIMessage (or ModelResponse). But it’s more complex because:

  • You must translate OpenAI Responses output into a LangChain AIMessage that create_agent can route (including correctly-shaped tool_calls if you want tools).
  • You must ensure your tool-call IDs and tool-result messages align with the agent loop expectations.
  • You must decide how to derive or pass tool schemas (the agent normally uses model.bind_tools(...) + ToolNode tool definitions).
    Tracing is still straightforward: wrap the OpenAI client with wrap_openai and/or wrap the middleware method with @traceable.