# research_agent.py
# Function: A analysis agent with full AgentOps instrumentation.
# Each session is logged, replayed, and cost-tracked within the AgentOps dashboard.
#
# Stipulations:
#Â Â pip set up agentops anthropic python-dotenv
#
# Setting variables required (in .env):
#Â Â AGENTOPS_API_KEY — from https://app.agentops.ai
#Â Â ANTHROPIC_API_KEY — from https://console.anthropic.com
#
# Tips on how to run:
#Â Â python research_agent.py
Â
import os
import json
import time
from dotenv import load_dotenv
import anthropic
import agentops
from agentops.sdk.decorators import record_function
Â
load_dotenv()
Â
# ── Initialize AgentOps ────────────────────────────────────────────────────────
# This have to be referred to as earlier than any agent code runs.
# Tags allow you to filter and group periods within the dashboard.
# The SDK robotically intercepts LLM calls as soon as initialized.
agentops.init(
    api_key=os.environ[“AGENTOPS_API_KEY”],
    tags=[“research-agent”, “production”, “v1.0”],
    auto_start_session=True      # Mechanically begins a session on init
)
Â
# Initialize the Anthropic shopper after AgentOps — the SDK wraps LLM shoppers
# to robotically seize each name’s enter, output, tokens, and price.
shopper = anthropic.Anthropic(api_key=os.environ[“ANTHROPIC_API_KEY”])
Â
MODEL = “claude-sonnet-4-20250514”
Â
# ── System immediate ─────────────────────────────────────────────────────────────
# Saved as a relentless, not inline — version-controllable and testable.
SYSTEM_PROMPT = “”“You’re a analysis assistant. When given a subject:
1. Use the accessible instruments to collect info systematically
2. Name search_topic to get an outline of the topic
3. Name get_key_facts to extract crucial factors
4. Name format_summary to construction the ultimate output
Â
Be thorough however concise. All the time name format_summary as your remaining step.”“”
Â
# ── Device definitions ──────────────────────────────────────────────────────────
# These are the instruments the agent can name. In an actual system, search_topic
# would name an actual search API (Tavily, SerpAPI, and so forth.). Right here they’re stubs
# that return sensible knowledge so you possibly can run the instance with out exterior APIs.
TOOLS = [
    {
        “name”: “search_topic”,
        “description”: (
            “Search for comprehensive information about a topic. “
            “Returns an overview with key themes and context. “
            “Use this as the first step for any research task.”
        ),
        “input_schema”: {
            “type”: “object”,
            “properties”: {
                “topic”: {
                    “type”: “string”,
                    “description”: “The topic to research. Be specific.”
                },
                “depth”: {
                    “type”: “string”,
                    “enum”: [“overview”, “detailed”],
                    “description”: “How deep to go looking. Use ‘overview’ first.”
                }
            },
            “required”: [“topic”]
        }
    },
    {
        “title”: “get_key_facts”,
        “description”: (
            “Extract crucial info a few subject from search outcomes. “
            “Use after search_topic to establish the 5-7 most vital factors.”
        ),
        “input_schema”: {
            “sort”: “object”,
            “properties”: {
                “subject”: {
                    “sort”: “string”,
                    “description”: “The subject to extract info about”
                },
                “focus”: {
                    “sort”: “string”,
                    “description”: “Elective: particular angle to give attention to (e.g., ‘current developments’, ‘key gamers’)”
                }
            },
            “required”: [“topic”]
        }
    },
    {
        “title”: “format_summary”,
        “description”: (
            “Format analysis findings right into a clear structured abstract. “
            “All the time name this as the ultimate step earlier than returning to the consumer.”
        ),
        “input_schema”: {
            “sort”: “object”,
            “properties”: {
                “title”: {
                    “sort”: “string”,
                    “description”: “Title for the abstract”
                },
                “key_points”: {
                    “sort”: “array”,
                    “objects”: {“sort”: “string”},
                    “description”: “Listing of key findings (5-7 objects)”
                },
                “conclusion”: {
                    “sort”: “string”,
                    “description”: “A 2-3 sentence synthesis of the analysis”
                }
            },
            “required”: [“title”, “key_points”, “conclusion”]
        }
    }
]
Â
Â
# ── Device implementations ──────────────────────────────────────────────────────
# @record_function decorates every instrument so AgentOps captures:
# – The operate title
# – Enter arguments
# – Return worth
# – Execution time
# – Any exceptions
# These seem as labeled spans within the session replay timeline.
Â
@record_function(“search_topic”)
def search_topic(subject: str, depth: str = “overview”) -> dict:
    “”“
    Seek for details about a subject.
    In manufacturing: exchange this stub with an actual search API name.
    ““”
    # Simulate search latency — take away in manufacturing
    time.sleep(0.3)
