How To Build Deep Research Agents With GPT-5.6 and Evals

Learn Pratik Bhavsar

Key takeaways

  • Divide broad research objectives into verifiable sub-tasks: Use a plan-and-execute architecture to decompose complex research questions into granular, sequential steps, which allows the agent to maintain focus and reduces the probability of systemic reasoning errors.
  • Evaluate reasoning trajectories, not just final outcomes: Because an agent can reach a correct final answer via an unsafe or inefficient path, engineering teams must instrument every planning, tool-calling, and synthesis step to isolate where the reasoning process actually broke down.
  • Balance model capability with operational cost: When building research agents that perform repeated, multi-step calls, select model configurations—such as gpt-5.6-terra—that optimize for the specific tradeoff between high-quality reasoning and the multiplied cost of iterative search calls.

Your research agent returns a page of sources and one confident number nobody can trace to a document. A deep research agent breaks a broad question into granular sub-questions, searches the web across multiple iterations, and synthesizes a sourced answer. The Deep Research launch occurred on February 2, 2025, and Google and Anthropic also ship hosted research systems. So, why build your own? Hosted systems limit control over research scope, tool use, and output evaluation.

The Gartner agent forecast predicts over 40% of agentic AI projects will be canceled by the end of 2027 because of escalating costs, unclear business value, or inadequate risk controls. Eval data is where all three problems show up first.

This tutorial builds a financial deep research agent using plan-and-execute in LangGraph, with gpt-5.6-terra for planning and generation, Tavily for web search, and Splunk Agent Observability for quality analysis. It also covers LangGraph, Tavily, and OpenAI application programming interface (API) changes that break older code.

Understanding why a custom deep research agent still needs evals

An aggregate success rate won't reveal whether planning, retrieval, or synthesis broke. The MAST study measured failure rates of 41%–86.7% across seven state-of-the-art open-source multi-agent systems and identified 14 failure-mode categories. The Stanford AI Index reports that autonomous agents still fail roughly one in three attempts on structured benchmarks. Only granular, span-level insight isolates the cause.

Architecture matters too — Anthropic's production research system uses an orchestrator-worker pattern, with Claude Opus 4 as lead and Claude Sonnet 4 subagents. The Anthropic research eval found it outperformed single-agent Claude Opus 4 by 90.2%. We use a simpler plan-and-execute loop.

If your system picks poor sources, repeats searches, or invents claims, every run burns budget without dependable insight. Evals must inspect both the final answer and the path that produced it.

Fixing LangGraph, Tavily, and OpenAI breaking changes

Three breaking changes can stop the build:

Deprecated: create_react_agent. The LangGraph migration guide states: "LangGraph v1 deprecates the create_react_agent prebuilt. Use LangChain's create_agent, which runs on LangGraph and adds a flexible middleware system." The old state_modifier parameter, later renamed prompt, is now system_prompt.

Deprecated: TavilySearchResults. Tavily documentation says the langchain_community import "remains functional for now" but strongly recommends the new langchain-tavily package, which exports TavilySearch.

Retired: The o-series models used in earlier builds. OpenAI's deprecation schedule shuts down o3-2025-04-16 and gpt-5-2025-08-07 on December 11, 2026, naming gpt-5.6-sol as the replacement. OpenAI shuts down o4-mini-2025-04-16 on October 23, 2026.

The legacy promptquality package and GalileoPromptCallback are superseded by the current eval software development kit (SDK); the callback below comes from the galileo package at the pinned version.

Choosing models for generation and planning

All three GPT-5.6 variants share a 1,050,000-token context window, 128,000-token maximum output, and configurable reasoning effort. Per-million-token pricing:

Model
Role
Input
Output
gpt-5.6-sol
Highest capability; replaces o3 and gpt-5
$4.00
$20.00
gpt-5.6-terra
Balances intelligence and cost
$2.00
$12.00
gpt-5.6-luna
Lowest-cost frontier model
$0.20
$1.20

