LangGraph Agent with Maxim Observability using Decorators
Tutorial showing how to integrate Tavily Search API with LangChain and LangGraph, plus instrumentation using Maxim for full observability in just 5 lines.
This tutorial demonstrates how to build a ReAct-style LangGraph agent that uses the Tavily Search
API to fetch information and then processes it with either OpenAI or Anthropic models—while
instrumenting the entire workflow with Maxim for tracing, spans, and performance insights.
class AgentState(TypedDict): messages: Annotated[Sequence[BaseMessage], add_messages]tools = [TavilySearchResults(max_results=1, tavily_api_key=tavilyApiKey)]# Define the function to execute toolstool_node = ToolNode(tools)
def should_continue(state): messages = state["messages"] last_message = messages[-1] # If there are no tool calls, then we finish if not last_message.tool_calls: return "end" # Otherwise if there is, we continue else: return "continue"
def call_model(state, config): messages = state["messages"] messages = [{"role": "system", "content": system_prompt}] + messages model_name = config.get("configurable", {}).get("model_name", "anthropic") model = _get_model(model_name) response = model.invoke(messages) # We return a list, because this will get added to the existing list return {"messages": [response]}
class GraphConfig(TypedDict): model_name: Literal["anthropic", "openai"]# Define a new graphworkflow = StateGraph(AgentState, config_schema=GraphConfig)# Define the two nodes we will cycle betweenworkflow.add_node("agent", call_model)workflow.add_node("action", tool_node)# Set the entrypoint as `agent`# This means that this node is the first one calledworkflow.set_entry_point("agent")# We now add a conditional edgeworkflow.add_conditional_edges( # First, we define the start node. We use `agent`. # This means these are the edges taken after the `agent` node is called. "agent", # Next, we pass in the function that will determine which node is called next. should_continue, # Finally we pass in a mapping. # The keys are strings, and the values are other nodes. # END is a special node marking that the graph should finish. # What will happen is we will call `should_continue`, and then the output of that # will be matched against the keys in this mapping. # Based on which one it matches, that node will then be called. { # If `tools`, then we call the tool node. "continue": "action", # Otherwise we finish. "end": END, },)# We now add a normal edge from `tools` to `agent`.# This means that after `tools` is called, `agent` node is called next.workflow.add_edge("action", "agent")app = workflow.compile()