stirrup 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- stirrup-0.1.0/LICENSE +21 -0
- stirrup-0.1.0/PKG-INFO +318 -0
- stirrup-0.1.0/README.md +253 -0
- stirrup-0.1.0/pyproject.toml +100 -0
- stirrup-0.1.0/src/stirrup/__init__.py +76 -0
- stirrup-0.1.0/src/stirrup/clients/__init__.py +14 -0
- stirrup-0.1.0/src/stirrup/clients/chat_completions_client.py +219 -0
- stirrup-0.1.0/src/stirrup/clients/litellm_client.py +141 -0
- stirrup-0.1.0/src/stirrup/clients/utils.py +161 -0
- stirrup-0.1.0/src/stirrup/constants.py +14 -0
- stirrup-0.1.0/src/stirrup/core/__init__.py +1 -0
- stirrup-0.1.0/src/stirrup/core/agent.py +1097 -0
- stirrup-0.1.0/src/stirrup/core/exceptions.py +7 -0
- stirrup-0.1.0/src/stirrup/core/models.py +599 -0
- stirrup-0.1.0/src/stirrup/prompts/__init__.py +22 -0
- stirrup-0.1.0/src/stirrup/prompts/base_system_prompt.txt +1 -0
- stirrup-0.1.0/src/stirrup/prompts/message_summarizer.txt +27 -0
- stirrup-0.1.0/src/stirrup/prompts/message_summarizer_bridge.txt +11 -0
- stirrup-0.1.0/src/stirrup/py.typed +0 -0
- stirrup-0.1.0/src/stirrup/tools/__init__.py +77 -0
- stirrup-0.1.0/src/stirrup/tools/calculator.py +32 -0
- stirrup-0.1.0/src/stirrup/tools/code_backends/__init__.py +38 -0
- stirrup-0.1.0/src/stirrup/tools/code_backends/base.py +454 -0
- stirrup-0.1.0/src/stirrup/tools/code_backends/docker.py +752 -0
- stirrup-0.1.0/src/stirrup/tools/code_backends/e2b.py +359 -0
- stirrup-0.1.0/src/stirrup/tools/code_backends/local.py +481 -0
- stirrup-0.1.0/src/stirrup/tools/finish.py +23 -0
- stirrup-0.1.0/src/stirrup/tools/mcp.py +500 -0
- stirrup-0.1.0/src/stirrup/tools/view_image.py +83 -0
- stirrup-0.1.0/src/stirrup/tools/web.py +336 -0
- stirrup-0.1.0/src/stirrup/utils/__init__.py +10 -0
- stirrup-0.1.0/src/stirrup/utils/logging.py +944 -0
- stirrup-0.1.0/src/stirrup/utils/text.py +11 -0
stirrup-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Artificial Analysis
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
stirrup-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: stirrup
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: The lightweight foundation for building agents
|
|
5
|
+
Keywords: ai,agent,llm,openai,anthropic,tools,framework
|
|
6
|
+
Author: Artificial Analysis, Inc.
|
|
7
|
+
Author-email: Artificial Analysis, Inc. <info@artificialanalysis.ai>
|
|
8
|
+
License: MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2025 Artificial Analysis
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
|
29
|
+
Classifier: Development Status :: 4 - Beta
|
|
30
|
+
Classifier: Intended Audience :: Developers
|
|
31
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
32
|
+
Classifier: Operating System :: OS Independent
|
|
33
|
+
Classifier: Programming Language :: Python :: 3
|
|
34
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
36
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
37
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
38
|
+
Classifier: Typing :: Typed
|
|
39
|
+
Requires-Dist: anyio>=4.0.0
|
|
40
|
+
Requires-Dist: filetype>=1.0.0
|
|
41
|
+
Requires-Dist: httpx>=0.20.0
|
|
42
|
+
Requires-Dist: json-schema-to-pydantic>=0.2.0
|
|
43
|
+
Requires-Dist: moviepy>=2.0.0
|
|
44
|
+
Requires-Dist: openai>=1.0.0
|
|
45
|
+
Requires-Dist: pillow>=10.4.0
|
|
46
|
+
Requires-Dist: pydantic>=2.0.0
|
|
47
|
+
Requires-Dist: tenacity>=5.0.0
|
|
48
|
+
Requires-Dist: trafilatura>=1.9.0
|
|
49
|
+
Requires-Dist: stirrup[litellm,e2b,docker,mcp] ; extra == 'all'
|
|
50
|
+
Requires-Dist: docker>=7.0.0 ; extra == 'docker'
|
|
51
|
+
Requires-Dist: python-dotenv>=1.0.0 ; extra == 'docker'
|
|
52
|
+
Requires-Dist: e2b-code-interpreter>=2.3.0 ; extra == 'e2b'
|
|
53
|
+
Requires-Dist: litellm>=1.79.3 ; extra == 'litellm'
|
|
54
|
+
Requires-Dist: mcp>=1.9.0 ; extra == 'mcp'
|
|
55
|
+
Requires-Python: >=3.12
|
|
56
|
+
Project-URL: Documentation, https://artificialanalysis.github.io/Stirrup
|
|
57
|
+
Project-URL: Homepage, https://github.com/ArtificialAnalysis/Stirrup
|
|
58
|
+
Project-URL: Repository, https://github.com/ArtificialAnalysis/Stirrup
|
|
59
|
+
Provides-Extra: all
|
|
60
|
+
Provides-Extra: docker
|
|
61
|
+
Provides-Extra: e2b
|
|
62
|
+
Provides-Extra: litellm
|
|
63
|
+
Provides-Extra: mcp
|
|
64
|
+
Description-Content-Type: text/markdown
|
|
65
|
+
|
|
66
|
+
<div align="center">
|
|
67
|
+
<a href="">
|
|
68
|
+
<picture>
|
|
69
|
+
<img alt="Stirrup" src="assets/stirrup-banner.png" width="700">
|
|
70
|
+
</picture>
|
|
71
|
+
</a>
|
|
72
|
+
<br></br>
|
|
73
|
+
<h1>The lightweight foundation for building agents</h1>
|
|
74
|
+
<br>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
<p align="center">
|
|
79
|
+
<a href="https://pypi.python.org/pypi/stirrup"><img src="https://img.shields.io/pypi/v/stirrup" alt="PyPI version" /></a> <!--
|
|
80
|
+
--><a href="https://github.com/ArtificialAnalysis/Stirrup/blob/main/LICENSE"><img src="https://img.shields.io/github/license/ArtificialAnalysis/Stirrup" alt="License" /></a> <!--
|
|
81
|
+
--><a href="https://artificialanalysis.github.io/Stirrup"><img src="https://img.shields.io/badge/MkDocs-4F46E5?logo=materialformkdocs&logoColor=fff" alt="MkDocs" /></a>
|
|
82
|
+
</p>
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
Stirrup is a lightweight framework, or starting point template, for building agents. It differs from other agent frameworks by:
|
|
86
|
+
|
|
87
|
+
- **Working with the model, not against it:** Stirrup gets out of the way and lets the model choose its own approach to completing tasks (similar to Claude Code). Many frameworks impose rigid workflows that can degrade results.
|
|
88
|
+
- **Best practices and tools built-in:** We analyzed the leading agents (Claude Code, Codex, and others) to understand and incorporate best practices relating to topics like context management and foundational tools (e.g., code execution).
|
|
89
|
+
- **Fully customizable:** Use Stirrup as a package or as a starting template to build your own fully customized agents.
|
|
90
|
+
|
|
91
|
+
## Features
|
|
92
|
+
|
|
93
|
+
- **Essential tools built-in:**
|
|
94
|
+
- Online search / web browsing
|
|
95
|
+
- Code execution (local, Docker container, E2B sandbox)
|
|
96
|
+
- MCP client
|
|
97
|
+
- Document input and output
|
|
98
|
+
- **Flexible tool execution:** A generic `Tool` class allows easy tool definition and extension
|
|
99
|
+
- **Context management:** Automatically summarizes conversation history when approaching context limits
|
|
100
|
+
- **Flexible provider support:** Pre-built support for OpenAI-compatible APIs and LiteLLM, or bring your own client
|
|
101
|
+
- **Multimodal support:** Process images, video, and audio with automatic format conversion
|
|
102
|
+
|
|
103
|
+
## Installation
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# Core framework
|
|
107
|
+
uv pip install stirrup # or: uv add stirrup
|
|
108
|
+
|
|
109
|
+
# With all optional components
|
|
110
|
+
uv pip install stirrup[all] # or: uv add stirrup[all]
|
|
111
|
+
|
|
112
|
+
# Individual extras
|
|
113
|
+
uv pip install stirrup[litellm] # or: uv add stirrup[litellm]
|
|
114
|
+
uv pip install stirrup[docker] # or: uv add stirrup[docker]
|
|
115
|
+
uv pip install stirrup[e2b] # or: uv add stirrup[e2b]
|
|
116
|
+
uv pip install stirrup[mcp] # or: uv add stirrup[mcp]
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Quick Start
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
import asyncio
|
|
123
|
+
|
|
124
|
+
from stirrup import Agent
|
|
125
|
+
from stirrup.clients.chat_completions_client import ChatCompletionsClient
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
async def main() -> None:
|
|
129
|
+
"""Run an agent that searches the web and creates a chart."""
|
|
130
|
+
|
|
131
|
+
# Create client using ChatCompletionsClient
|
|
132
|
+
# Automatically uses OPENROUTER_API_KEY environment variable
|
|
133
|
+
client = ChatCompletionsClient(
|
|
134
|
+
base_url="https://openrouter.ai/api/v1",
|
|
135
|
+
model="anthropic/claude-sonnet-4.5",
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# As no tools are provided, the agent will use the default tools, which consist of:
|
|
139
|
+
# - Web tools (web search and web fetching, note web search requires BRAVE_API_KEY)
|
|
140
|
+
# - Local code execution tool (to execute shell commands)
|
|
141
|
+
agent = Agent(client=client, name="agent", max_turns=15)
|
|
142
|
+
|
|
143
|
+
# Run with session context - handles tool lifecycle, logging and file outputs
|
|
144
|
+
async with agent.session(output_dir="./output/getting_started_example") as session:
|
|
145
|
+
finish_params, history, metadata = await session.run(
|
|
146
|
+
"""
|
|
147
|
+
What is the population of Australia over the last 3 years? Search the web to find out and create a
|
|
148
|
+
simple chart using matplotlib showing the current population per year."""
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
print("Finish params: ", finish_params)
|
|
152
|
+
print("History: ", history)
|
|
153
|
+
print("Metadata: ", metadata)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
if __name__ == "__main__":
|
|
157
|
+
asyncio.run(main())
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
> **Note:** This example uses OpenRouter. Set `OPENROUTER_API_KEY` in your environment before running. Web search requires a `BRAVE_API_KEY`. The agent will still work without it, but web search will be unavailable.
|
|
161
|
+
|
|
162
|
+
## How It Works
|
|
163
|
+
|
|
164
|
+
- **`Agent`** - Configures and runs the agent loop until a finish tool is called or max turns reached
|
|
165
|
+
- **`session()`** - Context manager that sets up tools, manages files, and handles cleanup
|
|
166
|
+
- **`Tool`** - Define tools with Pydantic parameters
|
|
167
|
+
- **`ToolProvider`** - Manage tools that require lifecycle (connections, temp directories, etc.)
|
|
168
|
+
- **`DEFAULT_TOOLS`** - Standard tools included by default: code execution and web tools
|
|
169
|
+
|
|
170
|
+
## Using Other LLM Providers
|
|
171
|
+
|
|
172
|
+
For non-OpenAI providers, change the base URL of the `ChatCompletionsClient`, use the `LiteLLMClient` (requires installation of optional `stirrup[litellm]` dependencies), or create your own client.
|
|
173
|
+
|
|
174
|
+
### OpenAI-Compatible APIs
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
# Create client using Deepseek's OpenAI-compatible endpoint
|
|
178
|
+
client = ChatCompletionsClient(
|
|
179
|
+
base_url="https://api.deepseek.com",
|
|
180
|
+
model="deepseek-chat", # or "deepseek-reasoner" for R1
|
|
181
|
+
api_key=os.environ["DEEPSEEK_API_KEY"],
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
agent = Agent(client=client, name="deepseek_agent")
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### LiteLLM (Anthropic, Google, etc.)
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
# Create LiteLLM client for Anthropic Claude
|
|
191
|
+
# See https://docs.litellm.ai/docs/providers for all supported providers
|
|
192
|
+
client = LiteLLMClient(
|
|
193
|
+
model_slug="anthropic/claude-sonnet-4-5",
|
|
194
|
+
max_tokens=200_000,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
# Pass client to Agent - model info comes from client.model_slug
|
|
198
|
+
agent = Agent(
|
|
199
|
+
client=client,
|
|
200
|
+
name="claude_agent",
|
|
201
|
+
)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
See [LiteLLM Example](https://artificialanalysis.github.io/Stirrup/examples/#litellm-multi-provider-support) or [Deepseek Example](https://artificialanalysis.github.io/Stirrup/examples/#openai-compatible-apis-deepseek-vllm-ollama) for complete examples.
|
|
205
|
+
|
|
206
|
+
## Default Tools
|
|
207
|
+
|
|
208
|
+
When you create an `Agent` without specifying tools, it uses `DEFAULT_TOOLS`:
|
|
209
|
+
|
|
210
|
+
| Tool Provider | Tools Provided | Description |
|
|
211
|
+
| --------------------------- | ------------------------- | ------------------------------------------------------------ |
|
|
212
|
+
| `LocalCodeExecToolProvider` | `code_exec` | Execute shell commands in an isolated temp directory |
|
|
213
|
+
| `WebToolProvider` | `web_fetch`, `web_search` | Fetch web pages and search (search requires `BRAVE_API_KEY`) |
|
|
214
|
+
|
|
215
|
+
## Extending with Pre-Built Tools
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
import asyncio
|
|
219
|
+
|
|
220
|
+
from stirrup import Agent
|
|
221
|
+
from stirrup.clients.chat_completions_client import ChatCompletionsClient
|
|
222
|
+
from stirrup.tools import CALCULATOR_TOOL, DEFAULT_TOOLS
|
|
223
|
+
|
|
224
|
+
# Create client for OpenRouter
|
|
225
|
+
client = ChatCompletionsClient(
|
|
226
|
+
base_url="https://openrouter.ai/api/v1",
|
|
227
|
+
model="anthropic/claude-sonnet-4.5",
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Create agent with default tools + calculator tool
|
|
231
|
+
agent = Agent(
|
|
232
|
+
client=client,
|
|
233
|
+
name="web_calculator_agent",
|
|
234
|
+
tools=[*DEFAULT_TOOLS, CALCULATOR_TOOL],
|
|
235
|
+
)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Defining Custom Tools
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
from pydantic import BaseModel, Field
|
|
242
|
+
|
|
243
|
+
from stirrup import Agent, Tool, ToolResult, ToolUseCountMetadata
|
|
244
|
+
from stirrup.clients.chat_completions_client import ChatCompletionsClient
|
|
245
|
+
from stirrup.tools import DEFAULT_TOOLS
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
class GreetParams(BaseModel):
|
|
249
|
+
"""Parameters for the greet tool."""
|
|
250
|
+
|
|
251
|
+
name: str = Field(description="Name of the person to greet")
|
|
252
|
+
formal: bool = Field(default=False, description="Use formal greeting")
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def greet(params: GreetParams) -> ToolResult[ToolUseCountMetadata]:
|
|
256
|
+
greeting = f"Good day, {params.name}." if params.formal else f"Hey {params.name}!"
|
|
257
|
+
|
|
258
|
+
return ToolResult(
|
|
259
|
+
content=greeting,
|
|
260
|
+
metadata=ToolUseCountMetadata(),
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
GREET_TOOL = Tool(
|
|
265
|
+
name="greet",
|
|
266
|
+
description="Greet someone by name",
|
|
267
|
+
parameters=GreetParams,
|
|
268
|
+
executor=greet,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
# Create client for OpenRouter
|
|
272
|
+
client = ChatCompletionsClient(
|
|
273
|
+
base_url="https://openrouter.ai/api/v1",
|
|
274
|
+
model="anthropic/claude-sonnet-4.5",
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
# Add custom tool to default tools
|
|
278
|
+
agent = Agent(
|
|
279
|
+
client=client,
|
|
280
|
+
name="greeting_agent",
|
|
281
|
+
tools=[*DEFAULT_TOOLS, GREET_TOOL],
|
|
282
|
+
)
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## Next Steps
|
|
286
|
+
|
|
287
|
+
- [Getting Started](https://artificialanalysis.github.io/Stirrup/getting-started/) - Installation and first agent tutorial
|
|
288
|
+
- [Core Concepts](https://artificialanalysis.github.io/Stirrup/concepts/) - Understand Agent, Tools, and Sessions
|
|
289
|
+
- [Examples](https://artificialanalysis.github.io/Stirrup/examples/) - Working examples for common patterns
|
|
290
|
+
- [Creating Tools](https://artificialanalysis.github.io/Stirrup/guides/tools/) - Build your own tools
|
|
291
|
+
|
|
292
|
+
## Documentation
|
|
293
|
+
|
|
294
|
+
Full documentation: [artificialanalysis.github.io/Stirrup](https://artificialanalysis.github.io/Stirrup)
|
|
295
|
+
|
|
296
|
+
Build and serve locally:
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
uv run mkdocs serve
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## Development
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
# Format and lint code
|
|
306
|
+
uv run ruff format
|
|
307
|
+
uv run ruff check
|
|
308
|
+
|
|
309
|
+
# Type check
|
|
310
|
+
uv run ty check
|
|
311
|
+
|
|
312
|
+
# Run tests
|
|
313
|
+
uv run pytest tests
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## License
|
|
317
|
+
|
|
318
|
+
Licensed under the [MIT LICENSE](LICENSE).
|
stirrup-0.1.0/README.md
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<a href="">
|
|
3
|
+
<picture>
|
|
4
|
+
<img alt="Stirrup" src="assets/stirrup-banner.png" width="700">
|
|
5
|
+
</picture>
|
|
6
|
+
</a>
|
|
7
|
+
<br></br>
|
|
8
|
+
<h1>The lightweight foundation for building agents</h1>
|
|
9
|
+
<br>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
<p align="center">
|
|
14
|
+
<a href="https://pypi.python.org/pypi/stirrup"><img src="https://img.shields.io/pypi/v/stirrup" alt="PyPI version" /></a> <!--
|
|
15
|
+
--><a href="https://github.com/ArtificialAnalysis/Stirrup/blob/main/LICENSE"><img src="https://img.shields.io/github/license/ArtificialAnalysis/Stirrup" alt="License" /></a> <!--
|
|
16
|
+
--><a href="https://artificialanalysis.github.io/Stirrup"><img src="https://img.shields.io/badge/MkDocs-4F46E5?logo=materialformkdocs&logoColor=fff" alt="MkDocs" /></a>
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
Stirrup is a lightweight framework, or starting point template, for building agents. It differs from other agent frameworks by:
|
|
21
|
+
|
|
22
|
+
- **Working with the model, not against it:** Stirrup gets out of the way and lets the model choose its own approach to completing tasks (similar to Claude Code). Many frameworks impose rigid workflows that can degrade results.
|
|
23
|
+
- **Best practices and tools built-in:** We analyzed the leading agents (Claude Code, Codex, and others) to understand and incorporate best practices relating to topics like context management and foundational tools (e.g., code execution).
|
|
24
|
+
- **Fully customizable:** Use Stirrup as a package or as a starting template to build your own fully customized agents.
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
- **Essential tools built-in:**
|
|
29
|
+
- Online search / web browsing
|
|
30
|
+
- Code execution (local, Docker container, E2B sandbox)
|
|
31
|
+
- MCP client
|
|
32
|
+
- Document input and output
|
|
33
|
+
- **Flexible tool execution:** A generic `Tool` class allows easy tool definition and extension
|
|
34
|
+
- **Context management:** Automatically summarizes conversation history when approaching context limits
|
|
35
|
+
- **Flexible provider support:** Pre-built support for OpenAI-compatible APIs and LiteLLM, or bring your own client
|
|
36
|
+
- **Multimodal support:** Process images, video, and audio with automatic format conversion
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Core framework
|
|
42
|
+
uv pip install stirrup # or: uv add stirrup
|
|
43
|
+
|
|
44
|
+
# With all optional components
|
|
45
|
+
uv pip install stirrup[all] # or: uv add stirrup[all]
|
|
46
|
+
|
|
47
|
+
# Individual extras
|
|
48
|
+
uv pip install stirrup[litellm] # or: uv add stirrup[litellm]
|
|
49
|
+
uv pip install stirrup[docker] # or: uv add stirrup[docker]
|
|
50
|
+
uv pip install stirrup[e2b] # or: uv add stirrup[e2b]
|
|
51
|
+
uv pip install stirrup[mcp] # or: uv add stirrup[mcp]
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Quick Start
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
import asyncio
|
|
58
|
+
|
|
59
|
+
from stirrup import Agent
|
|
60
|
+
from stirrup.clients.chat_completions_client import ChatCompletionsClient
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
async def main() -> None:
|
|
64
|
+
"""Run an agent that searches the web and creates a chart."""
|
|
65
|
+
|
|
66
|
+
# Create client using ChatCompletionsClient
|
|
67
|
+
# Automatically uses OPENROUTER_API_KEY environment variable
|
|
68
|
+
client = ChatCompletionsClient(
|
|
69
|
+
base_url="https://openrouter.ai/api/v1",
|
|
70
|
+
model="anthropic/claude-sonnet-4.5",
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# As no tools are provided, the agent will use the default tools, which consist of:
|
|
74
|
+
# - Web tools (web search and web fetching, note web search requires BRAVE_API_KEY)
|
|
75
|
+
# - Local code execution tool (to execute shell commands)
|
|
76
|
+
agent = Agent(client=client, name="agent", max_turns=15)
|
|
77
|
+
|
|
78
|
+
# Run with session context - handles tool lifecycle, logging and file outputs
|
|
79
|
+
async with agent.session(output_dir="./output/getting_started_example") as session:
|
|
80
|
+
finish_params, history, metadata = await session.run(
|
|
81
|
+
"""
|
|
82
|
+
What is the population of Australia over the last 3 years? Search the web to find out and create a
|
|
83
|
+
simple chart using matplotlib showing the current population per year."""
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
print("Finish params: ", finish_params)
|
|
87
|
+
print("History: ", history)
|
|
88
|
+
print("Metadata: ", metadata)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
if __name__ == "__main__":
|
|
92
|
+
asyncio.run(main())
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
> **Note:** This example uses OpenRouter. Set `OPENROUTER_API_KEY` in your environment before running. Web search requires a `BRAVE_API_KEY`. The agent will still work without it, but web search will be unavailable.
|
|
96
|
+
|
|
97
|
+
## How It Works
|
|
98
|
+
|
|
99
|
+
- **`Agent`** - Configures and runs the agent loop until a finish tool is called or max turns reached
|
|
100
|
+
- **`session()`** - Context manager that sets up tools, manages files, and handles cleanup
|
|
101
|
+
- **`Tool`** - Define tools with Pydantic parameters
|
|
102
|
+
- **`ToolProvider`** - Manage tools that require lifecycle (connections, temp directories, etc.)
|
|
103
|
+
- **`DEFAULT_TOOLS`** - Standard tools included by default: code execution and web tools
|
|
104
|
+
|
|
105
|
+
## Using Other LLM Providers
|
|
106
|
+
|
|
107
|
+
For non-OpenAI providers, change the base URL of the `ChatCompletionsClient`, use the `LiteLLMClient` (requires installation of optional `stirrup[litellm]` dependencies), or create your own client.
|
|
108
|
+
|
|
109
|
+
### OpenAI-Compatible APIs
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
# Create client using Deepseek's OpenAI-compatible endpoint
|
|
113
|
+
client = ChatCompletionsClient(
|
|
114
|
+
base_url="https://api.deepseek.com",
|
|
115
|
+
model="deepseek-chat", # or "deepseek-reasoner" for R1
|
|
116
|
+
api_key=os.environ["DEEPSEEK_API_KEY"],
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
agent = Agent(client=client, name="deepseek_agent")
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### LiteLLM (Anthropic, Google, etc.)
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
# Create LiteLLM client for Anthropic Claude
|
|
126
|
+
# See https://docs.litellm.ai/docs/providers for all supported providers
|
|
127
|
+
client = LiteLLMClient(
|
|
128
|
+
model_slug="anthropic/claude-sonnet-4-5",
|
|
129
|
+
max_tokens=200_000,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Pass client to Agent - model info comes from client.model_slug
|
|
133
|
+
agent = Agent(
|
|
134
|
+
client=client,
|
|
135
|
+
name="claude_agent",
|
|
136
|
+
)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
See [LiteLLM Example](https://artificialanalysis.github.io/Stirrup/examples/#litellm-multi-provider-support) or [Deepseek Example](https://artificialanalysis.github.io/Stirrup/examples/#openai-compatible-apis-deepseek-vllm-ollama) for complete examples.
|
|
140
|
+
|
|
141
|
+
## Default Tools
|
|
142
|
+
|
|
143
|
+
When you create an `Agent` without specifying tools, it uses `DEFAULT_TOOLS`:
|
|
144
|
+
|
|
145
|
+
| Tool Provider | Tools Provided | Description |
|
|
146
|
+
| --------------------------- | ------------------------- | ------------------------------------------------------------ |
|
|
147
|
+
| `LocalCodeExecToolProvider` | `code_exec` | Execute shell commands in an isolated temp directory |
|
|
148
|
+
| `WebToolProvider` | `web_fetch`, `web_search` | Fetch web pages and search (search requires `BRAVE_API_KEY`) |
|
|
149
|
+
|
|
150
|
+
## Extending with Pre-Built Tools
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
import asyncio
|
|
154
|
+
|
|
155
|
+
from stirrup import Agent
|
|
156
|
+
from stirrup.clients.chat_completions_client import ChatCompletionsClient
|
|
157
|
+
from stirrup.tools import CALCULATOR_TOOL, DEFAULT_TOOLS
|
|
158
|
+
|
|
159
|
+
# Create client for OpenRouter
|
|
160
|
+
client = ChatCompletionsClient(
|
|
161
|
+
base_url="https://openrouter.ai/api/v1",
|
|
162
|
+
model="anthropic/claude-sonnet-4.5",
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Create agent with default tools + calculator tool
|
|
166
|
+
agent = Agent(
|
|
167
|
+
client=client,
|
|
168
|
+
name="web_calculator_agent",
|
|
169
|
+
tools=[*DEFAULT_TOOLS, CALCULATOR_TOOL],
|
|
170
|
+
)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Defining Custom Tools
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
from pydantic import BaseModel, Field
|
|
177
|
+
|
|
178
|
+
from stirrup import Agent, Tool, ToolResult, ToolUseCountMetadata
|
|
179
|
+
from stirrup.clients.chat_completions_client import ChatCompletionsClient
|
|
180
|
+
from stirrup.tools import DEFAULT_TOOLS
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class GreetParams(BaseModel):
|
|
184
|
+
"""Parameters for the greet tool."""
|
|
185
|
+
|
|
186
|
+
name: str = Field(description="Name of the person to greet")
|
|
187
|
+
formal: bool = Field(default=False, description="Use formal greeting")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def greet(params: GreetParams) -> ToolResult[ToolUseCountMetadata]:
|
|
191
|
+
greeting = f"Good day, {params.name}." if params.formal else f"Hey {params.name}!"
|
|
192
|
+
|
|
193
|
+
return ToolResult(
|
|
194
|
+
content=greeting,
|
|
195
|
+
metadata=ToolUseCountMetadata(),
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
GREET_TOOL = Tool(
|
|
200
|
+
name="greet",
|
|
201
|
+
description="Greet someone by name",
|
|
202
|
+
parameters=GreetParams,
|
|
203
|
+
executor=greet,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# Create client for OpenRouter
|
|
207
|
+
client = ChatCompletionsClient(
|
|
208
|
+
base_url="https://openrouter.ai/api/v1",
|
|
209
|
+
model="anthropic/claude-sonnet-4.5",
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
# Add custom tool to default tools
|
|
213
|
+
agent = Agent(
|
|
214
|
+
client=client,
|
|
215
|
+
name="greeting_agent",
|
|
216
|
+
tools=[*DEFAULT_TOOLS, GREET_TOOL],
|
|
217
|
+
)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Next Steps
|
|
221
|
+
|
|
222
|
+
- [Getting Started](https://artificialanalysis.github.io/Stirrup/getting-started/) - Installation and first agent tutorial
|
|
223
|
+
- [Core Concepts](https://artificialanalysis.github.io/Stirrup/concepts/) - Understand Agent, Tools, and Sessions
|
|
224
|
+
- [Examples](https://artificialanalysis.github.io/Stirrup/examples/) - Working examples for common patterns
|
|
225
|
+
- [Creating Tools](https://artificialanalysis.github.io/Stirrup/guides/tools/) - Build your own tools
|
|
226
|
+
|
|
227
|
+
## Documentation
|
|
228
|
+
|
|
229
|
+
Full documentation: [artificialanalysis.github.io/Stirrup](https://artificialanalysis.github.io/Stirrup)
|
|
230
|
+
|
|
231
|
+
Build and serve locally:
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
uv run mkdocs serve
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Development
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
# Format and lint code
|
|
241
|
+
uv run ruff format
|
|
242
|
+
uv run ruff check
|
|
243
|
+
|
|
244
|
+
# Type check
|
|
245
|
+
uv run ty check
|
|
246
|
+
|
|
247
|
+
# Run tests
|
|
248
|
+
uv run pytest tests
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## License
|
|
252
|
+
|
|
253
|
+
Licensed under the [MIT LICENSE](LICENSE).
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "stirrup"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "The lightweight foundation for building agents"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = { file = "LICENSE" }
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "Artificial Analysis, Inc.", email = "info@artificialanalysis.ai" },
|
|
9
|
+
|
|
10
|
+
]
|
|
11
|
+
requires-python = ">=3.12"
|
|
12
|
+
keywords = ["ai", "agent", "llm", "openai", "anthropic", "tools", "framework"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 4 - Beta",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: 3.13",
|
|
21
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
22
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
23
|
+
"Typing :: Typed",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"anyio>=4.0.0",
|
|
27
|
+
"filetype>=1.0.0",
|
|
28
|
+
"httpx>=0.20.0",
|
|
29
|
+
"json-schema-to-pydantic>=0.2.0",
|
|
30
|
+
"moviepy>=2.0.0",
|
|
31
|
+
"openai>=1.0.0",
|
|
32
|
+
"pillow>=10.4.0",
|
|
33
|
+
"pydantic>=2.0.0",
|
|
34
|
+
"tenacity>=5.0.0",
|
|
35
|
+
"trafilatura>=1.9.0",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[project.optional-dependencies]
|
|
39
|
+
litellm = ["litellm>=1.79.3"]
|
|
40
|
+
e2b = ["e2b-code-interpreter>=2.3.0"]
|
|
41
|
+
docker = ["docker>=7.0.0", "python-dotenv>=1.0.0"]
|
|
42
|
+
mcp = ["mcp>=1.9.0"]
|
|
43
|
+
all = ["stirrup[litellm,e2b,docker,mcp]"]
|
|
44
|
+
|
|
45
|
+
[project.urls]
|
|
46
|
+
Homepage = "https://github.com/ArtificialAnalysis/Stirrup"
|
|
47
|
+
Documentation = "https://artificialanalysis.github.io/Stirrup"
|
|
48
|
+
Repository = "https://github.com/ArtificialAnalysis/Stirrup"
|
|
49
|
+
|
|
50
|
+
[build-system]
|
|
51
|
+
requires = ["uv_build>=0.9.3,<0.10.0"]
|
|
52
|
+
build-backend = "uv_build"
|
|
53
|
+
|
|
54
|
+
[dependency-groups]
|
|
55
|
+
dev = [
|
|
56
|
+
"griffe-pydantic>=1.1.8",
|
|
57
|
+
"mkdocs-material>=9.7.0",
|
|
58
|
+
"mkdocstrings>=0.30.1",
|
|
59
|
+
"mkdocstrings-python>=1.19.0",
|
|
60
|
+
"pytest>=9.0.0",
|
|
61
|
+
"rich>=14.2.0",
|
|
62
|
+
"ruff>=0.14.4",
|
|
63
|
+
"ty>=0.0.1a32",
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
[tool.pytest.ini_options]
|
|
67
|
+
addopts = ["--strict-markers"]
|
|
68
|
+
anyio_mode = "auto"
|
|
69
|
+
markers = [
|
|
70
|
+
"docker: marks tests as requiring the docker package (deselect with '-m \"not docker\"')",
|
|
71
|
+
"e2b: marks tests as requiring the e2b package (deselect with '-m \"not e2b\"')",
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
[tool.ruff]
|
|
75
|
+
line-length = 120
|
|
76
|
+
fix = true
|
|
77
|
+
show-fixes = true
|
|
78
|
+
|
|
79
|
+
[tool.ruff.lint]
|
|
80
|
+
select = [
|
|
81
|
+
"F",
|
|
82
|
+
"E",
|
|
83
|
+
"W",
|
|
84
|
+
"ANN",
|
|
85
|
+
"N",
|
|
86
|
+
"B",
|
|
87
|
+
"I",
|
|
88
|
+
"ARG",
|
|
89
|
+
"SLF",
|
|
90
|
+
"UP",
|
|
91
|
+
"PERF",
|
|
92
|
+
"RUF",
|
|
93
|
+
"SIM",
|
|
94
|
+
"S113",
|
|
95
|
+
"ASYNC",
|
|
96
|
+
]
|
|
97
|
+
ignore = [
|
|
98
|
+
"E501", # line too long
|
|
99
|
+
"ASYNC109", # timeout param in async fn - false positive when passing to external APIs
|
|
100
|
+
]
|