acp-sdk 0.1.0rc7__tar.gz → 0.1.0rc8__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.
- {acp_sdk-0.1.0rc7 → acp_sdk-0.1.0rc8}/PKG-INFO +5 -5
- {acp_sdk-0.1.0rc7 → acp_sdk-0.1.0rc8}/README.md +4 -4
- {acp_sdk-0.1.0rc7 → acp_sdk-0.1.0rc8}/examples/clients/advanced.py +2 -2
- acp_sdk-0.1.0rc8/examples/clients/session.py +18 -0
- {acp_sdk-0.1.0rc7 → acp_sdk-0.1.0rc8}/examples/clients/simple.py +2 -2
- {acp_sdk-0.1.0rc7 → acp_sdk-0.1.0rc8}/examples/clients/stream.py +1 -1
- {acp_sdk-0.1.0rc7 → acp_sdk-0.1.0rc8}/examples/servers/awaiting.py +2 -2
- {acp_sdk-0.1.0rc7 → acp_sdk-0.1.0rc8}/examples/servers/echo.py +4 -4
- {acp_sdk-0.1.0rc7 → acp_sdk-0.1.0rc8}/pyproject.toml +1 -1
- acp_sdk-0.1.0rc8/pytest.ini +5 -0
- acp_sdk-0.1.0rc8/src/acp_sdk/__init__.py +2 -0
- {acp_sdk-0.1.0rc7 → acp_sdk-0.1.0rc8}/src/acp_sdk/client/client.py +49 -10
- {acp_sdk-0.1.0rc7 → acp_sdk-0.1.0rc8}/src/acp_sdk/models/models.py +11 -1
- {acp_sdk-0.1.0rc7 → acp_sdk-0.1.0rc8}/src/acp_sdk/models/schemas.py +1 -1
- {acp_sdk-0.1.0rc7 → acp_sdk-0.1.0rc8}/src/acp_sdk/server/agent.py +6 -11
- {acp_sdk-0.1.0rc7 → acp_sdk-0.1.0rc8}/src/acp_sdk/server/app.py +17 -15
- {acp_sdk-0.1.0rc7 → acp_sdk-0.1.0rc8}/src/acp_sdk/server/bundle.py +28 -13
- {acp_sdk-0.1.0rc7 → acp_sdk-0.1.0rc8}/src/acp_sdk/server/server.py +24 -2
- acp_sdk-0.1.0rc8/src/acp_sdk/server/session.py +21 -0
- {acp_sdk-0.1.0rc7 → acp_sdk-0.1.0rc8}/src/acp_sdk/server/telemetry.py +7 -2
- acp_sdk-0.1.0rc8/src/acp_sdk/version.py +3 -0
- acp_sdk-0.1.0rc8/tests/conftest.py +4 -0
- acp_sdk-0.1.0rc8/tests/e2e/config.py +2 -0
- acp_sdk-0.1.0rc8/tests/e2e/fixtures/__init__.py +0 -0
- acp_sdk-0.1.0rc8/tests/e2e/fixtures/client.py +12 -0
- acp_sdk-0.1.0rc8/tests/e2e/fixtures/server.py +38 -0
- acp_sdk-0.1.0rc8/tests/e2e/test_suites/__init__.py +0 -0
- acp_sdk-0.1.0rc8/tests/e2e/test_suites/test_runs.py +89 -0
- acp_sdk-0.1.0rc7/examples/servers/multi-echo.py +0 -57
- acp_sdk-0.1.0rc7/src/acp_sdk/__init__.py +0 -1
- acp_sdk-0.1.0rc7/tests/test_e2e.py +0 -113
- {acp_sdk-0.1.0rc7 → acp_sdk-0.1.0rc8}/.gitignore +0 -0
- {acp_sdk-0.1.0rc7 → acp_sdk-0.1.0rc8}/.python-version +0 -0
- {acp_sdk-0.1.0rc7 → acp_sdk-0.1.0rc8}/src/acp_sdk/client/__init__.py +0 -0
- {acp_sdk-0.1.0rc7 → acp_sdk-0.1.0rc8}/src/acp_sdk/models/__init__.py +0 -0
- {acp_sdk-0.1.0rc7 → acp_sdk-0.1.0rc8}/src/acp_sdk/models/errors.py +0 -0
- {acp_sdk-0.1.0rc7 → acp_sdk-0.1.0rc8}/src/acp_sdk/py.typed +0 -0
- {acp_sdk-0.1.0rc7 → acp_sdk-0.1.0rc8}/src/acp_sdk/server/__init__.py +0 -0
- {acp_sdk-0.1.0rc7 → acp_sdk-0.1.0rc8}/src/acp_sdk/server/context.py +0 -0
- {acp_sdk-0.1.0rc7 → acp_sdk-0.1.0rc8}/src/acp_sdk/server/errors.py +0 -0
- {acp_sdk-0.1.0rc7 → acp_sdk-0.1.0rc8}/src/acp_sdk/server/logging.py +0 -0
- {acp_sdk-0.1.0rc7 → acp_sdk-0.1.0rc8}/src/acp_sdk/server/types.py +0 -0
- {acp_sdk-0.1.0rc7 → acp_sdk-0.1.0rc8}/src/acp_sdk/server/utils.py +0 -0
- {acp_sdk-0.1.0rc7/tests → acp_sdk-0.1.0rc8/tests/e2e}/__init__.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: acp-sdk
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.0rc8
|
4
4
|
Summary: Agent Communication Protocol SDK
|
5
5
|
Requires-Python: <4.0,>=3.11
|
6
6
|
Requires-Dist: opentelemetry-api>=1.31.1
|
@@ -47,7 +47,7 @@ The `client` submodule exposes [httpx]() based client with simple methods for co
|
|
47
47
|
|
48
48
|
```python
|
49
49
|
async with Client(base_url="http://localhost:8000") as client:
|
50
|
-
run = await client.run_sync(agent="echo",
|
50
|
+
run = await client.run_sync(agent="echo", inputs=[Message(TextMessagePart(content="Howdy!"))])
|
51
51
|
print(run.output)
|
52
52
|
```
|
53
53
|
|
@@ -59,13 +59,13 @@ The `server` submodule exposes [fastapi] application factory that makes it easy
|
|
59
59
|
server = Server()
|
60
60
|
|
61
61
|
@server.agent()
|
62
|
-
async def echo(
|
62
|
+
async def echo(inputs: list[Message], context: Context) -> AsyncGenerator[RunYield, RunYieldResume]:
|
63
63
|
"""Echoes everything"""
|
64
|
-
for
|
64
|
+
for message in inputs:
|
65
65
|
await asyncio.sleep(0.5)
|
66
66
|
yield {"thought": "I should echo everyting"}
|
67
67
|
await asyncio.sleep(0.5)
|
68
|
-
yield
|
68
|
+
yield message
|
69
69
|
|
70
70
|
|
71
71
|
server.run()
|
@@ -28,7 +28,7 @@ The `client` submodule exposes [httpx]() based client with simple methods for co
|
|
28
28
|
|
29
29
|
```python
|
30
30
|
async with Client(base_url="http://localhost:8000") as client:
|
31
|
-
run = await client.run_sync(agent="echo",
|
31
|
+
run = await client.run_sync(agent="echo", inputs=[Message(TextMessagePart(content="Howdy!"))])
|
32
32
|
print(run.output)
|
33
33
|
```
|
34
34
|
|
@@ -40,13 +40,13 @@ The `server` submodule exposes [fastapi] application factory that makes it easy
|
|
40
40
|
server = Server()
|
41
41
|
|
42
42
|
@server.agent()
|
43
|
-
async def echo(
|
43
|
+
async def echo(inputs: list[Message], context: Context) -> AsyncGenerator[RunYield, RunYieldResume]:
|
44
44
|
"""Echoes everything"""
|
45
|
-
for
|
45
|
+
for message in inputs:
|
46
46
|
await asyncio.sleep(0.5)
|
47
47
|
yield {"thought": "I should echo everyting"}
|
48
48
|
await asyncio.sleep(0.5)
|
49
|
-
yield
|
49
|
+
yield message
|
50
50
|
|
51
51
|
|
52
52
|
server.run()
|
@@ -13,8 +13,8 @@ async def example() -> None:
|
|
13
13
|
# Additional client configuration
|
14
14
|
)
|
15
15
|
) as client:
|
16
|
-
run = await client.run_sync(agent="echo",
|
17
|
-
print(run.
|
16
|
+
run = await client.run_sync(agent="echo", inputs=[Message(TextMessagePart(content="Howdy!"))])
|
17
|
+
print(run.outputs)
|
18
18
|
|
19
19
|
|
20
20
|
if __name__ == "__main__":
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import asyncio
|
2
|
+
|
3
|
+
from acp_sdk.client import Client
|
4
|
+
from acp_sdk.models import (
|
5
|
+
Message,
|
6
|
+
TextMessagePart,
|
7
|
+
)
|
8
|
+
|
9
|
+
|
10
|
+
async def example() -> None:
|
11
|
+
async with Client(base_url="http://localhost:8000") as client, client.session() as session:
|
12
|
+
run = await session.run_sync(agent="historian", inputs=[Message(TextMessagePart(content="Howdy!"))])
|
13
|
+
run = await session.run_sync(agent="historian", inputs=[Message(TextMessagePart(content="Howdy again!"))])
|
14
|
+
print(run.outputs)
|
15
|
+
|
16
|
+
|
17
|
+
if __name__ == "__main__":
|
18
|
+
asyncio.run(example())
|
@@ -9,8 +9,8 @@ from acp_sdk.models import (
|
|
9
9
|
|
10
10
|
async def example() -> None:
|
11
11
|
async with Client(base_url="http://localhost:8000") as client:
|
12
|
-
run = await client.run_sync(agent="echo",
|
13
|
-
print(run.
|
12
|
+
run = await client.run_sync(agent="echo", inputs=[Message(TextMessagePart(content="Howdy!"))])
|
13
|
+
print(run.outputs)
|
14
14
|
|
15
15
|
|
16
16
|
if __name__ == "__main__":
|
@@ -6,7 +6,7 @@ from acp_sdk.models import Message, TextMessagePart
|
|
6
6
|
|
7
7
|
async def example() -> None:
|
8
8
|
async with Client(base_url="http://localhost:8000") as client:
|
9
|
-
async for event in client.run_stream(agent="echo",
|
9
|
+
async for event in client.run_stream(agent="echo", inputs=[Message(TextMessagePart(content="Howdy!"))]):
|
10
10
|
print(event)
|
11
11
|
|
12
12
|
|
@@ -13,11 +13,11 @@ server = Server()
|
|
13
13
|
|
14
14
|
|
15
15
|
@server.agent()
|
16
|
-
async def awaiting(
|
16
|
+
async def awaiting(inputs: list[Message], context: Context) -> AsyncGenerator[Message | Await | Any, AwaitResume]:
|
17
17
|
"""Greets and awaits for more data"""
|
18
18
|
yield Message(TextMessagePart(content="Hello!"))
|
19
19
|
data = yield Await()
|
20
20
|
yield Message(TextMessagePart(content=f"Thanks for {data}"))
|
21
21
|
|
22
22
|
|
23
|
-
server()
|
23
|
+
server.run()
|
@@ -10,13 +10,13 @@ server = Server()
|
|
10
10
|
|
11
11
|
|
12
12
|
@server.agent()
|
13
|
-
async def echo(
|
13
|
+
async def echo(inputs: list[Message], context: Context) -> AsyncGenerator[RunYield, RunYieldResume]:
|
14
14
|
"""Echoes everything"""
|
15
|
-
for
|
15
|
+
for message in inputs:
|
16
16
|
await asyncio.sleep(0.5)
|
17
17
|
yield {"thought": "I should echo everyting"}
|
18
18
|
await asyncio.sleep(0.5)
|
19
|
-
yield
|
19
|
+
yield message
|
20
20
|
|
21
21
|
|
22
|
-
server()
|
22
|
+
server.run()
|
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
import uuid
|
2
|
+
from collections.abc import AsyncGenerator, AsyncIterator
|
3
|
+
from contextlib import asynccontextmanager
|
2
4
|
from types import TracebackType
|
3
5
|
from typing import Self
|
4
6
|
|
@@ -14,6 +16,7 @@ from acp_sdk.models import (
|
|
14
16
|
AgentReadResponse,
|
15
17
|
AgentsListResponse,
|
16
18
|
AwaitResume,
|
19
|
+
CreatedEvent,
|
17
20
|
Error,
|
18
21
|
Message,
|
19
22
|
Run,
|
@@ -25,12 +28,20 @@ from acp_sdk.models import (
|
|
25
28
|
RunMode,
|
26
29
|
RunResumeRequest,
|
27
30
|
RunResumeResponse,
|
31
|
+
SessionId,
|
28
32
|
)
|
29
33
|
|
30
34
|
|
31
35
|
class Client:
|
32
|
-
def __init__(
|
36
|
+
def __init__(
|
37
|
+
self,
|
38
|
+
*,
|
39
|
+
base_url: httpx.URL | str = "",
|
40
|
+
session_id: SessionId | None = None,
|
41
|
+
client: httpx.AsyncClient | None = None,
|
42
|
+
) -> None:
|
33
43
|
self.base_url = base_url
|
44
|
+
self.session_id = session_id
|
34
45
|
|
35
46
|
self._client = self._init_client(client)
|
36
47
|
|
@@ -51,6 +62,10 @@ class Client:
|
|
51
62
|
) -> None:
|
52
63
|
await self._client.__aexit__(exc_type, exc_value, traceback)
|
53
64
|
|
65
|
+
@asynccontextmanager
|
66
|
+
async def session(self, session_id: SessionId | None = None) -> AsyncGenerator[Self]:
|
67
|
+
yield Client(client=self._client, session_id=session_id or uuid.uuid4())
|
68
|
+
|
54
69
|
async def agents(self) -> AsyncIterator[Agent]:
|
55
70
|
response = await self._client.get("/agents")
|
56
71
|
self._raise_error(response)
|
@@ -62,30 +77,51 @@ class Client:
|
|
62
77
|
self._raise_error(response)
|
63
78
|
return AgentReadResponse.model_validate(response.json())
|
64
79
|
|
65
|
-
async def run_sync(self, *, agent: AgentName,
|
80
|
+
async def run_sync(self, *, agent: AgentName, inputs: list[Message]) -> Run:
|
66
81
|
response = await self._client.post(
|
67
82
|
"/runs",
|
68
|
-
|
83
|
+
content=RunCreateRequest(
|
84
|
+
agent_name=agent,
|
85
|
+
inputs=inputs,
|
86
|
+
mode=RunMode.SYNC,
|
87
|
+
session_id=self.session_id,
|
88
|
+
).model_dump_json(),
|
69
89
|
)
|
70
90
|
self._raise_error(response)
|
71
|
-
|
91
|
+
response = RunCreateResponse.model_validate(response.json())
|
92
|
+
self._set_session(response)
|
93
|
+
return response
|
72
94
|
|
73
|
-
async def run_async(self, *, agent: AgentName,
|
95
|
+
async def run_async(self, *, agent: AgentName, inputs: list[Message]) -> Run:
|
74
96
|
response = await self._client.post(
|
75
97
|
"/runs",
|
76
|
-
|
98
|
+
content=RunCreateRequest(
|
99
|
+
agent_name=agent,
|
100
|
+
inputs=inputs,
|
101
|
+
mode=RunMode.ASYNC,
|
102
|
+
session_id=self.session_id,
|
103
|
+
).model_dump_json(),
|
77
104
|
)
|
78
105
|
self._raise_error(response)
|
79
|
-
|
106
|
+
response = RunCreateResponse.model_validate(response.json())
|
107
|
+
self._set_session(response)
|
108
|
+
return response
|
80
109
|
|
81
|
-
async def run_stream(self, *, agent: AgentName,
|
110
|
+
async def run_stream(self, *, agent: AgentName, inputs: list[Message]) -> AsyncIterator[RunEvent]:
|
82
111
|
async with aconnect_sse(
|
83
112
|
self._client,
|
84
113
|
"POST",
|
85
114
|
"/runs",
|
86
|
-
|
115
|
+
content=RunCreateRequest(
|
116
|
+
agent_name=agent,
|
117
|
+
inputs=inputs,
|
118
|
+
mode=RunMode.STREAM,
|
119
|
+
session_id=self.session_id,
|
120
|
+
).model_dump_json(),
|
87
121
|
) as event_source:
|
88
122
|
async for event in self._validate_stream(event_source):
|
123
|
+
if isinstance(event, CreatedEvent):
|
124
|
+
self._set_session(event.run)
|
89
125
|
yield event
|
90
126
|
|
91
127
|
async def run_status(self, *, run_id: RunId) -> Run:
|
@@ -137,3 +173,6 @@ class Client:
|
|
137
173
|
response.raise_for_status()
|
138
174
|
except httpx.HTTPError:
|
139
175
|
raise ACPError(Error.model_validate(response.json()))
|
176
|
+
|
177
|
+
def _set_session(self, run: Run) -> None:
|
178
|
+
self.session_id = run.session_id
|
@@ -91,13 +91,18 @@ class AwaitResume(BaseModel):
|
|
91
91
|
pass
|
92
92
|
|
93
93
|
|
94
|
+
class Artifact(BaseModel):
|
95
|
+
pass
|
96
|
+
|
97
|
+
|
94
98
|
class Run(BaseModel):
|
95
99
|
run_id: RunId = Field(default_factory=uuid.uuid4)
|
96
100
|
agent_name: AgentName
|
97
101
|
session_id: SessionId | None = None
|
98
102
|
status: RunStatus = RunStatus.CREATED
|
99
103
|
await_: Await | None = Field(None, alias="await")
|
100
|
-
|
104
|
+
outputs: list[Message] = []
|
105
|
+
artifacts: list[Artifact] = []
|
101
106
|
error: Error | None = None
|
102
107
|
|
103
108
|
model_config = ConfigDict(populate_by_name=True)
|
@@ -117,6 +122,11 @@ class MessageEvent(BaseModel):
|
|
117
122
|
message: Message
|
118
123
|
|
119
124
|
|
125
|
+
class ArtifactEvent(BaseModel):
|
126
|
+
type: Literal["artifact"] = "artifact"
|
127
|
+
artifact: Artifact
|
128
|
+
|
129
|
+
|
120
130
|
class AwaitEvent(BaseModel):
|
121
131
|
type: Literal["await"] = "await"
|
122
132
|
await_: Await | None = Field(alias="await")
|
@@ -31,19 +31,14 @@ class Agent(abc.ABC):
|
|
31
31
|
|
32
32
|
@abc.abstractmethod
|
33
33
|
def run(
|
34
|
-
self,
|
34
|
+
self, inputs: list[Message], context: Context
|
35
35
|
) -> (
|
36
36
|
AsyncGenerator[RunYield, RunYieldResume] | Generator[RunYield, RunYieldResume] | Coroutine[RunYield] | RunYield
|
37
37
|
):
|
38
38
|
pass
|
39
39
|
|
40
|
-
async def session(self, session_id: SessionId | None) -> SessionId | None:
|
41
|
-
if session_id:
|
42
|
-
raise NotImplementedError()
|
43
|
-
return None
|
44
|
-
|
45
40
|
async def execute(
|
46
|
-
self,
|
41
|
+
self, inputs: list[Message], session_id: SessionId | None, executor: ThreadPoolExecutor
|
47
42
|
) -> AsyncGenerator[RunYield, RunYieldResume]:
|
48
43
|
yield_queue: janus.Queue[RunYield] = janus.Queue()
|
49
44
|
yield_resume_queue: janus.Queue[RunYieldResume] = janus.Queue()
|
@@ -53,13 +48,13 @@ class Agent(abc.ABC):
|
|
53
48
|
)
|
54
49
|
|
55
50
|
if inspect.isasyncgenfunction(self.run):
|
56
|
-
run = asyncio.create_task(self._run_async_gen(
|
51
|
+
run = asyncio.create_task(self._run_async_gen(inputs, context))
|
57
52
|
elif inspect.iscoroutinefunction(self.run):
|
58
|
-
run = asyncio.create_task(self._run_coro(
|
53
|
+
run = asyncio.create_task(self._run_coro(inputs, context))
|
59
54
|
elif inspect.isgeneratorfunction(self.run):
|
60
|
-
run = asyncio.get_running_loop().run_in_executor(executor, self._run_gen,
|
55
|
+
run = asyncio.get_running_loop().run_in_executor(executor, self._run_gen, inputs, context)
|
61
56
|
else:
|
62
|
-
run = asyncio.get_running_loop().run_in_executor(executor, self._run_func,
|
57
|
+
run = asyncio.get_running_loop().run_in_executor(executor, self._run_func, inputs, context)
|
63
58
|
|
64
59
|
try:
|
65
60
|
while True:
|
@@ -1,4 +1,3 @@
|
|
1
|
-
import asyncio
|
2
1
|
from collections.abc import AsyncGenerator
|
3
2
|
from concurrent.futures import ThreadPoolExecutor
|
4
3
|
from contextlib import asynccontextmanager
|
@@ -25,7 +24,7 @@ from acp_sdk.models import (
|
|
25
24
|
RunReadResponse,
|
26
25
|
RunResumeRequest,
|
27
26
|
RunResumeResponse,
|
28
|
-
|
27
|
+
SessionId,
|
29
28
|
)
|
30
29
|
from acp_sdk.models.errors import ACPError
|
31
30
|
from acp_sdk.server.agent import Agent
|
@@ -38,6 +37,7 @@ from acp_sdk.server.errors import (
|
|
38
37
|
http_exception_handler,
|
39
38
|
validation_exception_handler,
|
40
39
|
)
|
40
|
+
from acp_sdk.server.session import Session
|
41
41
|
from acp_sdk.server.utils import stream_sse
|
42
42
|
|
43
43
|
|
@@ -51,7 +51,7 @@ def create_app(*agents: Agent) -> FastAPI:
|
|
51
51
|
@asynccontextmanager
|
52
52
|
async def lifespan(app: FastAPI) -> AsyncGenerator[None]:
|
53
53
|
nonlocal executor
|
54
|
-
with ThreadPoolExecutor(
|
54
|
+
with ThreadPoolExecutor() as exec:
|
55
55
|
executor = exec
|
56
56
|
yield
|
57
57
|
|
@@ -61,6 +61,7 @@ def create_app(*agents: Agent) -> FastAPI:
|
|
61
61
|
|
62
62
|
agents: dict[AgentName, Agent] = {agent.name: agent for agent in agents}
|
63
63
|
runs: dict[RunId, RunBundle] = {}
|
64
|
+
sessions: dict[SessionId, Session] = {}
|
64
65
|
|
65
66
|
app.exception_handler(ACPError)(acp_error_handler)
|
66
67
|
app.exception_handler(StarletteHTTPException)(http_exception_handler)
|
@@ -96,17 +97,20 @@ def create_app(*agents: Agent) -> FastAPI:
|
|
96
97
|
@app.post("/runs")
|
97
98
|
async def create_run(request: RunCreateRequest) -> RunCreateResponse:
|
98
99
|
agent = find_agent(request.agent_name)
|
100
|
+
|
101
|
+
session = sessions.get(request.session_id, Session()) if request.session_id else Session()
|
102
|
+
nonlocal executor
|
99
103
|
bundle = RunBundle(
|
100
104
|
agent=agent,
|
101
|
-
run=Run(
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
+
run=Run(agent_name=agent.name, session_id=session.id),
|
106
|
+
inputs=request.inputs,
|
107
|
+
history=list(session.history()),
|
108
|
+
executor=executor,
|
105
109
|
)
|
110
|
+
session.append(bundle)
|
106
111
|
|
107
|
-
nonlocal executor
|
108
|
-
bundle.task = asyncio.create_task(bundle.execute(request.input, executor=executor))
|
109
112
|
runs[bundle.run.run_id] = bundle
|
113
|
+
sessions[session.id] = session
|
110
114
|
|
111
115
|
headers = {Headers.RUN_ID: str(bundle.run.run_id)}
|
112
116
|
|
@@ -140,8 +144,7 @@ def create_app(*agents: Agent) -> FastAPI:
|
|
140
144
|
@app.post("/runs/{run_id}")
|
141
145
|
async def resume_run(run_id: RunId, request: RunResumeRequest) -> RunResumeResponse:
|
142
146
|
bundle = find_run_bundle(run_id)
|
143
|
-
bundle.
|
144
|
-
await bundle.await_queue.put(request.await_)
|
147
|
+
await bundle.resume(request.await_)
|
145
148
|
match request.mode:
|
146
149
|
case RunMode.STREAM:
|
147
150
|
return StreamingResponse(
|
@@ -164,11 +167,10 @@ def create_app(*agents: Agent) -> FastAPI:
|
|
164
167
|
bundle = find_run_bundle(run_id)
|
165
168
|
if bundle.run.status.is_terminal:
|
166
169
|
raise HTTPException(
|
167
|
-
status_code=
|
168
|
-
detail=f"Run
|
170
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
171
|
+
detail=f"Run in terminal status {bundle.run.status} can't be cancelled",
|
169
172
|
)
|
170
|
-
bundle.
|
171
|
-
bundle.run.status = RunStatus.CANCELLING
|
173
|
+
await bundle.cancel()
|
172
174
|
return JSONResponse(status_code=status.HTTP_202_ACCEPTED, content=jsonable_encoder(bundle.run))
|
173
175
|
|
174
176
|
return app
|
@@ -3,11 +3,12 @@ import logging
|
|
3
3
|
from collections.abc import AsyncGenerator
|
4
4
|
from concurrent.futures import ThreadPoolExecutor
|
5
5
|
|
6
|
-
from opentelemetry import trace
|
7
6
|
from pydantic import ValidationError
|
8
7
|
|
9
8
|
from acp_sdk.models import (
|
10
9
|
AnyModel,
|
10
|
+
Artifact,
|
11
|
+
ArtifactEvent,
|
11
12
|
Await,
|
12
13
|
AwaitEvent,
|
13
14
|
AwaitResume,
|
@@ -27,20 +28,25 @@ from acp_sdk.models import (
|
|
27
28
|
from acp_sdk.models.errors import ErrorCode
|
28
29
|
from acp_sdk.server.agent import Agent
|
29
30
|
from acp_sdk.server.logging import logger
|
31
|
+
from acp_sdk.server.telemetry import get_tracer
|
30
32
|
|
31
33
|
|
32
34
|
class RunBundle:
|
33
|
-
def __init__(
|
35
|
+
def __init__(
|
36
|
+
self, *, agent: Agent, run: Run, inputs: list[Message], history: list[Message], executor: ThreadPoolExecutor
|
37
|
+
) -> None:
|
34
38
|
self.agent = agent
|
35
39
|
self.run = run
|
36
|
-
self.
|
40
|
+
self.inputs = inputs
|
41
|
+
self.history = history
|
37
42
|
|
38
43
|
self.stream_queue: asyncio.Queue[RunEvent] = asyncio.Queue()
|
39
|
-
self.composed_message = Message()
|
40
44
|
|
41
45
|
self.await_queue: asyncio.Queue[AwaitResume] = asyncio.Queue(maxsize=1)
|
42
46
|
self.await_or_terminate_event = asyncio.Event()
|
43
47
|
|
48
|
+
self.task = asyncio.create_task(self._execute(inputs, executor=executor))
|
49
|
+
|
44
50
|
async def stream(self) -> AsyncGenerator[RunEvent]:
|
45
51
|
while True:
|
46
52
|
event = await self.stream_queue.get()
|
@@ -64,20 +70,27 @@ class RunBundle:
|
|
64
70
|
async def resume(self, resume: AwaitResume) -> None:
|
65
71
|
self.stream_queue = asyncio.Queue()
|
66
72
|
await self.await_queue.put(resume)
|
73
|
+
self.run.status = RunStatus.IN_PROGRESS
|
74
|
+
self.run.await_ = None
|
75
|
+
|
76
|
+
async def cancel(self) -> None:
|
77
|
+
self.task.cancel()
|
78
|
+
self.run.status = RunStatus.CANCELLING
|
79
|
+
self.run.await_ = None
|
67
80
|
|
68
81
|
async def join(self) -> None:
|
69
82
|
await self.await_or_terminate_event.wait()
|
70
83
|
|
71
|
-
async def
|
72
|
-
with
|
84
|
+
async def _execute(self, inputs: list[Message], *, executor: ThreadPoolExecutor) -> None:
|
85
|
+
with get_tracer().start_as_current_span("run"):
|
73
86
|
run_logger = logging.LoggerAdapter(logger, {"run_id": str(self.run.run_id)})
|
74
87
|
|
75
|
-
await self.emit(CreatedEvent(run=self.run))
|
76
88
|
try:
|
77
|
-
|
78
|
-
run_logger.info("Session loaded")
|
89
|
+
await self.emit(CreatedEvent(run=self.run))
|
79
90
|
|
80
|
-
generator = self.agent.execute(
|
91
|
+
generator = self.agent.execute(
|
92
|
+
inputs=self.history + inputs, session_id=self.run.session_id, executor=executor
|
93
|
+
)
|
81
94
|
run_logger.info("Run started")
|
82
95
|
|
83
96
|
self.run.status = RunStatus.IN_PROGRESS
|
@@ -87,8 +100,11 @@ class RunBundle:
|
|
87
100
|
while True:
|
88
101
|
next = await generator.asend(await_resume)
|
89
102
|
if isinstance(next, Message):
|
90
|
-
self.
|
103
|
+
self.run.outputs.append(next)
|
91
104
|
await self.emit(MessageEvent(message=next))
|
105
|
+
elif isinstance(next, Artifact):
|
106
|
+
self.run.artifacts.append(next)
|
107
|
+
await self.emit(ArtifactEvent(artifact=next))
|
92
108
|
elif isinstance(next, Await):
|
93
109
|
self.run.await_ = next
|
94
110
|
self.run.status = RunStatus.AWAITING
|
@@ -103,7 +119,6 @@ class RunBundle:
|
|
103
119
|
)
|
104
120
|
run_logger.info("Run awaited")
|
105
121
|
await_resume = await self.await_()
|
106
|
-
self.run.status = RunStatus.IN_PROGRESS
|
107
122
|
await self.emit(InProgressEvent(run=self.run))
|
108
123
|
run_logger.info("Run resumed")
|
109
124
|
else:
|
@@ -113,7 +128,6 @@ class RunBundle:
|
|
113
128
|
except ValidationError:
|
114
129
|
raise TypeError("Invalid yield")
|
115
130
|
except StopAsyncIteration:
|
116
|
-
self.run.output = self.composed_message
|
117
131
|
self.run.status = RunStatus.COMPLETED
|
118
132
|
await self.emit(CompletedEvent(run=self.run))
|
119
133
|
run_logger.info("Run completed")
|
@@ -126,6 +140,7 @@ class RunBundle:
|
|
126
140
|
self.run.status = RunStatus.FAILED
|
127
141
|
await self.emit(FailedEvent(run=self.run))
|
128
142
|
run_logger.exception("Run failed")
|
143
|
+
raise
|
129
144
|
finally:
|
130
145
|
self.await_or_terminate_event.set()
|
131
146
|
await self.stream_queue.put(None)
|
@@ -7,7 +7,7 @@ from typing import Any, Callable
|
|
7
7
|
import uvicorn
|
8
8
|
import uvicorn.config
|
9
9
|
|
10
|
-
from acp_sdk.models import Message
|
10
|
+
from acp_sdk.models import Message, Metadata
|
11
11
|
from acp_sdk.server.agent import Agent
|
12
12
|
from acp_sdk.server.app import create_app
|
13
13
|
from acp_sdk.server.context import Context
|
@@ -21,7 +21,13 @@ class Server:
|
|
21
21
|
self._agents: list[Agent] = []
|
22
22
|
self._server: uvicorn.Server | None = None
|
23
23
|
|
24
|
-
def agent(
|
24
|
+
def agent(
|
25
|
+
self,
|
26
|
+
name: str | None = None,
|
27
|
+
description: str | None = None,
|
28
|
+
*,
|
29
|
+
metadata: Metadata | None = None,
|
30
|
+
) -> Callable:
|
25
31
|
"""Decorator to register an agent."""
|
26
32
|
|
27
33
|
def decorator(fn: Callable) -> Callable:
|
@@ -49,6 +55,10 @@ class Server:
|
|
49
55
|
def description(self) -> str:
|
50
56
|
return description or fn.__doc__ or ""
|
51
57
|
|
58
|
+
@property
|
59
|
+
def metadata(self) -> Metadata:
|
60
|
+
return metadata or Metadata()
|
61
|
+
|
52
62
|
async def run(self, input: Message, context: Context) -> AsyncGenerator[RunYield, RunYieldResume]:
|
53
63
|
try:
|
54
64
|
gen: AsyncGenerator[RunYield, RunYieldResume] = (
|
@@ -72,6 +82,10 @@ class Server:
|
|
72
82
|
def description(self) -> str:
|
73
83
|
return description or fn.__doc__ or ""
|
74
84
|
|
85
|
+
@property
|
86
|
+
def metadata(self) -> Metadata:
|
87
|
+
return metadata or Metadata()
|
88
|
+
|
75
89
|
async def run(self, input: Message, context: Context) -> Coroutine[RunYield]:
|
76
90
|
return await (fn(input, context) if has_context_param else fn(input))
|
77
91
|
|
@@ -87,6 +101,10 @@ class Server:
|
|
87
101
|
def description(self) -> str:
|
88
102
|
return description or fn.__doc__ or ""
|
89
103
|
|
104
|
+
@property
|
105
|
+
def metadata(self) -> Metadata:
|
106
|
+
return metadata or Metadata()
|
107
|
+
|
90
108
|
def run(self, input: Message, context: Context) -> Generator[RunYield, RunYieldResume]:
|
91
109
|
yield from (fn(input, context) if has_context_param else fn(input))
|
92
110
|
|
@@ -102,6 +120,10 @@ class Server:
|
|
102
120
|
def description(self) -> str:
|
103
121
|
return description or fn.__doc__ or ""
|
104
122
|
|
123
|
+
@property
|
124
|
+
def metadata(self) -> Metadata:
|
125
|
+
return metadata or Metadata()
|
126
|
+
|
105
127
|
def run(self, input: Message, context: Context) -> RunYield:
|
106
128
|
return fn(input, context) if has_context_param else fn(input)
|
107
129
|
|
@@ -0,0 +1,21 @@
|
|
1
|
+
import uuid
|
2
|
+
from collections.abc import Iterator
|
3
|
+
|
4
|
+
from acp_sdk.models import Message, SessionId
|
5
|
+
from acp_sdk.models.models import RunStatus
|
6
|
+
from acp_sdk.server.bundle import RunBundle
|
7
|
+
|
8
|
+
|
9
|
+
class Session:
|
10
|
+
def __init__(self) -> None:
|
11
|
+
self.id: SessionId = uuid.uuid4()
|
12
|
+
self.bundles: list[RunBundle] = []
|
13
|
+
|
14
|
+
def append(self, bundle: RunBundle) -> None:
|
15
|
+
self.bundles.append(bundle)
|
16
|
+
|
17
|
+
def history(self) -> Iterator[Message]:
|
18
|
+
for bundle in self.bundles:
|
19
|
+
if bundle.run.status == RunStatus.COMPLETED:
|
20
|
+
yield from bundle.inputs
|
21
|
+
yield from bundle.run.outputs
|
@@ -1,5 +1,4 @@
|
|
1
1
|
import logging
|
2
|
-
from importlib.metadata import version
|
3
2
|
|
4
3
|
from opentelemetry import metrics, trace
|
5
4
|
from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
|
@@ -18,6 +17,8 @@ from opentelemetry.sdk.resources import (
|
|
18
17
|
from opentelemetry.sdk.trace import TracerProvider
|
19
18
|
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
20
19
|
|
20
|
+
from acp_sdk.version import __version__
|
21
|
+
|
21
22
|
root_logger = logging.getLogger()
|
22
23
|
|
23
24
|
|
@@ -28,7 +29,7 @@ def configure_telemetry() -> None:
|
|
28
29
|
attributes={
|
29
30
|
SERVICE_NAME: "acp-server",
|
30
31
|
SERVICE_NAMESPACE: "acp",
|
31
|
-
SERVICE_VERSION:
|
32
|
+
SERVICE_VERSION: __version__,
|
32
33
|
}
|
33
34
|
)
|
34
35
|
|
@@ -50,3 +51,7 @@ def configure_telemetry() -> None:
|
|
50
51
|
processor = BatchLogRecordProcessor(OTLPLogExporter())
|
51
52
|
logger_provider.add_log_record_processor(processor)
|
52
53
|
root_logger.addHandler(LoggingHandler(logger_provider=logger_provider))
|
54
|
+
|
55
|
+
|
56
|
+
def get_tracer() -> trace.Tracer:
|
57
|
+
return trace.get_tracer("acp-sdk", __version__)
|
File without changes
|
@@ -0,0 +1,12 @@
|
|
1
|
+
from collections.abc import AsyncIterator
|
2
|
+
|
3
|
+
import pytest_asyncio
|
4
|
+
from acp_sdk.client import Client
|
5
|
+
|
6
|
+
from e2e.config import Config
|
7
|
+
|
8
|
+
|
9
|
+
@pytest_asyncio.fixture
|
10
|
+
async def client() -> AsyncIterator[Client]:
|
11
|
+
async with Client(base_url=f"http://localhost:{Config.PORT}") as client:
|
12
|
+
yield client
|
@@ -0,0 +1,38 @@
|
|
1
|
+
import time
|
2
|
+
from collections.abc import AsyncGenerator, AsyncIterator, Generator
|
3
|
+
from threading import Thread
|
4
|
+
|
5
|
+
import pytest
|
6
|
+
from acp_sdk.models import Await, AwaitResume, Message, TextMessagePart
|
7
|
+
from acp_sdk.server import Context, Server
|
8
|
+
|
9
|
+
from e2e.config import Config
|
10
|
+
|
11
|
+
|
12
|
+
@pytest.fixture(scope="module")
|
13
|
+
def server() -> Generator[None]:
|
14
|
+
server = Server()
|
15
|
+
|
16
|
+
@server.agent()
|
17
|
+
async def echo(inputs: list[Message], context: Context) -> AsyncIterator[Message]:
|
18
|
+
for message in inputs:
|
19
|
+
yield message
|
20
|
+
|
21
|
+
@server.agent()
|
22
|
+
async def awaiter(inputs: list[Message], context: Context) -> AsyncGenerator[Message | Await, AwaitResume]:
|
23
|
+
yield Await()
|
24
|
+
yield Message(TextMessagePart(content="empty"))
|
25
|
+
|
26
|
+
@server.agent()
|
27
|
+
async def failer(inputs: list[Message], context: Context) -> AsyncIterator[Message]:
|
28
|
+
raise RuntimeError("Whoops")
|
29
|
+
|
30
|
+
thread = Thread(target=server.run, kwargs={"port": Config.PORT}, daemon=True)
|
31
|
+
thread.start()
|
32
|
+
|
33
|
+
time.sleep(1)
|
34
|
+
|
35
|
+
yield server
|
36
|
+
|
37
|
+
server.should_exit = True
|
38
|
+
thread.join(timeout=2)
|
File without changes
|
@@ -0,0 +1,89 @@
|
|
1
|
+
import pytest
|
2
|
+
from acp_sdk.client import Client
|
3
|
+
from acp_sdk.models import AwaitResume, CompletedEvent, CreatedEvent, Message, RunStatus, TextMessagePart
|
4
|
+
from acp_sdk.models.models import InProgressEvent
|
5
|
+
from acp_sdk.server import Server
|
6
|
+
|
7
|
+
inputs = [Message(TextMessagePart(content="Hello!"))]
|
8
|
+
|
9
|
+
|
10
|
+
@pytest.mark.asyncio
|
11
|
+
async def test_run_sync(server: Server, client: Client) -> None:
|
12
|
+
run = await client.run_sync(agent="echo", inputs=inputs)
|
13
|
+
assert run.status == RunStatus.COMPLETED
|
14
|
+
assert run.outputs == inputs
|
15
|
+
|
16
|
+
|
17
|
+
@pytest.mark.asyncio
|
18
|
+
async def test_run_async(server: Server, client: Client) -> None:
|
19
|
+
run = await client.run_async(agent="echo", inputs=inputs)
|
20
|
+
assert run.status == RunStatus.CREATED
|
21
|
+
|
22
|
+
|
23
|
+
@pytest.mark.asyncio
|
24
|
+
async def test_run_stream(server: Server, client: Client) -> None:
|
25
|
+
event_stream = [event async for event in client.run_stream(agent="echo", inputs=inputs)]
|
26
|
+
assert isinstance(event_stream[0], CreatedEvent)
|
27
|
+
assert isinstance(event_stream[-1], CompletedEvent)
|
28
|
+
|
29
|
+
|
30
|
+
@pytest.mark.asyncio
|
31
|
+
async def test_run_status(server: Server, client: Client) -> None:
|
32
|
+
run = await client.run_async(agent="echo", inputs=inputs)
|
33
|
+
while run.status in (RunStatus.CREATED, RunStatus.IN_PROGRESS):
|
34
|
+
run = await client.run_status(run_id=run.run_id)
|
35
|
+
assert run.status == RunStatus.COMPLETED
|
36
|
+
|
37
|
+
|
38
|
+
@pytest.mark.asyncio
|
39
|
+
async def test_failure(server: Server, client: Client) -> None:
|
40
|
+
run = await client.run_sync(agent="failer", inputs=inputs)
|
41
|
+
assert run.status == RunStatus.FAILED
|
42
|
+
|
43
|
+
|
44
|
+
@pytest.mark.asyncio
|
45
|
+
async def test_run_cancel(server: Server, client: Client) -> None:
|
46
|
+
run = await client.run_sync(agent="awaiter", inputs=inputs)
|
47
|
+
assert run.status == RunStatus.AWAITING
|
48
|
+
run = await client.run_cancel(run_id=run.run_id)
|
49
|
+
assert run.status == RunStatus.CANCELLING
|
50
|
+
|
51
|
+
|
52
|
+
@pytest.mark.asyncio
|
53
|
+
async def test_run_resume_sync(server: Server, client: Client) -> None:
|
54
|
+
run = await client.run_sync(agent="awaiter", inputs=inputs)
|
55
|
+
assert run.status == RunStatus.AWAITING
|
56
|
+
assert run.await_ is not None
|
57
|
+
|
58
|
+
run = await client.run_resume_sync(run_id=run.run_id, await_=AwaitResume())
|
59
|
+
assert run.status == RunStatus.COMPLETED
|
60
|
+
|
61
|
+
|
62
|
+
@pytest.mark.asyncio
|
63
|
+
async def test_run_resume_async(server: Server, client: Client) -> None:
|
64
|
+
run = await client.run_sync(agent="awaiter", inputs=inputs)
|
65
|
+
assert run.status == RunStatus.AWAITING
|
66
|
+
assert run.await_ is not None
|
67
|
+
|
68
|
+
run = await client.run_resume_async(run_id=run.run_id, await_=AwaitResume())
|
69
|
+
assert run.status == RunStatus.IN_PROGRESS
|
70
|
+
|
71
|
+
|
72
|
+
@pytest.mark.asyncio
|
73
|
+
async def test_run_resume_stream(server: Server, client: Client) -> None:
|
74
|
+
run = await client.run_sync(agent="awaiter", inputs=inputs)
|
75
|
+
assert run.status == RunStatus.AWAITING
|
76
|
+
assert run.await_ is not None
|
77
|
+
|
78
|
+
event_stream = [event async for event in client.run_resume_stream(run_id=run.run_id, await_=AwaitResume())]
|
79
|
+
assert isinstance(event_stream[0], InProgressEvent)
|
80
|
+
assert isinstance(event_stream[-1], CompletedEvent)
|
81
|
+
|
82
|
+
|
83
|
+
@pytest.mark.asyncio
|
84
|
+
async def test_run_session(server: Server, client: Client) -> None:
|
85
|
+
async with client.session() as session:
|
86
|
+
run = await session.run_sync(agent="echo", inputs=inputs)
|
87
|
+
assert run.outputs == inputs
|
88
|
+
run = await session.run_sync(agent="echo", inputs=inputs)
|
89
|
+
assert run.outputs == inputs + inputs + inputs
|
@@ -1,57 +0,0 @@
|
|
1
|
-
from collections.abc import AsyncGenerator, Generator
|
2
|
-
|
3
|
-
from acp_sdk.models import (
|
4
|
-
Message,
|
5
|
-
)
|
6
|
-
from acp_sdk.server import Agent, Context, RunYield, RunYieldResume, Server
|
7
|
-
|
8
|
-
server = Server()
|
9
|
-
|
10
|
-
|
11
|
-
@server.agent()
|
12
|
-
async def async_gen_echo(input: Message) -> AsyncGenerator[RunYield, RunYieldResume]:
|
13
|
-
"""Echoes everything"""
|
14
|
-
yield {"thought": "I should echo everyting"}
|
15
|
-
yield input
|
16
|
-
|
17
|
-
|
18
|
-
@server.agent()
|
19
|
-
async def async_echo(input: Message, context: Context) -> RunYield:
|
20
|
-
"""Echoes everything"""
|
21
|
-
await context.yield_async({"thought": "I should echo everyting"})
|
22
|
-
return input
|
23
|
-
|
24
|
-
|
25
|
-
@server.agent()
|
26
|
-
def gen_echo(input: Message) -> Generator[RunYield, RunYieldResume]:
|
27
|
-
"""Echoes everything"""
|
28
|
-
yield {"thought": "I should echo everyting"}
|
29
|
-
yield input
|
30
|
-
|
31
|
-
|
32
|
-
@server.agent()
|
33
|
-
def sync_echo(input: Message, context: Context) -> RunYield:
|
34
|
-
"""Echoes everything"""
|
35
|
-
context.yield_sync({"thought": "I should echo everyting"})
|
36
|
-
return input
|
37
|
-
|
38
|
-
|
39
|
-
class EchoAgent(Agent):
|
40
|
-
@property
|
41
|
-
def name(self) -> str:
|
42
|
-
return "instance_echo"
|
43
|
-
|
44
|
-
@property
|
45
|
-
def description(self) -> str:
|
46
|
-
return "Echoes everything"
|
47
|
-
|
48
|
-
async def run(self, input: Message, context: Context) -> AsyncGenerator[RunYield, RunYieldResume]:
|
49
|
-
"""Echoes everything"""
|
50
|
-
yield {"thought": "I should echo everyting"}
|
51
|
-
yield input
|
52
|
-
|
53
|
-
|
54
|
-
server.register(EchoAgent())
|
55
|
-
|
56
|
-
|
57
|
-
server()
|
@@ -1 +0,0 @@
|
|
1
|
-
from acp_sdk.models import * # noqa: F403
|
@@ -1,113 +0,0 @@
|
|
1
|
-
import time
|
2
|
-
from collections.abc import AsyncGenerator, AsyncIterator, Generator
|
3
|
-
from threading import Thread
|
4
|
-
|
5
|
-
import pytest
|
6
|
-
import pytest_asyncio
|
7
|
-
from acp_sdk.client import Client
|
8
|
-
from acp_sdk.models import Await, AwaitResume, CompletedEvent, CreatedEvent, Message, RunStatus, TextMessagePart
|
9
|
-
from acp_sdk.models.models import InProgressEvent
|
10
|
-
from acp_sdk.server import Context, Server
|
11
|
-
|
12
|
-
PORT = 8000
|
13
|
-
|
14
|
-
|
15
|
-
@pytest.fixture
|
16
|
-
def server() -> Generator[None]:
|
17
|
-
server = Server()
|
18
|
-
|
19
|
-
@server.agent()
|
20
|
-
async def echo(input: Message, context: Context) -> AsyncIterator[Message]:
|
21
|
-
yield input
|
22
|
-
|
23
|
-
@server.agent()
|
24
|
-
async def awaiter(input: Message, context: Context) -> AsyncGenerator[Message | Await, AwaitResume]:
|
25
|
-
yield Await()
|
26
|
-
yield Message(TextMessagePart(content="empty"))
|
27
|
-
|
28
|
-
thread = Thread(target=server.run, kwargs={"port": PORT}, daemon=True)
|
29
|
-
thread.start()
|
30
|
-
|
31
|
-
time.sleep(1)
|
32
|
-
|
33
|
-
yield
|
34
|
-
|
35
|
-
server.should_exit = True
|
36
|
-
thread.join(timeout=2)
|
37
|
-
|
38
|
-
|
39
|
-
@pytest_asyncio.fixture
|
40
|
-
async def client() -> AsyncIterator[Client]:
|
41
|
-
async with Client(base_url=f"http://localhost:{PORT}") as client:
|
42
|
-
yield client
|
43
|
-
|
44
|
-
|
45
|
-
@pytest.mark.asyncio
|
46
|
-
async def test_run_sync(server: Server, client: Client) -> None:
|
47
|
-
input = Message(TextMessagePart(content="Hello!"))
|
48
|
-
run = await client.run_sync(agent="echo", input=input)
|
49
|
-
assert run.status == RunStatus.COMPLETED
|
50
|
-
assert run.output == input
|
51
|
-
|
52
|
-
|
53
|
-
@pytest.mark.asyncio
|
54
|
-
async def test_run_async(server: Server, client: Client) -> None:
|
55
|
-
input = Message(TextMessagePart(content="Hello!"))
|
56
|
-
run = await client.run_async(agent="echo", input=input)
|
57
|
-
assert run.status == RunStatus.CREATED
|
58
|
-
assert run.output is None
|
59
|
-
|
60
|
-
|
61
|
-
@pytest.mark.asyncio
|
62
|
-
async def test_run_stream(server: Server, client: Client) -> None:
|
63
|
-
input = Message(TextMessagePart(content="Hello!"))
|
64
|
-
event_stream = [event async for event in client.run_stream(agent="echo", input=input)]
|
65
|
-
assert isinstance(event_stream[0], CreatedEvent)
|
66
|
-
assert isinstance(event_stream[-1], CompletedEvent)
|
67
|
-
|
68
|
-
|
69
|
-
@pytest.mark.asyncio
|
70
|
-
async def test_run_status(server: Server, client: Client) -> None:
|
71
|
-
input = Message(TextMessagePart(content="Hello!"))
|
72
|
-
run = await client.run_async(agent="echo", input=input)
|
73
|
-
while run.status in (RunStatus.CREATED, RunStatus.IN_PROGRESS):
|
74
|
-
run = await client.run_status(run_id=run.run_id)
|
75
|
-
assert run.status == RunStatus.COMPLETED
|
76
|
-
|
77
|
-
|
78
|
-
@pytest.mark.asyncio
|
79
|
-
async def test_run_resume_sync(server: Server, client: Client) -> None:
|
80
|
-
input = Message(TextMessagePart(content="Hello!"))
|
81
|
-
|
82
|
-
run = await client.run_sync(agent="awaiter", input=input)
|
83
|
-
assert run.status == RunStatus.AWAITING
|
84
|
-
assert run.await_ is not None
|
85
|
-
|
86
|
-
run = await client.run_resume_sync(run_id=run.run_id, await_=AwaitResume())
|
87
|
-
assert run.status == RunStatus.COMPLETED
|
88
|
-
assert run.output is not None
|
89
|
-
|
90
|
-
|
91
|
-
@pytest.mark.asyncio
|
92
|
-
async def test_run_resume_async(server: Server, client: Client) -> None:
|
93
|
-
input = Message(TextMessagePart(content="Hello!"))
|
94
|
-
|
95
|
-
run = await client.run_sync(agent="awaiter", input=input)
|
96
|
-
assert run.status == RunStatus.AWAITING
|
97
|
-
assert run.await_ is not None
|
98
|
-
|
99
|
-
run = await client.run_resume_async(run_id=run.run_id, await_=AwaitResume())
|
100
|
-
assert run.status == RunStatus.AWAITING
|
101
|
-
|
102
|
-
|
103
|
-
@pytest.mark.asyncio
|
104
|
-
async def test_run_resume_stream(server: Server, client: Client) -> None:
|
105
|
-
input = Message(TextMessagePart(content="Hello!"))
|
106
|
-
|
107
|
-
run = await client.run_sync(agent="awaiter", input=input)
|
108
|
-
assert run.status == RunStatus.AWAITING
|
109
|
-
assert run.await_ is not None
|
110
|
-
|
111
|
-
event_stream = [event async for event in client.run_resume_stream(run_id=run.run_id, await_=AwaitResume())]
|
112
|
-
assert isinstance(event_stream[0], InProgressEvent)
|
113
|
-
assert isinstance(event_stream[-1], CompletedEvent)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|