camelai-run 0.7.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.
- camelai_run-0.7.0/PKG-INFO +125 -0
- camelai_run-0.7.0/README.md +113 -0
- camelai_run-0.7.0/camelai_run.egg-info/PKG-INFO +125 -0
- camelai_run-0.7.0/camelai_run.egg-info/SOURCES.txt +8 -0
- camelai_run-0.7.0/camelai_run.egg-info/dependency_links.txt +1 -0
- camelai_run-0.7.0/camelai_run.egg-info/requires.txt +4 -0
- camelai_run-0.7.0/camelai_run.egg-info/top_level.txt +1 -0
- camelai_run-0.7.0/camelai_run.py +1834 -0
- camelai_run-0.7.0/pyproject.toml +22 -0
- camelai_run-0.7.0/setup.cfg +4 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: camelai-run
|
|
3
|
+
Version: 0.7.0
|
|
4
|
+
Summary: Python SDK for camelRun
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://agents.camelai.dev
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: httpx==0.28.1
|
|
10
|
+
Provides-Extra: server
|
|
11
|
+
Requires-Dist: cryptography>=42; extra == "server"
|
|
12
|
+
|
|
13
|
+
# camelai-run
|
|
14
|
+
|
|
15
|
+
The Python SDK for camelRun: durable agents you upsert by key
|
|
16
|
+
and run, with tools that are ordinary functions in your code. The runtime runs
|
|
17
|
+
the model loop, keeps each agent's history and files, and runs model-written
|
|
18
|
+
code in a sandbox that can only call your tools.
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
pip install camelai-run
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Python 3.11 or later. Get an API key from the console at
|
|
25
|
+
<https://agents.camelai.dev/console> and export it as `CAMELAI_API_KEY`.
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
import asyncio
|
|
29
|
+
from camelai_run import Agents, tool
|
|
30
|
+
|
|
31
|
+
@tool
|
|
32
|
+
def weather(city: str) -> dict:
|
|
33
|
+
"""Today's weather in a city"""
|
|
34
|
+
return {"city": city, "forecast": "sunny", "highC": 24} # runs here, in your process
|
|
35
|
+
|
|
36
|
+
async def main():
|
|
37
|
+
async with Agents() as agents:
|
|
38
|
+
agent = await agents.upsert("quickstart", model="anthropic/claude-sonnet-5-5",
|
|
39
|
+
instructions="You are a concise assistant.", tools=[weather])
|
|
40
|
+
run = await agent.run("Should I bring an umbrella in Lisbon today?")
|
|
41
|
+
print(run.text)
|
|
42
|
+
|
|
43
|
+
asyncio.run(main())
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
- **Keyed agents.** `upsert(key, ...)` makes the agent for your key, or brings the
|
|
47
|
+
existing one to the configuration given; its history and files last until you
|
|
48
|
+
delete it.
|
|
49
|
+
- **Runs.** `run()` returns a `Run` (`status`, `text`, `inputs`, `error`,
|
|
50
|
+
`tool_errors`) and raises `RunError` on failure (unless `throw_on_error=False`).
|
|
51
|
+
No timeout unless you pass `timeout=`. `agent.stream()` yields text, tool calls
|
|
52
|
+
and results as they happen, then the run.
|
|
53
|
+
- **Tools.** `@tool` takes async or plain functions (plain ones run in a thread),
|
|
54
|
+
`timeout=` in seconds, and `needs_approval=True`. `context.idempotency_key` is
|
|
55
|
+
stable across retries; `context.progress("...")` reports progress.
|
|
56
|
+
- **People in the loop.** `await run.inputs[0].answer(True, from_="alice")` resumes
|
|
57
|
+
a run waiting on approval.
|
|
58
|
+
- **Events.** `on_event` may be a plain or an async function; it runs in order,
|
|
59
|
+
apart from the connection.
|
|
60
|
+
|
|
61
|
+
Documentation: [Quickstart](https://agents.camelai.dev/docs/quickstart.md),
|
|
62
|
+
[Concepts](https://agents.camelai.dev/docs/concepts.md),
|
|
63
|
+
[SDK reference](https://agents.camelai.dev/docs/reference/sdk.md),
|
|
64
|
+
and all of it as Markdown at <https://agents.camelai.dev/llms.txt>.
|
|
65
|
+
|
|
66
|
+
## Serving tools to many users
|
|
67
|
+
|
|
68
|
+
When one server answers tools for many users' agents, serve them over HTTP and let
|
|
69
|
+
the runtime say who each call is for. `serve_tools` is an ASGI app that verifies the
|
|
70
|
+
runtime's signed identity token on every request and hands each call a
|
|
71
|
+
`context.identity`:
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
pip install "camelai-run[server]"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from camelai_run import ToolContext, serve_tools, tool
|
|
79
|
+
|
|
80
|
+
@tool
|
|
81
|
+
async def list_todos(context: ToolContext) -> dict:
|
|
82
|
+
"""The current user's to-dos"""
|
|
83
|
+
who = context.identity # user (the actor, else the agent's subject), subject, tenant, agent, context
|
|
84
|
+
return {"todos": await db.todos(user=who.user, team=who.context["team"])}
|
|
85
|
+
|
|
86
|
+
# tenant: yours (GET /v1/me): tokens for other tenants' agents, which may claim any user, are refused.
|
|
87
|
+
app = serve_tools([list_todos], runtime="https://agents.camelai.dev", tenant="acme") # uvicorn, or mount in FastAPI
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Name the server in a definition with `mcpServers=[{"name": "todos", "url": ..., "auth": {"type": "runtime"}}]`,
|
|
91
|
+
create agents with `subject=` and `context=`, and run them with `user=`.
|
|
92
|
+
The same `@tool` functions get the same identity when attached to an agent.
|
|
93
|
+
`verify_runtime_token(token, runtime=..., tenant=..., audience=...)` checks a token on its own,
|
|
94
|
+
and `TestRuntime()` signs tokens for tests: `await TestRuntime().call_tool(app, url, "list_todos", {}, subject="alice")`.
|
|
95
|
+
|
|
96
|
+
Keep the API key on your backend: it can create and control every
|
|
97
|
+
agent in your tenant. Sign in at https://agents.camelai.dev/console to add provider
|
|
98
|
+
keys, create API tokens and watch agents. The TypeScript SDK is
|
|
99
|
+
[`@camelai/run`](https://www.npmjs.com/package/@camelai/run).
|
|
100
|
+
|
|
101
|
+
## Asking the user
|
|
102
|
+
|
|
103
|
+
A tool marked `@tool(needs_approval=True)` is approved before each call; inside a
|
|
104
|
+
tool, `context.confirm(message)`, `context.ask(message, schema)` and
|
|
105
|
+
`context.require_url(url, message)` ask the user. The run then returns with
|
|
106
|
+
`status == "input_required"`, and answering its inputs resumes it:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
@tool
|
|
110
|
+
async def delete_app(app: str, context: ToolContext) -> dict:
|
|
111
|
+
"""Delete an app"""
|
|
112
|
+
# Ask first: the call ends here, and runs again with the answer.
|
|
113
|
+
if not await context.confirm(f"Delete {app}? Its URL stops working."):
|
|
114
|
+
return {"cancelled": True}
|
|
115
|
+
return await apps.delete(app, idempotency_key=context.idempotency_key)
|
|
116
|
+
|
|
117
|
+
agent = await agents.upsert("ops", tools=[delete_app])
|
|
118
|
+
run = await agent.run("Delete the demo app", user="alice")
|
|
119
|
+
while run.status == "input_required":
|
|
120
|
+
run = await run.inputs[0].answer(True, from_="alice")
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Everything in a tool before an ask runs again when the user answers.
|
|
124
|
+
`agent.pending_inputs()` lists what an agent waits on, and
|
|
125
|
+
`agents.runtime.inbox(state="pending")` what all your agents do.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# camelai-run
|
|
2
|
+
|
|
3
|
+
The Python SDK for camelRun: durable agents you upsert by key
|
|
4
|
+
and run, with tools that are ordinary functions in your code. The runtime runs
|
|
5
|
+
the model loop, keeps each agent's history and files, and runs model-written
|
|
6
|
+
code in a sandbox that can only call your tools.
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
pip install camelai-run
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Python 3.11 or later. Get an API key from the console at
|
|
13
|
+
<https://agents.camelai.dev/console> and export it as `CAMELAI_API_KEY`.
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
import asyncio
|
|
17
|
+
from camelai_run import Agents, tool
|
|
18
|
+
|
|
19
|
+
@tool
|
|
20
|
+
def weather(city: str) -> dict:
|
|
21
|
+
"""Today's weather in a city"""
|
|
22
|
+
return {"city": city, "forecast": "sunny", "highC": 24} # runs here, in your process
|
|
23
|
+
|
|
24
|
+
async def main():
|
|
25
|
+
async with Agents() as agents:
|
|
26
|
+
agent = await agents.upsert("quickstart", model="anthropic/claude-sonnet-5-5",
|
|
27
|
+
instructions="You are a concise assistant.", tools=[weather])
|
|
28
|
+
run = await agent.run("Should I bring an umbrella in Lisbon today?")
|
|
29
|
+
print(run.text)
|
|
30
|
+
|
|
31
|
+
asyncio.run(main())
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
- **Keyed agents.** `upsert(key, ...)` makes the agent for your key, or brings the
|
|
35
|
+
existing one to the configuration given; its history and files last until you
|
|
36
|
+
delete it.
|
|
37
|
+
- **Runs.** `run()` returns a `Run` (`status`, `text`, `inputs`, `error`,
|
|
38
|
+
`tool_errors`) and raises `RunError` on failure (unless `throw_on_error=False`).
|
|
39
|
+
No timeout unless you pass `timeout=`. `agent.stream()` yields text, tool calls
|
|
40
|
+
and results as they happen, then the run.
|
|
41
|
+
- **Tools.** `@tool` takes async or plain functions (plain ones run in a thread),
|
|
42
|
+
`timeout=` in seconds, and `needs_approval=True`. `context.idempotency_key` is
|
|
43
|
+
stable across retries; `context.progress("...")` reports progress.
|
|
44
|
+
- **People in the loop.** `await run.inputs[0].answer(True, from_="alice")` resumes
|
|
45
|
+
a run waiting on approval.
|
|
46
|
+
- **Events.** `on_event` may be a plain or an async function; it runs in order,
|
|
47
|
+
apart from the connection.
|
|
48
|
+
|
|
49
|
+
Documentation: [Quickstart](https://agents.camelai.dev/docs/quickstart.md),
|
|
50
|
+
[Concepts](https://agents.camelai.dev/docs/concepts.md),
|
|
51
|
+
[SDK reference](https://agents.camelai.dev/docs/reference/sdk.md),
|
|
52
|
+
and all of it as Markdown at <https://agents.camelai.dev/llms.txt>.
|
|
53
|
+
|
|
54
|
+
## Serving tools to many users
|
|
55
|
+
|
|
56
|
+
When one server answers tools for many users' agents, serve them over HTTP and let
|
|
57
|
+
the runtime say who each call is for. `serve_tools` is an ASGI app that verifies the
|
|
58
|
+
runtime's signed identity token on every request and hands each call a
|
|
59
|
+
`context.identity`:
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
pip install "camelai-run[server]"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from camelai_run import ToolContext, serve_tools, tool
|
|
67
|
+
|
|
68
|
+
@tool
|
|
69
|
+
async def list_todos(context: ToolContext) -> dict:
|
|
70
|
+
"""The current user's to-dos"""
|
|
71
|
+
who = context.identity # user (the actor, else the agent's subject), subject, tenant, agent, context
|
|
72
|
+
return {"todos": await db.todos(user=who.user, team=who.context["team"])}
|
|
73
|
+
|
|
74
|
+
# tenant: yours (GET /v1/me): tokens for other tenants' agents, which may claim any user, are refused.
|
|
75
|
+
app = serve_tools([list_todos], runtime="https://agents.camelai.dev", tenant="acme") # uvicorn, or mount in FastAPI
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Name the server in a definition with `mcpServers=[{"name": "todos", "url": ..., "auth": {"type": "runtime"}}]`,
|
|
79
|
+
create agents with `subject=` and `context=`, and run them with `user=`.
|
|
80
|
+
The same `@tool` functions get the same identity when attached to an agent.
|
|
81
|
+
`verify_runtime_token(token, runtime=..., tenant=..., audience=...)` checks a token on its own,
|
|
82
|
+
and `TestRuntime()` signs tokens for tests: `await TestRuntime().call_tool(app, url, "list_todos", {}, subject="alice")`.
|
|
83
|
+
|
|
84
|
+
Keep the API key on your backend: it can create and control every
|
|
85
|
+
agent in your tenant. Sign in at https://agents.camelai.dev/console to add provider
|
|
86
|
+
keys, create API tokens and watch agents. The TypeScript SDK is
|
|
87
|
+
[`@camelai/run`](https://www.npmjs.com/package/@camelai/run).
|
|
88
|
+
|
|
89
|
+
## Asking the user
|
|
90
|
+
|
|
91
|
+
A tool marked `@tool(needs_approval=True)` is approved before each call; inside a
|
|
92
|
+
tool, `context.confirm(message)`, `context.ask(message, schema)` and
|
|
93
|
+
`context.require_url(url, message)` ask the user. The run then returns with
|
|
94
|
+
`status == "input_required"`, and answering its inputs resumes it:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
@tool
|
|
98
|
+
async def delete_app(app: str, context: ToolContext) -> dict:
|
|
99
|
+
"""Delete an app"""
|
|
100
|
+
# Ask first: the call ends here, and runs again with the answer.
|
|
101
|
+
if not await context.confirm(f"Delete {app}? Its URL stops working."):
|
|
102
|
+
return {"cancelled": True}
|
|
103
|
+
return await apps.delete(app, idempotency_key=context.idempotency_key)
|
|
104
|
+
|
|
105
|
+
agent = await agents.upsert("ops", tools=[delete_app])
|
|
106
|
+
run = await agent.run("Delete the demo app", user="alice")
|
|
107
|
+
while run.status == "input_required":
|
|
108
|
+
run = await run.inputs[0].answer(True, from_="alice")
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Everything in a tool before an ask runs again when the user answers.
|
|
112
|
+
`agent.pending_inputs()` lists what an agent waits on, and
|
|
113
|
+
`agents.runtime.inbox(state="pending")` what all your agents do.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: camelai-run
|
|
3
|
+
Version: 0.7.0
|
|
4
|
+
Summary: Python SDK for camelRun
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://agents.camelai.dev
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: httpx==0.28.1
|
|
10
|
+
Provides-Extra: server
|
|
11
|
+
Requires-Dist: cryptography>=42; extra == "server"
|
|
12
|
+
|
|
13
|
+
# camelai-run
|
|
14
|
+
|
|
15
|
+
The Python SDK for camelRun: durable agents you upsert by key
|
|
16
|
+
and run, with tools that are ordinary functions in your code. The runtime runs
|
|
17
|
+
the model loop, keeps each agent's history and files, and runs model-written
|
|
18
|
+
code in a sandbox that can only call your tools.
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
pip install camelai-run
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Python 3.11 or later. Get an API key from the console at
|
|
25
|
+
<https://agents.camelai.dev/console> and export it as `CAMELAI_API_KEY`.
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
import asyncio
|
|
29
|
+
from camelai_run import Agents, tool
|
|
30
|
+
|
|
31
|
+
@tool
|
|
32
|
+
def weather(city: str) -> dict:
|
|
33
|
+
"""Today's weather in a city"""
|
|
34
|
+
return {"city": city, "forecast": "sunny", "highC": 24} # runs here, in your process
|
|
35
|
+
|
|
36
|
+
async def main():
|
|
37
|
+
async with Agents() as agents:
|
|
38
|
+
agent = await agents.upsert("quickstart", model="anthropic/claude-sonnet-5-5",
|
|
39
|
+
instructions="You are a concise assistant.", tools=[weather])
|
|
40
|
+
run = await agent.run("Should I bring an umbrella in Lisbon today?")
|
|
41
|
+
print(run.text)
|
|
42
|
+
|
|
43
|
+
asyncio.run(main())
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
- **Keyed agents.** `upsert(key, ...)` makes the agent for your key, or brings the
|
|
47
|
+
existing one to the configuration given; its history and files last until you
|
|
48
|
+
delete it.
|
|
49
|
+
- **Runs.** `run()` returns a `Run` (`status`, `text`, `inputs`, `error`,
|
|
50
|
+
`tool_errors`) and raises `RunError` on failure (unless `throw_on_error=False`).
|
|
51
|
+
No timeout unless you pass `timeout=`. `agent.stream()` yields text, tool calls
|
|
52
|
+
and results as they happen, then the run.
|
|
53
|
+
- **Tools.** `@tool` takes async or plain functions (plain ones run in a thread),
|
|
54
|
+
`timeout=` in seconds, and `needs_approval=True`. `context.idempotency_key` is
|
|
55
|
+
stable across retries; `context.progress("...")` reports progress.
|
|
56
|
+
- **People in the loop.** `await run.inputs[0].answer(True, from_="alice")` resumes
|
|
57
|
+
a run waiting on approval.
|
|
58
|
+
- **Events.** `on_event` may be a plain or an async function; it runs in order,
|
|
59
|
+
apart from the connection.
|
|
60
|
+
|
|
61
|
+
Documentation: [Quickstart](https://agents.camelai.dev/docs/quickstart.md),
|
|
62
|
+
[Concepts](https://agents.camelai.dev/docs/concepts.md),
|
|
63
|
+
[SDK reference](https://agents.camelai.dev/docs/reference/sdk.md),
|
|
64
|
+
and all of it as Markdown at <https://agents.camelai.dev/llms.txt>.
|
|
65
|
+
|
|
66
|
+
## Serving tools to many users
|
|
67
|
+
|
|
68
|
+
When one server answers tools for many users' agents, serve them over HTTP and let
|
|
69
|
+
the runtime say who each call is for. `serve_tools` is an ASGI app that verifies the
|
|
70
|
+
runtime's signed identity token on every request and hands each call a
|
|
71
|
+
`context.identity`:
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
pip install "camelai-run[server]"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from camelai_run import ToolContext, serve_tools, tool
|
|
79
|
+
|
|
80
|
+
@tool
|
|
81
|
+
async def list_todos(context: ToolContext) -> dict:
|
|
82
|
+
"""The current user's to-dos"""
|
|
83
|
+
who = context.identity # user (the actor, else the agent's subject), subject, tenant, agent, context
|
|
84
|
+
return {"todos": await db.todos(user=who.user, team=who.context["team"])}
|
|
85
|
+
|
|
86
|
+
# tenant: yours (GET /v1/me): tokens for other tenants' agents, which may claim any user, are refused.
|
|
87
|
+
app = serve_tools([list_todos], runtime="https://agents.camelai.dev", tenant="acme") # uvicorn, or mount in FastAPI
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Name the server in a definition with `mcpServers=[{"name": "todos", "url": ..., "auth": {"type": "runtime"}}]`,
|
|
91
|
+
create agents with `subject=` and `context=`, and run them with `user=`.
|
|
92
|
+
The same `@tool` functions get the same identity when attached to an agent.
|
|
93
|
+
`verify_runtime_token(token, runtime=..., tenant=..., audience=...)` checks a token on its own,
|
|
94
|
+
and `TestRuntime()` signs tokens for tests: `await TestRuntime().call_tool(app, url, "list_todos", {}, subject="alice")`.
|
|
95
|
+
|
|
96
|
+
Keep the API key on your backend: it can create and control every
|
|
97
|
+
agent in your tenant. Sign in at https://agents.camelai.dev/console to add provider
|
|
98
|
+
keys, create API tokens and watch agents. The TypeScript SDK is
|
|
99
|
+
[`@camelai/run`](https://www.npmjs.com/package/@camelai/run).
|
|
100
|
+
|
|
101
|
+
## Asking the user
|
|
102
|
+
|
|
103
|
+
A tool marked `@tool(needs_approval=True)` is approved before each call; inside a
|
|
104
|
+
tool, `context.confirm(message)`, `context.ask(message, schema)` and
|
|
105
|
+
`context.require_url(url, message)` ask the user. The run then returns with
|
|
106
|
+
`status == "input_required"`, and answering its inputs resumes it:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
@tool
|
|
110
|
+
async def delete_app(app: str, context: ToolContext) -> dict:
|
|
111
|
+
"""Delete an app"""
|
|
112
|
+
# Ask first: the call ends here, and runs again with the answer.
|
|
113
|
+
if not await context.confirm(f"Delete {app}? Its URL stops working."):
|
|
114
|
+
return {"cancelled": True}
|
|
115
|
+
return await apps.delete(app, idempotency_key=context.idempotency_key)
|
|
116
|
+
|
|
117
|
+
agent = await agents.upsert("ops", tools=[delete_app])
|
|
118
|
+
run = await agent.run("Delete the demo app", user="alice")
|
|
119
|
+
while run.status == "input_required":
|
|
120
|
+
run = await run.inputs[0].answer(True, from_="alice")
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Everything in a tool before an ask runs again when the user answers.
|
|
124
|
+
`agent.pending_inputs()` lists what an agent waits on, and
|
|
125
|
+
`agents.runtime.inbox(state="pending")` what all your agents do.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
camelai_run
|