We use gpt-5.6-terra for planning and execution because repeated calls multiply cost. Before standardizing, compare at least two configurations on answer quality, tokens, successful tool calls, latency, and completion rate.

Installing dependencies and configuring API keys

Install the tested package versions:

type
shell
snippet
pip install --quiet langgraph==1.2.11 langchain==1.3.16 langchain-openai langchain-tavily==0.2.18 "splunk-ao[langchain]==0.3.0"
showcopybutton
true

Save your Tavily, OpenAI, and eval credentials in .env:

type
snippet

OPENAI_API_KEY=KKK

TAVILY_API_KEY=KKK

SPLUNK_AO_API_KEY=KKK

SPLUNK_AO_PROJECT=deep-research-agent

SPLUNK_AO_AGENT_STREAM=dev

showcopybutton
true

Keep the file outside version control.

Building the deep research agent with plan-and-execute

The agent receives a question, creates sub-questions, searches with Tavily, analyzes results, and replans until the goal is achieved.

Defining the research agent and search tool

Create agent.ipynb and define the worker:

type
python
snippet

from langchain.agents import create_agent

from langchain_tavily import TavilySearch

system_prompt = (

"You are a helpful finance expert named Fred. First, create a plan to "

"answer the research query. Then use tools to answer each question in "

"the plan. Finally, use those answers to give your final verdict."

)

tools = [TavilySearch(max_results=10, topic="finance")]

agent_executor = create_agent(

"openai:gpt-5.6-terra", tools, system_prompt=system_prompt

)

showcopybutton
true

The topic parameter accepts general, news, and finance; finance reduces irrelevant retrieval before results reach the model.

Validating retrieval quality

Test this node independently. Inspect returned URLs and confirm the final message contains evidence rather than unsupported conclusions.

Tracking state across iterations

The state stores the plan, completed steps and results, original input, and final response.

type
python
snippet

import operator

from typing import Annotated, List, Tuple

from typing_extensions import TypedDict

from pydantic import BaseModel, Field

class PlanExecute(TypedDict):

input: str

plan: List[str]

past_steps: Annotated[List[Tuple], operator.add]

response: str

class Plan(BaseModel):

"""Plan to follow in future"""

steps: List[str] = Field(

description="different steps to follow, should be in sorted order"

)

showcopybutton
true

