Skip to content

Commit 622a720

Browse files
committed
docs: align AI agents guide examples with their Actor templates
1 parent 034a077 commit 622a720

5 files changed

Lines changed: 74 additions & 35 deletions

File tree

docs/03_guides/13_ai_agents.mdx

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ title: Building AI agents
44
description: Host AI agents built with PydanticAI, CrewAI, LangGraph, LlamaIndex, or Smolagents as Apify Actors.
55
---
66

7+
import CodeBlock from '@theme/CodeBlock';
78
import RunnableCodeBlock from '@site/src/components/RunnableCodeBlock';
89

910
import PydanticaiExample from '!!raw-loader!roa-loader!./code/13_pydanticai.py';
10-
import CrewaiExample from '!!raw-loader!roa-loader!./code/13_crewai.py';
11+
import CrewaiExample from '!!raw-loader!./code/13_crewai.py';
1112
import LanggraphExample from '!!raw-loader!roa-loader!./code/13_langgraph.py';
1213
import LlamaindexExample from '!!raw-loader!roa-loader!./code/13_llamaindex.py';
1314
import SmolagentsExample from '!!raw-loader!roa-loader!./code/13_smolagents.py';
@@ -52,37 +53,39 @@ Each section below shows a complete, single-file Actor for one framework. They a
5253

5354
## PydanticAI
5455