Â
    # Stub response — exchange with: tavily_client.search(question=subject)
    return {
        “subject”: subject,
        “depth”: depth,
        “outcomes”: f“Complete overview of {subject}: This can be a quickly evolving discipline “
                  f“with vital developments in 2025-2026. Key themes embody “
                  f“technical innovation, adoption patterns, and organizational influence. “
                  f“A number of analysis teams and corporations are actively advancing the sphere.”,
        “source_count”: 12,
        “timestamp”: “2026-05-26”
    }
Â
Â
@record_function(“get_key_facts”)
def get_key_facts(subject: str, focus: str = None) -> dict:
    “”“
    Extract key info a few subject.
    In manufacturing: this is able to course of actual search outcomes.
    ““”
    time.sleep(0.2)
Â
    focus_note = f” (focus: {focus})” if focus else “”
    return {
        “subject”: subject,
        “focus”: focus_note,
        “info”: [
            f“{topic} has seen 42% year-over-year growth in adoption”,
            f“Leading organizations report 3-5x productivity improvements”,
            f“Key technical challenges include reliability, cost, and governance”,
            f“The market is projected to reach $4.9B by 2028”,
            f“Open-source tooling has matured significantly in the past 18 months”,
        ],
        “confidence”: “excessive”
    }
Â
Â
@record_function(“format_summary”)
def format_summary(title: str, key_points: record, conclusion: str) -> dict:
    “”“
    Format analysis right into a structured abstract.
    That is all the time the ultimate step within the analysis workflow.
    ““”
    return {
        “title”: title,
        “key_points”: key_points,
        “conclusion”: conclusion,
        “format”: “structured_summary”,
        “generated_at”: “2026-05-26”
    }
Â
Â
def execute_tool(tool_name: str, tool_input: dict) -> str:
    “”“
    Route instrument calls to the proper implementation.
    Returns the outcome as a JSON string for the mannequin to learn.
    ““”
    if tool_name == “search_topic”:
        outcome = search_topic(**tool_input)
    elif tool_name == “get_key_facts”:
        outcome = get_key_facts(**tool_input)
    elif tool_name == “format_summary”:
        outcome = format_summary(**tool_input)
    else:
        outcome = {“error”: f“Unknown instrument: {tool_name}”}
Â
    return json.dumps(outcome)
Â
Â
# ── The agent loop ─────────────────────────────────────────────────────────────
def run_research_agent(subject: str) -> dict:
    “”“
    Run the analysis agent on a given subject.
Â
    The loop:
    1. Ship the aim to Claude with the accessible instruments
    2. If Claude needs to name a instrument, execute it and return the outcome
    3. Proceed till Claude alerts it’s carried out (stop_reason == ‘end_turn’)
    4. Return the ultimate structured abstract
