acp-sdk 0.7.0__py3-none-any.whl → 0.7.2__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/client/client.py +10 -28
- acp_sdk/client/types.py +3 -0
- acp_sdk/client/utils.py +24 -0
- acp_sdk/models/models.py +8 -2
- acp_sdk/server/agent.py +14 -14
- acp_sdk/server/app.py +16 -4
- acp_sdk/server/bundle.py +5 -5
- acp_sdk/server/server.py +54 -1
- acp_sdk/server/session.py +1 -1
- acp_sdk/server/utils.py +36 -1
- {acp_sdk-0.7.0.dist-info → acp_sdk-0.7.2.dist-info}/METADATA +4 -4
- {acp_sdk-0.7.0.dist-info → acp_sdk-0.7.2.dist-info}/RECORD +13 -11
- {acp_sdk-0.7.0.dist-info → acp_sdk-0.7.2.dist-info}/WHEEL +0 -0
acp_sdk/client/client.py
CHANGED
@@ -11,6 +11,8 @@ from httpx_sse import EventSource, aconnect_sse
|
|
11
11
|
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
|
12
12
|
from pydantic import TypeAdapter
|
13
13
|
|
14
|
+
from acp_sdk.client.types import Input
|
15
|
+
from acp_sdk.client.utils import input_to_messages
|
14
16
|
from acp_sdk.instrumentation import get_tracer
|
15
17
|
from acp_sdk.models import (
|
16
18
|
ACPError,
|
@@ -21,7 +23,6 @@ from acp_sdk.models import (
|
|
21
23
|
AwaitResume,
|
22
24
|
Error,
|
23
25
|
Event,
|
24
|
-
Message,
|
25
26
|
Run,
|
26
27
|
RunCancelResponse,
|
27
28
|
RunCreatedEvent,
|
@@ -33,9 +34,6 @@ from acp_sdk.models import (
|
|
33
34
|
RunResumeResponse,
|
34
35
|
SessionId,
|
35
36
|
)
|
36
|
-
from acp_sdk.models.models import MessagePart
|
37
|
-
|
38
|
-
Input = list[Message] | Message | list[MessagePart] | MessagePart | list[str] | str
|
39
37
|
|
40
38
|
|
41
39
|
class Client:
|
@@ -122,12 +120,17 @@ class Client:
|
|
122
120
|
response = AgentReadResponse.model_validate(response.json())
|
123
121
|
return Agent(**response.model_dump())
|
124
122
|
|
123
|
+
async def ping(self) -> bool:
|
124
|
+
response = await self._client.get("/healthcheck")
|
125
|
+
self._raise_error(response)
|
126
|
+
return response.json() == "OK"
|
127
|
+
|
125
128
|
async def run_sync(self, input: Input, *, agent: AgentName) -> Run:
|
126
129
|
response = await self._client.post(
|
127
130
|
"/runs",
|
128
131
|
content=RunCreateRequest(
|
129
132
|
agent_name=agent,
|
130
|
-
input=
|
133
|
+
input=input_to_messages(input),
|
131
134
|
mode=RunMode.SYNC,
|
132
135
|
session_id=self._session_id,
|
133
136
|
).model_dump_json(),
|
@@ -142,7 +145,7 @@ class Client:
|
|
142
145
|
"/runs",
|
143
146
|
content=RunCreateRequest(
|
144
147
|
agent_name=agent,
|
145
|
-
input=
|
148
|
+
input=input_to_messages(input),
|
146
149
|
mode=RunMode.ASYNC,
|
147
150
|
session_id=self._session_id,
|
148
151
|
).model_dump_json(),
|
@@ -159,7 +162,7 @@ class Client:
|
|
159
162
|
"/runs",
|
160
163
|
content=RunCreateRequest(
|
161
164
|
agent_name=agent,
|
162
|
-
input=
|
165
|
+
input=input_to_messages(input),
|
163
166
|
mode=RunMode.STREAM,
|
164
167
|
session_id=self._session_id,
|
165
168
|
).model_dump_json(),
|
@@ -227,24 +230,3 @@ class Client:
|
|
227
230
|
|
228
231
|
def _set_session(self, run: Run) -> None:
|
229
232
|
self._session_id = run.session_id
|
230
|
-
|
231
|
-
def _unify_inputs(self, input: Input) -> list[Message]:
|
232
|
-
if isinstance(input, list):
|
233
|
-
if len(input) == 0:
|
234
|
-
return []
|
235
|
-
if all(isinstance(item, Message) for item in input):
|
236
|
-
return input
|
237
|
-
elif all(isinstance(item, MessagePart) for item in input):
|
238
|
-
return [Message(parts=input)]
|
239
|
-
elif all(isinstance(item, str) for item in input):
|
240
|
-
return [Message(parts=[MessagePart(content=content) for content in input])]
|
241
|
-
else:
|
242
|
-
raise RuntimeError("List with mixed types is not supported")
|
243
|
-
else:
|
244
|
-
if isinstance(input, str):
|
245
|
-
input = MessagePart(content=input)
|
246
|
-
if isinstance(input, MessagePart):
|
247
|
-
input = Message(parts=[input])
|
248
|
-
if isinstance(input, Message):
|
249
|
-
input = [input]
|
250
|
-
return input
|
acp_sdk/client/types.py
ADDED
acp_sdk/client/utils.py
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
from acp_sdk.client.types import Input
|
2
|
+
from acp_sdk.models.models import Message, MessagePart
|
3
|
+
|
4
|
+
|
5
|
+
def input_to_messages(input: Input) -> list[Message]:
|
6
|
+
if isinstance(input, list):
|
7
|
+
if len(input) == 0:
|
8
|
+
return []
|
9
|
+
if all(isinstance(item, Message) for item in input):
|
10
|
+
return input
|
11
|
+
elif all(isinstance(item, MessagePart) for item in input):
|
12
|
+
return [Message(parts=input)]
|
13
|
+
elif all(isinstance(item, str) for item in input):
|
14
|
+
return [Message(parts=[MessagePart(content=content) for content in input])]
|
15
|
+
else:
|
16
|
+
raise TypeError("List with mixed types is not supported")
|
17
|
+
else:
|
18
|
+
if isinstance(input, str):
|
19
|
+
input = MessagePart(content=input)
|
20
|
+
if isinstance(input, MessagePart):
|
21
|
+
input = Message(parts=[input])
|
22
|
+
if isinstance(input, Message):
|
23
|
+
input = [input]
|
24
|
+
return input
|
acp_sdk/models/models.py
CHANGED
@@ -47,6 +47,11 @@ class Dependency(BaseModel):
|
|
47
47
|
name: str
|
48
48
|
|
49
49
|
|
50
|
+
class Capability(BaseModel):
|
51
|
+
name: str
|
52
|
+
description: str
|
53
|
+
|
54
|
+
|
50
55
|
class Metadata(BaseModel):
|
51
56
|
annotations: AnyModel | None = None
|
52
57
|
documentation: str | None = None
|
@@ -54,7 +59,8 @@ class Metadata(BaseModel):
|
|
54
59
|
programming_language: str | None = None
|
55
60
|
natural_languages: list[str] | None = None
|
56
61
|
framework: str | None = None
|
57
|
-
|
62
|
+
capabilities: list[Capability] | None = None
|
63
|
+
domains: list[str] | None = None
|
58
64
|
tags: list[str] | None = None
|
59
65
|
created_at: datetime | None = None
|
60
66
|
updated_at: datetime | None = None
|
@@ -93,7 +99,7 @@ class Message(BaseModel):
|
|
93
99
|
def __add__(self, other: "Message") -> "Message":
|
94
100
|
if not isinstance(other, Message):
|
95
101
|
raise TypeError(f"Cannot concatenate Message with {type(other).__name__}")
|
96
|
-
return Message(
|
102
|
+
return Message(parts=self.parts + other.parts)
|
97
103
|
|
98
104
|
def __str__(self) -> str:
|
99
105
|
return "".join(
|
acp_sdk/server/agent.py
CHANGED
@@ -32,14 +32,14 @@ class Agent(abc.ABC):
|
|
32
32
|
|
33
33
|
@abc.abstractmethod
|
34
34
|
def run(
|
35
|
-
self,
|
35
|
+
self, input: list[Message], context: Context
|
36
36
|
) -> (
|
37
37
|
AsyncGenerator[RunYield, RunYieldResume] | Generator[RunYield, RunYieldResume] | Coroutine[RunYield] | RunYield
|
38
38
|
):
|
39
39
|
pass
|
40
40
|
|
41
41
|
async def execute(
|
42
|
-
self,
|
42
|
+
self, input: list[Message], session_id: SessionId | None, executor: ThreadPoolExecutor
|
43
43
|
) -> AsyncGenerator[RunYield, RunYieldResume]:
|
44
44
|
yield_queue: janus.Queue[RunYield] = janus.Queue()
|
45
45
|
yield_resume_queue: janus.Queue[RunYieldResume] = janus.Queue()
|
@@ -49,13 +49,13 @@ class Agent(abc.ABC):
|
|
49
49
|
)
|
50
50
|
|
51
51
|
if inspect.isasyncgenfunction(self.run):
|
52
|
-
run = asyncio.create_task(self._run_async_gen(
|
52
|
+
run = asyncio.create_task(self._run_async_gen(input, context))
|
53
53
|
elif inspect.iscoroutinefunction(self.run):
|
54
|
-
run = asyncio.create_task(self._run_coro(
|
54
|
+
run = asyncio.create_task(self._run_coro(input, context))
|
55
55
|
elif inspect.isgeneratorfunction(self.run):
|
56
|
-
run = asyncio.get_running_loop().run_in_executor(executor, self._run_gen,
|
56
|
+
run = asyncio.get_running_loop().run_in_executor(executor, self._run_gen, input, context)
|
57
57
|
else:
|
58
|
-
run = asyncio.get_running_loop().run_in_executor(executor, self._run_func,
|
58
|
+
run = asyncio.get_running_loop().run_in_executor(executor, self._run_func, input, context)
|
59
59
|
|
60
60
|
try:
|
61
61
|
while True:
|
@@ -66,7 +66,7 @@ class Agent(abc.ABC):
|
|
66
66
|
finally:
|
67
67
|
await run # Raise exceptions
|
68
68
|
|
69
|
-
async def _run_async_gen(self, input: Message, context: Context) -> None:
|
69
|
+
async def _run_async_gen(self, input: list[Message], context: Context) -> None:
|
70
70
|
try:
|
71
71
|
gen: AsyncGenerator[RunYield, RunYieldResume] = self.run(input, context)
|
72
72
|
value = None
|
@@ -77,13 +77,13 @@ class Agent(abc.ABC):
|
|
77
77
|
finally:
|
78
78
|
context.shutdown()
|
79
79
|
|
80
|
-
async def _run_coro(self, input: Message, context: Context) -> None:
|
80
|
+
async def _run_coro(self, input: list[Message], context: Context) -> None:
|
81
81
|
try:
|
82
82
|
await context.yield_async(await self.run(input, context))
|
83
83
|
finally:
|
84
84
|
context.shutdown()
|
85
85
|
|
86
|
-
def _run_gen(self, input: Message, context: Context) -> None:
|
86
|
+
def _run_gen(self, input: list[Message], context: Context) -> None:
|
87
87
|
try:
|
88
88
|
gen: Generator[RunYield, RunYieldResume] = self.run(input, context)
|
89
89
|
value = None
|
@@ -94,7 +94,7 @@ class Agent(abc.ABC):
|
|
94
94
|
finally:
|
95
95
|
context.shutdown()
|
96
96
|
|
97
|
-
def _run_func(self, input: Message, context: Context) -> None:
|
97
|
+
def _run_func(self, input: list[Message], context: Context) -> None:
|
98
98
|
try:
|
99
99
|
context.yield_sync(self.run(input, context))
|
100
100
|
finally:
|
@@ -139,7 +139,7 @@ def agent(
|
|
139
139
|
if inspect.isasyncgenfunction(fn):
|
140
140
|
|
141
141
|
class AsyncGenDecoratorAgent(DecoratorAgentBase):
|
142
|
-
async def run(self, input: Message, context: Context) -> AsyncGenerator[RunYield, RunYieldResume]:
|
142
|
+
async def run(self, input: list[Message], context: Context) -> AsyncGenerator[RunYield, RunYieldResume]:
|
143
143
|
try:
|
144
144
|
gen: AsyncGenerator[RunYield, RunYieldResume] = (
|
145
145
|
fn(input, context) if has_context_param else fn(input)
|
@@ -154,21 +154,21 @@ def agent(
|
|
154
154
|
elif inspect.iscoroutinefunction(fn):
|
155
155
|
|
156
156
|
class CoroDecoratorAgent(DecoratorAgentBase):
|
157
|
-
async def run(self, input: Message, context: Context) -> Coroutine[RunYield]:
|
157
|
+
async def run(self, input: list[Message], context: Context) -> Coroutine[RunYield]:
|
158
158
|
return await (fn(input, context) if has_context_param else fn(input))
|
159
159
|
|
160
160
|
agent = CoroDecoratorAgent()
|
161
161
|
elif inspect.isgeneratorfunction(fn):
|
162
162
|
|
163
163
|
class GenDecoratorAgent(DecoratorAgentBase):
|
164
|
-
def run(self, input: Message, context: Context) -> Generator[RunYield, RunYieldResume]:
|
164
|
+
def run(self, input: list[Message], context: Context) -> Generator[RunYield, RunYieldResume]:
|
165
165
|
yield from (fn(input, context) if has_context_param else fn(input))
|
166
166
|
|
167
167
|
agent = GenDecoratorAgent()
|
168
168
|
else:
|
169
169
|
|
170
170
|
class FuncDecoratorAgent(DecoratorAgentBase):
|
171
|
-
def run(self, input: Message, context: Context) -> RunYield:
|
171
|
+
def run(self, input: list[Message], context: Context) -> RunYield:
|
172
172
|
return fn(input, context) if has_context_param else fn(input)
|
173
173
|
|
174
174
|
agent = FuncDecoratorAgent()
|
acp_sdk/server/app.py
CHANGED
@@ -5,7 +5,7 @@ from datetime import datetime, timedelta
|
|
5
5
|
from enum import Enum
|
6
6
|
|
7
7
|
from cachetools import TTLCache
|
8
|
-
from fastapi import FastAPI, HTTPException, status
|
8
|
+
from fastapi import Depends, FastAPI, HTTPException, status
|
9
9
|
from fastapi.encoders import jsonable_encoder
|
10
10
|
from fastapi.responses import JSONResponse, StreamingResponse
|
11
11
|
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
|
@@ -47,7 +47,12 @@ class Headers(str, Enum):
|
|
47
47
|
RUN_ID = "Run-ID"
|
48
48
|
|
49
49
|
|
50
|
-
def create_app(
|
50
|
+
def create_app(
|
51
|
+
*agents: Agent,
|
52
|
+
run_limit: int = 1000,
|
53
|
+
run_ttl: timedelta = timedelta(hours=1),
|
54
|
+
dependencies: list[Depends] | None = None,
|
55
|
+
) -> FastAPI:
|
51
56
|
executor: ThreadPoolExecutor
|
52
57
|
|
53
58
|
@asynccontextmanager
|
@@ -57,7 +62,10 @@ def create_app(*agents: Agent, run_limit: int = 1000, run_ttl: timedelta = timed
|
|
57
62
|
executor = exec
|
58
63
|
yield
|
59
64
|
|
60
|
-
app = FastAPI(
|
65
|
+
app = FastAPI(
|
66
|
+
lifespan=lifespan,
|
67
|
+
dependencies=dependencies,
|
68
|
+
)
|
61
69
|
|
62
70
|
FastAPIInstrumentor.instrument_app(app)
|
63
71
|
|
@@ -96,6 +104,10 @@ def create_app(*agents: Agent, run_limit: int = 1000, run_ttl: timedelta = timed
|
|
96
104
|
agent = find_agent(name)
|
97
105
|
return AgentModel(name=agent.name, description=agent.description, metadata=agent.metadata)
|
98
106
|
|
107
|
+
@app.get("/healthcheck")
|
108
|
+
async def healthcheck() -> str:
|
109
|
+
return "OK"
|
110
|
+
|
99
111
|
@app.post("/runs")
|
100
112
|
async def create_run(request: RunCreateRequest) -> RunCreateResponse:
|
101
113
|
agent = find_agent(request.agent_name)
|
@@ -105,7 +117,7 @@ def create_app(*agents: Agent, run_limit: int = 1000, run_ttl: timedelta = timed
|
|
105
117
|
bundle = RunBundle(
|
106
118
|
agent=agent,
|
107
119
|
run=Run(agent_name=agent.name, session_id=session.id),
|
108
|
-
|
120
|
+
input=request.input,
|
109
121
|
history=list(session.history()),
|
110
122
|
executor=executor,
|
111
123
|
)
|
acp_sdk/server/bundle.py
CHANGED
@@ -35,11 +35,11 @@ from acp_sdk.server.logging import logger
|
|
35
35
|
|
36
36
|
class RunBundle:
|
37
37
|
def __init__(
|
38
|
-
self, *, agent: Agent, run: Run,
|
38
|
+
self, *, agent: Agent, run: Run, input: list[Message], history: list[Message], executor: ThreadPoolExecutor
|
39
39
|
) -> None:
|
40
40
|
self.agent = agent
|
41
41
|
self.run = run
|
42
|
-
self.
|
42
|
+
self.input = input
|
43
43
|
self.history = history
|
44
44
|
|
45
45
|
self.stream_queue: asyncio.Queue[Event] = asyncio.Queue()
|
@@ -47,7 +47,7 @@ class RunBundle:
|
|
47
47
|
self.await_queue: asyncio.Queue[AwaitResume] = asyncio.Queue(maxsize=1)
|
48
48
|
self.await_or_terminate_event = asyncio.Event()
|
49
49
|
|
50
|
-
self.task = asyncio.create_task(self._execute(
|
50
|
+
self.task = asyncio.create_task(self._execute(input, executor=executor))
|
51
51
|
|
52
52
|
async def stream(self) -> AsyncGenerator[Event]:
|
53
53
|
while True:
|
@@ -83,7 +83,7 @@ class RunBundle:
|
|
83
83
|
async def join(self) -> None:
|
84
84
|
await self.await_or_terminate_event.wait()
|
85
85
|
|
86
|
-
async def _execute(self,
|
86
|
+
async def _execute(self, input: list[Message], *, executor: ThreadPoolExecutor) -> None:
|
87
87
|
with get_tracer().start_as_current_span("run"):
|
88
88
|
run_logger = logging.LoggerAdapter(logger, {"run_id": str(self.run.run_id)})
|
89
89
|
|
@@ -99,7 +99,7 @@ class RunBundle:
|
|
99
99
|
await self.emit(RunCreatedEvent(run=self.run))
|
100
100
|
|
101
101
|
generator = self.agent.execute(
|
102
|
-
|
102
|
+
input=self.history + input, session_id=self.run.session_id, executor=executor
|
103
103
|
)
|
104
104
|
run_logger.info("Run started")
|
105
105
|
|
acp_sdk/server/server.py
CHANGED
@@ -4,6 +4,7 @@ from collections.abc import Awaitable
|
|
4
4
|
from datetime import timedelta
|
5
5
|
from typing import Any, Callable
|
6
6
|
|
7
|
+
import requests
|
7
8
|
import uvicorn
|
8
9
|
import uvicorn.config
|
9
10
|
|
@@ -12,7 +13,9 @@ from acp_sdk.server.agent import Agent
|
|
12
13
|
from acp_sdk.server.agent import agent as agent_decorator
|
13
14
|
from acp_sdk.server.app import create_app
|
14
15
|
from acp_sdk.server.logging import configure_logger as configure_logger_func
|
16
|
+
from acp_sdk.server.logging import logger
|
15
17
|
from acp_sdk.server.telemetry import configure_telemetry as configure_telemetry_func
|
18
|
+
from acp_sdk.server.utils import async_request_with_retry
|
16
19
|
|
17
20
|
|
18
21
|
class Server:
|
@@ -43,6 +46,7 @@ class Server:
|
|
43
46
|
self,
|
44
47
|
configure_logger: bool = True,
|
45
48
|
configure_telemetry: bool = False,
|
49
|
+
self_registration: bool = True,
|
46
50
|
run_limit: int = 1000,
|
47
51
|
run_ttl: timedelta = timedelta(hours=1),
|
48
52
|
host: str = "127.0.0.1",
|
@@ -158,7 +162,14 @@ class Server:
|
|
158
162
|
h11_max_incomplete_event_size,
|
159
163
|
)
|
160
164
|
self._server = uvicorn.Server(config)
|
161
|
-
|
165
|
+
|
166
|
+
asyncio.run(self._serve(self_registration=self_registration))
|
167
|
+
|
168
|
+
async def _serve(self, self_registration: bool = True) -> None:
|
169
|
+
registration_task = asyncio.create_task(self._register_agent()) if self_registration else None
|
170
|
+
await self._server.serve()
|
171
|
+
if registration_task:
|
172
|
+
registration_task.cancel()
|
162
173
|
|
163
174
|
@property
|
164
175
|
def should_exit(self) -> bool:
|
@@ -167,3 +178,45 @@ class Server:
|
|
167
178
|
@should_exit.setter
|
168
179
|
def should_exit(self, value: bool) -> None:
|
169
180
|
self._server.should_exit = value
|
181
|
+
|
182
|
+
async def _register_agent(self) -> None:
|
183
|
+
"""If not in PRODUCTION mode, register agent to the beeai platform and provide missing env variables"""
|
184
|
+
if os.getenv("PRODUCTION_MODE", False):
|
185
|
+
logger.debug("Agent is not automatically registered in the production mode.")
|
186
|
+
return
|
187
|
+
|
188
|
+
url = os.getenv("PLATFORM_URL", "http://127.0.0.1:8333")
|
189
|
+
for agent in self._agents:
|
190
|
+
request_data = {
|
191
|
+
"location": f"http://{self._server.config.host}:{self._server.config.port}",
|
192
|
+
"id": agent.name,
|
193
|
+
}
|
194
|
+
try:
|
195
|
+
await async_request_with_retry(
|
196
|
+
lambda client, data=request_data: client.post(
|
197
|
+
f"{url}/api/v1/provider/register/unmanaged", json=data
|
198
|
+
)
|
199
|
+
)
|
200
|
+
logger.info("Agent registered to the beeai server.")
|
201
|
+
|
202
|
+
# check missing env keyes
|
203
|
+
envs_request = await async_request_with_retry(lambda client: client.get(f"{url}/api/v1/env"))
|
204
|
+
envs = envs_request.get("env")
|
205
|
+
|
206
|
+
# register all available envs
|
207
|
+
missing_keyes = []
|
208
|
+
for env in agent.metadata.model_dump().get("env", []):
|
209
|
+
server_env = envs.get(env.get("name"))
|
210
|
+
if server_env:
|
211
|
+
logger.debug(f"Env variable {env['name']} = '{server_env}' added dynamically")
|
212
|
+
os.environ[env["name"]] = server_env
|
213
|
+
elif env.get("required"):
|
214
|
+
missing_keyes.append(env)
|
215
|
+
if len(missing_keyes):
|
216
|
+
logger.error(f"Can not run agent, missing required env variables: {missing_keyes}")
|
217
|
+
raise Exception("Missing env variables")
|
218
|
+
|
219
|
+
except requests.exceptions.ConnectionError as e:
|
220
|
+
logger.warning(f"Can not reach server, check if running on {url} : {e}")
|
221
|
+
except (requests.exceptions.HTTPError, Exception) as e:
|
222
|
+
logger.warning(f"Agent can not be registered to beeai server: {e}")
|
acp_sdk/server/session.py
CHANGED
acp_sdk/server/utils.py
CHANGED
@@ -1,8 +1,13 @@
|
|
1
|
-
|
1
|
+
import asyncio
|
2
|
+
from collections.abc import AsyncGenerator, Coroutine
|
3
|
+
from typing import Any, Callable
|
2
4
|
|
5
|
+
import httpx
|
6
|
+
import requests
|
3
7
|
from pydantic import BaseModel
|
4
8
|
|
5
9
|
from acp_sdk.server.bundle import RunBundle
|
10
|
+
from acp_sdk.server.logging import logger
|
6
11
|
|
7
12
|
|
8
13
|
def encode_sse(model: BaseModel) -> str:
|
@@ -12,3 +17,33 @@ def encode_sse(model: BaseModel) -> str:
|
|
12
17
|
async def stream_sse(bundle: RunBundle) -> AsyncGenerator[str]:
|
13
18
|
async for event in bundle.stream():
|
14
19
|
yield encode_sse(event)
|
20
|
+
|
21
|
+
|
22
|
+
async def async_request_with_retry(
|
23
|
+
request_func: Callable[[httpx.AsyncClient], Coroutine[Any, Any, httpx.Response]],
|
24
|
+
max_retries: int = 5,
|
25
|
+
backoff_factor: float = 1,
|
26
|
+
) -> dict[str, Any]:
|
27
|
+
async with httpx.AsyncClient() as client:
|
28
|
+
retries = 0
|
29
|
+
while retries < max_retries:
|
30
|
+
try:
|
31
|
+
response = await request_func(client)
|
32
|
+
response.raise_for_status()
|
33
|
+
return response.json()
|
34
|
+
except httpx.HTTPStatusError as e:
|
35
|
+
if e.response.status_code in [429, 500, 502, 503, 504, 509]:
|
36
|
+
retries += 1
|
37
|
+
backoff = backoff_factor * (2 ** (retries - 1))
|
38
|
+
logger.warning(f"Request retry (try {retries}/{max_retries}), waiting {backoff} seconds...")
|
39
|
+
await asyncio.sleep(backoff)
|
40
|
+
else:
|
41
|
+
logger.debug("A non-retryable error was encountered.")
|
42
|
+
raise
|
43
|
+
except httpx.RequestError:
|
44
|
+
retries += 1
|
45
|
+
backoff = backoff_factor * (2 ** (retries - 1))
|
46
|
+
logger.warning(f"Request retry (try {retries}/{max_retries}), waiting {backoff} seconds...")
|
47
|
+
await asyncio.sleep(backoff)
|
48
|
+
|
49
|
+
raise requests.exceptions.ConnectionError(f"Request failed after {max_retries} retries.")
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: acp-sdk
|
3
|
-
Version: 0.7.
|
3
|
+
Version: 0.7.2
|
4
4
|
Summary: Agent Communication Protocol SDK
|
5
5
|
Author: IBM Corp.
|
6
6
|
Maintainer-email: Tomas Pilar <thomas7pilar@gmail.com>
|
@@ -44,9 +44,9 @@ Register an agent and run the server:
|
|
44
44
|
server = Server()
|
45
45
|
|
46
46
|
@server.agent()
|
47
|
-
async def echo(
|
47
|
+
async def echo(input: list[Message]):
|
48
48
|
"""Echoes everything"""
|
49
|
-
for message in
|
49
|
+
for message in input:
|
50
50
|
yield message
|
51
51
|
|
52
52
|
server.run(port=8000)
|
@@ -56,7 +56,7 @@ From another process, connect to the server and run the agent:
|
|
56
56
|
|
57
57
|
```py
|
58
58
|
async with Client(base_url="http://localhost:8000") as client:
|
59
|
-
run = await client.run_sync(agent="echo",
|
59
|
+
run = await client.run_sync(agent="echo", input=[Message(parts=[MessagePart(content="Howdy!")])])
|
60
60
|
print(run)
|
61
61
|
|
62
62
|
```
|
@@ -3,23 +3,25 @@ acp_sdk/instrumentation.py,sha256=JqSyvILN3sGAfOZrmckQq4-M_4_5alyPn95DK0o5lfA,16
|
|
3
3
|
acp_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
4
|
acp_sdk/version.py,sha256=Niy83rgvigB4hL_rR-O4ySvI7dj6xnqkyOe_JTymi9s,73
|
5
5
|
acp_sdk/client/__init__.py,sha256=Bca1DORrswxzZsrR2aUFpATuNG2xNSmYvF1Z2WJaVbc,51
|
6
|
-
acp_sdk/client/client.py,sha256=
|
6
|
+
acp_sdk/client/client.py,sha256=WJNKGCml3A3zrOdSOIC8cS5s-ySfWLDWSuvAr9y25x8,8504
|
7
|
+
acp_sdk/client/types.py,sha256=_H6zYt-2OHOOYRtssRnbDIiwmgsl2-KIXc9lb-mJLFA,133
|
8
|
+
acp_sdk/client/utils.py,sha256=2jhJyrPJmVFRoDJh0q_JMqOMlC3IxCh-6HXed-PIZS8,924
|
7
9
|
acp_sdk/models/__init__.py,sha256=numSDBDT1QHx7n_Y3Deb5VOvKWcUBxbOEaMwQBSRHxc,151
|
8
10
|
acp_sdk/models/errors.py,sha256=rEyaMVvQuBi7fwWe_d0PGGySYsD3FZTluQ-SkC0yhAs,444
|
9
|
-
acp_sdk/models/models.py,sha256=
|
11
|
+
acp_sdk/models/models.py,sha256=aA_ylU-Z0ZAlqPB8j-JfqJ2q516pgvTIyZ0wbfY8-UU,6654
|
10
12
|
acp_sdk/models/schemas.py,sha256=OQ2bEfwFFZ1D9mhpgzfPBCcnPNvbl4qRfCWChH2wCro,644
|
11
13
|
acp_sdk/server/__init__.py,sha256=mxBBBFaZuMEUENRMLwp1XZkuLeT9QghcFmNvjnqvAAU,377
|
12
|
-
acp_sdk/server/agent.py,sha256=
|
13
|
-
acp_sdk/server/app.py,sha256=
|
14
|
-
acp_sdk/server/bundle.py,sha256=
|
14
|
+
acp_sdk/server/agent.py,sha256=wvwpi83osmW7zQWxVnzyVMXIvzOswAfhKWHscVIldhA,6245
|
15
|
+
acp_sdk/server/app.py,sha256=tMWbtJgEEqIYe6gtJiVRCzt0OTTATsLLZt51SfBIqLc,6806
|
16
|
+
acp_sdk/server/bundle.py,sha256=P3DfmzQOewsK8jr8yG8_RsGSrXQI4PsZCQbspVxKlus,6552
|
15
17
|
acp_sdk/server/context.py,sha256=MgnLV6qcDIhc_0BjW7r4Jj1tHts4ZuwpdTGIBnz2Mgo,1036
|
16
18
|
acp_sdk/server/errors.py,sha256=GSO8yYIqEeX8Y4Lz86ks35dMTHiQiXuOrLYYx0eXsbI,2110
|
17
19
|
acp_sdk/server/logging.py,sha256=Oc8yZigCsuDnHHPsarRzu0RX3NKaLEgpELM2yovGKDI,411
|
18
|
-
acp_sdk/server/server.py,sha256=
|
19
|
-
acp_sdk/server/session.py,sha256=
|
20
|
+
acp_sdk/server/server.py,sha256=3b_nhbDR_VUz9lneJow4Jpw-6cF2xf2k94grYmfbm1E,8144
|
21
|
+
acp_sdk/server/session.py,sha256=fCRPHc2sQrz4G0n25I1S4LB7mI1wo0yYQJ1V9WRWH3g,627
|
20
22
|
acp_sdk/server/telemetry.py,sha256=1BUxNg-xL_Vqgs27PDWNc3HikrQW2lidAtT_FKlp_Qk,1833
|
21
23
|
acp_sdk/server/types.py,sha256=teBNRWSks8XP1SCQKGEtbNWQahVD3RAOPnysTxcQPxI,292
|
22
|
-
acp_sdk/server/utils.py,sha256=
|
23
|
-
acp_sdk-0.7.
|
24
|
-
acp_sdk-0.7.
|
25
|
-
acp_sdk-0.7.
|
24
|
+
acp_sdk/server/utils.py,sha256=y-DDWv_QI25OJJYP5cni2FzfolKXtH3S2SYOm6OL_gc,1835
|
25
|
+
acp_sdk-0.7.2.dist-info/METADATA,sha256=-g7nf-ZOtPPNVh46fhjUjYljp6Lodnu4qSwD_vSJqbI,1651
|
26
|
+
acp_sdk-0.7.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
27
|
+
acp_sdk-0.7.2.dist-info/RECORD,,
|
File without changes
|