Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ async def contact_mothership(query: str) -> str:

async def main() -> None:
model = ai.get_model("anthropic/claude-sonnet-4")
agent = ai.agent(tools=[contact_mothership])
agent = ai.Agent(tools=[contact_mothership])

messages = [
ai.system_message(
Expand Down
2 changes: 1 addition & 1 deletion docs/ai-python/content/docs/basics/agents.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ async def contact_mothership(query: str) -> str:

async def main() -> None:
model = ai.get_model("anthropic/claude-sonnet-4")
agent = ai.agent(tools=[contact_mothership])
agent = ai.Agent(tools=[contact_mothership])
messages = [
ai.system_message(
"Use the contact_mothership tool when asked about the future."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ mothership_model = ai.get_model("anthropic/claude-sonnet-4")
@ai.tool
async def ask_mothership(topic: str) -> ai.SubAgentTool:
"""Ask a specialist agent for mothership guidance."""
sub_agent = ai.agent()
sub_agent = ai.Agent()
sub_messages = [
ai.system_message("Answer as the mothership operations desk."),
ai.user_message(topic),
Expand Down
2 changes: 1 addition & 1 deletion docs/ai-python/content/docs/basics/tools.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ tools = await ai.mcp.get_http_tools(
headers={"Authorization": "Bearer your_access_token_here"},
)

agent = ai.agent(tools=tools)
agent = ai.Agent(tools=tools)
```

Use `ai.mcp.get_stdio_tools` for subprocess-based MCP servers.
10 changes: 4 additions & 6 deletions docs/ai-python/content/docs/reference/ai/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -236,21 +236,19 @@ Part builder exports:

## Tools and Agents

### agent
### Agent

`agent` creates an `Agent`.
`Agent` runs the default agent loop.

```python
agent = ai.agent(tools=[contact_mothership])
agent = ai.Agent(tools=[contact_mothership])
```

Arguments:

- `tools`: Optional `AgentTool` values from `tool` and schema-only `Tool`
declarations for provider-executed tools.

Returns `Agent`.

### AgentTool

`AgentTool` binds a model-facing `Tool` declaration to an executable Python
Expand All @@ -264,7 +262,7 @@ tool.validator
tool.require_approval
```

Pass `AgentTool` values to `agent(tools=[...])`.
Pass `AgentTool` values to `Agent(tools=[...])`.

### Context

Expand Down
4 changes: 2 additions & 2 deletions docs/ai-python/content/docs/reference/mcp.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ uv add "ai[mcp]"
## get_stdio_tools

`get_stdio_tools` starts an MCP server subprocess and returns `AgentTool`
values that can be passed to `ai.agent`.
values that can be passed to `ai.Agent`.

```python
tools = await ai.mcp.get_stdio_tools(
Expand All @@ -35,7 +35,7 @@ Arguments:
## get_http_tools

`get_http_tools` connects to an MCP HTTP server and returns `AgentTool` values
that can be passed to `ai.agent`.
that can be passed to `ai.Agent`.

```python
tools = await ai.mcp.get_http_tools("https://example.com/mcp")
Expand Down
2 changes: 1 addition & 1 deletion skills/ai-python-basics/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ async def get_weather(city: str) -> str:

async def main() -> None:
model = ai.get_model("anthropic/claude-sonnet-4")
agent = ai.agent(tools=[get_weather])
agent = ai.Agent(tools=[get_weather])
messages = [
ai.system_message("Use tools when useful."),
ai.user_message("What is the weather in San Francisco?"),
Expand Down
2 changes: 1 addition & 1 deletion skills/ai-python-streaming-tools/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Use `ai.SubAgentTool` when a tool runs another agent and streams its events.
```python
@ai.tool
async def research(topic: str) -> ai.SubAgentTool:
researcher = ai.agent()
researcher = ai.Agent()

messages = [
ai.system_message("Research briefly."),
Expand Down
2 changes: 1 addition & 1 deletion skills/ai-python-subagents/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ agent.
```python
@ai.tool
async def research(topic: str) -> ai.SubAgentTool:
child = ai.agent(tools=[lookup])
child = ai.Agent(tools=[lookup])
messages = [
ai.system_message("Research briefly."),
ai.user_message(topic),
Expand Down
2 changes: 0 additions & 2 deletions src/ai/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
ToolCall,
ToolRunner,
abort_pending_hook,
agent,
cancel_hook,
hook,
mcp,
Expand Down Expand Up @@ -176,7 +175,6 @@
"UnsupportedProviderError",
"VideoParams",
"abort_pending_hook",
"agent",
"assistant_message",
"cancel_hook",
"content_output",
Expand Down
2 changes: 0 additions & 2 deletions src/ai/agents/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
ToolCall,
ToolCallCallable,
ToolRunner,
agent,
pending_tool_result,
tool,
tool_result,
Expand Down Expand Up @@ -53,7 +52,6 @@
"ToolCallCallable",
"ToolRunner",
"abort_pending_hook",
"agent",
"cancel_hook",
"hook",
"mcp",
Expand Down
10 changes: 1 addition & 9 deletions src/ai/agents/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ async def fetch(url: str) -> StreamingStatusTool[str]:

@ai.tool
async def research(topic: str) -> SubAgentTool:
sub = ai.agent(tools=[...])
sub = ai.Agent(tools=[...])
async with sub.run(model, messages) as stream:
async for event in stream:
yield event
Expand Down Expand Up @@ -1394,11 +1394,3 @@ async def _stream() -> AsyncGenerator[events_.AgentEvent]:
middleware_.deactivate(mw_token)

yield AgentStream(_stream(), context)


def agent(
*,
tools: Sequence[AgentTool | Tool] | None = None,
) -> Agent:
"""Create an Agent."""
return Agent(tools=tools)
2 changes: 1 addition & 1 deletion tests/agents/mcp/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ async def fake_fn(**kwargs: str) -> str:
# Replace the executor (which would try to connect) with our fake.
native = dataclasses.replace(native, fn=fake_fn)

my_agent = ai.agent(tools=[native])
my_agent = ai.Agent(tools=[native])

call1 = [
tool_call_msg(
Expand Down
2 changes: 1 addition & 1 deletion tests/agents/test_aggregate_marker.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ async def alias_progress_tool(query: str) -> ai.StreamingStatusTool[str]:

async def test_alias_declared_tool_runs_end_to_end() -> None:
"""An alias-declared streaming tool behaves like the kwarg form."""
my_agent = ai.agent(tools=[alias_progress_tool])
my_agent = ai.Agent(tools=[alias_progress_tool])

call = [
tool_call_msg(
Expand Down
6 changes: 3 additions & 3 deletions tests/agents/test_generator_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ async def progress_tool(query: str) -> AsyncGenerator[str]:
async def test_generator_tool_streams_and_returns_result() -> None:
"""Generator tool yields streaming events visible to consumer;
final text becomes the tool result fed back to the LLM."""
my_agent = ai.agent(tools=[progress_tool])
my_agent = ai.Agent(tools=[progress_tool])

# Turn 1: LLM calls progress_tool
# Turn 2: LLM produces final text after seeing the tool result
Expand Down Expand Up @@ -119,7 +119,7 @@ async def inner_fact(topic: str) -> str:
@ai.tool(aggregator=ai.agents.MessageAggregator)
async def research_tool(topic: str) -> AsyncGenerator[agent_events_.AgentEvent]:
"""Nested agent that researches a topic."""
inner = ai.agent(tools=[inner_fact])
inner = ai.Agent(tools=[inner_fact])

msgs = [
ai.system_message("Be concise."),
Expand All @@ -138,7 +138,7 @@ async def test_yield_from_nested_agent() -> None:
queue and discards bare Messages, so the parent agent's
context.messages stays clean.
"""
outer = ai.agent(tools=[research_tool])
outer = ai.Agent(tools=[research_tool])

# Inner agent: text-only reply (no tools, to keep it simple)
inner_reply = [text_msg("Mars has two moons.", id="inner-msg")]
Expand Down
14 changes: 7 additions & 7 deletions tests/agents/test_run_blocked.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def _multi_call_msg(*tc: tuple[str, str]) -> messages_.Message:

async def test_gated_tool_block_cycle() -> None:
"""Park on approval -> RunBlocked; the resolved HookEvent unblocks."""
my_agent = ai.agent(tools=[gated])
my_agent = ai.Agent(tools=[gated])
mock_llm(
[
[tool_call_msg(name="gated", args='{"x": 1}')],
Expand Down Expand Up @@ -118,7 +118,7 @@ async def slow(x: int) -> str:
await release.wait()
return "slow done"

my_agent = ai.agent(tools=[gated, slow])
my_agent = ai.Agent(tools=[gated, slow])
mock_llm(
[
[_multi_call_msg(("tc-1", "gated"), ("tc-2", "slow"))],
Expand Down Expand Up @@ -154,7 +154,7 @@ async def slow(x: int) -> str:
async def test_parallel_gated_tools() -> None:
"""Blocking needs *every* in-flight tool parked, and the signal
re-fires when the run parks again on the remaining hook."""
my_agent = ai.agent(tools=[gated])
my_agent = ai.Agent(tools=[gated])
mock_llm(
[
[_multi_call_msg(("tc-1", "gated"), ("tc-2", "gated"))],
Expand Down Expand Up @@ -203,7 +203,7 @@ async def loop(

async def test_abort_leaves_blocked() -> None:
"""Serverless abort: the run ends still blocked, hooks still pending."""
my_agent = ai.agent(tools=[gated])
my_agent = ai.Agent(tools=[gated])
mock_llm([[tool_call_msg(name="gated", args='{"x": 1}')]])

blocked_events: list[events_.RunBlocked] = []
Expand Down Expand Up @@ -231,7 +231,7 @@ async def plain(x: int) -> str:
"""A plain tool."""
return "plain done"

my_agent = ai.agent(tools=[plain])
my_agent = ai.Agent(tools=[plain])
mock_llm(
[
[tool_call_msg(name="plain", args='{"x": 1}')],
Expand Down Expand Up @@ -260,7 +260,7 @@ async def test_block_signal_waits_for_stream_end() -> None:
messages_.TextPart(text="still streaming..."),
],
)
my_agent = ai.agent(tools=[gated])
my_agent = ai.Agent(tools=[gated])
mock_llm([[msg], [text_msg("done", id="msg-2")]])

delivered: list[events_.AgentEvent] = []
Expand Down Expand Up @@ -291,7 +291,7 @@ async def self_gated(x: int) -> str:
await ai.hook("self_gate", payload=Confirmation)
return "ran"

my_agent = ai.agent(tools=[self_gated])
my_agent = ai.Agent(tools=[self_gated])
mock_llm(
[
[tool_call_msg(name="self_gated", args='{"x": 1}')],
Expand Down
8 changes: 4 additions & 4 deletions tests/agents/test_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ async def concat(a: str, b: str) -> str:

async def test_agent_text_only() -> None:
"""Agent default loop with no tool calls returns after one LLM call."""
my_agent = ai.agent(tools=[double])
my_agent = ai.Agent(tools=[double])

llm = mock_llm([[text_msg("Hello!")]])
async with my_agent.run(MOCK_MODEL, [ai.user_message("Hi")]) as stream:
Expand All @@ -47,7 +47,7 @@ async def test_agent_text_only() -> None:

async def test_agent_tool_then_text() -> None:
"""Agent default loop calls tool, feeds result back, gets final text."""
my_agent = ai.agent(tools=[double])
my_agent = ai.Agent(tools=[double])

call1 = [tool_call_msg(tc_id="tc-1", name="double", args='{"x": 5}')]
call2 = [text_msg("The answer is 10.")]
Expand All @@ -69,7 +69,7 @@ async def test_agent_tool_then_text() -> None:

async def test_agent_parallel_tools() -> None:
"""LLM returns two tool calls in one message; both execute."""
my_agent = ai.agent(tools=[double])
my_agent = ai.Agent(tools=[double])

two_tools = messages.Message(
id="msg-1",
Expand Down Expand Up @@ -104,7 +104,7 @@ async def test_agent_parallel_tools() -> None:

async def test_agent_multi_turn() -> None:
"""LLM calls a tool, then calls another tool, then returns text."""
my_agent = ai.agent(tools=[double, concat])
my_agent = ai.Agent(tools=[double, concat])

turn1 = [
tool_call_msg(
Expand Down
6 changes: 3 additions & 3 deletions tests/agents/test_validation_error_approval.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ async def edit(path: str, edits: list[TextEdit]) -> str:
async def test_invalid_args_with_approval_returns_error_result() -> None:
"""Invalid tool args on an approval-gated tool should produce an error
tool result without firing the approval hook."""
my_agent = ai.agent(tools=[edit])
my_agent = ai.Agent(tools=[edit])

# Model sends edits as a dict instead of a list — this will fail validation
bad_call = messages_.Message(
Expand Down Expand Up @@ -86,7 +86,7 @@ async def test_invalid_args_with_approval_returns_error_result() -> None:
async def test_invalid_args_skips_approval_hook() -> None:
"""Invalid args should produce a validation error result without
ever prompting for approval."""
my_agent = ai.agent(tools=[edit])
my_agent = ai.Agent(tools=[edit])

bad_call = messages_.Message(
id="msg-1",
Expand Down Expand Up @@ -132,7 +132,7 @@ async def test_invalid_args_skips_approval_hook() -> None:

async def test_completely_invalid_json_with_approval() -> None:
"""Completely unparseable tool_args should also be handled gracefully."""
my_agent = ai.agent(tools=[edit])
my_agent = ai.Agent(tools=[edit])

bad_call = messages_.Message(
id="msg-1",
Expand Down
12 changes: 6 additions & 6 deletions tests/test_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ async def double(x: int) -> int:
"""Double a number."""
return x * 2

my_agent = ai.agent(tools=[double])
my_agent = ai.Agent(tools=[double])
call1 = [tool_call_msg(tc_id="tc-1", name="double", args='{"x": 7}')]
call2 = [text_msg("14")]
mock_llm([call1, call2])
Expand Down Expand Up @@ -131,7 +131,7 @@ async def wrap_agent_run(
yield event
order.append("inner-after")

my_agent = ai.agent()
my_agent = ai.Agent()
mock_llm([[text_msg("Hi")]])

async with my_agent.run(
Expand Down Expand Up @@ -164,7 +164,7 @@ async def echo(x: int) -> int:
"""Echo a number."""
return x

my_agent = ai.agent(tools=[echo])
my_agent = ai.Agent(tools=[echo])
call1 = [tool_call_msg(tc_id="tc-1", name="echo", args='{"x": 42}')]
call2 = [text_msg("done")]
mock_llm([call1, call2])
Expand Down Expand Up @@ -195,7 +195,7 @@ async def echo(x: int) -> int:
"""Echo a number."""
return x

my_agent = ai.agent(tools=[echo])
my_agent = ai.Agent(tools=[echo])
call1 = [tool_call_msg(tc_id="original-id", name="echo", args='{"x": 42}')]
call2 = [text_msg("done")]
mock_llm([call1, call2])
Expand Down Expand Up @@ -226,7 +226,7 @@ async def wrap_model(
call.messages.append(ai.system_message("injected"))
return await next(call)

my_agent = ai.agent()
my_agent = ai.Agent()
mock_llm([[text_msg("Hi")]])

async with my_agent.run(
Expand Down Expand Up @@ -261,7 +261,7 @@ async def double(x: int) -> int:
"""Double a number."""
return x * 2

my_agent = ai.agent(tools=[double])
my_agent = ai.Agent(tools=[double])
# Send completely invalid JSON args — parse will fail, kwargs will be {}.
call1 = [tool_call_msg(tc_id="tc-bad", name="double", args="not json")]
call2 = [text_msg("done")]
Expand Down
Loading