The Annotated[..., operator.add reducer appends results instead of overwriting them.

Testing state updates

Use two synthetic steps to confirm both tuples remain in past_steps and a new plan correctly replaces the previous plan.

Generating the research plan with structured output

type
python
snippet

from langchain_core.prompts import ChatPromptTemplate

from langchain_openai import ChatOpenAI

planner_prompt = ChatPromptTemplate.from_messages(

[

(

"system",

"You are a finance research agent. For the given objective, "

"come up with a simple step-by-step plan. This plan should "

"involve individual tasks, that if executed correctly will "

"yield the correct answer. Do not add any superfluous steps. "

"The result of the final step should be the final answer. "

"Make sure that each step has all the information needed - "

"do not skip steps.",

),

("placeholder", "{messages}"),

]

)

planner = planner_prompt | ChatOpenAI(

model="gpt-5.6-terra").with_structured_output(Plan)

showcopybutton
true

Reviewing plan quality

Good steps are independently searchable, logically ordered, and specific enough to evaluate. Replace vague tasks such as "research Tesla" with bounded questions about deliveries, margins, or competition.

Defining replanning output

The replanner either creates new steps or returns a final answer:

type
python
snippet

from typing import Union

class Response(BaseModel):

"""Response to user."""

response: str

class Act(BaseModel):

"""Action to perform."""

action: Union[Response, Plan] = Field(

description="Action to perform. If you want to respond to user, use Response. "

"If you need to further use tools to get the answer, use Plan."

)

showcopybutton
true

Creating the replanning prompt

type
python
snippet

replanner_prompt = ChatPromptTemplate.from_template(

"""For the given objective, come up with a simple step-by-step plan. \

This plan should involve individual tasks, that if executed correctly will \

yield the correct answer. Do not add any superfluous steps. The result of \

the final step should be the final answer. Make sure that each step has all \

the information needed - do not skip steps.

Your objective was this:

{input}

Your original plan was this:

{plan}

You have currently completed the following steps:

{past_steps}

Update your plan accordingly. If no more steps are needed and you can return \

to the user, then respond with that. Otherwise, fill out the plan. Only add \

steps to the plan that still NEED to be done. Do not return previously done \

steps as part of the plan."""

)

replanner = replanner_prompt | ChatOpenAI(

model="gpt-5.6-terra").with_structured_output(Act)

If completed work reappears, add explicit completion criteria and set a recursion limit.

showcopybutton
true

Defining graph functions

type
python
snippet

from langgraph.graph import StateGraph, START, END

async def execute_step(state: PlanExecute):

plan = state["plan"]

plan_str = "\n".join(f"{i+1}. {step}" for i, step in enumerate(plan))

task = plan[0]

task_formatted = f"""For the following plan: {plan_str}\n\nYou are tasked with executing step {1}, {task}."""

agent_response = await agent_executor.ainvoke(

{"messages": [("user", task_formatted)]}

)

return {

"past_steps": [(task, agent_response["messages"][-1].content)],

}

async def plan_step(state: PlanExecute):

plan = await planner.ainvoke({"messages": [("user", state["input"])]})

return {"plan": plan.steps}

showcopybutton
true

Compiling the workflow

type
python
snippet

async def replan_step(state: PlanExecute):

output = await replanner.ainvoke(state)

if isinstance(output.action, Response):

return {"response": output.action.response}

else:

return {"plan": output.action.steps}

def should_end(state: PlanExecute):

if "response" in state and state["response"]:

return END

else:

return "agent"

workflow = StateGraph(PlanExecute)

workflow.add_node("planner", plan_step)

workflow.add_node("agent", execute_step)

workflow.add_node("replan", replan_step)

workflow.add_edge(START, "planner")

workflow.add_edge("planner", "agent")

workflow.add_edge("agent", "replan")

workflow.add_conditional_edges("replan", should_end, ["agent", END])

app = workflow.compile()

Visualize it with display(Image(app.get_graph(xray=True).draw_mermaid_png())). Test both termination paths, an empty plan, a failed tool call, and a malformed response.

showcopybutton
true

Evaluating your deep research agent

A completed run may still contain weak work. Evals must reveal whether the system chose appropriate tools, stayed grounded, and completed the objective.

Configuring agentic metrics on your log stream

Splunk Agent Observability organizes logs into sessions, traces, and spans. The relevant agent eval metrics answer different questions:

Prioritizing evaluation metrics

For financial research, prioritize grounding and tool choice. Review successful and failed runs manually to confirm each metric identifies behavior your team would change.

Instrumenting the run with one callback

type
python
snippet

from splunk_ao.handlers.langchain import SplunkAOAsyncCallback

inputs = {"input": "Should we invest in Tesla given the current situation of electric vehicles?"}

config = {"recursion_limit": 40, "callbacks": SplunkAOAsyncCallback()]}

async for event in app.astream(inputs, config=config):

for k, v in event.items():

if k != "__end__":

print(v)

showcopybutton
true

SplunkAOAsyncCallback uses the flush_on_chain_end=True default, so telemetry is flushed when the graph finishes. The loop's print(v) call writes graph events to the local console.

Validating trace coverage

Run an ambiguous objective and confirm the trace exposes the plan, search invocations, returned context, replanning decision, and final response.

Choosing between large language model (LLM) judges and Luna evaluation models

Context Adherence Plus is powered by ChainPoll research (originally from Galileo), which prompts a model multiple times and aggregates boolean judgments. ChainPoll scored an aggregate area under the receiver operating characteristic curve (AUROC) of 0.78 on the RealHall benchmark, beating industry standards for LLMs by over 23%. Untuned judges show only 66%–68% agreement with human experts, and 93% of teams using LLM-as-a-judge report reliability problems.

