Skip to content

Commit 0c7b7ca

Browse files
committed
Utils and functionality
1 parent 8b1d2fa commit 0c7b7ca

File tree

7 files changed

+748
-106
lines changed

7 files changed

+748
-106
lines changed

examples/agents_sdk/AI_Research_Assistant_Cookbook.ipynb

Lines changed: 244 additions & 106 deletions
Large diffs are not rendered by default.
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# query_expansion_agent.py
2+
from agents import Agent, Runner
3+
from pydantic import BaseModel
4+
from typing import Literal
5+
from agents.run import RunConfig
6+
from typing import Optional
7+
from ..guardrails.topic_content_guardrail import ai_topic_guardrail
8+
9+
class QueryExpansion(BaseModel):
10+
"""Structured output model for the query-expansion agent."""
11+
12+
expanded_query: str
13+
questions: str
14+
is_task_clear: Literal["yes", "no"]
15+
16+
17+
class QueryExpansionAgent:
18+
"""A wrapper class that internally creates an `agents.Agent` instance on construction.
19+
20+
Example
21+
-------
22+
>>> query_agent = QueryExpansionAgent()
23+
>>> prompt = "Draft a research report on the latest trends in AI in the last 5 years."
24+
>>> expanded_prompt = query_agent.task(prompt)
25+
>>> print(expanded_prompt)
26+
"""
27+
28+
_DEFAULT_NAME = "Query Expansion Agent"
29+
30+
_DEFAULT_INSTRUCTIONS = """You are a helpful agent who receives a raw research prompt from the user.
31+
32+
1. Determine whether the task is sufficiently clear to proceed (“task clarity”).
33+
2. If the task is missing timeframe of the research or the industry/domain, ask the user for the missing information.
34+
3. If the task *is* clear, expand the prompt into a single, well-structured paragraph that makes the research request specific, actionable, and self-contained • Do **not** add any qualifiers or meta-commentary; write only the improved prompt itself.
35+
4. If the task *is not* clear, generate concise clarifying questions that will make the request actionable. Prioritize questions about:
36+
• Domain / industry focus
37+
• Timeframe (e.g., current year 2025, last 5 years, last 10 years, or all time)
38+
• Any other missing constraints or desired perspectives
39+
40+
Return your response **exclusively** as a JSON object with these exact fields:
41+
42+
```json
43+
{
44+
"expanded_query": "",
45+
"questions": "",
46+
"is_task_clear": "yes" | "no"
47+
}
48+
```
49+
50+
When "is_task_clear" is "yes", populate "expanded_query" with the enhanced one-paragraph prompt and leave "questions" empty.
51+
52+
When "is_task_clear" is "no", populate "questions" with your list of clarifying questions and leave "expanded_query" empty.
53+
54+
"""
55+
56+
def __init__(self, *, model: str = "o3-mini", tools: list | None = None, name: str | None = None,
57+
instructions: str | None = None, input_guardrails: list | None = None):
58+
59+
# Initialise the underlying `agents.Agent` with a structured `output_type` so it
60+
# returns a validated `QueryExpansion` instance instead of a raw string.
61+
self._agent = Agent(
62+
name=name or self._DEFAULT_NAME,
63+
instructions=instructions or self._DEFAULT_INSTRUCTIONS,
64+
tools=tools or [],
65+
model=model,
66+
output_type=QueryExpansion,
67+
input_guardrails=input_guardrails or [ai_topic_guardrail],
68+
)
69+
self._last_result = None
70+
71+
# ------------------------------------------------------------------
72+
# Public helpers
73+
# ------------------------------------------------------------------
74+
75+
@property
76+
def agent(self) -> Agent: # type: ignore[name-defined]
77+
"""Return the underlying ``agents.Agent`` instance."""
78+
return self._agent
79+
80+
async def task(self, prompt: str) -> QueryExpansion:
81+
"""Fire-and-forget API that auto-threads each call."""
82+
83+
cfg = RunConfig(tracing_disabled=True)
84+
85+
# ── First turn ──────────────────────────────────────────────
86+
if self._last_result is None:
87+
self._last_result = await Runner.run(
88+
self._agent, prompt, run_config=cfg
89+
)
90+
# ── Later turns ─────────────────────────────────────────────
91+
else:
92+
new_input = (
93+
self._last_result.to_input_list() +
94+
[{"role": "user", "content": prompt}]
95+
)
96+
self._last_result = await Runner.run(
97+
self._agent, new_input, run_config=cfg
98+
)
99+
100+
return self._last_result.final_output
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# web_page_summary_agent.py
2+
3+
from agents import Agent, Runner, RunConfig
4+
5+
6+
class WebPageSummaryAgent:
7+
_DEFAULT_NAME = "Web Page Summary Agent"
8+
9+
# NOTE: Placeholders will be filled at construction time so that the agent carries
10+
# all contextual instructions. The task method will then only need the raw content.
11+
_DEFAULT_INSTRUCTIONS = """You are an AI assistant tasked with summarising provided web page content.
12+
"Return a concise summary in {character_limit} characters or less focused on '{search_term}'. "
13+
"Only output the summary text; do not include any qualifiers or metadata."""
14+
15+
def __init__(
16+
self,
17+
search_term: str,
18+
character_limit: int = 1000,
19+
*,
20+
model: str = "gpt-4o",
21+
tools: list | None = None,
22+
name: str | None = None,
23+
instructions: str | None = None,
24+
) -> None:
25+
# Format instructions with the dynamic values unless explicitly overridden.
26+
formatted_instructions = instructions or self._DEFAULT_INSTRUCTIONS.format(
27+
search_term=search_term, character_limit=character_limit
28+
)
29+
30+
self._agent = Agent(
31+
name=name or self._DEFAULT_NAME,
32+
instructions=formatted_instructions,
33+
tools=tools or [],
34+
model=model,
35+
)
36+
37+
# ------------------------------------------------------------------
38+
# Public helpers
39+
# ------------------------------------------------------------------
40+
41+
@property
42+
def agent(self) -> Agent: # type: ignore[name-defined]
43+
"""Return the underlying :class:`agents.Agent` instance."""
44+
45+
return self._agent
46+
47+
async def task(self, content: str) -> str:
48+
49+
cfg = RunConfig(tracing_disabled=True)
50+
51+
result = await Runner.run(self._agent, content, run_config=cfg)
52+
return result.final_output
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
from pydantic import BaseModel
2+
from agents import Agent, Runner, RunConfig
3+
4+
_NUM_SEARCH_TERMS = 5
5+
6+
class SearchTerms(BaseModel):
7+
"""Structured output model for search-terms suggestions."""
8+
9+
Search_Queries: list[str]
10+
11+
12+
class WebSearchTermsGenerationAgent:
13+
_DEFAULT_NAME = "Search Terms Agent"
14+
15+
# Template for building default instructions at runtime.
16+
# NOTE: Double braces are used so that ``str.format`` leaves the JSON braces intact.
17+
_DEFAULT_INSTRUCTIONS_TEMPLATE = """You are a helpful agent assigned a research task. Your job is to provide the top
18+
{num_search_terms} Search Queries relevant to the given topic in this year (2025). The output should be in JSON format.
19+
20+
Example format provided below:
21+
<START OF EXAMPLE>
22+
{{
23+
"Search_Queries": [
24+
"Top ranked auto insurance companies US 2025 by market capitalization",
25+
"Geico rates and comparison with other auto insurance companies",
26+
"Insurance premiums of top ranked companies in the US in 2025",
27+
"Total cost of insuring autos in US 2025",
28+
"Top customer service feedback for auto insurance in 2025"
29+
]
30+
}}
31+
</END OF EXAMPLE>
32+
"""
33+
34+
35+
def __init__(
36+
self,
37+
num_search_terms: int = _NUM_SEARCH_TERMS,
38+
*,
39+
model: str = "gpt-4o",
40+
tools: list | None = None,
41+
name: str | None = None,
42+
instructions: str | None = None,
43+
):
44+
# Store number of search terms for potential future use
45+
self._num_search_terms = num_search_terms
46+
47+
# Build default instructions if none were provided.
48+
final_instructions = (
49+
instructions
50+
or self._DEFAULT_INSTRUCTIONS_TEMPLATE.format(
51+
num_search_terms=num_search_terms
52+
)
53+
)
54+
55+
self._agent = Agent(
56+
name=name or self._DEFAULT_NAME,
57+
instructions=final_instructions,
58+
tools=tools or [],
59+
model=model,
60+
output_type=SearchTerms,
61+
)
62+
63+
# ------------------------------------------------------------------
64+
# Public helpers
65+
# ------------------------------------------------------------------
66+
67+
@property
68+
def agent(self) -> Agent: # type: ignore[name-defined]
69+
"""Return the underlying ``agents.Agent`` instance."""
70+
71+
return self._agent
72+
73+
async def task(self, prompt: str) -> SearchTerms: # type: ignore[name-defined]
74+
"""Execute the agent synchronously and return the structured ``SearchTerms`` output."""
75+
cfg = RunConfig(tracing_disabled=True)
76+
result = await Runner.run(self._agent, prompt, run_config=cfg)
77+
return result.final_output
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# topic_guardrail.py
2+
from typing import List
3+
4+
from pydantic import BaseModel
5+
6+
from agents import ( # type: ignore (SDK imports)
7+
Agent,
8+
GuardrailFunctionOutput,
9+
RunContextWrapper,
10+
Runner,
11+
TResponseInputItem,
12+
input_guardrail,
13+
)
14+
15+
# ---------------------------------------------------------------------------
16+
# 1. Tiny classifier agent → “Is this prompt about AI?”
17+
# ---------------------------------------------------------------------------
18+
19+
class TopicCheckOutput(BaseModel):
20+
"""Structured result returned by the classifier."""
21+
is_about_ai: bool # True → prompt is AI-related
22+
reasoning: str # short rationale (useful for logs)
23+
24+
topic_guardrail_agent = Agent(
25+
name="Topic guardrail (AI)",
26+
instructions=(
27+
"You are a binary classifier. "
28+
"Reply with is_about_ai = true **only** when the user's request is mainly "
29+
"about artificial-intelligence research, applications, tooling, ethics, "
30+
"policy, or market trends. "
31+
"Return is_about_ai = false for all other domains (finance, biology, history, etc.)."
32+
),
33+
model="gpt-4o-mini", # lightweight, fast
34+
output_type=TopicCheckOutput,
35+
)
36+
37+
# ---------------------------------------------------------------------------
38+
# 2. Guardrail function (decorated) that wraps the classifier
39+
# ---------------------------------------------------------------------------
40+
41+
@input_guardrail
42+
async def ai_topic_guardrail(
43+
ctx: RunContextWrapper[None],
44+
agent: Agent,
45+
input: str | List[TResponseInputItem],
46+
) -> GuardrailFunctionOutput:
47+
result = await Runner.run(topic_guardrail_agent, input, context=ctx.context)
48+
49+
output = GuardrailFunctionOutput(
50+
output_info=result.final_output,
51+
tripwire_triggered=not result.final_output.is_about_ai,
52+
)
53+
54+
return output
55+
56+
# Optional: tidy public surface
57+
__all__ = ["ai_topic_guardrail", "TopicCheckOutput"]

0 commit comments

Comments
 (0)