acp-sdk 1.0.0rc3__py3-none-any.whl → 1.0.0rc4__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/__init__.py +1 -0
- acp_sdk/models/models.py +0 -36
- acp_sdk/models/schemas.py +39 -0
- acp_sdk/server/__init__.py +7 -2
- acp_sdk/server/agent.py +43 -4
- acp_sdk/server/app.py +161 -0
- acp_sdk/server/bundle.py +5 -2
- acp_sdk/server/context.py +20 -0
- acp_sdk/server/server.py +108 -162
- acp_sdk/server/types.py +6 -0
- acp_sdk-1.0.0rc4.dist-info/METADATA +78 -0
- acp_sdk-1.0.0rc4.dist-info/RECORD +22 -0
- acp_sdk-1.0.0rc3.dist-info/METADATA +0 -51
- acp_sdk-1.0.0rc3.dist-info/RECORD +0 -19
- {acp_sdk-1.0.0rc3.dist-info → acp_sdk-1.0.0rc4.dist-info}/WHEEL +0 -0
acp_sdk/models/__init__.py
CHANGED
acp_sdk/models/models.py
CHANGED
@@ -175,43 +175,7 @@ RunEvent = Union[
|
|
175
175
|
]
|
176
176
|
|
177
177
|
|
178
|
-
class RunCreateRequest(BaseModel):
|
179
|
-
agent_name: AgentName
|
180
|
-
session_id: SessionId | None = None
|
181
|
-
input: Message
|
182
|
-
mode: RunMode = RunMode.SYNC
|
183
|
-
|
184
|
-
|
185
|
-
class RunCreateResponse(Run):
|
186
|
-
pass
|
187
|
-
|
188
|
-
|
189
|
-
class RunResumeRequest(BaseModel):
|
190
|
-
await_: AwaitResume = Field(alias="await")
|
191
|
-
mode: RunMode
|
192
|
-
|
193
|
-
|
194
|
-
class RunResumeResponse(Run):
|
195
|
-
pass
|
196
|
-
|
197
|
-
|
198
|
-
class RunReadResponse(Run):
|
199
|
-
pass
|
200
|
-
|
201
|
-
|
202
|
-
class RunCancelResponse(Run):
|
203
|
-
pass
|
204
|
-
|
205
|
-
|
206
178
|
class Agent(BaseModel):
|
207
179
|
name: str
|
208
180
|
description: str | None = None
|
209
181
|
metadata: Metadata = Metadata()
|
210
|
-
|
211
|
-
|
212
|
-
class AgentsListResponse(BaseModel):
|
213
|
-
agents: list[Agent]
|
214
|
-
|
215
|
-
|
216
|
-
class AgentReadResponse(Agent):
|
217
|
-
pass
|
@@ -0,0 +1,39 @@
|
|
1
|
+
from pydantic import BaseModel, Field
|
2
|
+
|
3
|
+
from acp_sdk.models.models import Agent, AgentName, AwaitResume, Message, Run, RunMode, SessionId
|
4
|
+
|
5
|
+
|
6
|
+
class AgentsListResponse(BaseModel):
|
7
|
+
agents: list[Agent]
|
8
|
+
|
9
|
+
|
10
|
+
class AgentReadResponse(Agent):
|
11
|
+
pass
|
12
|
+
|
13
|
+
|
14
|
+
class RunCreateRequest(BaseModel):
|
15
|
+
agent_name: AgentName
|
16
|
+
session_id: SessionId | None = None
|
17
|
+
input: Message
|
18
|
+
mode: RunMode = RunMode.SYNC
|
19
|
+
|
20
|
+
|
21
|
+
class RunCreateResponse(Run):
|
22
|
+
pass
|
23
|
+
|
24
|
+
|
25
|
+
class RunResumeRequest(BaseModel):
|
26
|
+
await_: AwaitResume = Field(alias="await")
|
27
|
+
mode: RunMode
|
28
|
+
|
29
|
+
|
30
|
+
class RunResumeResponse(Run):
|
31
|
+
pass
|
32
|
+
|
33
|
+
|
34
|
+
class RunReadResponse(Run):
|
35
|
+
pass
|
36
|
+
|
37
|
+
|
38
|
+
class RunCancelResponse(Run):
|
39
|
+
pass
|
acp_sdk/server/__init__.py
CHANGED
@@ -1,3 +1,8 @@
|
|
1
1
|
from acp_sdk.server.agent import Agent as Agent
|
2
|
-
from acp_sdk.server.
|
3
|
-
from acp_sdk.server.
|
2
|
+
from acp_sdk.server.agent import SyncAgent as SyncAgent
|
3
|
+
from acp_sdk.server.app import create_app as create_app
|
4
|
+
from acp_sdk.server.context import Context as Context
|
5
|
+
from acp_sdk.server.context import SyncContext as SyncContext
|
6
|
+
from acp_sdk.server.server import Server as Server
|
7
|
+
from acp_sdk.server.types import RunYield as RunYield
|
8
|
+
from acp_sdk.server.types import RunYieldResume as RunYieldResume
|
acp_sdk/server/agent.py
CHANGED
@@ -1,15 +1,18 @@
|
|
1
1
|
import abc
|
2
|
+
import asyncio
|
2
3
|
from collections.abc import AsyncGenerator
|
4
|
+
from concurrent.futures import ThreadPoolExecutor
|
5
|
+
|
6
|
+
import janus
|
3
7
|
|
4
8
|
from acp_sdk.models import (
|
5
9
|
AgentName,
|
6
|
-
Await,
|
7
|
-
AwaitResume,
|
8
10
|
Message,
|
9
11
|
SessionId,
|
10
12
|
)
|
11
13
|
from acp_sdk.models.models import Metadata
|
12
|
-
from acp_sdk.server.context import Context
|
14
|
+
from acp_sdk.server.context import Context, SyncContext
|
15
|
+
from acp_sdk.server.types import RunYield, RunYieldResume
|
13
16
|
|
14
17
|
|
15
18
|
class Agent(abc.ABC):
|
@@ -26,10 +29,46 @@ class Agent(abc.ABC):
|
|
26
29
|
return Metadata()
|
27
30
|
|
28
31
|
@abc.abstractmethod
|
29
|
-
def run(
|
32
|
+
def run(
|
33
|
+
self, input: Message, context: Context, executor: ThreadPoolExecutor
|
34
|
+
) -> AsyncGenerator[RunYield, RunYieldResume]:
|
30
35
|
pass
|
31
36
|
|
32
37
|
async def session(self, session_id: SessionId | None) -> SessionId | None:
|
33
38
|
if session_id:
|
34
39
|
raise NotImplementedError()
|
35
40
|
return None
|
41
|
+
|
42
|
+
|
43
|
+
class SyncAgent(Agent):
|
44
|
+
@abc.abstractmethod
|
45
|
+
def run_sync(self, input: Message, context: SyncContext, executor: ThreadPoolExecutor) -> RunYield | None:
|
46
|
+
pass
|
47
|
+
|
48
|
+
async def run(
|
49
|
+
self, input: Message, context: Context, executor: ThreadPoolExecutor
|
50
|
+
) -> AsyncGenerator[RunYield, RunYieldResume]:
|
51
|
+
yield_queue: janus.Queue[RunYield] = janus.Queue()
|
52
|
+
yield_resume_queue: janus.Queue[RunYieldResume] = janus.Queue()
|
53
|
+
|
54
|
+
run_future = asyncio.get_running_loop().run_in_executor(
|
55
|
+
executor,
|
56
|
+
self.run_sync,
|
57
|
+
input,
|
58
|
+
SyncContext(
|
59
|
+
session_id=context.session_id,
|
60
|
+
yield_queue=yield_queue.sync_q,
|
61
|
+
yield_resume_queue=yield_resume_queue.sync_q,
|
62
|
+
),
|
63
|
+
executor,
|
64
|
+
)
|
65
|
+
|
66
|
+
while True:
|
67
|
+
yield_task = asyncio.create_task(yield_queue.async_q.get())
|
68
|
+
done, _ = await asyncio.wait([yield_task, run_future], return_when=asyncio.FIRST_COMPLETED)
|
69
|
+
if yield_task in done:
|
70
|
+
resume = yield await yield_task
|
71
|
+
await yield_resume_queue.async_q.put(resume)
|
72
|
+
if run_future in done:
|
73
|
+
yield await run_future
|
74
|
+
break
|
acp_sdk/server/app.py
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
import asyncio
|
2
|
+
from collections.abc import AsyncGenerator
|
3
|
+
from concurrent.futures import ThreadPoolExecutor
|
4
|
+
from contextlib import asynccontextmanager
|
5
|
+
|
6
|
+
from fastapi import FastAPI, HTTPException, status
|
7
|
+
from fastapi.responses import JSONResponse, StreamingResponse
|
8
|
+
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
|
9
|
+
|
10
|
+
from acp_sdk.models import (
|
11
|
+
Agent as AgentModel,
|
12
|
+
)
|
13
|
+
from acp_sdk.models import (
|
14
|
+
AgentName,
|
15
|
+
AgentReadResponse,
|
16
|
+
AgentsListResponse,
|
17
|
+
Run,
|
18
|
+
RunCancelResponse,
|
19
|
+
RunCreateRequest,
|
20
|
+
RunCreateResponse,
|
21
|
+
RunId,
|
22
|
+
RunMode,
|
23
|
+
RunReadResponse,
|
24
|
+
RunResumeRequest,
|
25
|
+
RunResumeResponse,
|
26
|
+
RunStatus,
|
27
|
+
)
|
28
|
+
from acp_sdk.models.errors import ACPError
|
29
|
+
from acp_sdk.server.agent import Agent
|
30
|
+
from acp_sdk.server.bundle import RunBundle
|
31
|
+
from acp_sdk.server.errors import (
|
32
|
+
RequestValidationError,
|
33
|
+
StarletteHTTPException,
|
34
|
+
acp_error_handler,
|
35
|
+
catch_all_exception_handler,
|
36
|
+
http_exception_handler,
|
37
|
+
validation_exception_handler,
|
38
|
+
)
|
39
|
+
from acp_sdk.server.utils import stream_sse
|
40
|
+
|
41
|
+
|
42
|
+
def create_app(*agents: Agent) -> FastAPI:
|
43
|
+
executor: ThreadPoolExecutor
|
44
|
+
|
45
|
+
@asynccontextmanager
|
46
|
+
async def lifespan(app: FastAPI) -> AsyncGenerator[None]:
|
47
|
+
nonlocal executor
|
48
|
+
with ThreadPoolExecutor(max_workers=5) as exec:
|
49
|
+
executor = exec
|
50
|
+
yield
|
51
|
+
|
52
|
+
app = FastAPI(lifespan=lifespan)
|
53
|
+
|
54
|
+
FastAPIInstrumentor.instrument_app(app)
|
55
|
+
|
56
|
+
agents: dict[AgentName, Agent] = {agent.name: agent for agent in agents}
|
57
|
+
runs: dict[RunId, RunBundle] = {}
|
58
|
+
|
59
|
+
app.exception_handler(ACPError)(acp_error_handler)
|
60
|
+
app.exception_handler(StarletteHTTPException)(http_exception_handler)
|
61
|
+
app.exception_handler(RequestValidationError)(validation_exception_handler)
|
62
|
+
app.exception_handler(Exception)(catch_all_exception_handler)
|
63
|
+
|
64
|
+
def find_run_bundle(run_id: RunId) -> RunBundle:
|
65
|
+
bundle = runs.get(run_id)
|
66
|
+
if not bundle:
|
67
|
+
raise HTTPException(status_code=404, detail=f"Run {run_id} not found")
|
68
|
+
return bundle
|
69
|
+
|
70
|
+
def find_agent(agent_name: AgentName) -> Agent:
|
71
|
+
agent = agents.get(agent_name, None)
|
72
|
+
if not agent:
|
73
|
+
raise HTTPException(status_code=404, detail=f"Agent {agent_name} not found")
|
74
|
+
return agent
|
75
|
+
|
76
|
+
@app.get("/agents")
|
77
|
+
async def list_agents() -> AgentsListResponse:
|
78
|
+
return AgentsListResponse(
|
79
|
+
agents=[
|
80
|
+
AgentModel(name=agent.name, description=agent.description, metadata=agent.metadata)
|
81
|
+
for agent in agents.values()
|
82
|
+
]
|
83
|
+
)
|
84
|
+
|
85
|
+
@app.get("/agents/{name}")
|
86
|
+
async def read_agent(name: AgentName) -> AgentReadResponse:
|
87
|
+
agent = find_agent(name)
|
88
|
+
return AgentModel(name=agent.name, description=agent.description, metadata=agent.metadata)
|
89
|
+
|
90
|
+
@app.post("/runs")
|
91
|
+
async def create_run(request: RunCreateRequest) -> RunCreateResponse:
|
92
|
+
agent = find_agent(request.agent_name)
|
93
|
+
bundle = RunBundle(
|
94
|
+
agent=agent,
|
95
|
+
run=Run(
|
96
|
+
agent_name=agent.name,
|
97
|
+
session_id=request.session_id,
|
98
|
+
),
|
99
|
+
)
|
100
|
+
|
101
|
+
nonlocal executor
|
102
|
+
bundle.task = asyncio.create_task(bundle.execute(request.input, executor=executor))
|
103
|
+
runs[bundle.run.run_id] = bundle
|
104
|
+
|
105
|
+
match request.mode:
|
106
|
+
case RunMode.STREAM:
|
107
|
+
return StreamingResponse(
|
108
|
+
stream_sse(bundle),
|
109
|
+
media_type="text/event-stream",
|
110
|
+
)
|
111
|
+
case RunMode.SYNC:
|
112
|
+
await bundle.join()
|
113
|
+
return bundle.run
|
114
|
+
case RunMode.ASYNC:
|
115
|
+
return JSONResponse(
|
116
|
+
status_code=status.HTTP_202_ACCEPTED,
|
117
|
+
content=bundle.run.model_dump(),
|
118
|
+
)
|
119
|
+
case _:
|
120
|
+
raise NotImplementedError()
|
121
|
+
|
122
|
+
@app.get("/runs/{run_id}")
|
123
|
+
async def read_run(run_id: RunId) -> RunReadResponse:
|
124
|
+
bundle = find_run_bundle(run_id)
|
125
|
+
return bundle.run
|
126
|
+
|
127
|
+
@app.post("/runs/{run_id}")
|
128
|
+
async def resume_run(run_id: RunId, request: RunResumeRequest) -> RunResumeResponse:
|
129
|
+
bundle = find_run_bundle(run_id)
|
130
|
+
bundle.stream_queue = asyncio.Queue() # TODO improve
|
131
|
+
await bundle.await_queue.put(request.await_)
|
132
|
+
match request.mode:
|
133
|
+
case RunMode.STREAM:
|
134
|
+
return StreamingResponse(
|
135
|
+
stream_sse(bundle),
|
136
|
+
media_type="text/event-stream",
|
137
|
+
)
|
138
|
+
case RunMode.SYNC:
|
139
|
+
await bundle.join()
|
140
|
+
return bundle.run
|
141
|
+
case RunMode.ASYNC:
|
142
|
+
return JSONResponse(
|
143
|
+
status_code=status.HTTP_202_ACCEPTED,
|
144
|
+
content=bundle.run.model_dump(),
|
145
|
+
)
|
146
|
+
case _:
|
147
|
+
raise NotImplementedError()
|
148
|
+
|
149
|
+
@app.post("/runs/{run_id}/cancel")
|
150
|
+
async def cancel_run(run_id: RunId) -> RunCancelResponse:
|
151
|
+
bundle = find_run_bundle(run_id)
|
152
|
+
if bundle.run.status.is_terminal:
|
153
|
+
raise HTTPException(
|
154
|
+
status_code=403,
|
155
|
+
detail=f"Run with terminal status {bundle.run.status} can't be cancelled",
|
156
|
+
)
|
157
|
+
bundle.task.cancel()
|
158
|
+
bundle.run.status = RunStatus.CANCELLING
|
159
|
+
return JSONResponse(status_code=status.HTTP_202_ACCEPTED, content=bundle.run.model_dump())
|
160
|
+
|
161
|
+
return app
|
acp_sdk/server/bundle.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
import asyncio
|
2
2
|
import logging
|
3
3
|
from collections.abc import AsyncGenerator
|
4
|
+
from concurrent.futures import ThreadPoolExecutor
|
4
5
|
|
5
6
|
from opentelemetry import trace
|
6
7
|
from pydantic import ValidationError
|
@@ -68,7 +69,7 @@ class RunBundle:
|
|
68
69
|
async def join(self) -> None:
|
69
70
|
await self.await_or_terminate_event.wait()
|
70
71
|
|
71
|
-
async def execute(self, input: Message) -> None:
|
72
|
+
async def execute(self, input: Message, *, executor: ThreadPoolExecutor) -> None:
|
72
73
|
with trace.get_tracer(__name__).start_as_current_span("execute"):
|
73
74
|
run_logger = logging.LoggerAdapter(logger, {"run_id": self.run.run_id})
|
74
75
|
|
@@ -77,7 +78,9 @@ class RunBundle:
|
|
77
78
|
self.run.session_id = await self.agent.session(self.run.session_id)
|
78
79
|
run_logger.info("Session loaded")
|
79
80
|
|
80
|
-
generator = self.agent.run(
|
81
|
+
generator = self.agent.run(
|
82
|
+
input=input, context=Context(session_id=self.run.session_id), executor=executor
|
83
|
+
)
|
81
84
|
run_logger.info("Run started")
|
82
85
|
|
83
86
|
self.run.status = RunStatus.IN_PROGRESS
|
acp_sdk/server/context.py
CHANGED
@@ -1,6 +1,26 @@
|
|
1
|
+
import janus
|
2
|
+
|
1
3
|
from acp_sdk.models import SessionId
|
4
|
+
from acp_sdk.server.types import RunYield, RunYieldResume
|
2
5
|
|
3
6
|
|
4
7
|
class Context:
|
5
8
|
def __init__(self, *, session_id: SessionId | None = None) -> None:
|
6
9
|
self.session_id = session_id
|
10
|
+
|
11
|
+
|
12
|
+
class SyncContext(Context):
|
13
|
+
def __init__(
|
14
|
+
self,
|
15
|
+
*,
|
16
|
+
session_id: SessionId | None = None,
|
17
|
+
yield_queue: janus.SyncQueue[RunYield],
|
18
|
+
yield_resume_queue: janus.SyncQueue[RunYieldResume],
|
19
|
+
) -> None:
|
20
|
+
super().__init__(session_id=session_id)
|
21
|
+
self._yield_queue = yield_queue
|
22
|
+
self._yield_resume_queue = yield_resume_queue
|
23
|
+
|
24
|
+
def yield_(self, data: RunYield) -> RunYieldResume:
|
25
|
+
self._yield_queue.put(data)
|
26
|
+
return self._yield_resume_queue.get()
|
acp_sdk/server/server.py
CHANGED
@@ -1,164 +1,110 @@
|
|
1
|
-
import
|
2
|
-
from
|
3
|
-
|
4
|
-
from
|
5
|
-
|
6
|
-
from
|
7
|
-
|
8
|
-
from acp_sdk.
|
9
|
-
|
10
|
-
)
|
11
|
-
from acp_sdk.models import (
|
12
|
-
AgentName,
|
13
|
-
AgentReadResponse,
|
14
|
-
AgentsListResponse,
|
15
|
-
Run,
|
16
|
-
RunCancelResponse,
|
17
|
-
RunCreateRequest,
|
18
|
-
RunCreateResponse,
|
19
|
-
RunId,
|
20
|
-
RunMode,
|
21
|
-
RunReadResponse,
|
22
|
-
RunResumeRequest,
|
23
|
-
RunResumeResponse,
|
24
|
-
RunStatus,
|
25
|
-
)
|
26
|
-
from acp_sdk.models.errors import ACPError
|
27
|
-
from acp_sdk.server.agent import Agent
|
28
|
-
from acp_sdk.server.bundle import RunBundle
|
29
|
-
from acp_sdk.server.errors import (
|
30
|
-
RequestValidationError,
|
31
|
-
StarletteHTTPException,
|
32
|
-
acp_error_handler,
|
33
|
-
catch_all_exception_handler,
|
34
|
-
http_exception_handler,
|
35
|
-
validation_exception_handler,
|
36
|
-
)
|
1
|
+
import inspect
|
2
|
+
from collections.abc import AsyncGenerator
|
3
|
+
from concurrent.futures import ThreadPoolExecutor
|
4
|
+
from typing import Any, Callable
|
5
|
+
|
6
|
+
from acp_sdk.models import Message
|
7
|
+
from acp_sdk.server.agent import Agent, SyncAgent
|
8
|
+
from acp_sdk.server.app import create_app
|
9
|
+
from acp_sdk.server.context import Context
|
37
10
|
from acp_sdk.server.logging import configure_logger as configure_logger_func
|
38
11
|
from acp_sdk.server.telemetry import configure_telemetry as configure_telemetry_func
|
39
|
-
from acp_sdk.server.
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
@app.post("/runs/{run_id}/cancel")
|
140
|
-
async def cancel_run(run_id: RunId) -> RunCancelResponse:
|
141
|
-
bundle = find_run_bundle(run_id)
|
142
|
-
if bundle.run.status.is_terminal:
|
143
|
-
raise HTTPException(
|
144
|
-
status_code=403,
|
145
|
-
detail=f"Run with terminal status {bundle.run.status} can't be cancelled",
|
146
|
-
)
|
147
|
-
bundle.task.cancel()
|
148
|
-
bundle.run.status = RunStatus.CANCELLING
|
149
|
-
return JSONResponse(status_code=status.HTTP_202_ACCEPTED, content=bundle.run.model_dump())
|
150
|
-
|
151
|
-
return app
|
152
|
-
|
153
|
-
|
154
|
-
def serve(
|
155
|
-
*agents: Agent, configure_logger: bool = True, configure_telemetry: bool = False, **kwargs: dict[str, Any]
|
156
|
-
) -> None:
|
157
|
-
import uvicorn
|
158
|
-
|
159
|
-
if configure_logger:
|
160
|
-
configure_logger_func()
|
161
|
-
if configure_telemetry:
|
162
|
-
configure_telemetry_func()
|
163
|
-
|
164
|
-
uvicorn.run(create_app(*agents), **kwargs)
|
12
|
+
from acp_sdk.server.types import RunYield, RunYieldResume
|
13
|
+
|
14
|
+
|
15
|
+
class Server:
|
16
|
+
def __init__(self) -> None:
|
17
|
+
self.agents: list[Agent] = []
|
18
|
+
|
19
|
+
def agent(self, name: str | None = None, description: str | None = None) -> Callable:
|
20
|
+
"""Decorator to register an agent."""
|
21
|
+
|
22
|
+
def decorator(fn: Callable) -> Callable:
|
23
|
+
# check agent's function signature
|
24
|
+
signature = inspect.signature(fn)
|
25
|
+
parameters = list(signature.parameters.values())
|
26
|
+
|
27
|
+
# validate agent's function
|
28
|
+
if inspect.isasyncgenfunction(fn):
|
29
|
+
if len(parameters) != 2:
|
30
|
+
raise TypeError(
|
31
|
+
"The agent generator function must have one 'input' argument and one 'context' argument"
|
32
|
+
)
|
33
|
+
else:
|
34
|
+
if len(parameters) != 2:
|
35
|
+
raise TypeError("The agent function must have one 'input' argument and one 'context' argument")
|
36
|
+
|
37
|
+
agent: Agent
|
38
|
+
if inspect.isasyncgenfunction(fn):
|
39
|
+
|
40
|
+
class DecoratedAgent(Agent):
|
41
|
+
@property
|
42
|
+
def name(self) -> str:
|
43
|
+
return name or fn.__name__
|
44
|
+
|
45
|
+
@property
|
46
|
+
def description(self) -> str:
|
47
|
+
return description or fn.__doc__ or ""
|
48
|
+
|
49
|
+
async def run(
|
50
|
+
self, input: Message, context: Context, executor: ThreadPoolExecutor
|
51
|
+
) -> AsyncGenerator[RunYield, RunYieldResume]:
|
52
|
+
gen: AsyncGenerator[RunYield, RunYieldResume] = fn(input, context)
|
53
|
+
value = None
|
54
|
+
while True:
|
55
|
+
try:
|
56
|
+
value = yield await gen.asend(value)
|
57
|
+
except StopAsyncIteration:
|
58
|
+
break
|
59
|
+
|
60
|
+
agent = DecoratedAgent()
|
61
|
+
elif inspect.iscoroutinefunction(fn):
|
62
|
+
|
63
|
+
class DecoratedAgent(Agent):
|
64
|
+
@property
|
65
|
+
def name(self) -> str:
|
66
|
+
return name or fn.__name__
|
67
|
+
|
68
|
+
@property
|
69
|
+
def description(self) -> str:
|
70
|
+
return description or fn.__doc__ or ""
|
71
|
+
|
72
|
+
async def run(
|
73
|
+
self, input: Message, context: Context, executor: ThreadPoolExecutor
|
74
|
+
) -> AsyncGenerator[RunYield, RunYieldResume]:
|
75
|
+
yield await fn(input, context)
|
76
|
+
|
77
|
+
agent = DecoratedAgent()
|
78
|
+
else:
|
79
|
+
|
80
|
+
class DecoratedAgent(SyncAgent):
|
81
|
+
@property
|
82
|
+
def name(self) -> str:
|
83
|
+
return name or fn.__name__
|
84
|
+
|
85
|
+
@property
|
86
|
+
def description(self) -> str:
|
87
|
+
return description or fn.__doc__ or ""
|
88
|
+
|
89
|
+
def run_sync(self, input: Message, context: Context, executor: ThreadPoolExecutor) -> None:
|
90
|
+
return fn(input, context)
|
91
|
+
|
92
|
+
agent = DecoratedAgent()
|
93
|
+
|
94
|
+
self.register(agent)
|
95
|
+
return fn
|
96
|
+
|
97
|
+
return decorator
|
98
|
+
|
99
|
+
def register(self, *agents: Agent) -> None:
|
100
|
+
self.agents.extend(agents)
|
101
|
+
|
102
|
+
def run(self, configure_logger: bool = True, configure_telemetry: bool = False, **kwargs: dict[str, Any]) -> None:
|
103
|
+
import uvicorn
|
104
|
+
|
105
|
+
if configure_logger:
|
106
|
+
configure_logger_func()
|
107
|
+
if configure_telemetry:
|
108
|
+
configure_telemetry_func()
|
109
|
+
|
110
|
+
uvicorn.run(create_app(*self.agents), **kwargs)
|
acp_sdk/server/types.py
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: acp-sdk
|
3
|
+
Version: 1.0.0rc4
|
4
|
+
Summary: Agent Communication Protocol SDK
|
5
|
+
Requires-Python: <4.0,>=3.11
|
6
|
+
Requires-Dist: opentelemetry-api>=1.31.1
|
7
|
+
Requires-Dist: pydantic>=2.11.1
|
8
|
+
Provides-Extra: client
|
9
|
+
Requires-Dist: httpx-sse>=0.4.0; extra == 'client'
|
10
|
+
Requires-Dist: httpx>=0.28.1; extra == 'client'
|
11
|
+
Requires-Dist: opentelemetry-instrumentation-httpx>=0.52b1; extra == 'client'
|
12
|
+
Provides-Extra: server
|
13
|
+
Requires-Dist: fastapi[standard]>=0.115.8; extra == 'server'
|
14
|
+
Requires-Dist: janus>=2.0.0; extra == 'server'
|
15
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.31.1; extra == 'server'
|
16
|
+
Requires-Dist: opentelemetry-instrumentation-fastapi>=0.52b1; extra == 'server'
|
17
|
+
Requires-Dist: opentelemetry-sdk>=1.31.1; extra == 'server'
|
18
|
+
Description-Content-Type: text/markdown
|
19
|
+
|
20
|
+
# Agent Communication Protocol SDK for Python
|
21
|
+
|
22
|
+
Agent Communication Protocol SDK for Python provides allows developers to serve and consume agents over the Agent Communication Protocol.
|
23
|
+
|
24
|
+
## Prerequisites
|
25
|
+
|
26
|
+
✅ Python >= 3.11
|
27
|
+
|
28
|
+
## Installation
|
29
|
+
|
30
|
+
Install to use client:
|
31
|
+
|
32
|
+
```shell
|
33
|
+
pip install acp-sdk[client]
|
34
|
+
```
|
35
|
+
|
36
|
+
Install to use server:
|
37
|
+
|
38
|
+
```shell
|
39
|
+
pip install acp-sdk[server]
|
40
|
+
```
|
41
|
+
|
42
|
+
## Overview
|
43
|
+
|
44
|
+
### Client
|
45
|
+
|
46
|
+
The `client` submodule exposes [httpx]() based client with simple methods for communication over ACP.
|
47
|
+
|
48
|
+
```python
|
49
|
+
async with Client(base_url="http://localhost:8000") as client:
|
50
|
+
run = await client.run_sync(agent="echo", input=Message(TextMessagePart(content="Howdy!")))
|
51
|
+
print(run.output)
|
52
|
+
```
|
53
|
+
|
54
|
+
### Server
|
55
|
+
|
56
|
+
The `server` submodule exposes [fastapi] application factory that makes it easy to expose any agent over ACP.
|
57
|
+
|
58
|
+
```python
|
59
|
+
class EchoAgent(Agent):
|
60
|
+
@property
|
61
|
+
def name(self) -> str:
|
62
|
+
return "echo"
|
63
|
+
|
64
|
+
@property
|
65
|
+
def description(self) -> str:
|
66
|
+
return "Echoes everything"
|
67
|
+
|
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
|
+
|
74
|
+
|
75
|
+
serve(EchoAgent())
|
76
|
+
```
|
77
|
+
|
78
|
+
➡️ Explore more in our [examples library](/python/examples).
|
@@ -0,0 +1,22 @@
|
|
1
|
+
acp_sdk/__init__.py,sha256=XlcAbmvJfvJK5tmOFdxXP19aDwxxdG_jygsTCvXtjTk,43
|
2
|
+
acp_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
+
acp_sdk/client/__init__.py,sha256=Bca1DORrswxzZsrR2aUFpATuNG2xNSmYvF1Z2WJaVbc,51
|
4
|
+
acp_sdk/client/client.py,sha256=W6iXxH65UUODnvt3tnvgdqT_pP1meZUF2HwJpWCM0BM,5029
|
5
|
+
acp_sdk/models/__init__.py,sha256=numSDBDT1QHx7n_Y3Deb5VOvKWcUBxbOEaMwQBSRHxc,151
|
6
|
+
acp_sdk/models/errors.py,sha256=rEyaMVvQuBi7fwWe_d0PGGySYsD3FZTluQ-SkC0yhAs,444
|
7
|
+
acp_sdk/models/models.py,sha256=ACZPQp-wm43tdlx83hrX7QmdtBRWVXvU5bsotUOidHM,3908
|
8
|
+
acp_sdk/models/schemas.py,sha256=a0T29vfy93uVIce3ikbd3GSTdKBokMmfXWNVRJD3Eb8,662
|
9
|
+
acp_sdk/server/__init__.py,sha256=Qd0uTLaStTGxRaHvtXQpTWoruGvRGu8L17d8IMoPgu8,447
|
10
|
+
acp_sdk/server/agent.py,sha256=pAJkUR0OJn7qzsW7JPk3YIjRoPrSFljcWXcH4kBuRCM,2214
|
11
|
+
acp_sdk/server/app.py,sha256=7alacNo9y_f0-BjZI0dm_rzSweW-n5YiuY0hrVtEIR8,5420
|
12
|
+
acp_sdk/server/bundle.py,sha256=J-1prwhTHeK29ONjFERbv7gNY3YVSCeVI0p5687y-WQ,5076
|
13
|
+
acp_sdk/server/context.py,sha256=DIs4T61nIkangT2Mdk5oEHmK__XzfssqtodQ266CvoQ,758
|
14
|
+
acp_sdk/server/errors.py,sha256=fWlgVsQ5hs_AXwzc-wvy6QgoDWEMRUBlSrfJfhHHMyE,2085
|
15
|
+
acp_sdk/server/logging.py,sha256=Oc8yZigCsuDnHHPsarRzu0RX3NKaLEgpELM2yovGKDI,411
|
16
|
+
acp_sdk/server/server.py,sha256=hhju0fXo0vmkD5QYgT_zbZUDZXnCCFQzF3AZCMSgWko,4120
|
17
|
+
acp_sdk/server/telemetry.py,sha256=EwmtUrWMYid7XHiX76V1J6CigJPa2NrzEPOX0fBoY3o,1838
|
18
|
+
acp_sdk/server/types.py,sha256=2yJPkfUzjVIhHmc0SegGTMqDROe2uFgycb-7CATvYVw,161
|
19
|
+
acp_sdk/server/utils.py,sha256=EfrF9VCyVk3AM_ao-BIB9EzGbfTrh4V2Bz-VFr6f6Sg,351
|
20
|
+
acp_sdk-1.0.0rc4.dist-info/METADATA,sha256=qHSrUOETNJflMDnlTh8dWQh1sHNdO0OZOStlOJgzjdk,2147
|
21
|
+
acp_sdk-1.0.0rc4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
22
|
+
acp_sdk-1.0.0rc4.dist-info/RECORD,,
|
@@ -1,51 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.4
|
2
|
-
Name: acp-sdk
|
3
|
-
Version: 1.0.0rc3
|
4
|
-
Summary: Agent Communication Protocol SDK
|
5
|
-
Requires-Python: <4.0,>=3.11
|
6
|
-
Requires-Dist: opentelemetry-api>=1.31.1
|
7
|
-
Requires-Dist: pydantic>=2.11.1
|
8
|
-
Provides-Extra: client
|
9
|
-
Requires-Dist: httpx-sse>=0.4.0; extra == 'client'
|
10
|
-
Requires-Dist: httpx>=0.28.1; extra == 'client'
|
11
|
-
Requires-Dist: opentelemetry-instrumentation-httpx>=0.52b1; extra == 'client'
|
12
|
-
Provides-Extra: server
|
13
|
-
Requires-Dist: fastapi[standard]>=0.115.8; extra == 'server'
|
14
|
-
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.31.1; extra == 'server'
|
15
|
-
Requires-Dist: opentelemetry-instrumentation-fastapi>=0.52b1; extra == 'server'
|
16
|
-
Requires-Dist: opentelemetry-sdk>=1.31.1; extra == 'server'
|
17
|
-
Description-Content-Type: text/markdown
|
18
|
-
|
19
|
-
# Agent Communication Protocol SDK for Python
|
20
|
-
|
21
|
-
## Prerequisites
|
22
|
-
|
23
|
-
✅ Python >= 3.13
|
24
|
-
|
25
|
-
## Installation
|
26
|
-
|
27
|
-
Install using pip:
|
28
|
-
|
29
|
-
```shell
|
30
|
-
pip install acp-sdk
|
31
|
-
```
|
32
|
-
|
33
|
-
## Examples
|
34
|
-
|
35
|
-
The SDK can be used to implement both clients and servers.
|
36
|
-
|
37
|
-
### Server
|
38
|
-
|
39
|
-
```shell
|
40
|
-
python examples/servers/echo.py
|
41
|
-
```
|
42
|
-
|
43
|
-
### Client
|
44
|
-
|
45
|
-
To run an example client:
|
46
|
-
|
47
|
-
```shell
|
48
|
-
python examples/clients/simple.py
|
49
|
-
```
|
50
|
-
|
51
|
-
➡️ Explore more in our [examples library](/python/examples).
|
@@ -1,19 +0,0 @@
|
|
1
|
-
acp_sdk/__init__.py,sha256=XlcAbmvJfvJK5tmOFdxXP19aDwxxdG_jygsTCvXtjTk,43
|
2
|
-
acp_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
-
acp_sdk/client/__init__.py,sha256=Bca1DORrswxzZsrR2aUFpATuNG2xNSmYvF1Z2WJaVbc,51
|
4
|
-
acp_sdk/client/client.py,sha256=W6iXxH65UUODnvt3tnvgdqT_pP1meZUF2HwJpWCM0BM,5029
|
5
|
-
acp_sdk/models/__init__.py,sha256=klWKtCE_R8LIQEin0nqgiEyzGZbcvxntalk6qWqr7Js,100
|
6
|
-
acp_sdk/models/errors.py,sha256=rEyaMVvQuBi7fwWe_d0PGGySYsD3FZTluQ-SkC0yhAs,444
|
7
|
-
acp_sdk/models/models.py,sha256=HnDBIuANsiM-Kl7T1aElGgnOib14OxV28RyrxDQyiig,4433
|
8
|
-
acp_sdk/server/__init__.py,sha256=aVVbBzZdFdX3vnIgX0r4WfTHbAq1tVh9tt-oKJ7BxRg,156
|
9
|
-
acp_sdk/server/agent.py,sha256=hMYrbS6b-4nYBB2NV9Zw5RPbmD3oUMl5y-OcnjvNoo4,802
|
10
|
-
acp_sdk/server/bundle.py,sha256=kMLyHQZxM-hL-B79Qcxu4JdvA_M86Tj_kk1C2bSQ9Fw,4936
|
11
|
-
acp_sdk/server/context.py,sha256=oILWXHNTXq3mLX0RM7jiIKMC19dEiw7AYy6X0L6OUXg,163
|
12
|
-
acp_sdk/server/errors.py,sha256=fWlgVsQ5hs_AXwzc-wvy6QgoDWEMRUBlSrfJfhHHMyE,2085
|
13
|
-
acp_sdk/server/logging.py,sha256=Oc8yZigCsuDnHHPsarRzu0RX3NKaLEgpELM2yovGKDI,411
|
14
|
-
acp_sdk/server/server.py,sha256=yN_QzPBCNTlfkXsae6UpDliEn_PONliH4OglvuMD-Sc,5498
|
15
|
-
acp_sdk/server/telemetry.py,sha256=EwmtUrWMYid7XHiX76V1J6CigJPa2NrzEPOX0fBoY3o,1838
|
16
|
-
acp_sdk/server/utils.py,sha256=EfrF9VCyVk3AM_ao-BIB9EzGbfTrh4V2Bz-VFr6f6Sg,351
|
17
|
-
acp_sdk-1.0.0rc3.dist-info/METADATA,sha256=dGD1WNqlpUAE3rYQ-axzeWsmcS8RRxN0UFh_LLx_g9U,1183
|
18
|
-
acp_sdk-1.0.0rc3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
19
|
-
acp_sdk-1.0.0rc3.dist-info/RECORD,,
|
File without changes
|