55-
[PydanticAI](https://ai.pydantic.dev/) is an agent framework from the team behind Pydantic. It is strongly typed and integrates naturally with the [Pydantic models](./input-validation) the Apify SDK already uses. The following Actor runs an agent for a single prompt:
56+
[PydanticAI](https://ai.pydantic.dev/) is an agent framework from the team behind Pydantic. It's strongly typed and integrates naturally with the [Pydantic models](./input-validation) the Apify SDK already uses. The following Actor generates a joke and returns it as a typed object:
5657

5758
<RunnableCodeBlock className="language-python" language="python">
5859
{PydanticaiExample}
5960
</RunnableCodeBlock>
6061

61-
Set `output_type` to a Pydantic model instead of `str` to get a validated object back, which maps directly onto a dataset row.
62+
The `output_type=Joke` argument makes the agent return a validated `Joke` instance, which maps directly onto a dataset row. Set `output_type=str` instead to get plain text back.
6263

6364
## CrewAI
6465

65-
[CrewAI](https://www.crewai.com/) models a problem as a "crew" of role-playing agents that work through tasks. The following Actor defines a single analyst agent and one task:
66+
[CrewAI](https://www.crewai.com/) models a problem as a "crew" of role-playing agents that work through tasks. The following Actor defines a single analyst agent that uses an Apify Actor as a tool to answer a query:
6667

67-
<RunnableCodeBlock className="language-python" language="python">
68+
{/* Not runnable from the docs: CrewAI reads the LLM key from OPENAI_API_KEY, which the shared example runner doesn't provide. */}
69+
<CodeBlock className="language-python">
6870
{CrewaiExample}
69-
</RunnableCodeBlock>
71+
</CodeBlock>
7072

7173
Note that:
7274

7375
- `kickoff_async` runs the crew without blocking the Actor's event loop.
76+
- `ApifyActorsTool('apify/instagram-scraper')` gives the agent the [Instagram Scraper](https://apify.com/apify/instagram-scraper) as a tool. It comes from the `crewai[tools]` package and reads its token from `APIFY_API_TOKEN`, which the example sets from the platform's `APIFY_TOKEN`.
7477
- CrewAI calls the LLM through [LiteLLM](https://docs.litellm.ai/), so this example reads `OPENAI_API_KEY`. To route it through the Apify OpenRouter proxy instead, configure a custom `LLM` (see the [CrewAI LLM docs](https://docs.crewai.com/concepts/llms)).
75-
- On a fresh container, crewAI shows a one-time trace-consent prompt. The template sets `CREWAI_TESTING=true` to suppress it; do the same in your Dockerfile.
78+
- On a fresh container, CrewAI shows a one-time trace-consent prompt. The template sets `CREWAI_TESTING=true` to suppress it. Do the same in your Dockerfile.
7679

7780
## LangGraph
7881

79-
[LangGraph](https://www.langchain.com/langgraph) builds an agent as a graph with explicit state, which makes complex, multi-step control flow easy to follow. It builds on [LangChain](https://www.langchain.com/), so any LangChain chat model and tool works. The following Actor runs a single-turn agent:
82+
[LangGraph](https://www.langchain.com/langgraph) builds an agent as a graph with explicit state, which makes complex, multi-step control flow easy to follow. It builds on [LangChain](https://www.langchain.com/), so any LangChain chat model and tool works. The following Actor runs an agent with a single tool:
8083

8184
<RunnableCodeBlock className="language-python" language="python">
8285
{LanggraphExample}
8386
</RunnableCodeBlock>
8487

85-
The example passes an empty `tools` list for brevity. Add LangChain tools to give the agent abilities, and read the final answer from the last message in the returned state.
88+
The agent decides on its own when to call the `sum_numbers` tool, and the answer is read from the last message in the returned state. Add more LangChain tools to extend it, including Apify Actors (see [Using Apify Actors as tools](#using-apify-actors-as-tools)).
8689

8790
## LlamaIndex
8891

@@ -96,6 +99,7 @@ Note that:
9699

97100
- `OpenAILike` is the LlamaIndex LLM class for OpenAI-compatible endpoints such as the Apify OpenRouter proxy. It needs the `llama-index-llms-openai-like` package.
98101
- The agent decides on its own when to call the `word_count` tool. Add `FunctionTool`s of your own to extend it.
102+
- To reason over your own data, wrap an Apify Actor in a `FunctionTool` (see [Using Apify Actors as tools](#using-apify-actors-as-tools)), as the [LlamaIndex template](https://apify.com/templates/python-llamaindex-agent) does with a contact-details scraper.
99103

100104
## Smolagents
101105

@@ -105,21 +109,23 @@ Note that:
105109
{SmolagentsExample}
106110
</RunnableCodeBlock>
107111

108-
A `CodeAgent` executes the Python code it generates, so run it in the isolated Actor container rather than on your own machine. Pass tools such as `WebSearchTool` in the `tools` list to let it gather information.
112+
This agent uses `WebSearchTool` to fetch the latest news, then writes and runs Python code to summarize it. Because a `CodeAgent` executes the code it generates, run it in the isolated Actor container rather than on your own machine.
109113

110114
## Using Apify Actors as tools
111115

112116
An agent becomes useful once it can act on the world. On Apify, any of the thousands of Actors in the [Apify Store](https://apify.com/store) can become a tool: a scraper that fetches data, a browser automation that fills a form, or an API wrapper that posts a message.
113117

114-
For LangChain-based frameworks, such as LangGraph and CrewAI, the [`langchain-apify`](https://github.com/apify/langchain-apify) package exposes any Actor as a tool with a single line:
118+
Some frameworks ship a ready-made wrapper for this. For LangGraph and other [LangChain](https://www.langchain.com/)-based agents, the [`langchain-apify`](https://github.com/apify/langchain-apify) package exposes any Actor as a tool with a single line:
115119

116120
```python
117-
from crewai_tools import ApifyActorsTool
121+
from langchain_apify import ApifyActorsTool
118122

119123
# Let the agent scrape Instagram profiles by calling the apify/instagram-scraper Actor.
120124
tools = [ApifyActorsTool('apify/instagram-scraper')]
121125
```
122126

127+
CrewAI ships the same tool as `crewai_tools.ApifyActorsTool`, as the [CrewAI example](#crewai) above shows.
128+
123129
For any other framework, call the Actor directly through the Actor's preconfigured API client and read its dataset:
124130

125131
```python
@@ -137,7 +143,7 @@ For details on calling other Actors, see [Interacting with other Actors](../conc
137143
Agents run on the standard [Apify Python base image](https://hub.docker.com/r/apify/actor-python), so no browser or extra system dependencies are needed. Add `apify` and your framework's packages to `requirements.txt`:
138144

139145
- PydanticAI: `pydantic-ai`
140-
- CrewAI: `crewai` (or `crewai[tools]` for `ApifyActorsTool`)
146+
- CrewAI: `crewai[tools]` (the `tools` extra provides `ApifyActorsTool`)
141147
- LangGraph: `langchain`, `langchain-openai`, `langgraph`
142148
- LlamaIndex: `llama-index`, `llama-index-llms-openai-like`
143149
- Smolagents: `smolagents[openai]`

docs/03_guides/code/13_crewai.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,46 @@
11
import asyncio
2+
import os
23

34
from crewai import Agent, Crew, Task
5+
from crewai_tools import ApifyActorsTool
46

57
from apify import Actor
68

9+
# On a fresh container, CrewAI shows a one-time trace-consent prompt that blocks on
10+
# stdin. `CREWAI_TESTING=true` is the only flag that suppresses it.
11+
os.environ.setdefault('CREWAI_TESTING', 'true')
12+
713

814
async def main() -> None:
915
async with Actor:
1016
actor_input = await Actor.get_input() or {}
11-
topic = actor_input.get('topic', 'the Apify platform')
17+
query = actor_input.get(
18+
'query', 'Summarize the latest posts on the @openai Instagram profile.'
19+
)
1220
model = actor_input.get('model', 'gpt-4o-mini')
1321

1422
# CrewAI calls the LLM through LiteLLM, which reads OPENAI_API_KEY.
23+
# `ApifyActorsTool` reads APIFY_API_TOKEN. The platform injects APIFY_TOKEN.
24+
os.environ.setdefault('APIFY_API_TOKEN', os.environ['APIFY_TOKEN'])
25+
26+
# The agent can run any Apify Actor as a tool, here the Instagram scraper.
1527
analyst = Agent(
16-
role='Research Analyst',
17-
goal=f'Write a short, accurate summary about {topic}.',
18-
backstory='An analyst who turns a topic into a concise brief.',
28+
role='Social Media Analyst',
29+
goal='Analyze social media profiles and summarize the findings.',
30+
backstory='An analyst who turns raw social media data into concise insights.',
31+
tools=[ApifyActorsTool('apify/instagram-scraper')],
1932
llm=model,
2033
)
2134
task = Task(
22-
description=f'Write a three-sentence summary about {topic}.',
23-
expected_output='A three-sentence summary.',
35+
description=query,
36+
expected_output='A short, readable summary that answers the query.',
2437
agent=analyst,
2538
)
2639

2740
# `kickoff_async` keeps the Actor's event loop responsive.
2841
result = await Crew(agents=[analyst], tasks=[task]).kickoff_async()
2942
Actor.log.info(f'Crew result:\n{result.raw}')
30-
await Actor.push_data({'topic': topic, 'summary': result.raw})
43+
await Actor.push_data({'query': query, 'summary': result.raw})
3144

3245

3346
if __name__ == '__main__':

docs/03_guides/code/13_langgraph.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,24 @@
22
import os
33

44
from langchain.agents import create_agent
5+
from langchain_core.tools import tool
56
from langchain_openai import ChatOpenAI
67

78
from apify import Actor
89

910
OPENROUTER_BASE_URL = 'https://openrouter.apify.actor/api/v1'
1011

1112

13+
@tool
14+
def sum_numbers(numbers: list[int]) -> int:
15+
"""Return the sum of a list of numbers."""
16+
return sum(numbers)
17+
18+
1219
async def main() -> None:
1320
async with Actor:
1421
actor_input = await Actor.get_input() or {}
15-
query = actor_input.get('query', 'What is an Apify Actor?')
22+
query = actor_input.get('query', 'What is the sum of 128, 64, and 32?')
1623
model = actor_input.get('model', 'openai/gpt-4o-mini')
1724

1825
# Route the LLM through the Apify OpenRouter proxy (no provider key needed).
@@ -21,7 +28,8 @@ async def main() -> None:
2128
base_url=OPENROUTER_BASE_URL,
2229
api_key=os.environ['APIFY_TOKEN'],
2330
)
24-
agent = create_agent(llm, tools=[])
31+
# The agent decides on its own when to call the `sum_numbers` tool.
32+
agent = create_agent(llm, tools=[sum_numbers])
2533

2634
result = await agent.ainvoke({'messages': [('user', query)]})
2735
answer = result['messages'][-1].content

docs/03_guides/code/13_pydanticai.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import asyncio
22
import os
33

4+
from pydantic import BaseModel
45
from pydantic_ai import Agent
56
from pydantic_ai.models.openai import OpenAIChatModel
67
from pydantic_ai.providers.openai import OpenAIProvider
@@ -10,26 +11,36 @@
1011
OPENROUTER_BASE_URL = 'https://openrouter.apify.actor/api/v1'
1112

1213

14+
class Joke(BaseModel):
15+
"""The agent's typed output: a joke split into its setup and punchline."""
16+
17+
setup: str
18+
punchline: str
19+
20+
1321
async def main() -> None:
1422
async with Actor:
1523
actor_input = await Actor.get_input() or {}
16-
prompt = actor_input.get('prompt', 'Explain Apify Actors in two sentences.')
24+
topic = actor_input.get('topic', 'bad weather')
1725
model = actor_input.get('model', 'openai/gpt-4o-mini')
1826

1927
# Route the LLM through the Apify OpenRouter proxy (no provider key needed).
2028
provider = OpenAIProvider(
2129
base_url=OPENROUTER_BASE_URL,
2230
api_key=os.environ['APIFY_TOKEN'],
2331
)
32+
# `output_type=Joke` makes the agent return a validated `Joke` instance.
2433
agent = Agent(
2534
OpenAIChatModel(model, provider=provider),
26-
output_type=str,
27-
system_prompt='You are a concise, helpful assistant.',
35+
output_type=Joke,
36+
system_prompt='You are a witty comedian. Write a single short joke.',
2837
)
2938

30-
result = await agent.run(user_prompt=prompt)
31-
Actor.log.info(f'Agent response:\n{result.output}')
32-
await Actor.push_data({'prompt': prompt, 'response': result.output})
39+
joke = (await agent.run(user_prompt=f'Tell me a joke about {topic}.')).output
40+
Actor.log.info(f'Joke:\n{joke.setup}\n{joke.punchline}')
41+
await Actor.push_data(
42+
{'topic': topic, 'setup': joke.setup, 'punchline': joke.punchline}
43+
)
3344

3445

3546
if __name__ == '__main__':

docs/03_guides/code/13_smolagents.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import asyncio
22
import os
33

4-
from smolagents import CodeAgent, OpenAIServerModel
4+
from smolagents import CodeAgent, OpenAIServerModel, WebSearchTool
55

66
from apify import Actor
77

@@ -11,7 +11,7 @@
1111
async def main() -> None:
1212
async with Actor:
1313
actor_input = await Actor.get_input() or {}
14-
task = actor_input.get('task', 'Compute the 12th Fibonacci number.')
14+
topic = actor_input.get('topic', 'open source AI')
1515
model = actor_input.get('model', 'openai/gpt-4o-mini')
1616

1717
# Route the LLM through the Apify OpenRouter proxy (no provider key needed).
@@ -20,12 +20,13 @@ async def main() -> None:
2020
api_base=OPENROUTER_BASE_URL,
2121
api_key=os.environ['APIFY_TOKEN'],
2222
)
23-
# A `CodeAgent` writes and runs Python code to solve the task.
24-
agent = CodeAgent(tools=[], model=llm)
23+
# A `CodeAgent` writes and runs Python code to solve the task. Here it uses
24+
# `WebSearchTool` to gather the latest news before summarizing it.
25+
agent = CodeAgent(tools=[WebSearchTool()], model=llm)
2526

26-
result = agent.run(task)
27-
Actor.log.info(f'Agent result:\n{result}')
28-
await Actor.push_data({'task': task, 'result': str(result)})
27+
result = agent.run(f'Find and summarize the latest news about {topic}.')
28+
Actor.log.info(f'Summary:\n{result}')
29+
await Actor.push_data({'topic': topic, 'summary': str(result)})
2930

3031

3132
if __name__ == '__main__':

0 commit comments

Comments
 (0)