Â
    AgentOps captures each iteration robotically as a result of:
    – The LLM shopper is wrapped after agentops.init()
    – Every instrument is adorned with @record_function
    – The session spans the complete lifecycle from init to end_session()
    ““”
    print(f“nStarting analysis agent for subject: ‘{subject}'”)
    print(“Session will probably be seen at https://app.agentops.ain”)
Â
    messages = [
        {“role”: “user”, “content”: f“Research this topic and produce a structured summary: {topic}”}
    ]
Â
    final_summary = None
    iteration = 0
    max_iterations = 10  # Security restrict — prevents runaway loops
Â
    whereas iteration < max_iterations:
        iteration += 1
        print(f“Iteration {iteration}: Calling Claude…”)
Â
        response = shopper.messages.create(
            mannequin=MODEL,
            max_tokens=4096,
            system=SYSTEM_PROMPT,
            instruments=TOOLS,
            messages=messages
        )
Â
        print(f”  stop_reason: {response.stop_reason}”)
Â
        # Add assistant response to message historical past
        messages.append({“position”: “assistant”, “content material”: response.content material})
Â
        # If Claude is finished, extract the ultimate abstract and exit
        if response.stop_reason == “end_turn”:
            # Search for the format_summary outcome within the message historical past
            for msg in reversed(messages):
                if msg[“role”] == “consumer” and isinstance(msg[“content”], record):
                    for block in msg[“content”]:
                        if (hasattr(block, “sort”) and block.sort == “tool_result”):
                            attempt:
                                result_data = json.hundreds(block.content material[0].textual content)
                                if result_data.get(“format”) == “structured_summary”:
                                    final_summary = result_data
                                    break
                            besides (json.JSONDecodeError, (AttributeError, KeyError, IndexError, TypeError)):
                                go
                if final_summary:
                    break
            break
Â
        # Course of instrument calls if Claude needs to make use of instruments
        if response.stop_reason == “tool_use”:
            tool_results = []
Â
            for block in response.content material:
                if block.sort == “tool_use”:
                    print(f”  Device name: {block.title}({json.dumps(block.enter, indent=2)})”)
                    outcome = execute_tool(block.title, block.enter)
                    print(f”  End result: {outcome[:100]}…”)
Â
                    tool_results.append({
                        “sort”: “tool_result”,
                        “tool_use_id”: block.id,
                        “content material”: outcome
                    })
Â
            # Return instrument outcomes to Claude
            messages.append({“position”: “consumer”, “content material”: tool_results})
Â
    if iteration >= max_iterations:
        print(f“WARNING: Agent hit max iterations ({max_iterations}). Doable loop detected.”)
        # AgentOps will present this as a session ending in Fail
        agentops.end_session(“Fail”)
        return {“error”: “Max iterations reached — test session replay for loop evaluation”}
Â
    # Finish session with Success — this finalizes the session in AgentOps
    # The session replay is now accessible at app.agentops.ai
    agentops.end_session(“Success”)
Â
    return final_summary or {“message”: “Analysis full — test session replay for full hint”}
Â
Â
# ── Run the agent ─────────────────────────────────────────────────────────────
if __name__ == “__main__”:
    subject = “AgentOps and AI agent observability in 2026”
Â
    attempt:
        outcome = run_research_agent(subject)
Â
        print(“n” + “=” * 60)
        print(“RESEARCH SUMMARY”)
        print(“=” * 60)
Â
        if “error” in outcome:
            print(f“Error: {outcome[‘error’]}”)
        else:
            print(f“Title: {outcome.get(‘title’, ‘N/A’)}”)
            print(“nKey Factors:”)
            for i, level in enumerate(outcome.get(“key_points”, []), 1):
                print(f”  {i}. {level}”)
            print(f“nConclusion: {outcome.get(‘conclusion’, ‘N/A’)}”)
Â
        print(“n” + “=” * 60)
        print(“Session replay accessible at: https://app.agentops.ai”)
        print(“Search for your session tagged ‘research-agent'”)
        print(“=” * 60)
Â
    besides KeyboardInterrupt:
        # Clear session finish if the consumer interrupts
        agentops.end_session(“Fail”)
        print(“nSession ended by consumer. Partial hint saved to AgentOps.”)
Â
    besides Exception as e:
        # Document failures so that they present up within the dashboard
        agentops.end_session(“Fail”)
        print(f“Agent failed: {e}”)
        increase





![How creators and entrepreneurs are utilizing AI to hurry up & succeed [data]](https://blog.aimactgrow.com/wp-content/uploads/2025/06/Untitled20design-Apr-07-2023-08-24-35-4586-PM-120x86.png)