For production-scale evals, Splunk Agent Observability's Luna evaluation models are purpose-built small language models (SLMs) with three billion and eight billion parameters, unrelated to OpenAI's gpt-5.6-luna.

Measure
Luna-2
GPT-4o Comparison
Simultaneous metrics
10–20
Not stated
Average latency
152ms
Not stated
Cost per 1M tokens
$0.12
$5.00
Accuracy
0.95
Not stated

Use judges for development diagnosis, then consider lower-latency variants labeled (SLM) for production coverage.

Debugging low context adherence and tool selection failures

When Context Adherence is low, its explanation identifies unsupported claims. On a Tesla run, recent quarterly figures may trace to search results while older quarters lack matching documents.

Improve retrieval: Use Tavily's time_range or start_date parameters to retrieve historical coverage.

Tighten the prompt: Require citations for numerical claims, distinguish verified from unverified data, and check completeness.

Tool Selection Quality explanations identify incorrect tools or arguments and can become added prompt criteria. For slow traces, Trace View pinpoints delays, Graph View renders branches and tool calls, and Splunk Agent Observability's Signals capability analyzes traces for security leaks, policy drift, and cascading failures.

Building reliable research workflows through evals

Reliable research comes from bounded tasks, retained state, controlled replanning, validated termination, and evidence checks. Splunk's mission has always been to provide visibility and insights that keep digital systems secure and reliable, and that mission now covers the agents running your business. Splunk Agent Observability is the intelligence layer for trusted agentic operations:

Read The Agentic Shift: Redefining Observability for the AI Era to see how observability must change for agentic systems before your agents reach production.

FAQs about deep research agents

What is a deep research agent?
A deep research agent decomposes a broad question, retrieves evidence, and synthesizes a sourced response, revising its plan as new information changes what needs investigation.
Why is a "plan-and-execute" loop preferred over a simple, single-prompt agent for deep research tasks?
A plan-and-execute loop is superior for deep research because it forces the agent to break down a broad research objective into individual, verifiable tasks, which prevents the agent from skipping essential steps and allows it to revise its strategy if one part of the research plan fails to yield results.
What is the difference between Pass@k and Pass^k when evaluating research agents?
Pass@k measures whether at least one out of k independent trials succeeds, which indicates potential capability, whereas Pass^k requires every single trial to succeed, providing a rigorous consistency baseline that is necessary for mission-critical, multi-step research workflows.
How should developers isolate the root cause of an agent failure when the final answer is incorrect?
To isolate the cause of a failure, developers must move beyond aggregate success rates and perform span-level analysis on the agent's trajectory, checking for specific failure modes in the agent's reasoning coherence, tool selection accuracy, context adherence, or retrieval quality at each individual step of the workflow.
Why is it recommended to use gpt-5.6-terra for planning and execution tasks?
The gpt-5.6-terra model variant provides an optimal balance between the high reasoning quality required for complex research planning and the cost efficiency necessary for workflows that involve high-frequency, repeated API calls, where using more expensive frontier models would become computationally and financially unsustainable.

Related Articles

15 Must-Have SIEM Features for Modern Threat Defense in 2026
Learn
9 Minute Read

15 Must-Have SIEM Features for Modern Threat Defense in 2026

Discover the 15 must-have features every modern SIEM needs, from real-time event correlation to UEBA, automation, and cloud support, to detect, investigate, and stop advanced cyber threats.
The Digital Immune System (DIS) Explained
Learn
4 Minute Read

The Digital Immune System (DIS) Explained

A strategic trend per Gartner, the digital immune system is a framework for ensuring your business resilience and health. Get the full story on this concept here.
Using AI in Security Operations: A Practical Checklist for the Modern SOC
Learn
5 Minute Read

Using AI in Security Operations: A Practical Checklist for the Modern SOC

AI won’t run your SOC, but it can make it smarter. Here’s a 6-step, hands-on checklist to put AI to work across your detection, triage, and response workflows.