acp-sdk 0.1.0rc5__py3-none-any.whl → 0.1.0rc7__py3-none-any.whl
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.
- acp_sdk/models/models.py +3 -3
- acp_sdk/models/schemas.py +3 -1
- acp_sdk/server/app.py +17 -4
- acp_sdk/server/bundle.py +1 -1
- acp_sdk/server/server.py +142 -19
- {acp_sdk-0.1.0rc5.dist-info → acp_sdk-0.1.0rc7.dist-info}/METADATA +11 -15
- {acp_sdk-0.1.0rc5.dist-info → acp_sdk-0.1.0rc7.dist-info}/RECORD +8 -8
- {acp_sdk-0.1.0rc5.dist-info → acp_sdk-0.1.0rc7.dist-info}/WHEEL +0 -0
acp_sdk/models/models.py
CHANGED
@@ -58,8 +58,8 @@ class Message(RootModel):
|
|
58
58
|
|
59
59
|
|
60
60
|
AgentName = str
|
61
|
-
SessionId =
|
62
|
-
RunId =
|
61
|
+
SessionId = uuid.UUID
|
62
|
+
RunId = uuid.UUID
|
63
63
|
|
64
64
|
|
65
65
|
class RunMode(str, Enum):
|
@@ -92,7 +92,7 @@ class AwaitResume(BaseModel):
|
|
92
92
|
|
93
93
|
|
94
94
|
class Run(BaseModel):
|
95
|
-
run_id: RunId =
|
95
|
+
run_id: RunId = Field(default_factory=uuid.uuid4)
|
96
96
|
agent_name: AgentName
|
97
97
|
session_id: SessionId | None = None
|
98
98
|
status: RunStatus = RunStatus.CREATED
|
acp_sdk/models/schemas.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
from pydantic import BaseModel, Field
|
1
|
+
from pydantic import BaseModel, ConfigDict, Field
|
2
2
|
|
3
3
|
from acp_sdk.models.models import Agent, AgentName, AwaitResume, Message, Run, RunMode, SessionId
|
4
4
|
|
@@ -26,6 +26,8 @@ class RunResumeRequest(BaseModel):
|
|
26
26
|
await_: AwaitResume = Field(alias="await")
|
27
27
|
mode: RunMode
|
28
28
|
|
29
|
+
model_config = ConfigDict(populate_by_name=True)
|
30
|
+
|
29
31
|
|
30
32
|
class RunResumeResponse(Run):
|
31
33
|
pass
|
acp_sdk/server/app.py
CHANGED
@@ -2,8 +2,10 @@ import asyncio
|
|
2
2
|
from collections.abc import AsyncGenerator
|
3
3
|
from concurrent.futures import ThreadPoolExecutor
|
4
4
|
from contextlib import asynccontextmanager
|
5
|
+
from enum import Enum
|
5
6
|
|
6
7
|
from fastapi import FastAPI, HTTPException, status
|
8
|
+
from fastapi.encoders import jsonable_encoder
|
7
9
|
from fastapi.responses import JSONResponse, StreamingResponse
|
8
10
|
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
|
9
11
|
|
@@ -39,6 +41,10 @@ from acp_sdk.server.errors import (
|
|
39
41
|
from acp_sdk.server.utils import stream_sse
|
40
42
|
|
41
43
|
|
44
|
+
class Headers(str, Enum):
|
45
|
+
RUN_ID = "Run-ID"
|
46
|
+
|
47
|
+
|
42
48
|
def create_app(*agents: Agent) -> FastAPI:
|
43
49
|
executor: ThreadPoolExecutor
|
44
50
|
|
@@ -102,19 +108,26 @@ def create_app(*agents: Agent) -> FastAPI:
|
|
102
108
|
bundle.task = asyncio.create_task(bundle.execute(request.input, executor=executor))
|
103
109
|
runs[bundle.run.run_id] = bundle
|
104
110
|
|
111
|
+
headers = {Headers.RUN_ID: str(bundle.run.run_id)}
|
112
|
+
|
105
113
|
match request.mode:
|
106
114
|
case RunMode.STREAM:
|
107
115
|
return StreamingResponse(
|
108
116
|
stream_sse(bundle),
|
117
|
+
headers=headers,
|
109
118
|
media_type="text/event-stream",
|
110
119
|
)
|
111
120
|
case RunMode.SYNC:
|
112
121
|
await bundle.join()
|
113
|
-
return
|
122
|
+
return JSONResponse(
|
123
|
+
headers=headers,
|
124
|
+
content=jsonable_encoder(bundle.run),
|
125
|
+
)
|
114
126
|
case RunMode.ASYNC:
|
115
127
|
return JSONResponse(
|
116
128
|
status_code=status.HTTP_202_ACCEPTED,
|
117
|
-
|
129
|
+
headers=headers,
|
130
|
+
content=jsonable_encoder(bundle.run),
|
118
131
|
)
|
119
132
|
case _:
|
120
133
|
raise NotImplementedError()
|
@@ -141,7 +154,7 @@ def create_app(*agents: Agent) -> FastAPI:
|
|
141
154
|
case RunMode.ASYNC:
|
142
155
|
return JSONResponse(
|
143
156
|
status_code=status.HTTP_202_ACCEPTED,
|
144
|
-
content=bundle.run
|
157
|
+
content=jsonable_encoder(bundle.run),
|
145
158
|
)
|
146
159
|
case _:
|
147
160
|
raise NotImplementedError()
|
@@ -156,6 +169,6 @@ def create_app(*agents: Agent) -> FastAPI:
|
|
156
169
|
)
|
157
170
|
bundle.task.cancel()
|
158
171
|
bundle.run.status = RunStatus.CANCELLING
|
159
|
-
return JSONResponse(status_code=status.HTTP_202_ACCEPTED, content=bundle.run
|
172
|
+
return JSONResponse(status_code=status.HTTP_202_ACCEPTED, content=jsonable_encoder(bundle.run))
|
160
173
|
|
161
174
|
return app
|
acp_sdk/server/bundle.py
CHANGED
@@ -70,7 +70,7 @@ class RunBundle:
|
|
70
70
|
|
71
71
|
async def execute(self, input: Message, *, executor: ThreadPoolExecutor) -> None:
|
72
72
|
with trace.get_tracer(__name__).start_as_current_span("execute"):
|
73
|
-
run_logger = logging.LoggerAdapter(logger, {"run_id": self.run.run_id})
|
73
|
+
run_logger = logging.LoggerAdapter(logger, {"run_id": str(self.run.run_id)})
|
74
74
|
|
75
75
|
await self.emit(CreatedEvent(run=self.run))
|
76
76
|
try:
|
acp_sdk/server/server.py
CHANGED
@@ -1,7 +1,12 @@
|
|
1
|
+
import asyncio
|
1
2
|
import inspect
|
2
|
-
|
3
|
+
import os
|
4
|
+
from collections.abc import AsyncGenerator, Awaitable, Coroutine, Generator
|
3
5
|
from typing import Any, Callable
|
4
6
|
|
7
|
+
import uvicorn
|
8
|
+
import uvicorn.config
|
9
|
+
|
5
10
|
from acp_sdk.models import Message
|
6
11
|
from acp_sdk.server.agent import Agent
|
7
12
|
from acp_sdk.server.app import create_app
|
@@ -13,25 +18,24 @@ from acp_sdk.server.types import RunYield, RunYieldResume
|
|
13
18
|
|
14
19
|
class Server:
|
15
20
|
def __init__(self) -> None:
|
16
|
-
self.
|
21
|
+
self._agents: list[Agent] = []
|
22
|
+
self._server: uvicorn.Server | None = None
|
17
23
|
|
18
24
|
def agent(self, name: str | None = None, description: str | None = None) -> Callable:
|
19
25
|
"""Decorator to register an agent."""
|
20
26
|
|
21
27
|
def decorator(fn: Callable) -> Callable:
|
22
|
-
# check agent's function signature
|
23
28
|
signature = inspect.signature(fn)
|
24
29
|
parameters = list(signature.parameters.values())
|
25
30
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
raise TypeError("The agent function must have one 'input' argument and one 'context' argument")
|
31
|
+
if len(parameters) == 0:
|
32
|
+
raise TypeError("The agent function must have at least 'input' argument")
|
33
|
+
if len(parameters) > 2:
|
34
|
+
raise TypeError("The agent function must have only 'input' and 'context' arguments")
|
35
|
+
if len(parameters) == 2 and parameters[1].name != "context":
|
36
|
+
raise TypeError("The second argument of the agent function must be 'context'")
|
37
|
+
|
38
|
+
has_context_param = len(parameters) == 2
|
35
39
|
|
36
40
|
agent: Agent
|
37
41
|
if inspect.isasyncgenfunction(fn):
|
@@ -47,7 +51,9 @@ class Server:
|
|
47
51
|
|
48
52
|
async def run(self, input: Message, context: Context) -> AsyncGenerator[RunYield, RunYieldResume]:
|
49
53
|
try:
|
50
|
-
gen: AsyncGenerator[RunYield, RunYieldResume] =
|
54
|
+
gen: AsyncGenerator[RunYield, RunYieldResume] = (
|
55
|
+
fn(input, context) if has_context_param else fn(input)
|
56
|
+
)
|
51
57
|
value = None
|
52
58
|
while True:
|
53
59
|
value = yield await gen.asend(value)
|
@@ -67,7 +73,7 @@ class Server:
|
|
67
73
|
return description or fn.__doc__ or ""
|
68
74
|
|
69
75
|
async def run(self, input: Message, context: Context) -> Coroutine[RunYield]:
|
70
|
-
return await fn(input, context)
|
76
|
+
return await (fn(input, context) if has_context_param else fn(input))
|
71
77
|
|
72
78
|
agent = DecoratedAgent()
|
73
79
|
elif inspect.isgeneratorfunction(fn):
|
@@ -82,7 +88,7 @@ class Server:
|
|
82
88
|
return description or fn.__doc__ or ""
|
83
89
|
|
84
90
|
def run(self, input: Message, context: Context) -> Generator[RunYield, RunYieldResume]:
|
85
|
-
yield from fn(input, context)
|
91
|
+
yield from (fn(input, context) if has_context_param else fn(input))
|
86
92
|
|
87
93
|
agent = DecoratedAgent()
|
88
94
|
else:
|
@@ -97,7 +103,7 @@ class Server:
|
|
97
103
|
return description or fn.__doc__ or ""
|
98
104
|
|
99
105
|
def run(self, input: Message, context: Context) -> RunYield:
|
100
|
-
return fn(input, context)
|
106
|
+
return fn(input, context) if has_context_param else fn(input)
|
101
107
|
|
102
108
|
agent = DecoratedAgent()
|
103
109
|
|
@@ -107,9 +113,67 @@ class Server:
|
|
107
113
|
return decorator
|
108
114
|
|
109
115
|
def register(self, *agents: Agent) -> None:
|
110
|
-
self.
|
116
|
+
self._agents.extend(agents)
|
117
|
+
|
118
|
+
def run(
|
119
|
+
self,
|
120
|
+
configure_logger: bool = True,
|
121
|
+
configure_telemetry: bool = False,
|
122
|
+
host: str = "127.0.0.1",
|
123
|
+
port: int = 8000,
|
124
|
+
uds: str | None = None,
|
125
|
+
fd: int | None = None,
|
126
|
+
loop: uvicorn.config.LoopSetupType = "auto",
|
127
|
+
http: type[asyncio.Protocol] | uvicorn.config.HTTPProtocolType = "auto",
|
128
|
+
ws: type[asyncio.Protocol] | uvicorn.config.WSProtocolType = "auto",
|
129
|
+
ws_max_size: int = 16 * 1024 * 1024,
|
130
|
+
ws_max_queue: int = 32,
|
131
|
+
ws_ping_interval: float | None = 20.0,
|
132
|
+
ws_ping_timeout: float | None = 20.0,
|
133
|
+
ws_per_message_deflate: bool = True,
|
134
|
+
lifespan: uvicorn.config.LifespanType = "auto",
|
135
|
+
env_file: str | os.PathLike[str] | None = None,
|
136
|
+
log_config: dict[str, Any]
|
137
|
+
| str
|
138
|
+
| uvicorn.config.RawConfigParser
|
139
|
+
| uvicorn.config.IO[Any]
|
140
|
+
| None = uvicorn.config.LOGGING_CONFIG,
|
141
|
+
log_level: str | int | None = None,
|
142
|
+
access_log: bool = True,
|
143
|
+
use_colors: bool | None = None,
|
144
|
+
interface: uvicorn.config.InterfaceType = "auto",
|
145
|
+
reload: bool = False,
|
146
|
+
reload_dirs: list[str] | str | None = None,
|
147
|
+
reload_delay: float = 0.25,
|
148
|
+
reload_includes: list[str] | str | None = None,
|
149
|
+
reload_excludes: list[str] | str | None = None,
|
150
|
+
workers: int | None = None,
|
151
|
+
proxy_headers: bool = True,
|
152
|
+
server_header: bool = True,
|
153
|
+
date_header: bool = True,
|
154
|
+
forwarded_allow_ips: list[str] | str | None = None,
|
155
|
+
root_path: str = "",
|
156
|
+
limit_concurrency: int | None = None,
|
157
|
+
limit_max_requests: int | None = None,
|
158
|
+
backlog: int = 2048,
|
159
|
+
timeout_keep_alive: int = 5,
|
160
|
+
timeout_notify: int = 30,
|
161
|
+
timeout_graceful_shutdown: int | None = None,
|
162
|
+
callback_notify: Callable[..., Awaitable[None]] | None = None,
|
163
|
+
ssl_keyfile: str | os.PathLike[str] | None = None,
|
164
|
+
ssl_certfile: str | os.PathLike[str] | None = None,
|
165
|
+
ssl_keyfile_password: str | None = None,
|
166
|
+
ssl_version: int = uvicorn.config.SSL_PROTOCOL_VERSION,
|
167
|
+
ssl_cert_reqs: int = uvicorn.config.ssl.CERT_NONE,
|
168
|
+
ssl_ca_certs: str | None = None,
|
169
|
+
ssl_ciphers: str = "TLSv1",
|
170
|
+
headers: list[tuple[str, str]] | None = None,
|
171
|
+
factory: bool = False,
|
172
|
+
h11_max_incomplete_event_size: int | None = None,
|
173
|
+
) -> None:
|
174
|
+
if self._server:
|
175
|
+
raise RuntimeError("The server is already running")
|
111
176
|
|
112
|
-
def run(self, configure_logger: bool = True, configure_telemetry: bool = False, **kwargs: dict[str, Any]) -> None:
|
113
177
|
import uvicorn
|
114
178
|
|
115
179
|
if configure_logger:
|
@@ -117,4 +181,63 @@ class Server:
|
|
117
181
|
if configure_telemetry:
|
118
182
|
configure_telemetry_func()
|
119
183
|
|
120
|
-
uvicorn.
|
184
|
+
config = uvicorn.Config(
|
185
|
+
create_app(*self._agents),
|
186
|
+
host,
|
187
|
+
port,
|
188
|
+
uds,
|
189
|
+
fd,
|
190
|
+
loop,
|
191
|
+
http,
|
192
|
+
ws,
|
193
|
+
ws_max_size,
|
194
|
+
ws_max_queue,
|
195
|
+
ws_ping_interval,
|
196
|
+
ws_ping_timeout,
|
197
|
+
ws_per_message_deflate,
|
198
|
+
lifespan,
|
199
|
+
env_file,
|
200
|
+
log_config,
|
201
|
+
log_level,
|
202
|
+
access_log,
|
203
|
+
use_colors,
|
204
|
+
interface,
|
205
|
+
reload,
|
206
|
+
reload_dirs,
|
207
|
+
reload_delay,
|
208
|
+
reload_includes,
|
209
|
+
reload_excludes,
|
210
|
+
workers,
|
211
|
+
proxy_headers,
|
212
|
+
server_header,
|
213
|
+
date_header,
|
214
|
+
forwarded_allow_ips,
|
215
|
+
root_path,
|
216
|
+
limit_concurrency,
|
217
|
+
limit_max_requests,
|
218
|
+
backlog,
|
219
|
+
timeout_keep_alive,
|
220
|
+
timeout_notify,
|
221
|
+
timeout_graceful_shutdown,
|
222
|
+
callback_notify,
|
223
|
+
ssl_keyfile,
|
224
|
+
ssl_certfile,
|
225
|
+
ssl_keyfile_password,
|
226
|
+
ssl_version,
|
227
|
+
ssl_cert_reqs,
|
228
|
+
ssl_ca_certs,
|
229
|
+
ssl_ciphers,
|
230
|
+
headers,
|
231
|
+
factory,
|
232
|
+
h11_max_incomplete_event_size,
|
233
|
+
)
|
234
|
+
self._server = uvicorn.Server(config)
|
235
|
+
self._server.run()
|
236
|
+
|
237
|
+
@property
|
238
|
+
def should_exit(self) -> bool:
|
239
|
+
return self._server.should_exit if self._server else False
|
240
|
+
|
241
|
+
@should_exit.setter
|
242
|
+
def should_exit(self, value: bool) -> None:
|
243
|
+
self._server.should_exit = value
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: acp-sdk
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.0rc7
|
4
4
|
Summary: Agent Communication Protocol SDK
|
5
5
|
Requires-Python: <4.0,>=3.11
|
6
6
|
Requires-Dist: opentelemetry-api>=1.31.1
|
@@ -56,23 +56,19 @@ async with Client(base_url="http://localhost:8000") as client:
|
|
56
56
|
The `server` submodule exposes [fastapi] application factory that makes it easy to expose any agent over ACP.
|
57
57
|
|
58
58
|
```python
|
59
|
-
|
60
|
-
@property
|
61
|
-
def name(self) -> str:
|
62
|
-
return "echo"
|
59
|
+
server = Server()
|
63
60
|
|
64
|
-
|
65
|
-
|
66
|
-
|
61
|
+
@server.agent()
|
62
|
+
async def echo(input: Message, context: Context) -> AsyncGenerator[RunYield, RunYieldResume]:
|
63
|
+
"""Echoes everything"""
|
64
|
+
for part in input:
|
65
|
+
await asyncio.sleep(0.5)
|
66
|
+
yield {"thought": "I should echo everyting"}
|
67
|
+
await asyncio.sleep(0.5)
|
68
|
+
yield Message(part)
|
67
69
|
|
68
|
-
async def run(self, input: Message, *, context: Context) -> AsyncGenerator[Message | Await, AwaitResume]:
|
69
|
-
for part in input:
|
70
|
-
await asyncio.sleep(0.5)
|
71
|
-
yield {"thought": "I should echo everyting"}
|
72
|
-
yield Message(part)
|
73
70
|
|
74
|
-
|
75
|
-
serve(EchoAgent())
|
71
|
+
server.run()
|
76
72
|
```
|
77
73
|
|
78
74
|
➡️ Explore more in our [examples library](/python/examples).
|
@@ -4,19 +4,19 @@ acp_sdk/client/__init__.py,sha256=Bca1DORrswxzZsrR2aUFpATuNG2xNSmYvF1Z2WJaVbc,51
|
|
4
4
|
acp_sdk/client/client.py,sha256=W6iXxH65UUODnvt3tnvgdqT_pP1meZUF2HwJpWCM0BM,5029
|
5
5
|
acp_sdk/models/__init__.py,sha256=numSDBDT1QHx7n_Y3Deb5VOvKWcUBxbOEaMwQBSRHxc,151
|
6
6
|
acp_sdk/models/errors.py,sha256=rEyaMVvQuBi7fwWe_d0PGGySYsD3FZTluQ-SkC0yhAs,444
|
7
|
-
acp_sdk/models/models.py,sha256=
|
8
|
-
acp_sdk/models/schemas.py,sha256=
|
7
|
+
acp_sdk/models/models.py,sha256=JhjI_M9GO-1lMrpZJo0IChDVrzUfnDyH5R5hFcqkXEE,3936
|
8
|
+
acp_sdk/models/schemas.py,sha256=TM_0aRsBj_9G__zzWPYDx4wAXsGs2RUH57IOTi4--Ek,728
|
9
9
|
acp_sdk/server/__init__.py,sha256=CowMcwN_WSsnO_-ZoqWQKtNVfa21MW3X3trZ9haJjaA,329
|
10
10
|
acp_sdk/server/agent.py,sha256=SZfJK634d4s8fRh0u1Tc56l58XCRHAh3hq5Hcabh6tI,3442
|
11
|
-
acp_sdk/server/app.py,sha256=
|
12
|
-
acp_sdk/server/bundle.py,sha256=
|
11
|
+
acp_sdk/server/app.py,sha256=rf8I2qcaaXoM-36Za8vs3Re7gygcTSMs4MXbMUBJENU,5803
|
12
|
+
acp_sdk/server/bundle.py,sha256=LZzinifpbKxEBivoxOSdxUmzEcu3XNAkCiIJc2Ivveg,4987
|
13
13
|
acp_sdk/server/context.py,sha256=MgnLV6qcDIhc_0BjW7r4Jj1tHts4ZuwpdTGIBnz2Mgo,1036
|
14
14
|
acp_sdk/server/errors.py,sha256=fWlgVsQ5hs_AXwzc-wvy6QgoDWEMRUBlSrfJfhHHMyE,2085
|
15
15
|
acp_sdk/server/logging.py,sha256=Oc8yZigCsuDnHHPsarRzu0RX3NKaLEgpELM2yovGKDI,411
|
16
|
-
acp_sdk/server/server.py,sha256=
|
16
|
+
acp_sdk/server/server.py,sha256=B1XGptw2kTgaB52klI4aNqDAlmyT2rNpMtKDSCxyVc4,8665
|
17
17
|
acp_sdk/server/telemetry.py,sha256=EwmtUrWMYid7XHiX76V1J6CigJPa2NrzEPOX0fBoY3o,1838
|
18
18
|
acp_sdk/server/types.py,sha256=2yJPkfUzjVIhHmc0SegGTMqDROe2uFgycb-7CATvYVw,161
|
19
19
|
acp_sdk/server/utils.py,sha256=EfrF9VCyVk3AM_ao-BIB9EzGbfTrh4V2Bz-VFr6f6Sg,351
|
20
|
-
acp_sdk-0.1.
|
21
|
-
acp_sdk-0.1.
|
22
|
-
acp_sdk-0.1.
|
20
|
+
acp_sdk-0.1.0rc7.dist-info/METADATA,sha256=IoDvdN2USySSWbm7I05iIuuRz-7vCzaBtsnilmgW5IQ,2033
|
21
|
+
acp_sdk-0.1.0rc7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
22
|
+
acp_sdk-0.1.0rc7.dist-info/RECORD,,
|
File without changes
|