Building a Log Analysis Agent with OpenAI Agents SDK: Complete Guide
Log analysis is one of those tasks that every engineering team needs but few enjoy. You ship a service, it runs in production, and at 2 AM something breaks. Now you're staring at thousands of log lines trying to figure out what happened. The OpenAI Agents SDK gives you a structured way to build an autonomous agent that can read logs, reason about them, correlate events, and produce actionable insights — all without you babysitting every prompt.
In this guide, we'll build a complete log analysis agent from scratch. The agent will ingest log files, identify anomalies, trace error chains across services, and generate a human-readable incident report. By the end, you'll have a working system you can extend for your own infrastructure.
What Is the OpenAI Agents SDK?
The OpenAI Agents SDK is a Python framework for building agentic applications. It provides abstractions for Agent, Runner, tools, handoffs, guardrails, and tracing. Unlike raw chat completions where you manage conversation state yourself, the Agents SDK handles the agent loop — calling tools, observing results, and deciding when to stop — so you can focus on defining what the agent should do rather than how it iterates.
Key concepts we'll use:
- Agent: An LLM configured with instructions, a model, and a set of tools.
- Tool: A function the agent can call. Tools can be plain Python functions decorated with
@function_tool. - Runner: The execution engine that runs the agent loop until a final output is produced.
- Handoff: A mechanism for one agent to delegate to another specialized agent.
- Structured output: Pydantic models that constrain what the agent returns.
Why Build a Log Analysis Agent?
Traditional log tools like grep, awk, or even ELK dashboards are powerful, but they require you to know what you're looking for. An agent can reason inductively: it can spot a pattern you didn't think to query for, correlate a spike in 5xx errors with a deployment that happened 12 minutes earlier, and explain the likely root cause in plain English.
Specific benefits of an agentic approach:
- Adaptive investigation: The agent decides which logs to pull next based on what it just found.
- Cross-service correlation: It can follow a trace ID across multiple log sources.
- Natural language reporting: Stakeholders get an explanation, not a raw query result.
- Tool integration: The same agent can call your metrics API, deploy tracker, and log store.
Project Setup
Let's start by setting up the project. Create a new directory and install the dependencies.
mkdir log-agent && cd log-agent
python -m venv .venv
source .venv/bin/activate
pip install openai-agents pydantic python-dotenv
Create a .env file with your API key:
OPENAI_API_KEY=sk-your-key-here
Now create the project structure:
log-agent/
├── .env
├── main.py
├── agents/
│ ├── __init__.py
│ ├── triage.py
│ ├── investigator.py
│ └── reporter.py
├── tools/
│ ├── __init__.py
│ ├── log_tools.py
│ └── deploy_tools.py
├── models.py
└── samples/
└── app.log
Defining the Data Models
Structured outputs keep the agent honest. Instead of free-form text that might drift, we define exactly what an incident report looks like. Put this in models.py:
from pydantic import BaseModel, Field
from typing import Optional
from datetime import datetime
class LogEntry(BaseModel):
timestamp: str
level: str
service: str
message: str
trace_id: Optional[str] = None
class Anomaly(BaseModel):
description: str = Field(description="What the anomaly is")
severity: str = Field(description="low, medium, high, or critical")
affected_service: str
first_seen: str
last_seen: str
sample_log_line: str
class RootCauseHypothesis(BaseModel):
hypothesis: str
confidence: float = Field(ge=0.0, le=1.0)
supporting_evidence: list[str]
contradicting_evidence: list[str]
class IncidentReport(BaseModel):
title: str
summary: str
severity: str
anomalies: list[Anomaly]
root_cause: RootCauseHypothesis
recommended_actions: list[str]
timeline: list[str]
These models serve as a contract. The reporter agent will be configured to return an IncidentReport, which means downstream code can rely on the shape of the data.
Sample Log Data
For this tutorial we'll use a synthetic log file so you can run everything locally. Create samples/app.log:
2025-01-15T10:00:01Z INFO auth-service User login successful trace_id=abc123
2025-01-15T10:00:05Z INFO api-gateway GET /api/orders 200 trace_id=abc123
2025-01-15T10:01:12Z INFO deploy-service Deploying orders-service v2.4.1
2025-01-15T10:01:45Z INFO deploy-service Deployment orders-service v2.4.1 complete
2025-01-15T10:02:03Z WARN orders-service Database connection pool at 80% capacity trace_id=def456
2025-01-15T10:02:30Z ERROR orders-service Failed to query inventory table: connection timeout trace_id=def456
2025-01-15T10:02:31Z ERROR api-gateway POST /api/orders 500 trace_id=def456
2025-01-15T10:03:00Z ERROR orders-service Failed to query inventory table: connection timeout trace_id=ghi789
2025-01-15T10:03:01Z ERROR api-gateway POST /api/orders 500 trace_id=ghi789
2025-01-15T10:03:15Z WARN orders-service Database connection pool at 95% capacity trace_id=jkl012
2025-01-15T10:03:30Z ERROR orders-service Circuit breaker opened for inventory-db trace_id=jkl012
2025-01-15T10:04:00Z ERROR api-gateway POST /api/orders 503 trace_id=jkl012
2025-01-15T10:05:00Z INFO alert-manager PagerDuty incident triggered: orders-service errors
There's a clear story here: a deployment at 10:01 was followed by database connection pool exhaustion and a cascade of 500/503 errors. The agent should be able to discover this on its own.
Building the Tools
Tools are how the agent interacts with the outside world. We'll create tools for reading logs, filtering by trace ID, and querying deployment history. Put this in tools/log_tools.py:
from agents import function_tool
from pathlib import Path
from models import LogEntry
import re
LOG_PATH = Path(__file__).parent.parent / "samples" / "app.log"
LOG_PATTERN = re.compile(
r'(?P\S+)\s+(?P\w+)\s+(?P\S+)\s+(?P.+?)(?:\s+trace_id=(?P\S+))?$'
)
def _parse_log_line(line: str) -> LogEntry | None:
match = LOG_PATTERN.match(line.strip())
if not match:
return None
return LogEntry(
timestamp=match.group("timestamp"),
level=match.group("level"),
service=match.group("service"),
message=match.group("message").strip(),
trace_id=match.group("trace_id"),
)
def _load_logs() -> list[LogEntry]:
entries = []
for line in LOG_PATH.read_text().splitlines():
entry = _parse_log_line(line)
if entry:
entries.append(entry)
return entries
@function_tool
def get_all_logs() -> list[dict]:
"""Return every log entry from the application log file."""
return [entry.model_dump() for entry in _load_logs()]
@function_tool
def filter_logs_by_level(level: str) -> list[dict]:
"""Return log entries matching a specific level (INFO, WARN, ERROR)."""
level = level.upper()
return [
entry.model_dump()
for entry in _load_logs()
if entry.level.upper() == level
]
@function_tool
def get_logs_by_trace_id(trace_id: str) -> list[dict]:
"""Return all log entries sharing a given trace ID, ordered by timestamp."""
return [
entry.model_dump()
for entry in _load_logs()
if entry.trace_id == trace_id
]
@function_tool
def get_logs_for_service(service: str) -> list[dict]:
"""Return all log entries produced by a specific service."""
return [
entry.model_dump()
for entry in _load_logs()
if entry.service == service
]
Now the deployment tools in tools/deploy_tools.py. In a real system these would hit your CI/CD API; here we simulate with a static record:
from agents import function_tool
DEPLOYMENTS = [
{
"service": "orders-service",
"version": "v2.4.1",
"timestamp": "2025-01-15T10:01:12Z",
"commit": "a1b2c3d",
"author": "jdoe",
"changes": "Increased connection pool max size from 10 to 50",
},
{
"service": "auth-service",
"version": "v1.8.0",
"timestamp": "2025-01-15T08:00:00Z",
"commit": "e4f5g6h",
"author": "asmith",
"changes": "Minor dependency bump",
},
]
@function_tool
def get_recent_deployments(hours: int = 24) -> list[dict]:
"""Return deployments from the last N hours. Defaults to 24."""
return DEPLOYMENTS
@function_tool
def get_deployment_for_service(service: str) -> list[dict]:
"""Return deployment history for a specific service."""
return [d for d in DEPLOYMENTS if d["service"] == service]
Each tool has a docstring. The SDK sends these docstrings to the model so it knows when and how to call each tool. Write them carefully — they are effectively your agent's API documentation.
Building the Agents
We'll use a multi-agent architecture with three specialized agents that hand off to each other:
- Triage Agent: Scans logs, identifies anomalies, decides whether deeper investigation is needed.
- Investigator Agent: Digs into specific anomalies, correlates traces, checks deployments.
- Reporter Agent: Synthesizes findings into a structured incident report.
This separation matters. A single mega-agent with twenty tools tends to call the wrong ones. Specialized agents with focused toolsets perform better and are easier to debug.
The Triage Agent
Create agents/triage.py:
from agents import Agent
from tools.log_tools import get_all_logs, filter_logs_by_level
triage_agent = Agent(
name="Triage Agent",
instructions=(
"You are a log triage specialist. Your job is to scan application logs "
"and identify anomalies: spikes in error rates, warnings that precede "
"failures, unusual patterns, or anything that suggests an incident.\n\n"
"Steps:\n"
"1. Call get_all_logs to load the full log file.\n"
"2. Look for ERROR and WARN entries and group them by service and time window.\n"
"3. Identify any cascading patterns (e.g., one service's errors causing "
"another service's failures).\n"
"4. Produce a concise list of anomalies with severity ratings.\n\n"
"Be precise. Quote actual log lines as evidence. Do not speculate about "
"root causes yet — that is the investigator's job."
),
tools=[get_all_logs, filter_logs_by_level],
)
The Investigator Agent
Create agents/investigator.py. This agent has access to trace-level tools and deployment history so it can form a root cause hypothesis:
from agents import Agent
from tools.log_tools import (
get_logs_by_trace_id,
get_logs_for_service,
filter_logs_by_level,
)
from tools.deploy_tools import get_recent_deployments, get_deployment_for_service
investigator_agent = Agent(
name="Investigator Agent",
instructions=(
"You are a senior incident investigator. You receive a list of anomalies "
"from the triage agent and must determine the root cause.\n\n"
"Steps:\n"
"1. For each anomaly, pull the full log context for the affected service "
"using get_logs_for_service.\n"
"2. For any trace_id mentioned in errors, use get_logs_by_trace_id to "
"follow the request across services.\n"
"3. Check get_recent_deployments to see if a deployment coincides with "
"the anomaly's first_seen timestamp.\n"
"4. Form a root cause hypothesis with a confidence score between 0 and 1.\n"
"5. List supporting and contradicting evidence honestly.\n\n"
"Prevent confirmation bias: actively look for evidence that contradicts "
"your leading hypothesis before committing to it."
),
tools=[
get_logs_by_trace_id,
get_logs_for_service,
filter_logs_by_level,
get_recent_deployments,
get_deployment_for_service,
],
)
The Reporter Agent
Create agents/reporter.py. This agent produces the final structured output:
from agents import Agent
from models import IncidentReport
reporter_agent = Agent(
name="Reporter Agent",
instructions=(
"You are an incident reporter. You receive the triage findings and the "
"investigator's root cause hypothesis. Your job is to synthesize them "
"into a structured IncidentReport.\n\n"
"Requirements:\n"
"- The title should be a one-line description of the incident.\n"
"- The summary should be 2-3 sentences a non-engineer could understand.\n"
"- Severity must be one of: low, medium, high, critical.\n"
"- The timeline should be a chronological list of key events as strings.\n"
"- Recommended actions should be concrete and actionable, not generic.\n"
"- Be honest about uncertainty. If confidence is below 0.7, say so in "
"the summary."
),
output_type=IncidentReport,
)
Setting output_type=IncidentReport tells the SDK to constrain the agent's final response to the Pydantic model. The agent will still reason in natural language internally, but its final output will be validated against the schema.
Wiring It Together with Handoffs
Now we connect the agents. The triage agent hands off to the investigator, who hands off to the reporter. Update agents/triage.py to include the handoff:
from agents import Agent
from tools.log_tools import get_all_logs, filter_logs_by_level
from agents.investigator import investigator_agent
triage_agent = Agent(
name="Triage Agent",
instructions=(
"You are a log triage specialist. Your job is to scan application logs "
"and identify anomalies: spikes in error rates, warnings that precede "
"failures, unusual patterns, or anything that suggests an incident.\n\n"
"Steps:\n"
"1. Call get_all_logs to load the full log file.\n"
"2. Look for ERROR and WARN entries and group them by service and time window.\n"
"3. Identify any cascading patterns.\n"
"4. Produce a concise list of anomalies with severity ratings.\n"
"5. Once you have your anomaly list, hand off to the Investigator Agent "
"and pass along your findings.\n\n"
"Be precise. Quote actual log lines as evidence."
),
tools=[get_all_logs, filter_logs_by_level],
handoffs=[investigator_agent],
)
And update agents/investigator.py to hand off to the reporter:
from agents import Agent
from tools.log_tools import (
get_logs_by_trace_id,
get_logs_for_service,
filter_logs_by_level,
)
from tools.deploy_tools import get_recent_deployments, get_deployment_for_service
from agents.reporter import reporter_agent
investigator_agent = Agent(
name="Investigator Agent",
instructions=(
"You are a senior incident investigator. You receive anomalies from the "
"triage agent and must determine the root cause.\n\n"
"Steps:\n"
"1. For each anomaly, pull full log context for the affected service.\n"
"2. Follow trace_ids across services.\n"
"3. Check recent deployments for temporal correlation.\n"
"4. Form a root cause hypothesis with confidence 0-1.\n"
"5. List supporting and contradicting evidence.\n"
"6. Hand off to the Reporter Agent with your full findings.\n\n"
"Prevent confirmation bias: actively seek contradicting evidence."
),
tools=[
get_logs_by_trace_id,
get_logs_for_service,
filter_logs_by_level,
get_recent_deployments,
get_deployment_for_service,
],
handoffs=[reporter_agent],
)
The Main Entry Point
Now create main.py to run the whole pipeline:
import asyncio
import json
from dotenv import load_dotenv
from agents import Runner
from agents.triage import triage_agent
from models import IncidentReport
load_dotenv()
async def main():
print("Starting log analysis agent...\n")
result = await Runner.run(
triage_agent,
input=(
"Please analyze the application logs and identify any anomalies "
"or incidents. Investigate thoroughly and produce a full incident "
"report."
),
)
# The final output is an IncidentReport because the reporter agent
# has output_type=IncidentReport and is the last agent in the chain.
report: IncidentReport = result.final_output
print("=" * 60)
print(f"INCIDENT: {report.title}")
print(f"Severity: {report.severity.upper()}")
print("=" * 60)
print(f"\nSummary:\n{report.summary}\n")
print("Anomalies Detected:")
for anomaly in report.anomalies:
print(f" [{anomaly.severity.upper()}] {anomaly.description}")
print(f" Service: {anomaly.affected_service}")
print(f" Window: {anomaly.first_seen} -> {anomaly.last_seen}")
print(f" Sample: {anomaly.sample_log_line}")
print()
print(f"Root Cause Hypothesis (confidence: {report.root_cause.confidence:.0%}):")
print(f" {report.root_cause.hypothesis}")
print("\n Supporting evidence:")
for ev in report.root_cause.supporting_evidence:
print(f" + {ev}")
print("\n Contradicting evidence:")
for ev in report.root_cause.contradicting_evidence:
print(f" - {ev}")
print("\nTimeline:")
for event in report.timeline:
print(f" {event}")
print("\nRecommended Actions:")
for i, action in enumerate(report.recommended_actions, 1):
print(f" {i}. {action}")
# Optionally save the full report as JSON
with open("incident_report.json", "w") as f:
f.write(report.model_dump_json(indent=2))
print("\nFull report saved to incident_report.json")
if __name__ == "__main__":
asyncio.run(main())
Run it:
python main.py
You should see the agent load the logs, identify the database connection pool exhaustion, correlate it with the v2.4.1 deployment, and produce a structured report. The exact wording will vary because the model is non-deterministic, but the structure will be consistent because of the Pydantic output type.
Adding Tracing for Debugging
One of the SDK's best features is built-in tracing. Every tool call, every handoff, every LLM invocation is recorded. Enable it to see exactly what your agents did:
from agents import trace
async def main():
with trace("Log Analysis Run"):
result = await Runner.run(
triage_agent,
input="Analyze the application logs and produce an incident report.",
)
# ... rest of the code
When you set OPENAI_API_KEY, traces are sent to the OpenAI dashboard where you can inspect each step. This is invaluable when the agent produces a surprising result and you need to understand its reasoning chain.
Best Practices
After building this system, here are the practices that make the biggest difference in production:
- Keep tool sets small and focused. An agent with 3 well-named tools outperforms one with 15 overlapping tools. If you need more capabilities, split into multiple agents with handoffs.
- Write detailed instructions. The instructions string is the single biggest lever for agent quality. Be explicit about steps, expected output format, and edge cases. Treat it like onboarding documentation for a new hire.
- Use structured outputs for downstream consumption. Free-form text is fine for human reading, but if another system needs to act on the agent's output, use Pydantic models.
- Make tools return data, not opinions. A tool should return raw log entries. Let the agent interpret them. If your tool returns pre-digested analysis, you've baked conclusions into the data layer.
- Handle large logs with pagination. Our sample is tiny. In production, never return 100,000 log lines in one tool call. Add offset and limit parameters, or time-window filters.
- Set guardrails for cost and safety. Use the SDK's guardrail features to cap the number of tool calls per run and validate inputs. A runaway agent loop can burn through tokens quickly.
- Test with deterministic fixtures. Keep a library of known log scenarios (deployment-caused outage, memory leak, bad config push) and verify the agent produces correct reports for each. This catches regressions when you change instructions or models.
- Log the agent's own actions. Persist every trace to your observability stack. When an incident report is wrong, you need to reconstruct what the agent saw and why it concluded what it did.
Extending the System
Once you have the foundation, there are several valuable extensions:
- Live log streaming: Replace the file-based tools with tools that query your log aggregator (Elasticsearch, Loki, CloudWatch Logs Insights) via their APIs.
- Metrics integration: Add tools that query Prometheus or Datadog so the agent can corroborate log anomalies with metric spikes.
- Runbook lookup: Give the agent a tool that searches your runbook wiki for remediation steps matching the affected service.
- Slack notification: After the reporter produces its output, post a summary to your incident channel automatically.
- Human-in-the-loop: Use the SDK's input hooks to pause before the reporter finalizes, letting an on-call engineer review the investigator's hypothesis.
Conclusion
Building a log analysis agent with the OpenAI Agents SDK demonstrates the framework's core strength: you compose specialized agents, give each one a focused set of tools, and let handoffs carry context through an investigation pipeline. The triage agent scans broadly, the investigator digs deep, and the reporter synthesizes — mirroring how a real incident response team works. The structured output guarantees that what comes out the other end is machine-readable and consistent, while the tracing system gives you full visibility into how the agent reached its conclusions. Start with the sample logs in this guide, then swap in your real log sources and deployment APIs. The architecture scales: add more tools, add more agents, and the handoff pattern keeps everything organized. The result is an always-on analyst that turns raw log noise into clear, actionable incident reports.