alpineagents 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.
- alpineagents-0.1.0/.github/workflows/ci.yml +29 -0
- alpineagents-0.1.0/.github/workflows/docs.yml +40 -0
- alpineagents-0.1.0/.gitignore +10 -0
- alpineagents-0.1.0/LICENSE +21 -0
- alpineagents-0.1.0/PKG-INFO +200 -0
- alpineagents-0.1.0/README.md +161 -0
- alpineagents-0.1.0/docs/api/agent.md +7 -0
- alpineagents-0.1.0/docs/api/errors.md +19 -0
- alpineagents-0.1.0/docs/api/io.md +9 -0
- alpineagents-0.1.0/docs/api/loops.md +17 -0
- alpineagents-0.1.0/docs/api/models.md +9 -0
- alpineagents-0.1.0/docs/api/state.md +7 -0
- alpineagents-0.1.0/docs/api/testing.md +9 -0
- alpineagents-0.1.0/docs/api/tools.md +11 -0
- alpineagents-0.1.0/docs/api/types.md +37 -0
- alpineagents-0.1.0/docs/concepts/agent.md +99 -0
- alpineagents-0.1.0/docs/concepts/errors.md +79 -0
- alpineagents-0.1.0/docs/concepts/glossary.md +26 -0
- alpineagents-0.1.0/docs/concepts/loops.md +101 -0
- alpineagents-0.1.0/docs/concepts/overview.md +78 -0
- alpineagents-0.1.0/docs/concepts/state.md +126 -0
- alpineagents-0.1.0/docs/concepts/tools.md +104 -0
- alpineagents-0.1.0/docs/guides/approval.md +41 -0
- alpineagents-0.1.0/docs/guides/async.md +46 -0
- alpineagents-0.1.0/docs/guides/chat.md +23 -0
- alpineagents-0.1.0/docs/guides/context-size.md +45 -0
- alpineagents-0.1.0/docs/guides/mcp.md +51 -0
- alpineagents-0.1.0/docs/guides/models.md +64 -0
- alpineagents-0.1.0/docs/guides/plan-first.md +29 -0
- alpineagents-0.1.0/docs/guides/progress.md +99 -0
- alpineagents-0.1.0/docs/guides/stop-conditions.md +56 -0
- alpineagents-0.1.0/docs/guides/structured-output.md +36 -0
- alpineagents-0.1.0/docs/guides/testing.md +64 -0
- alpineagents-0.1.0/docs/guides/tool-state.md +30 -0
- alpineagents-0.1.0/docs/guides/verify.md +25 -0
- alpineagents-0.1.0/docs/index.md +1 -0
- alpineagents-0.1.0/docs_src/approval.py +29 -0
- alpineagents-0.1.0/docs_src/approval_always.py +15 -0
- alpineagents-0.1.0/docs_src/async_agent.py +31 -0
- alpineagents-0.1.0/docs_src/chat.py +8 -0
- alpineagents-0.1.0/docs_src/coding_agent.py +39 -0
- alpineagents-0.1.0/docs_src/context_size.py +12 -0
- alpineagents-0.1.0/docs_src/custom_model.py +23 -0
- alpineagents-0.1.0/docs_src/human.py +14 -0
- alpineagents-0.1.0/docs_src/mcp_servers.py +16 -0
- alpineagents-0.1.0/docs_src/model_settings.py +19 -0
- alpineagents-0.1.0/docs_src/no_api_key.py +7 -0
- alpineagents-0.1.0/docs_src/plan_first.py +12 -0
- alpineagents-0.1.0/docs_src/quickstart.py +14 -0
- alpineagents-0.1.0/docs_src/reporter.py +13 -0
- alpineagents-0.1.0/docs_src/stop_conditions.py +12 -0
- alpineagents-0.1.0/docs_src/structured_output.py +18 -0
- alpineagents-0.1.0/docs_src/submit_tool.py +15 -0
- alpineagents-0.1.0/docs_src/test_example.py +29 -0
- alpineagents-0.1.0/docs_src/tool_object.py +23 -0
- alpineagents-0.1.0/docs_src/tool_state.py +14 -0
- alpineagents-0.1.0/docs_src/verify.py +20 -0
- alpineagents-0.1.0/mkdocs.yml +93 -0
- alpineagents-0.1.0/pyproject.toml +65 -0
- alpineagents-0.1.0/src/alpineagents/__init__.py +81 -0
- alpineagents-0.1.0/src/alpineagents/_async.py +139 -0
- alpineagents-0.1.0/src/alpineagents/_runner.py +552 -0
- alpineagents-0.1.0/src/alpineagents/_structured.py +175 -0
- alpineagents-0.1.0/src/alpineagents/_tokens.py +61 -0
- alpineagents-0.1.0/src/alpineagents/agent.py +1196 -0
- alpineagents-0.1.0/src/alpineagents/blocks.py +51 -0
- alpineagents-0.1.0/src/alpineagents/errors.py +79 -0
- alpineagents-0.1.0/src/alpineagents/human.py +141 -0
- alpineagents-0.1.0/src/alpineagents/loop.py +337 -0
- alpineagents-0.1.0/src/alpineagents/mcp_tools.py +507 -0
- alpineagents-0.1.0/src/alpineagents/models/__init__.py +8 -0
- alpineagents-0.1.0/src/alpineagents/models/anthropic.py +520 -0
- alpineagents-0.1.0/src/alpineagents/models/base.py +215 -0
- alpineagents-0.1.0/src/alpineagents/models/openai_compatible.py +493 -0
- alpineagents-0.1.0/src/alpineagents/models/resolve.py +95 -0
- alpineagents-0.1.0/src/alpineagents/reporter.py +89 -0
- alpineagents-0.1.0/src/alpineagents/state.py +1097 -0
- alpineagents-0.1.0/src/alpineagents/terminal.py +366 -0
- alpineagents-0.1.0/src/alpineagents/testing.py +236 -0
- alpineagents-0.1.0/src/alpineagents/tool.py +626 -0
- alpineagents-0.1.0/src/alpineagents/types.py +410 -0
- alpineagents-0.1.0/tests/mcp_demo_server.py +46 -0
- alpineagents-0.1.0/tests/test_agent.py +891 -0
- alpineagents-0.1.0/tests/test_anthropic.py +442 -0
- alpineagents-0.1.0/tests/test_async.py +815 -0
- alpineagents-0.1.0/tests/test_docs.py +331 -0
- alpineagents-0.1.0/tests/test_e2e.py +808 -0
- alpineagents-0.1.0/tests/test_integration.py +354 -0
- alpineagents-0.1.0/tests/test_loop.py +632 -0
- alpineagents-0.1.0/tests/test_mcp.py +360 -0
- alpineagents-0.1.0/tests/test_model.py +794 -0
- alpineagents-0.1.0/tests/test_model_event.py +87 -0
- alpineagents-0.1.0/tests/test_resolve.py +79 -0
- alpineagents-0.1.0/tests/test_review_agent.py +405 -0
- alpineagents-0.1.0/tests/test_review_decorators.py +374 -0
- alpineagents-0.1.0/tests/test_review_io.py +263 -0
- alpineagents-0.1.0/tests/test_review_models.py +545 -0
- alpineagents-0.1.0/tests/test_review_state.py +348 -0
- alpineagents-0.1.0/tests/test_state.py +887 -0
- alpineagents-0.1.0/tests/test_terminal.py +287 -0
- alpineagents-0.1.0/tests/test_testing.py +163 -0
- alpineagents-0.1.0/tests/test_tool.py +595 -0
- alpineagents-0.1.0/uv.lock +1597 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
fail-fast: false
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.11", "3.12", "3.13", "3.14"]
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v5
|
|
17
|
+
- uses: astral-sh/setup-uv@v6
|
|
18
|
+
with:
|
|
19
|
+
python-version: ${{ matrix.python-version }}
|
|
20
|
+
activate-environment: true
|
|
21
|
+
- run: uv pip install -e ".[dev]"
|
|
22
|
+
- run: python -m pytest -q
|
|
23
|
+
|
|
24
|
+
lint:
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v5
|
|
28
|
+
- uses: astral-sh/setup-uv@v6
|
|
29
|
+
- run: uvx ruff@0.13.2 check .
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: Docs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
pages: write
|
|
11
|
+
id-token: write
|
|
12
|
+
|
|
13
|
+
concurrency:
|
|
14
|
+
group: pages
|
|
15
|
+
cancel-in-progress: true
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
build:
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v5
|
|
22
|
+
- uses: astral-sh/setup-uv@v6
|
|
23
|
+
with:
|
|
24
|
+
python-version: "3.13"
|
|
25
|
+
activate-environment: true
|
|
26
|
+
- run: uv pip install -e ".[docs]"
|
|
27
|
+
- run: mkdocs build --strict
|
|
28
|
+
- uses: actions/upload-pages-artifact@v4
|
|
29
|
+
with:
|
|
30
|
+
path: site
|
|
31
|
+
|
|
32
|
+
deploy:
|
|
33
|
+
needs: build
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
environment:
|
|
36
|
+
name: github-pages
|
|
37
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
38
|
+
steps:
|
|
39
|
+
- id: deployment
|
|
40
|
+
uses: actions/deploy-pages@v4
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 JAEGYUN JUNG
|
|
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.
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: alpineagents
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: An agent framework that keeps configuration (Agent) and history (State) apart, with a plain Python function as the loop
|
|
5
|
+
Project-URL: Homepage, https://github.com/TGoddessana/alpineagents
|
|
6
|
+
Project-URL: Source, https://github.com/TGoddessana/alpineagents
|
|
7
|
+
Project-URL: Issues, https://github.com/TGoddessana/alpineagents/issues
|
|
8
|
+
Author: JAEGYUN JUNG
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agent-framework,agents,ai-agents,anthropic,claude,llm,mcp,openai,tool-calling
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Requires-Dist: anthropic
|
|
25
|
+
Requires-Dist: openai
|
|
26
|
+
Requires-Dist: pydantic>=2
|
|
27
|
+
Requires-Dist: typing-extensions>=4.6
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: mcp>=2.2; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest-asyncio; extra == 'dev'
|
|
32
|
+
Provides-Extra: docs
|
|
33
|
+
Requires-Dist: mkdocs-material; extra == 'docs'
|
|
34
|
+
Requires-Dist: mkdocs<2; extra == 'docs'
|
|
35
|
+
Requires-Dist: mkdocstrings[python]; extra == 'docs'
|
|
36
|
+
Provides-Extra: mcp
|
|
37
|
+
Requires-Dist: mcp>=2.2; extra == 'mcp'
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
|
|
40
|
+
# alpineagents
|
|
41
|
+
|
|
42
|
+
[](https://github.com/TGoddessana/alpineagents/actions/workflows/ci.yml)
|
|
43
|
+
[](https://github.com/TGoddessana/alpineagents/blob/main/LICENSE)
|
|
44
|
+
[](https://tgoddessana.github.io/alpineagents/)
|
|
45
|
+
|
|
46
|
+
A Python agent framework where the agent loop is a function you write.
|
|
47
|
+
|
|
48
|
+
- `Agent` holds the settings: the model, the system prompt and the tools.
|
|
49
|
+
- `State` holds one task: the messages so far and the answer.
|
|
50
|
+
- A loop takes both and repeats one turn until a stop condition is true.
|
|
51
|
+
|
|
52
|
+
## Why
|
|
53
|
+
|
|
54
|
+
In most frameworks the loop lives inside the library. LangGraph has you declare it as a graph. The OpenAI Agents SDK
|
|
55
|
+
and PydanticAI run it for you.
|
|
56
|
+
|
|
57
|
+
In alpineagents the loop is a few lines of Python in your own code:
|
|
58
|
+
|
|
59
|
+
- `until=` decides when it stops.
|
|
60
|
+
- `limit=` sets the maximum number of turns.
|
|
61
|
+
- Compaction runs only where you call it.
|
|
62
|
+
|
|
63
|
+
The default loop, `alpineagents.default_loop`, is written the same way. Read it, or copy it as a starting point.
|
|
64
|
+
Misuse raises an error that says how to fix it.
|
|
65
|
+
|
|
66
|
+
## Install
|
|
67
|
+
|
|
68
|
+
Requires Python 3.11 or later.
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pip install alpineagents # or: uv add alpineagents
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Anthropic models read `ANTHROPIC_API_KEY`. OpenAI-compatible servers read `OPENAI_API_KEY`.
|
|
75
|
+
|
|
76
|
+
## Quick start
|
|
77
|
+
|
|
78
|
+
Define a tool, create an Agent, run a task:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from pathlib import Path
|
|
82
|
+
|
|
83
|
+
from alpineagents import Agent, tool
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@tool
|
|
87
|
+
def read_file(path: str) -> str:
|
|
88
|
+
"""Read a text file"""
|
|
89
|
+
file = Path(path)
|
|
90
|
+
return file.read_text() if file.exists() else f"No such file: {path}"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
agent = Agent(model="claude-sonnet-5", tools=[read_file])
|
|
94
|
+
print(agent.run("Summarize README.md in three lines"))
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
- `@tool` turns the function into a tool. The type hints and the docstring tell the model how to call it.
|
|
98
|
+
- `agent.run` repeats turns until the model answers, then returns the answer.
|
|
99
|
+
- Progress is printed to the terminal while it runs.
|
|
100
|
+
|
|
101
|
+
### Without an API key
|
|
102
|
+
|
|
103
|
+
`FakeModel` returns prepared replies in order. It needs no API key and no network. Replace the `print` line above with:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from alpineagents.testing import FakeModel, tool_call
|
|
107
|
+
|
|
108
|
+
fake = FakeModel([
|
|
109
|
+
tool_call("read_file", path="README.md"),
|
|
110
|
+
"README.md describes a Python agent framework.",
|
|
111
|
+
])
|
|
112
|
+
print(agent.copy(model=fake).run("Summarize README.md"))
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
The first reply asks for `read_file`. The Agent runs the tool, and the second reply is the answer.
|
|
116
|
+
|
|
117
|
+
## Write your own loop
|
|
118
|
+
|
|
119
|
+
The Agent above uses `default_loop`. To change what happens in a turn, write the loop yourself:
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
from pathlib import Path
|
|
123
|
+
|
|
124
|
+
from alpineagents import Agent, State, compact_if_full, loop, tool
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@tool
|
|
128
|
+
def list_files(folder: str = ".") -> list[str]:
|
|
129
|
+
"""List the files in a folder"""
|
|
130
|
+
return sorted(p.name for p in Path(folder).iterdir())
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@tool
|
|
134
|
+
def read_file(path: str) -> str:
|
|
135
|
+
"""Read a file"""
|
|
136
|
+
file = Path(path)
|
|
137
|
+
return file.read_text() if file.exists() else f"No such file: {path}"
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@tool
|
|
141
|
+
def write_file(path: str, content: str) -> None:
|
|
142
|
+
"""Create a file, or replace its content"""
|
|
143
|
+
Path(path).write_text(content)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@loop(until=State.is_answered, limit=30)
|
|
147
|
+
def coding(agent: Agent, state: State):
|
|
148
|
+
compact_if_full(agent, state)
|
|
149
|
+
agent.think(state)
|
|
150
|
+
if state.wants_tools():
|
|
151
|
+
agent.use_tools(state)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
agent = Agent(
|
|
155
|
+
model="claude-sonnet-5",
|
|
156
|
+
system="You are a coding assistant. Read a file before you change it.",
|
|
157
|
+
tools=[list_files, read_file, write_file],
|
|
158
|
+
loop=coding,
|
|
159
|
+
)
|
|
160
|
+
print(agent.run("Add a test for the add() function in calc.py"))
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
- `coding` is one turn: summarize the context if it is more than 60% full, ask the model, run the tools it asked for.
|
|
164
|
+
- `@loop` repeats the turn. It stops when `State.is_answered` is true, or after 30 turns.
|
|
165
|
+
- To change the agent, add, remove or reorder lines in `coding`.
|
|
166
|
+
|
|
167
|
+
## Documentation
|
|
168
|
+
|
|
169
|
+
[tgoddessana.github.io/alpineagents](https://tgoddessana.github.io/alpineagents/). Read it in this order:
|
|
170
|
+
|
|
171
|
+
1. [Concepts](https://tgoddessana.github.io/alpineagents/concepts/overview/): how a run works, then Agent, State,
|
|
172
|
+
loops and tools. About 15 minutes.
|
|
173
|
+
2. [Guides](https://tgoddessana.github.io/alpineagents/guides/stop-conditions/): one task per page, for example
|
|
174
|
+
[asking before a tool runs](https://tgoddessana.github.io/alpineagents/guides/approval/) or
|
|
175
|
+
[testing an agent](https://tgoddessana.github.io/alpineagents/guides/testing/).
|
|
176
|
+
3. [API reference](https://tgoddessana.github.io/alpineagents/api/agent/): every class and method.
|
|
177
|
+
|
|
178
|
+
## Roadmap
|
|
179
|
+
|
|
180
|
+
- OpenAI adapter, LiteLLM adapter
|
|
181
|
+
- Subagents (`tools=[researcher]`). Passing an Agent in `tools=` raises `NotImplementedError` for now
|
|
182
|
+
- Skills (`skills=`). Passing `skills=` raises `NotImplementedError` for now
|
|
183
|
+
- `agent.run_tool`, `agent.load_skill`
|
|
184
|
+
- `state.save()` / `State.load()`
|
|
185
|
+
- Multimodal tool results (`Image`, `File`)
|
|
186
|
+
- `alpineagents[prices]` (cost calculation with genai-prices; for now `usage.cost` is filled only when you pass
|
|
187
|
+
`price=Price(...)`)
|
|
188
|
+
- `alpineagents add` CLI (copies the default loop and block sources into your project)
|
|
189
|
+
- Per-adapter server-side compaction optimizations
|
|
190
|
+
|
|
191
|
+
## Development
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
uv venv && uv pip install -e ".[dev]"
|
|
195
|
+
.venv/bin/python -m pytest -q
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## License
|
|
199
|
+
|
|
200
|
+
MIT. See [LICENSE](https://github.com/TGoddessana/alpineagents/blob/main/LICENSE).
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# alpineagents
|
|
2
|
+
|
|
3
|
+
[](https://github.com/TGoddessana/alpineagents/actions/workflows/ci.yml)
|
|
4
|
+
[](https://github.com/TGoddessana/alpineagents/blob/main/LICENSE)
|
|
5
|
+
[](https://tgoddessana.github.io/alpineagents/)
|
|
6
|
+
|
|
7
|
+
A Python agent framework where the agent loop is a function you write.
|
|
8
|
+
|
|
9
|
+
- `Agent` holds the settings: the model, the system prompt and the tools.
|
|
10
|
+
- `State` holds one task: the messages so far and the answer.
|
|
11
|
+
- A loop takes both and repeats one turn until a stop condition is true.
|
|
12
|
+
|
|
13
|
+
## Why
|
|
14
|
+
|
|
15
|
+
In most frameworks the loop lives inside the library. LangGraph has you declare it as a graph. The OpenAI Agents SDK
|
|
16
|
+
and PydanticAI run it for you.
|
|
17
|
+
|
|
18
|
+
In alpineagents the loop is a few lines of Python in your own code:
|
|
19
|
+
|
|
20
|
+
- `until=` decides when it stops.
|
|
21
|
+
- `limit=` sets the maximum number of turns.
|
|
22
|
+
- Compaction runs only where you call it.
|
|
23
|
+
|
|
24
|
+
The default loop, `alpineagents.default_loop`, is written the same way. Read it, or copy it as a starting point.
|
|
25
|
+
Misuse raises an error that says how to fix it.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
Requires Python 3.11 or later.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install alpineagents # or: uv add alpineagents
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Anthropic models read `ANTHROPIC_API_KEY`. OpenAI-compatible servers read `OPENAI_API_KEY`.
|
|
36
|
+
|
|
37
|
+
## Quick start
|
|
38
|
+
|
|
39
|
+
Define a tool, create an Agent, run a task:
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from pathlib import Path
|
|
43
|
+
|
|
44
|
+
from alpineagents import Agent, tool
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@tool
|
|
48
|
+
def read_file(path: str) -> str:
|
|
49
|
+
"""Read a text file"""
|
|
50
|
+
file = Path(path)
|
|
51
|
+
return file.read_text() if file.exists() else f"No such file: {path}"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
agent = Agent(model="claude-sonnet-5", tools=[read_file])
|
|
55
|
+
print(agent.run("Summarize README.md in three lines"))
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
- `@tool` turns the function into a tool. The type hints and the docstring tell the model how to call it.
|
|
59
|
+
- `agent.run` repeats turns until the model answers, then returns the answer.
|
|
60
|
+
- Progress is printed to the terminal while it runs.
|
|
61
|
+
|
|
62
|
+
### Without an API key
|
|
63
|
+
|
|
64
|
+
`FakeModel` returns prepared replies in order. It needs no API key and no network. Replace the `print` line above with:
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from alpineagents.testing import FakeModel, tool_call
|
|
68
|
+
|
|
69
|
+
fake = FakeModel([
|
|
70
|
+
tool_call("read_file", path="README.md"),
|
|
71
|
+
"README.md describes a Python agent framework.",
|
|
72
|
+
])
|
|
73
|
+
print(agent.copy(model=fake).run("Summarize README.md"))
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The first reply asks for `read_file`. The Agent runs the tool, and the second reply is the answer.
|
|
77
|
+
|
|
78
|
+
## Write your own loop
|
|
79
|
+
|
|
80
|
+
The Agent above uses `default_loop`. To change what happens in a turn, write the loop yourself:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from pathlib import Path
|
|
84
|
+
|
|
85
|
+
from alpineagents import Agent, State, compact_if_full, loop, tool
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@tool
|
|
89
|
+
def list_files(folder: str = ".") -> list[str]:
|
|
90
|
+
"""List the files in a folder"""
|
|
91
|
+
return sorted(p.name for p in Path(folder).iterdir())
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@tool
|
|
95
|
+
def read_file(path: str) -> str:
|
|
96
|
+
"""Read a file"""
|
|
97
|
+
file = Path(path)
|
|
98
|
+
return file.read_text() if file.exists() else f"No such file: {path}"
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@tool
|
|
102
|
+
def write_file(path: str, content: str) -> None:
|
|
103
|
+
"""Create a file, or replace its content"""
|
|
104
|
+
Path(path).write_text(content)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@loop(until=State.is_answered, limit=30)
|
|
108
|
+
def coding(agent: Agent, state: State):
|
|
109
|
+
compact_if_full(agent, state)
|
|
110
|
+
agent.think(state)
|
|
111
|
+
if state.wants_tools():
|
|
112
|
+
agent.use_tools(state)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
agent = Agent(
|
|
116
|
+
model="claude-sonnet-5",
|
|
117
|
+
system="You are a coding assistant. Read a file before you change it.",
|
|
118
|
+
tools=[list_files, read_file, write_file],
|
|
119
|
+
loop=coding,
|
|
120
|
+
)
|
|
121
|
+
print(agent.run("Add a test for the add() function in calc.py"))
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
- `coding` is one turn: summarize the context if it is more than 60% full, ask the model, run the tools it asked for.
|
|
125
|
+
- `@loop` repeats the turn. It stops when `State.is_answered` is true, or after 30 turns.
|
|
126
|
+
- To change the agent, add, remove or reorder lines in `coding`.
|
|
127
|
+
|
|
128
|
+
## Documentation
|
|
129
|
+
|
|
130
|
+
[tgoddessana.github.io/alpineagents](https://tgoddessana.github.io/alpineagents/). Read it in this order:
|
|
131
|
+
|
|
132
|
+
1. [Concepts](https://tgoddessana.github.io/alpineagents/concepts/overview/): how a run works, then Agent, State,
|
|
133
|
+
loops and tools. About 15 minutes.
|
|
134
|
+
2. [Guides](https://tgoddessana.github.io/alpineagents/guides/stop-conditions/): one task per page, for example
|
|
135
|
+
[asking before a tool runs](https://tgoddessana.github.io/alpineagents/guides/approval/) or
|
|
136
|
+
[testing an agent](https://tgoddessana.github.io/alpineagents/guides/testing/).
|
|
137
|
+
3. [API reference](https://tgoddessana.github.io/alpineagents/api/agent/): every class and method.
|
|
138
|
+
|
|
139
|
+
## Roadmap
|
|
140
|
+
|
|
141
|
+
- OpenAI adapter, LiteLLM adapter
|
|
142
|
+
- Subagents (`tools=[researcher]`). Passing an Agent in `tools=` raises `NotImplementedError` for now
|
|
143
|
+
- Skills (`skills=`). Passing `skills=` raises `NotImplementedError` for now
|
|
144
|
+
- `agent.run_tool`, `agent.load_skill`
|
|
145
|
+
- `state.save()` / `State.load()`
|
|
146
|
+
- Multimodal tool results (`Image`, `File`)
|
|
147
|
+
- `alpineagents[prices]` (cost calculation with genai-prices; for now `usage.cost` is filled only when you pass
|
|
148
|
+
`price=Price(...)`)
|
|
149
|
+
- `alpineagents add` CLI (copies the default loop and block sources into your project)
|
|
150
|
+
- Per-adapter server-side compaction optimizations
|
|
151
|
+
|
|
152
|
+
## Development
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
uv venv && uv pip install -e ".[dev]"
|
|
156
|
+
.venv/bin/python -m pytest -q
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
MIT. See [LICENSE](https://github.com/TGoddessana/alpineagents/blob/main/LICENSE).
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Errors
|
|
2
|
+
|
|
3
|
+
Concepts: [Errors and interruptions](../concepts/errors.md).
|
|
4
|
+
|
|
5
|
+
::: alpineagents.AlpineAgentsError
|
|
6
|
+
|
|
7
|
+
::: alpineagents.ProviderError
|
|
8
|
+
|
|
9
|
+
::: alpineagents.RateLimitError
|
|
10
|
+
|
|
11
|
+
::: alpineagents.ContextTooLongError
|
|
12
|
+
|
|
13
|
+
::: alpineagents.AuthError
|
|
14
|
+
|
|
15
|
+
::: alpineagents.OutputError
|
|
16
|
+
|
|
17
|
+
::: alpineagents.NoHumanError
|
|
18
|
+
|
|
19
|
+
::: alpineagents.MCPConnectionError
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Loops and blocks
|
|
2
|
+
|
|
3
|
+
Concepts: [Loops](../concepts/loops.md).
|
|
4
|
+
|
|
5
|
+
::: alpineagents.loop.loop
|
|
6
|
+
|
|
7
|
+
::: alpineagents.Loop
|
|
8
|
+
|
|
9
|
+
::: alpineagents.default_loop
|
|
10
|
+
|
|
11
|
+
::: alpineagents.adefault_loop
|
|
12
|
+
|
|
13
|
+
::: alpineagents.CompactIfFull
|
|
14
|
+
|
|
15
|
+
::: alpineagents.compact_if_full
|
|
16
|
+
|
|
17
|
+
::: alpineagents.acompact_if_full
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Tools and MCP
|
|
2
|
+
|
|
3
|
+
Concepts: [Tools](../concepts/tools.md). Guide: [MCP servers](../guides/mcp.md).
|
|
4
|
+
|
|
5
|
+
::: alpineagents.tool.tool
|
|
6
|
+
|
|
7
|
+
::: alpineagents.Tool
|
|
8
|
+
options:
|
|
9
|
+
filters: ["!^_", "!^(prepare|invoke|format_result|is_error_result|fn|needs_self|bound_to)$"]
|
|
10
|
+
|
|
11
|
+
::: alpineagents.MCP
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Data types
|
|
2
|
+
|
|
3
|
+
The data objects that the other classes take and return.
|
|
4
|
+
|
|
5
|
+
::: alpineagents.Message
|
|
6
|
+
|
|
7
|
+
::: alpineagents.ToolCall
|
|
8
|
+
|
|
9
|
+
::: alpineagents.ToolSpec
|
|
10
|
+
|
|
11
|
+
::: alpineagents.Request
|
|
12
|
+
|
|
13
|
+
::: alpineagents.Reply
|
|
14
|
+
|
|
15
|
+
::: alpineagents.Usage
|
|
16
|
+
|
|
17
|
+
::: alpineagents.Price
|
|
18
|
+
|
|
19
|
+
::: alpineagents.HistoryEntry
|
|
20
|
+
options:
|
|
21
|
+
filters: ["!^_", "!^(substate)$"]
|
|
22
|
+
|
|
23
|
+
::: alpineagents.ContextChange
|
|
24
|
+
|
|
25
|
+
::: alpineagents.ModelEvent
|
|
26
|
+
|
|
27
|
+
::: alpineagents.ToolOutcome
|
|
28
|
+
|
|
29
|
+
## Content blocks
|
|
30
|
+
|
|
31
|
+
The parts of a `Message`. `ToolCall` above is also one.
|
|
32
|
+
|
|
33
|
+
::: alpineagents.types.TextBlock
|
|
34
|
+
|
|
35
|
+
::: alpineagents.types.ToolResultBlock
|
|
36
|
+
|
|
37
|
+
::: alpineagents.types.RawBlock
|