acp-sdk 1.0.0rc1__tar.gz → 1.0.0rc3__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-1.0.0rc3/.python-version +1 -0
- {acp_sdk-1.0.0rc1 → acp_sdk-1.0.0rc3}/PKG-INFO +4 -6
- {acp_sdk-1.0.0rc1 → acp_sdk-1.0.0rc3}/README.md +2 -4
- {acp_sdk-1.0.0rc1 → acp_sdk-1.0.0rc3}/examples/clients/advanced.py +4 -5
- {acp_sdk-1.0.0rc1 → acp_sdk-1.0.0rc3}/examples/clients/simple.py +3 -6
- {acp_sdk-1.0.0rc1 → acp_sdk-1.0.0rc3}/examples/clients/stream.py +2 -4
- acp_sdk-1.0.0rc3/examples/servers/awaiting.py +28 -0
- acp_sdk-1.0.0rc3/examples/servers/echo.py +29 -0
- {acp_sdk-1.0.0rc1 → acp_sdk-1.0.0rc3}/pyproject.toml +13 -2
- acp_sdk-1.0.0rc3/src/acp_sdk/__init__.py +1 -0
- acp_sdk-1.0.0rc3/src/acp_sdk/client/__init__.py +1 -0
- {acp_sdk-1.0.0rc1 → acp_sdk-1.0.0rc3}/src/acp_sdk/client/client.py +29 -25
- acp_sdk-1.0.0rc3/src/acp_sdk/models/__init__.py +2 -0
- acp_sdk-1.0.0rc3/src/acp_sdk/models/errors.py +23 -0
- {acp_sdk-1.0.0rc1/src/acp_sdk → acp_sdk-1.0.0rc3/src/acp_sdk/models}/models.py +17 -19
- acp_sdk-1.0.0rc3/src/acp_sdk/server/__init__.py +3 -0
- {acp_sdk-1.0.0rc1 → acp_sdk-1.0.0rc3}/src/acp_sdk/server/agent.py +8 -5
- {acp_sdk-1.0.0rc1 → acp_sdk-1.0.0rc3}/src/acp_sdk/server/bundle.py +22 -24
- {acp_sdk-1.0.0rc1 → acp_sdk-1.0.0rc3}/src/acp_sdk/server/context.py +1 -1
- acp_sdk-1.0.0rc3/src/acp_sdk/server/errors.py +54 -0
- acp_sdk-1.0.0rc3/src/acp_sdk/server/logging.py +16 -0
- {acp_sdk-1.0.0rc1 → acp_sdk-1.0.0rc3}/src/acp_sdk/server/server.py +49 -22
- acp_sdk-1.0.0rc3/src/acp_sdk/server/telemetry.py +52 -0
- {acp_sdk-1.0.0rc1 → acp_sdk-1.0.0rc3}/src/acp_sdk/server/utils.py +4 -2
- acp_sdk-1.0.0rc1/.python-version +0 -1
- acp_sdk-1.0.0rc1/examples/servers/echo.py +0 -22
- acp_sdk-1.0.0rc1/examples/servers/multi-agent.py +0 -169
- acp_sdk-1.0.0rc1/src/acp_sdk/__init__.py +0 -0
- acp_sdk-1.0.0rc1/src/acp_sdk/client/__init__.py +0 -1
- acp_sdk-1.0.0rc1/src/acp_sdk/server/__init__.py +0 -2
- acp_sdk-1.0.0rc1/src/acp_sdk/server/telemetry.py +0 -45
- {acp_sdk-1.0.0rc1 → acp_sdk-1.0.0rc3}/.gitignore +0 -0
- {acp_sdk-1.0.0rc1 → acp_sdk-1.0.0rc3}/src/acp_sdk/py.typed +0 -0
@@ -0,0 +1 @@
|
|
1
|
+
3.11
|
@@ -1,8 +1,8 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: acp-sdk
|
3
|
-
Version: 1.0.
|
3
|
+
Version: 1.0.0rc3
|
4
4
|
Summary: Agent Communication Protocol SDK
|
5
|
-
Requires-Python: <4.0,>=3.
|
5
|
+
Requires-Python: <4.0,>=3.11
|
6
6
|
Requires-Dist: opentelemetry-api>=1.31.1
|
7
7
|
Requires-Dist: pydantic>=2.11.1
|
8
8
|
Provides-Extra: client
|
@@ -30,16 +30,14 @@ Install using pip:
|
|
30
30
|
pip install acp-sdk
|
31
31
|
```
|
32
32
|
|
33
|
-
##
|
33
|
+
## Examples
|
34
34
|
|
35
35
|
The SDK can be used to implement both clients and servers.
|
36
36
|
|
37
37
|
### Server
|
38
38
|
|
39
|
-
To run an example server use [FastAPI CLI](https://fastapi.tiangolo.com/fastapi-cli/) or other [deployment strategies](https://fastapi.tiangolo.com/deployment/):
|
40
|
-
|
41
39
|
```shell
|
42
|
-
|
40
|
+
python examples/servers/echo.py
|
43
41
|
```
|
44
42
|
|
45
43
|
### Client
|
@@ -12,16 +12,14 @@ Install using pip:
|
|
12
12
|
pip install acp-sdk
|
13
13
|
```
|
14
14
|
|
15
|
-
##
|
15
|
+
## Examples
|
16
16
|
|
17
17
|
The SDK can be used to implement both clients and servers.
|
18
18
|
|
19
19
|
### Server
|
20
20
|
|
21
|
-
To run an example server use [FastAPI CLI](https://fastapi.tiangolo.com/fastapi-cli/) or other [deployment strategies](https://fastapi.tiangolo.com/deployment/):
|
22
|
-
|
23
21
|
```shell
|
24
|
-
|
22
|
+
python examples/servers/echo.py
|
25
23
|
```
|
26
24
|
|
27
25
|
### Client
|
@@ -1,10 +1,11 @@
|
|
1
1
|
import asyncio
|
2
|
+
|
3
|
+
import httpx
|
2
4
|
from acp_sdk.client.client import Client
|
3
5
|
from acp_sdk.models import Message, TextMessagePart
|
4
|
-
import httpx
|
5
6
|
|
6
7
|
|
7
|
-
async def example():
|
8
|
+
async def example() -> None:
|
8
9
|
async with Client(
|
9
10
|
client=httpx.AsyncClient(
|
10
11
|
base_url="http://localhost:8000",
|
@@ -12,9 +13,7 @@ async def example():
|
|
12
13
|
# Additional client configuration
|
13
14
|
)
|
14
15
|
) as client:
|
15
|
-
run = await client.run_sync(
|
16
|
-
agent="echo", input=Message(TextMessagePart(content="Howdy!"))
|
17
|
-
)
|
16
|
+
run = await client.run_sync(agent="echo", input=Message(TextMessagePart(content="Howdy!")))
|
18
17
|
print(run.output)
|
19
18
|
|
20
19
|
|
@@ -1,18 +1,15 @@
|
|
1
1
|
import asyncio
|
2
2
|
|
3
|
+
from acp_sdk.client import Client
|
3
4
|
from acp_sdk.models import (
|
4
5
|
Message,
|
5
6
|
TextMessagePart,
|
6
7
|
)
|
7
8
|
|
8
|
-
from acp_sdk.client import Client
|
9
|
-
|
10
9
|
|
11
|
-
async def example():
|
10
|
+
async def example() -> None:
|
12
11
|
async with Client(base_url="http://localhost:8000") as client:
|
13
|
-
run = await client.run_sync(
|
14
|
-
agent="echo", input=Message(TextMessagePart(content="Howdy!"))
|
15
|
-
)
|
12
|
+
run = await client.run_sync(agent="echo", input=Message(TextMessagePart(content="Howdy!")))
|
16
13
|
print(run.output)
|
17
14
|
|
18
15
|
|
@@ -4,11 +4,9 @@ from acp_sdk.client import Client
|
|
4
4
|
from acp_sdk.models import Message, TextMessagePart
|
5
5
|
|
6
6
|
|
7
|
-
async def example():
|
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(
|
10
|
-
agent="echo", input=Message(TextMessagePart(content="Howdy!"))
|
11
|
-
):
|
9
|
+
async for event in client.run_stream(agent="echo", input=Message(TextMessagePart(content="Howdy!"))):
|
12
10
|
print(event)
|
13
11
|
|
14
12
|
|
@@ -0,0 +1,28 @@
|
|
1
|
+
from collections.abc import AsyncGenerator
|
2
|
+
|
3
|
+
from acp_sdk.models import (
|
4
|
+
Await,
|
5
|
+
AwaitResume,
|
6
|
+
Message,
|
7
|
+
TextMessagePart,
|
8
|
+
)
|
9
|
+
from acp_sdk.server import Agent, serve
|
10
|
+
from acp_sdk.server.context import Context
|
11
|
+
|
12
|
+
|
13
|
+
class AwaitingAgent(Agent):
|
14
|
+
@property
|
15
|
+
def name(self) -> str:
|
16
|
+
return "awaiting"
|
17
|
+
|
18
|
+
@property
|
19
|
+
def description(self) -> str:
|
20
|
+
return "Greets and awaits for more data"
|
21
|
+
|
22
|
+
async def run(self, input: Message, *, context: Context) -> AsyncGenerator[Message | Await, AwaitResume]:
|
23
|
+
yield Message(TextMessagePart(content="Hello!"))
|
24
|
+
data = yield Await()
|
25
|
+
yield Message(TextMessagePart(content=f"Thanks for {data}"))
|
26
|
+
|
27
|
+
|
28
|
+
serve(AwaitingAgent())
|
@@ -0,0 +1,29 @@
|
|
1
|
+
import asyncio
|
2
|
+
from collections.abc import AsyncGenerator
|
3
|
+
|
4
|
+
from acp_sdk.models import (
|
5
|
+
Await,
|
6
|
+
AwaitResume,
|
7
|
+
Message,
|
8
|
+
)
|
9
|
+
from acp_sdk.server import Agent, serve
|
10
|
+
from acp_sdk.server.context import Context
|
11
|
+
|
12
|
+
|
13
|
+
class EchoAgent(Agent):
|
14
|
+
@property
|
15
|
+
def name(self) -> str:
|
16
|
+
return "echo"
|
17
|
+
|
18
|
+
@property
|
19
|
+
def description(self) -> str:
|
20
|
+
return "Echoes everything"
|
21
|
+
|
22
|
+
async def run(self, input: Message, *, context: Context) -> AsyncGenerator[Message | Await, AwaitResume]:
|
23
|
+
for part in input:
|
24
|
+
await asyncio.sleep(0.5)
|
25
|
+
yield {"thought": "I should echo everyting"}
|
26
|
+
yield Message(part)
|
27
|
+
|
28
|
+
|
29
|
+
serve(EchoAgent())
|
@@ -1,10 +1,10 @@
|
|
1
1
|
[project]
|
2
2
|
name = "acp-sdk"
|
3
|
-
version = "1.0.
|
3
|
+
version = "1.0.0rc3"
|
4
4
|
description = "Agent Communication Protocol SDK"
|
5
5
|
readme = "README.md"
|
6
6
|
authors = []
|
7
|
-
requires-python = ">=3.
|
7
|
+
requires-python = ">=3.11, <4.0"
|
8
8
|
dependencies = ["opentelemetry-api>=1.31.1", "pydantic>=2.11.1"]
|
9
9
|
|
10
10
|
[project.optional-dependencies]
|
@@ -20,6 +20,17 @@ server = [
|
|
20
20
|
"opentelemetry-sdk>=1.31.1",
|
21
21
|
]
|
22
22
|
|
23
|
+
[tool.uv]
|
24
|
+
dev-dependencies = [
|
25
|
+
"httpx>=0.28.1",
|
26
|
+
"httpx-sse>=0.4.0",
|
27
|
+
"opentelemetry-instrumentation-httpx>=0.52b1",
|
28
|
+
"fastapi[standard]>=0.115.8",
|
29
|
+
"opentelemetry-exporter-otlp-proto-http>=1.31.1",
|
30
|
+
"opentelemetry-instrumentation-fastapi>=0.52b1",
|
31
|
+
"opentelemetry-sdk>=1.31.1",
|
32
|
+
]
|
33
|
+
|
23
34
|
[build-system]
|
24
35
|
requires = ["hatchling"]
|
25
36
|
build-backend = "hatchling.build"
|
@@ -0,0 +1 @@
|
|
1
|
+
from acp_sdk.models import * # noqa: F403
|
@@ -0,0 +1 @@
|
|
1
|
+
from acp_sdk.client.client import Client as Client
|
@@ -1,21 +1,24 @@
|
|
1
|
+
from collections.abc import AsyncIterator
|
1
2
|
from types import TracebackType
|
2
|
-
from typing import
|
3
|
+
from typing import Self
|
3
4
|
|
4
5
|
import httpx
|
5
6
|
from httpx_sse import EventSource, aconnect_sse
|
6
|
-
|
7
7
|
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
|
8
|
+
from pydantic import TypeAdapter
|
8
9
|
|
9
10
|
from acp_sdk.models import (
|
11
|
+
ACPError,
|
10
12
|
Agent,
|
11
13
|
AgentName,
|
12
14
|
AgentReadResponse,
|
13
15
|
AgentsListResponse,
|
14
16
|
AwaitResume,
|
17
|
+
Error,
|
15
18
|
Message,
|
19
|
+
Run,
|
16
20
|
RunCancelResponse,
|
17
21
|
RunCreateRequest,
|
18
|
-
Run,
|
19
22
|
RunCreateResponse,
|
20
23
|
RunEvent,
|
21
24
|
RunId,
|
@@ -23,23 +26,20 @@ from acp_sdk.models import (
|
|
23
26
|
RunResumeRequest,
|
24
27
|
RunResumeResponse,
|
25
28
|
)
|
26
|
-
from pydantic import TypeAdapter
|
27
29
|
|
28
30
|
|
29
31
|
class Client:
|
30
|
-
def __init__(
|
31
|
-
self, *, base_url: httpx.URL | str = "", client: httpx.AsyncClient | None = None
|
32
|
-
):
|
32
|
+
def __init__(self, *, base_url: httpx.URL | str = "", client: httpx.AsyncClient | None = None) -> None:
|
33
33
|
self.base_url = base_url
|
34
34
|
|
35
35
|
self._client = self._init_client(client)
|
36
36
|
|
37
|
-
def _init_client(self, client: httpx.AsyncClient | None = None):
|
37
|
+
def _init_client(self, client: httpx.AsyncClient | None = None) -> httpx.AsyncClient:
|
38
38
|
client = client or httpx.AsyncClient(base_url=self.base_url)
|
39
39
|
HTTPXClientInstrumentor.instrument_client(client)
|
40
40
|
return client
|
41
41
|
|
42
|
-
async def __aenter__(self):
|
42
|
+
async def __aenter__(self) -> Self:
|
43
43
|
await self._client.__aenter__()
|
44
44
|
return self
|
45
45
|
|
@@ -48,56 +48,54 @@ class Client:
|
|
48
48
|
exc_type: type[BaseException] | None = None,
|
49
49
|
exc_value: BaseException | None = None,
|
50
50
|
traceback: TracebackType | None = None,
|
51
|
-
):
|
51
|
+
) -> None:
|
52
52
|
await self._client.__aexit__(exc_type, exc_value, traceback)
|
53
53
|
|
54
54
|
async def agents(self) -> AsyncIterator[Agent]:
|
55
55
|
response = await self._client.get("/agents")
|
56
|
+
self._raise_error(response)
|
56
57
|
for agent in AgentsListResponse.model_validate(response.json()).agents:
|
57
58
|
yield agent
|
58
59
|
|
59
60
|
async def agent(self, *, name: AgentName) -> Agent:
|
60
61
|
response = await self._client.get(f"/agents/{name}")
|
62
|
+
self._raise_error(response)
|
61
63
|
return AgentReadResponse.model_validate(response.json())
|
62
64
|
|
63
65
|
async def run_sync(self, *, agent: AgentName, input: Message) -> Run:
|
64
66
|
response = await self._client.post(
|
65
67
|
"/runs",
|
66
|
-
json=RunCreateRequest(
|
67
|
-
agent_name=agent, input=input, mode=RunMode.SYNC
|
68
|
-
).model_dump(),
|
68
|
+
json=RunCreateRequest(agent_name=agent, input=input, mode=RunMode.SYNC).model_dump(),
|
69
69
|
)
|
70
|
+
self._raise_error(response)
|
70
71
|
return RunCreateResponse.model_validate(response.json())
|
71
72
|
|
72
73
|
async def run_async(self, *, agent: AgentName, input: Message) -> Run:
|
73
74
|
response = await self._client.post(
|
74
75
|
"/runs",
|
75
|
-
json=RunCreateRequest(
|
76
|
-
agent_name=agent, input=input, mode=RunMode.ASYNC
|
77
|
-
).model_dump(),
|
76
|
+
json=RunCreateRequest(agent_name=agent, input=input, mode=RunMode.ASYNC).model_dump(),
|
78
77
|
)
|
78
|
+
self._raise_error(response)
|
79
79
|
return RunCreateResponse.model_validate(response.json())
|
80
80
|
|
81
|
-
async def run_stream(
|
82
|
-
self, *, agent: AgentName, input: Message
|
83
|
-
) -> AsyncIterator[RunEvent]:
|
81
|
+
async def run_stream(self, *, agent: AgentName, input: Message) -> AsyncIterator[RunEvent]:
|
84
82
|
async with aconnect_sse(
|
85
83
|
self._client,
|
86
84
|
"POST",
|
87
85
|
"/runs",
|
88
|
-
json=RunCreateRequest(
|
89
|
-
agent_name=agent, input=input, mode=RunMode.STREAM
|
90
|
-
).model_dump(),
|
86
|
+
json=RunCreateRequest(agent_name=agent, input=input, mode=RunMode.STREAM).model_dump(),
|
91
87
|
) as event_source:
|
92
88
|
async for event in self._validate_stream(event_source):
|
93
89
|
yield event
|
94
90
|
|
95
91
|
async def run_status(self, *, run_id: RunId) -> Run:
|
96
92
|
response = await self._client.get(f"/runs/{run_id}")
|
93
|
+
self._raise_error(response)
|
97
94
|
return Run.model_validate(response.json())
|
98
95
|
|
99
96
|
async def run_cancel(self, *, run_id: RunId) -> Run:
|
100
97
|
response = await self._client.post(f"/runs/{run_id}/cancel")
|
98
|
+
self._raise_error(response)
|
101
99
|
return RunCancelResponse.model_validate(response.json())
|
102
100
|
|
103
101
|
async def run_resume_sync(self, *, run_id: RunId, await_: AwaitResume) -> Run:
|
@@ -105,6 +103,7 @@ class Client:
|
|
105
103
|
f"/runs/{run_id}",
|
106
104
|
json=RunResumeRequest(await_=await_, mode=RunMode.SYNC).model_dump(),
|
107
105
|
)
|
106
|
+
self._raise_error(response)
|
108
107
|
return RunResumeResponse.model_validate(response.json())
|
109
108
|
|
110
109
|
async def run_resume_async(self, *, run_id: RunId, await_: AwaitResume) -> Run:
|
@@ -112,11 +111,10 @@ class Client:
|
|
112
111
|
f"/runs/{run_id}",
|
113
112
|
json=RunResumeRequest(await_=await_, mode=RunMode.ASYNC).model_dump(),
|
114
113
|
)
|
114
|
+
self._raise_error(response)
|
115
115
|
return RunResumeResponse.model_validate(response.json())
|
116
116
|
|
117
|
-
async def run_resume_stream(
|
118
|
-
self, *, run_id: RunId, await_: AwaitResume
|
119
|
-
) -> AsyncIterator[RunEvent]:
|
117
|
+
async def run_resume_stream(self, *, run_id: RunId, await_: AwaitResume) -> AsyncIterator[RunEvent]:
|
120
118
|
async with aconnect_sse(
|
121
119
|
self._client,
|
122
120
|
"POST",
|
@@ -133,3 +131,9 @@ class Client:
|
|
133
131
|
async for event in event_source.aiter_sse():
|
134
132
|
event = TypeAdapter(RunEvent).validate_json(event.data)
|
135
133
|
yield event
|
134
|
+
|
135
|
+
def _raise_error(self, response: httpx.Response) -> None:
|
136
|
+
try:
|
137
|
+
response.raise_for_status()
|
138
|
+
except httpx.HTTPError:
|
139
|
+
raise ACPError(Error.model_validate(response.json()))
|
@@ -0,0 +1,23 @@
|
|
1
|
+
from enum import Enum
|
2
|
+
|
3
|
+
from pydantic import BaseModel
|
4
|
+
|
5
|
+
|
6
|
+
class ErrorCode(str, Enum):
|
7
|
+
SERVER_ERROR = "server_error"
|
8
|
+
INVALID_INPUT = "invalid_input"
|
9
|
+
NOT_FOUND = "not_found"
|
10
|
+
|
11
|
+
|
12
|
+
class Error(BaseModel):
|
13
|
+
code: ErrorCode
|
14
|
+
message: str
|
15
|
+
|
16
|
+
|
17
|
+
class ACPError(Exception):
|
18
|
+
def __init__(self, error: Error) -> None:
|
19
|
+
super().__init__()
|
20
|
+
self.error = error
|
21
|
+
|
22
|
+
def __str__(self) -> str:
|
23
|
+
return str(self.error.message)
|
@@ -1,13 +1,15 @@
|
|
1
|
-
from enum import Enum
|
2
|
-
from typing import Annotated, Literal, Union
|
3
1
|
import uuid
|
2
|
+
from collections.abc import Iterator
|
3
|
+
from enum import Enum
|
4
|
+
from typing import Any, Literal, Union
|
4
5
|
|
5
6
|
from pydantic import AnyUrl, BaseModel, ConfigDict, Field, RootModel
|
6
7
|
|
8
|
+
from acp_sdk.models.errors import Error
|
9
|
+
|
7
10
|
|
8
|
-
class
|
9
|
-
|
10
|
-
message: str
|
11
|
+
class Metadata(BaseModel):
|
12
|
+
model_config = ConfigDict(extra="allow")
|
11
13
|
|
12
14
|
|
13
15
|
class AnyModel(BaseModel):
|
@@ -40,24 +42,19 @@ MessagePart = Union[TextMessagePart, ImageMessagePart, ArtifactMessagePart]
|
|
40
42
|
class Message(RootModel):
|
41
43
|
root: list[MessagePart]
|
42
44
|
|
43
|
-
def __init__(self, *items: MessagePart):
|
45
|
+
def __init__(self, *items: MessagePart) -> None:
|
44
46
|
super().__init__(root=list(items))
|
45
47
|
|
46
|
-
def __iter__(self):
|
48
|
+
def __iter__(self) -> Iterator[MessagePart]:
|
47
49
|
return iter(self.root)
|
48
50
|
|
49
|
-
def __getitem__(self, item):
|
50
|
-
return self.root[item]
|
51
|
-
|
52
51
|
def __add__(self, other: "Message") -> "Message":
|
53
52
|
if not isinstance(other, Message):
|
54
53
|
raise TypeError(f"Cannot concatenate Message with {type(other).__name__}")
|
55
54
|
return Message(*(self.root + other.root))
|
56
55
|
|
57
|
-
def __str__(self):
|
58
|
-
return "".join(
|
59
|
-
str(part) for part in self.root if isinstance(part, TextMessagePart)
|
60
|
-
)
|
56
|
+
def __str__(self) -> str:
|
57
|
+
return "".join(str(part) for part in self.root if isinstance(part, TextMessagePart))
|
61
58
|
|
62
59
|
|
63
60
|
AgentName = str
|
@@ -101,14 +98,14 @@ class Run(BaseModel):
|
|
101
98
|
status: RunStatus = RunStatus.CREATED
|
102
99
|
await_: Await | None = Field(None, alias="await")
|
103
100
|
output: Message | None = None
|
104
|
-
error:
|
101
|
+
error: Error | None = None
|
105
102
|
|
106
103
|
model_config = ConfigDict(populate_by_name=True)
|
107
104
|
|
108
105
|
def model_dump_json(
|
109
106
|
self,
|
110
|
-
**kwargs,
|
111
|
-
):
|
107
|
+
**kwargs: dict[str, Any],
|
108
|
+
) -> str:
|
112
109
|
return super().model_dump_json(
|
113
110
|
by_alias=True,
|
114
111
|
**kwargs,
|
@@ -128,8 +125,8 @@ class AwaitEvent(BaseModel):
|
|
128
125
|
|
129
126
|
def model_dump_json(
|
130
127
|
self,
|
131
|
-
**kwargs,
|
132
|
-
):
|
128
|
+
**kwargs: dict[str, Any],
|
129
|
+
) -> str:
|
133
130
|
return super().model_dump_json(
|
134
131
|
by_alias=True,
|
135
132
|
**kwargs,
|
@@ -209,6 +206,7 @@ class RunCancelResponse(Run):
|
|
209
206
|
class Agent(BaseModel):
|
210
207
|
name: str
|
211
208
|
description: str | None = None
|
209
|
+
metadata: Metadata = Metadata()
|
212
210
|
|
213
211
|
|
214
212
|
class AgentsListResponse(BaseModel):
|
@@ -1,13 +1,14 @@
|
|
1
1
|
import abc
|
2
|
-
from
|
2
|
+
from collections.abc import AsyncGenerator
|
3
3
|
|
4
4
|
from acp_sdk.models import (
|
5
5
|
AgentName,
|
6
|
-
Message,
|
7
6
|
Await,
|
8
7
|
AwaitResume,
|
8
|
+
Message,
|
9
9
|
SessionId,
|
10
10
|
)
|
11
|
+
from acp_sdk.models.models import Metadata
|
11
12
|
from acp_sdk.server.context import Context
|
12
13
|
|
13
14
|
|
@@ -20,10 +21,12 @@ class Agent(abc.ABC):
|
|
20
21
|
def description(self) -> str:
|
21
22
|
return ""
|
22
23
|
|
24
|
+
@property
|
25
|
+
def metadata(self) -> Metadata:
|
26
|
+
return Metadata()
|
27
|
+
|
23
28
|
@abc.abstractmethod
|
24
|
-
def run(
|
25
|
-
self, input: Message, *, context: Context
|
26
|
-
) -> AsyncGenerator[Message | Await, AwaitResume]:
|
29
|
+
def run(self, input: Message, *, context: Context) -> AsyncGenerator[Message | Await, AwaitResume]:
|
27
30
|
pass
|
28
31
|
|
29
32
|
async def session(self, session_id: SessionId | None) -> SessionId | None:
|
@@ -1,35 +1,36 @@
|
|
1
1
|
import asyncio
|
2
2
|
import logging
|
3
|
+
from collections.abc import AsyncGenerator
|
3
4
|
|
4
5
|
from opentelemetry import trace
|
5
6
|
from pydantic import ValidationError
|
6
7
|
|
7
|
-
from acp_sdk.server.agent import Agent
|
8
8
|
from acp_sdk.models import (
|
9
|
-
ACPError,
|
10
9
|
AnyModel,
|
11
10
|
Await,
|
12
11
|
AwaitEvent,
|
12
|
+
AwaitResume,
|
13
13
|
CancelledEvent,
|
14
14
|
CompletedEvent,
|
15
15
|
CreatedEvent,
|
16
|
+
Error,
|
16
17
|
FailedEvent,
|
17
18
|
GenericEvent,
|
18
19
|
InProgressEvent,
|
19
20
|
Message,
|
20
21
|
MessageEvent,
|
21
22
|
Run,
|
22
|
-
AwaitResume,
|
23
23
|
RunEvent,
|
24
24
|
RunStatus,
|
25
25
|
)
|
26
|
+
from acp_sdk.models.errors import ErrorCode
|
27
|
+
from acp_sdk.server.agent import Agent
|
26
28
|
from acp_sdk.server.context import Context
|
27
|
-
|
28
|
-
logger = logging.getLogger("uvicorn.error")
|
29
|
+
from acp_sdk.server.logging import logger
|
29
30
|
|
30
31
|
|
31
32
|
class RunBundle:
|
32
|
-
def __init__(self, *, agent: Agent, run: Run, task: asyncio.Task | None = None):
|
33
|
+
def __init__(self, *, agent: Agent, run: Run, task: asyncio.Task | None = None) -> None:
|
33
34
|
self.agent = agent
|
34
35
|
self.run = run
|
35
36
|
self.task = task
|
@@ -40,20 +41,19 @@ class RunBundle:
|
|
40
41
|
self.await_queue: asyncio.Queue[AwaitResume] = asyncio.Queue(maxsize=1)
|
41
42
|
self.await_or_terminate_event = asyncio.Event()
|
42
43
|
|
43
|
-
async def stream(self):
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
pass
|
44
|
+
async def stream(self) -> AsyncGenerator[RunEvent]:
|
45
|
+
while True:
|
46
|
+
event = await self.stream_queue.get()
|
47
|
+
if event is None:
|
48
|
+
break
|
49
|
+
yield event
|
50
|
+
self.stream_queue.task_done()
|
51
51
|
|
52
|
-
async def emit(self, event: RunEvent):
|
52
|
+
async def emit(self, event: RunEvent) -> None:
|
53
53
|
await self.stream_queue.put(event)
|
54
54
|
|
55
55
|
async def await_(self) -> AwaitResume:
|
56
|
-
self.stream_queue.
|
56
|
+
await self.stream_queue.put(None)
|
57
57
|
self.await_queue.empty()
|
58
58
|
self.await_or_terminate_event.set()
|
59
59
|
self.await_or_terminate_event.clear()
|
@@ -61,14 +61,14 @@ class RunBundle:
|
|
61
61
|
self.await_queue.task_done()
|
62
62
|
return resume
|
63
63
|
|
64
|
-
async def resume(self, resume: AwaitResume):
|
64
|
+
async def resume(self, resume: AwaitResume) -> None:
|
65
65
|
self.stream_queue = asyncio.Queue()
|
66
66
|
await self.await_queue.put(resume)
|
67
67
|
|
68
|
-
async def join(self):
|
68
|
+
async def join(self) -> None:
|
69
69
|
await self.await_or_terminate_event.wait()
|
70
70
|
|
71
|
-
async def execute(self, input: Message):
|
71
|
+
async def execute(self, input: Message) -> None:
|
72
72
|
with trace.get_tracer(__name__).start_as_current_span("execute"):
|
73
73
|
run_logger = logging.LoggerAdapter(logger, {"run_id": self.run.run_id})
|
74
74
|
|
@@ -77,9 +77,7 @@ class RunBundle:
|
|
77
77
|
self.run.session_id = await self.agent.session(self.run.session_id)
|
78
78
|
run_logger.info("Session loaded")
|
79
79
|
|
80
|
-
generator = self.agent.run(
|
81
|
-
input=input, context=Context(session_id=self.run.session_id)
|
82
|
-
)
|
80
|
+
generator = self.agent.run(input=input, context=Context(session_id=self.run.session_id))
|
83
81
|
run_logger.info("Run started")
|
84
82
|
|
85
83
|
self.run.status = RunStatus.IN_PROGRESS
|
@@ -124,10 +122,10 @@ class RunBundle:
|
|
124
122
|
await self.emit(CancelledEvent(run=self.run))
|
125
123
|
run_logger.info("Run cancelled")
|
126
124
|
except Exception as e:
|
127
|
-
self.run.error =
|
125
|
+
self.run.error = Error(code=ErrorCode.SERVER_ERROR, message=str(e))
|
128
126
|
self.run.status = RunStatus.FAILED
|
129
127
|
await self.emit(FailedEvent(run=self.run))
|
130
128
|
run_logger.exception("Run failed")
|
131
129
|
finally:
|
132
130
|
self.await_or_terminate_event.set()
|
133
|
-
self.stream_queue.
|
131
|
+
await self.stream_queue.put(None)
|
@@ -0,0 +1,54 @@
|
|
1
|
+
from fastapi import Request, status
|
2
|
+
from fastapi.exceptions import RequestValidationError
|
3
|
+
from fastapi.responses import JSONResponse
|
4
|
+
from starlette.exceptions import HTTPException as StarletteHTTPException
|
5
|
+
|
6
|
+
from acp_sdk.models import Error, ErrorCode
|
7
|
+
from acp_sdk.models.errors import ACPError
|
8
|
+
from acp_sdk.server.logging import logger
|
9
|
+
|
10
|
+
|
11
|
+
def error_code_to_status_code(error_code: ErrorCode) -> int:
|
12
|
+
match error_code:
|
13
|
+
case ErrorCode.NOT_FOUND:
|
14
|
+
return status.HTTP_404_NOT_FOUND
|
15
|
+
case ErrorCode.INVALID_INPUT:
|
16
|
+
return status.HTTP_422_UNPROCESSABLE_ENTITY
|
17
|
+
case _:
|
18
|
+
return status.HTTP_500_INTERNAL_SERVER_ERROR
|
19
|
+
|
20
|
+
|
21
|
+
def status_code_to_error_code(status_code: int) -> ErrorCode:
|
22
|
+
match status_code:
|
23
|
+
case status.HTTP_400_BAD_REQUEST:
|
24
|
+
return ErrorCode.INVALID_INPUT
|
25
|
+
case status.HTTP_404_NOT_FOUND:
|
26
|
+
return ErrorCode.NOT_FOUND
|
27
|
+
case status.HTTP_422_UNPROCESSABLE_ENTITY:
|
28
|
+
return ErrorCode.INVALID_INPUT
|
29
|
+
case _:
|
30
|
+
return ErrorCode.SERVER_ERROR
|
31
|
+
|
32
|
+
|
33
|
+
async def acp_error_handler(request: Request, exc: ACPError, *, status_code: int | None = None) -> JSONResponse:
|
34
|
+
error = exc.error
|
35
|
+
return JSONResponse(status_code=status_code or error_code_to_status_code(error.code), content=error.model_dump())
|
36
|
+
|
37
|
+
|
38
|
+
async def http_exception_handler(request: Request, exc: StarletteHTTPException) -> JSONResponse:
|
39
|
+
return await acp_error_handler(
|
40
|
+
request,
|
41
|
+
ACPError(Error(code=status_code_to_error_code(exc.status_code), message=exc.detail)),
|
42
|
+
status_code=exc.status_code,
|
43
|
+
)
|
44
|
+
|
45
|
+
|
46
|
+
async def validation_exception_handler(request: Request, exc: RequestValidationError) -> JSONResponse:
|
47
|
+
return await acp_error_handler(request, ACPError(Error(code=ErrorCode.INVALID_INPUT, message=str(exc))))
|
48
|
+
|
49
|
+
|
50
|
+
async def catch_all_exception_handler(request: Request, exc: Exception) -> JSONResponse:
|
51
|
+
logger.error(exc)
|
52
|
+
return await acp_error_handler(
|
53
|
+
request, ACPError(Error(code=ErrorCode.SERVER_ERROR, message="An unexpected error occurred"))
|
54
|
+
)
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import logging
|
2
|
+
|
3
|
+
from uvicorn.logging import DefaultFormatter
|
4
|
+
|
5
|
+
logger = logging.getLogger("acp")
|
6
|
+
|
7
|
+
|
8
|
+
def configure_logger() -> None:
|
9
|
+
"""Utility that configures the root logger"""
|
10
|
+
root_logger = logging.getLogger()
|
11
|
+
|
12
|
+
handler = logging.StreamHandler()
|
13
|
+
handler.setFormatter(DefaultFormatter(fmt="%(levelprefix)s %(message)s"))
|
14
|
+
|
15
|
+
root_logger.addHandler(handler)
|
16
|
+
root_logger.setLevel(logging.INFO)
|
@@ -1,17 +1,17 @@
|
|
1
1
|
import asyncio
|
2
|
-
|
3
|
-
from acp_sdk.server.telemetry import configure_telemetry
|
4
|
-
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
|
2
|
+
from typing import Any
|
5
3
|
|
6
4
|
from fastapi import FastAPI, HTTPException, status
|
7
5
|
from fastapi.responses import JSONResponse, StreamingResponse
|
6
|
+
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
|
8
7
|
|
9
|
-
from acp_sdk.server.agent import Agent
|
10
8
|
from acp_sdk.models import (
|
11
|
-
AgentName,
|
12
9
|
Agent as AgentModel,
|
13
|
-
|
10
|
+
)
|
11
|
+
from acp_sdk.models import (
|
12
|
+
AgentName,
|
14
13
|
AgentReadResponse,
|
14
|
+
AgentsListResponse,
|
15
15
|
Run,
|
16
16
|
RunCancelResponse,
|
17
17
|
RunCreateRequest,
|
@@ -23,47 +23,63 @@ from acp_sdk.models import (
|
|
23
23
|
RunResumeResponse,
|
24
24
|
RunStatus,
|
25
25
|
)
|
26
|
+
from acp_sdk.models.errors import ACPError
|
27
|
+
from acp_sdk.server.agent import Agent
|
26
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
|
+
)
|
37
|
+
from acp_sdk.server.logging import configure_logger as configure_logger_func
|
38
|
+
from acp_sdk.server.telemetry import configure_telemetry as configure_telemetry_func
|
27
39
|
from acp_sdk.server.utils import stream_sse
|
28
40
|
|
29
41
|
|
30
42
|
def create_app(*agents: Agent) -> FastAPI:
|
31
43
|
app = FastAPI(title="acp-agents")
|
32
44
|
|
33
|
-
configure_telemetry()
|
34
45
|
FastAPIInstrumentor.instrument_app(app)
|
35
46
|
|
36
47
|
agents: dict[AgentName, Agent] = {agent.name: agent for agent in agents}
|
37
|
-
runs: dict[RunId, RunBundle] =
|
48
|
+
runs: dict[RunId, RunBundle] = {}
|
38
49
|
|
39
|
-
|
40
|
-
|
50
|
+
app.exception_handler(ACPError)(acp_error_handler)
|
51
|
+
app.exception_handler(StarletteHTTPException)(http_exception_handler)
|
52
|
+
app.exception_handler(RequestValidationError)(validation_exception_handler)
|
53
|
+
app.exception_handler(Exception)(catch_all_exception_handler)
|
54
|
+
|
55
|
+
def find_run_bundle(run_id: RunId) -> RunBundle:
|
56
|
+
bundle = runs.get(run_id)
|
41
57
|
if not bundle:
|
42
58
|
raise HTTPException(status_code=404, detail=f"Run {run_id} not found")
|
43
59
|
return bundle
|
44
60
|
|
45
|
-
def find_agent(agent_name: AgentName):
|
61
|
+
def find_agent(agent_name: AgentName) -> Agent:
|
46
62
|
agent = agents.get(agent_name, None)
|
47
63
|
if not agent:
|
48
64
|
raise HTTPException(status_code=404, detail=f"Agent {agent_name} not found")
|
49
65
|
return agent
|
50
66
|
|
51
67
|
@app.get("/agents")
|
52
|
-
async def
|
68
|
+
async def list_agents() -> AgentsListResponse:
|
53
69
|
return AgentsListResponse(
|
54
70
|
agents=[
|
55
|
-
AgentModel(name=agent.name, description=agent.description)
|
71
|
+
AgentModel(name=agent.name, description=agent.description, metadata=agent.metadata)
|
56
72
|
for agent in agents.values()
|
57
73
|
]
|
58
74
|
)
|
59
75
|
|
60
76
|
@app.get("/agents/{name}")
|
61
|
-
async def
|
77
|
+
async def read_agent(name: AgentName) -> AgentReadResponse:
|
62
78
|
agent = find_agent(name)
|
63
|
-
return AgentModel(name=agent.name, description=agent.description)
|
79
|
+
return AgentModel(name=agent.name, description=agent.description, metadata=agent.metadata)
|
64
80
|
|
65
81
|
@app.post("/runs")
|
66
|
-
async def
|
82
|
+
async def create_run(request: RunCreateRequest) -> RunCreateResponse:
|
67
83
|
agent = find_agent(request.agent_name)
|
68
84
|
bundle = RunBundle(
|
69
85
|
agent=agent,
|
@@ -94,12 +110,12 @@ def create_app(*agents: Agent) -> FastAPI:
|
|
94
110
|
raise NotImplementedError()
|
95
111
|
|
96
112
|
@app.get("/runs/{run_id}")
|
97
|
-
async def
|
113
|
+
async def read_run(run_id: RunId) -> RunReadResponse:
|
98
114
|
bundle = find_run_bundle(run_id)
|
99
115
|
return bundle.run
|
100
116
|
|
101
117
|
@app.post("/runs/{run_id}")
|
102
|
-
async def
|
118
|
+
async def resume_run(run_id: RunId, request: RunResumeRequest) -> RunResumeResponse:
|
103
119
|
bundle = find_run_bundle(run_id)
|
104
120
|
bundle.stream_queue = asyncio.Queue() # TODO improve
|
105
121
|
await bundle.await_queue.put(request.await_)
|
@@ -121,7 +137,7 @@ def create_app(*agents: Agent) -> FastAPI:
|
|
121
137
|
raise NotImplementedError()
|
122
138
|
|
123
139
|
@app.post("/runs/{run_id}/cancel")
|
124
|
-
async def
|
140
|
+
async def cancel_run(run_id: RunId) -> RunCancelResponse:
|
125
141
|
bundle = find_run_bundle(run_id)
|
126
142
|
if bundle.run.status.is_terminal:
|
127
143
|
raise HTTPException(
|
@@ -130,8 +146,19 @@ def create_app(*agents: Agent) -> FastAPI:
|
|
130
146
|
)
|
131
147
|
bundle.task.cancel()
|
132
148
|
bundle.run.status = RunStatus.CANCELLING
|
133
|
-
return JSONResponse(
|
134
|
-
status_code=status.HTTP_202_ACCEPTED, content=bundle.run.model_dump()
|
135
|
-
)
|
149
|
+
return JSONResponse(status_code=status.HTTP_202_ACCEPTED, content=bundle.run.model_dump())
|
136
150
|
|
137
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)
|
@@ -0,0 +1,52 @@
|
|
1
|
+
import logging
|
2
|
+
from importlib.metadata import version
|
3
|
+
|
4
|
+
from opentelemetry import metrics, trace
|
5
|
+
from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
|
6
|
+
from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter
|
7
|
+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
8
|
+
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
|
9
|
+
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
|
10
|
+
from opentelemetry.sdk.metrics import MeterProvider
|
11
|
+
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
|
12
|
+
from opentelemetry.sdk.resources import (
|
13
|
+
SERVICE_NAME,
|
14
|
+
SERVICE_NAMESPACE,
|
15
|
+
SERVICE_VERSION,
|
16
|
+
Resource,
|
17
|
+
)
|
18
|
+
from opentelemetry.sdk.trace import TracerProvider
|
19
|
+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
20
|
+
|
21
|
+
root_logger = logging.getLogger()
|
22
|
+
|
23
|
+
|
24
|
+
def configure_telemetry() -> None:
|
25
|
+
"""Utility that configures opentelemetry with OTLP exporter"""
|
26
|
+
|
27
|
+
resource = Resource(
|
28
|
+
attributes={
|
29
|
+
SERVICE_NAME: "acp-server",
|
30
|
+
SERVICE_NAMESPACE: "acp",
|
31
|
+
SERVICE_VERSION: version("acp-sdk"),
|
32
|
+
}
|
33
|
+
)
|
34
|
+
|
35
|
+
# Traces
|
36
|
+
provider = TracerProvider(resource=resource)
|
37
|
+
processor = BatchSpanProcessor(OTLPSpanExporter())
|
38
|
+
provider.add_span_processor(processor)
|
39
|
+
trace.set_tracer_provider(provider)
|
40
|
+
|
41
|
+
# Metrics
|
42
|
+
meter_provider = MeterProvider(
|
43
|
+
resource=resource,
|
44
|
+
metric_readers=[PeriodicExportingMetricReader(OTLPMetricExporter())],
|
45
|
+
)
|
46
|
+
metrics.set_meter_provider(meter_provider)
|
47
|
+
|
48
|
+
# Logs
|
49
|
+
logger_provider = LoggerProvider(resource=resource)
|
50
|
+
processor = BatchLogRecordProcessor(OTLPLogExporter())
|
51
|
+
logger_provider.add_log_record_processor(processor)
|
52
|
+
root_logger.addHandler(LoggingHandler(logger_provider=logger_provider))
|
@@ -1,12 +1,14 @@
|
|
1
|
+
from collections.abc import AsyncGenerator
|
2
|
+
|
1
3
|
from pydantic import BaseModel
|
2
4
|
|
3
5
|
from acp_sdk.server.bundle import RunBundle
|
4
6
|
|
5
7
|
|
6
|
-
def encode_sse(model: BaseModel):
|
8
|
+
def encode_sse(model: BaseModel) -> str:
|
7
9
|
return f"data: {model.model_dump_json()}\n\n"
|
8
10
|
|
9
11
|
|
10
|
-
async def stream_sse(bundle: RunBundle):
|
12
|
+
async def stream_sse(bundle: RunBundle) -> AsyncGenerator[str]:
|
11
13
|
async for event in bundle.stream():
|
12
14
|
yield encode_sse(event)
|
acp_sdk-1.0.0rc1/.python-version
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
3.13
|
@@ -1,22 +0,0 @@
|
|
1
|
-
from acp_sdk.models import (
|
2
|
-
Message,
|
3
|
-
)
|
4
|
-
from acp_sdk.server import Agent, create_app
|
5
|
-
|
6
|
-
from acp_sdk.server.context import Context
|
7
|
-
|
8
|
-
|
9
|
-
class EchoAgent(Agent):
|
10
|
-
@property
|
11
|
-
def name(self):
|
12
|
-
return "echo"
|
13
|
-
|
14
|
-
@property
|
15
|
-
def description(self):
|
16
|
-
return "Echoes everything"
|
17
|
-
|
18
|
-
async def run(self, input: Message, *, context: Context):
|
19
|
-
yield input
|
20
|
-
|
21
|
-
|
22
|
-
app = create_app(EchoAgent())
|
@@ -1,169 +0,0 @@
|
|
1
|
-
import asyncio
|
2
|
-
from typing import AsyncGenerator
|
3
|
-
import uuid
|
4
|
-
|
5
|
-
from acp_sdk.models import (
|
6
|
-
Message,
|
7
|
-
Await,
|
8
|
-
AwaitResume,
|
9
|
-
SessionId,
|
10
|
-
TextMessagePart,
|
11
|
-
)
|
12
|
-
from acp_sdk.server import Agent, serve
|
13
|
-
|
14
|
-
from beeai_framework.agents.react import ReActAgent
|
15
|
-
from beeai_framework.backend.chat import ChatModel, ChatModelParameters
|
16
|
-
from beeai_framework.backend.message import UserMessage
|
17
|
-
from beeai_framework.memory import TokenMemory
|
18
|
-
from beeai_framework.tools.search import DuckDuckGoSearchTool, WikipediaTool
|
19
|
-
from beeai_framework.tools.weather.openmeteo import OpenMeteoTool
|
20
|
-
|
21
|
-
from acp_sdk.server.context import Context
|
22
|
-
|
23
|
-
|
24
|
-
class EchoAgent(Agent):
|
25
|
-
@property
|
26
|
-
def name(self):
|
27
|
-
return "echo"
|
28
|
-
|
29
|
-
@property
|
30
|
-
def description(self):
|
31
|
-
return "Echoes everything"
|
32
|
-
|
33
|
-
async def run(self, input: Message, *, context: Context):
|
34
|
-
yield input
|
35
|
-
|
36
|
-
|
37
|
-
class LazyEchoAgent(Agent):
|
38
|
-
@property
|
39
|
-
def name(self):
|
40
|
-
return "lazy_echo"
|
41
|
-
|
42
|
-
@property
|
43
|
-
def description(self):
|
44
|
-
return "Echoes everything with 1 minute delay"
|
45
|
-
|
46
|
-
async def run(self, input: Message, *, context: Context):
|
47
|
-
await asyncio.sleep(60)
|
48
|
-
yield input
|
49
|
-
|
50
|
-
|
51
|
-
class StreamingEchoAgent(Agent):
|
52
|
-
@property
|
53
|
-
def name(self):
|
54
|
-
return "streaming_echo"
|
55
|
-
|
56
|
-
@property
|
57
|
-
def description(self):
|
58
|
-
return "Echoes all message parts in a stream"
|
59
|
-
|
60
|
-
async def run(self, input: Message, *, context: Context):
|
61
|
-
for part in input:
|
62
|
-
yield {"thought": "nothing really"}
|
63
|
-
yield Message(part)
|
64
|
-
|
65
|
-
|
66
|
-
class AwaitingAgent(Agent):
|
67
|
-
@property
|
68
|
-
def name(self):
|
69
|
-
return "awaiting"
|
70
|
-
|
71
|
-
@property
|
72
|
-
def description(self):
|
73
|
-
return "Greets and awaits for more data"
|
74
|
-
|
75
|
-
async def run(
|
76
|
-
self, input: Message, *, context: Context
|
77
|
-
) -> AsyncGenerator[Message | Await, AwaitResume]:
|
78
|
-
yield Message(TextMessagePart(content="Hello!"))
|
79
|
-
data = yield Await()
|
80
|
-
yield Message(TextMessagePart(content=f"Thanks for {data}"))
|
81
|
-
|
82
|
-
|
83
|
-
class BeeAIAgent(Agent):
|
84
|
-
def __init__(self):
|
85
|
-
self.llm = ChatModel.from_name(
|
86
|
-
"ollama:llama3.1",
|
87
|
-
ChatModelParameters(temperature=0),
|
88
|
-
)
|
89
|
-
|
90
|
-
@property
|
91
|
-
def name(self):
|
92
|
-
return "beeai"
|
93
|
-
|
94
|
-
@property
|
95
|
-
def description(self):
|
96
|
-
return "Beeai agent powered by ollama and no tools"
|
97
|
-
|
98
|
-
async def run(self, input: Message, *, context: Context):
|
99
|
-
memory = TokenMemory(self.llm)
|
100
|
-
await memory.add_many(
|
101
|
-
(
|
102
|
-
UserMessage(part.content)
|
103
|
-
for part in input
|
104
|
-
if isinstance(part, TextMessagePart)
|
105
|
-
)
|
106
|
-
)
|
107
|
-
output = await ReActAgent(llm=self.llm, tools=[], memory=memory).run(
|
108
|
-
prompt=None
|
109
|
-
)
|
110
|
-
for content in output.result.get_texts():
|
111
|
-
yield Message(TextMessagePart(content=content.text))
|
112
|
-
|
113
|
-
|
114
|
-
class BeeAIAgentAdvanced(Agent):
|
115
|
-
def __init__(self):
|
116
|
-
self.llm = ChatModel.from_name(
|
117
|
-
"ollama:llama3.1",
|
118
|
-
ChatModelParameters(temperature=0),
|
119
|
-
)
|
120
|
-
self.tools = [
|
121
|
-
WikipediaTool(),
|
122
|
-
OpenMeteoTool(),
|
123
|
-
DuckDuckGoSearchTool(),
|
124
|
-
]
|
125
|
-
self.memories: dict[SessionId, TokenMemory] = dict()
|
126
|
-
|
127
|
-
@property
|
128
|
-
def name(self):
|
129
|
-
return "beeai_advanced"
|
130
|
-
|
131
|
-
@property
|
132
|
-
def description(self):
|
133
|
-
return "Beeai agent powered by ollama with tools and sessions"
|
134
|
-
|
135
|
-
async def session(self, session_id: SessionId | None):
|
136
|
-
if session_id and session_id not in self.memories:
|
137
|
-
raise ValueError("Memory not found")
|
138
|
-
|
139
|
-
session_id = session_id or str(uuid.uuid4())
|
140
|
-
memory = self.memories.get(session_id, TokenMemory(self.llm))
|
141
|
-
self.memories[session_id] = memory
|
142
|
-
return session_id
|
143
|
-
|
144
|
-
async def run(self, input: Message, *, context: Context):
|
145
|
-
await self.memories[context.session_id].add_many(
|
146
|
-
(
|
147
|
-
UserMessage(part.content)
|
148
|
-
for part in input
|
149
|
-
if isinstance(part, TextMessagePart)
|
150
|
-
)
|
151
|
-
)
|
152
|
-
output = await ReActAgent(
|
153
|
-
llm=self.llm, tools=self.tools, memory=self.memories[context.session_id]
|
154
|
-
).run(prompt=None)
|
155
|
-
for content in output.result.get_texts():
|
156
|
-
yield Message(TextMessagePart(content=content.text))
|
157
|
-
|
158
|
-
|
159
|
-
if __name__ == "__main__":
|
160
|
-
asyncio.run(
|
161
|
-
serve(
|
162
|
-
EchoAgent(),
|
163
|
-
LazyEchoAgent(),
|
164
|
-
StreamingEchoAgent(),
|
165
|
-
AwaitingAgent(),
|
166
|
-
BeeAIAgent(),
|
167
|
-
BeeAIAgentAdvanced(),
|
168
|
-
)
|
169
|
-
)
|
File without changes
|
@@ -1 +0,0 @@
|
|
1
|
-
from acp_sdk.client.client import Client
|
@@ -1,45 +0,0 @@
|
|
1
|
-
import logging
|
2
|
-
from importlib.metadata import version
|
3
|
-
|
4
|
-
from opentelemetry import trace
|
5
|
-
from opentelemetry.sdk.resources import (
|
6
|
-
Resource,
|
7
|
-
SERVICE_NAME,
|
8
|
-
SERVICE_NAMESPACE,
|
9
|
-
SERVICE_VERSION,
|
10
|
-
)
|
11
|
-
from opentelemetry.sdk.trace import TracerProvider
|
12
|
-
from opentelemetry.sdk.trace.export import BatchSpanProcessor, SpanExportResult
|
13
|
-
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
14
|
-
|
15
|
-
logger = logging.getLogger("uvicorn.error")
|
16
|
-
|
17
|
-
|
18
|
-
class SilentOTLPSpanExporter(OTLPSpanExporter):
|
19
|
-
def export(self, spans):
|
20
|
-
try:
|
21
|
-
return super().export(spans)
|
22
|
-
except Exception as e:
|
23
|
-
logger.warning(f"OpenTelemetry Exporter failed silently: {e}")
|
24
|
-
return SpanExportResult.FAILURE
|
25
|
-
|
26
|
-
|
27
|
-
def configure_telemetry():
|
28
|
-
current_provider = trace.get_tracer_provider()
|
29
|
-
|
30
|
-
# Detect default provider and override
|
31
|
-
if isinstance(current_provider, trace.ProxyTracerProvider):
|
32
|
-
provider = TracerProvider(
|
33
|
-
resource=Resource(
|
34
|
-
attributes={
|
35
|
-
SERVICE_NAME: "acp-server",
|
36
|
-
SERVICE_NAMESPACE: "acp",
|
37
|
-
SERVICE_VERSION: version("acp-sdk"),
|
38
|
-
}
|
39
|
-
)
|
40
|
-
)
|
41
|
-
|
42
|
-
processor = BatchSpanProcessor(SilentOTLPSpanExporter())
|
43
|
-
provider.add_span_processor(processor)
|
44
|
-
|
45
|
-
trace.set_tracer_provider(provider)
|
File without changes
|
File without changes
|