acp-sdk 1.0.0rc1__tar.gz → 1.0.0rc2__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.0rc2/.python-version +1 -0
- {acp_sdk-1.0.0rc1 → acp_sdk-1.0.0rc2}/PKG-INFO +2 -2
- {acp_sdk-1.0.0rc1 → acp_sdk-1.0.0rc2}/examples/clients/advanced.py +4 -5
- {acp_sdk-1.0.0rc1 → acp_sdk-1.0.0rc2}/examples/clients/simple.py +3 -6
- {acp_sdk-1.0.0rc1 → acp_sdk-1.0.0rc2}/examples/clients/stream.py +2 -4
- acp_sdk-1.0.0rc2/examples/servers/awaiting.py +29 -0
- acp_sdk-1.0.0rc2/examples/servers/echo.py +29 -0
- {acp_sdk-1.0.0rc1 → acp_sdk-1.0.0rc2}/pyproject.toml +13 -2
- acp_sdk-1.0.0rc2/src/acp_sdk/client/__init__.py +1 -0
- {acp_sdk-1.0.0rc1 → acp_sdk-1.0.0rc2}/src/acp_sdk/client/client.py +13 -25
- {acp_sdk-1.0.0rc1 → acp_sdk-1.0.0rc2}/src/acp_sdk/models.py +11 -15
- acp_sdk-1.0.0rc2/src/acp_sdk/server/__init__.py +2 -0
- {acp_sdk-1.0.0rc1 → acp_sdk-1.0.0rc2}/src/acp_sdk/server/agent.py +3 -5
- {acp_sdk-1.0.0rc1 → acp_sdk-1.0.0rc2}/src/acp_sdk/server/bundle.py +18 -20
- {acp_sdk-1.0.0rc1 → acp_sdk-1.0.0rc2}/src/acp_sdk/server/context.py +1 -1
- {acp_sdk-1.0.0rc1 → acp_sdk-1.0.0rc2}/src/acp_sdk/server/server.py +19 -23
- {acp_sdk-1.0.0rc1 → acp_sdk-1.0.0rc2}/src/acp_sdk/server/telemetry.py +5 -4
- {acp_sdk-1.0.0rc1 → acp_sdk-1.0.0rc2}/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/client/__init__.py +0 -1
- acp_sdk-1.0.0rc1/src/acp_sdk/server/__init__.py +0 -2
- {acp_sdk-1.0.0rc1 → acp_sdk-1.0.0rc2}/.gitignore +0 -0
- {acp_sdk-1.0.0rc1 → acp_sdk-1.0.0rc2}/README.md +0 -0
- {acp_sdk-1.0.0rc1 → acp_sdk-1.0.0rc2}/src/acp_sdk/__init__.py +0 -0
- {acp_sdk-1.0.0rc1 → acp_sdk-1.0.0rc2}/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.0rc2
|
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
|
@@ -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,29 @@
|
|
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
|
10
|
+
from acp_sdk.server.context import Context
|
11
|
+
from acp_sdk.server.server import create_app
|
12
|
+
|
13
|
+
|
14
|
+
class AwaitingAgent(Agent):
|
15
|
+
@property
|
16
|
+
def name(self) -> str:
|
17
|
+
return "awaiting"
|
18
|
+
|
19
|
+
@property
|
20
|
+
def description(self) -> str:
|
21
|
+
return "Greets and awaits for more data"
|
22
|
+
|
23
|
+
async def run(self, input: Message, *, context: Context) -> AsyncGenerator[Message | Await, AwaitResume]:
|
24
|
+
yield Message(TextMessagePart(content="Hello!"))
|
25
|
+
data = yield Await()
|
26
|
+
yield Message(TextMessagePart(content=f"Thanks for {data}"))
|
27
|
+
|
28
|
+
|
29
|
+
app = create_app(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, create_app
|
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
|
+
app = create_app(EchoAgent())
|
@@ -1,10 +1,10 @@
|
|
1
1
|
[project]
|
2
2
|
name = "acp-sdk"
|
3
|
-
version = "1.0.
|
3
|
+
version = "1.0.0rc2"
|
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.client.client import Client as Client
|
@@ -1,10 +1,11 @@
|
|
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 (
|
10
11
|
Agent,
|
@@ -13,9 +14,9 @@ from acp_sdk.models import (
|
|
13
14
|
AgentsListResponse,
|
14
15
|
AwaitResume,
|
15
16
|
Message,
|
17
|
+
Run,
|
16
18
|
RunCancelResponse,
|
17
19
|
RunCreateRequest,
|
18
|
-
Run,
|
19
20
|
RunCreateResponse,
|
20
21
|
RunEvent,
|
21
22
|
RunId,
|
@@ -23,23 +24,20 @@ from acp_sdk.models import (
|
|
23
24
|
RunResumeRequest,
|
24
25
|
RunResumeResponse,
|
25
26
|
)
|
26
|
-
from pydantic import TypeAdapter
|
27
27
|
|
28
28
|
|
29
29
|
class Client:
|
30
|
-
def __init__(
|
31
|
-
self, *, base_url: httpx.URL | str = "", client: httpx.AsyncClient | None = None
|
32
|
-
):
|
30
|
+
def __init__(self, *, base_url: httpx.URL | str = "", client: httpx.AsyncClient | None = None) -> None:
|
33
31
|
self.base_url = base_url
|
34
32
|
|
35
33
|
self._client = self._init_client(client)
|
36
34
|
|
37
|
-
def _init_client(self, client: httpx.AsyncClient | None = None):
|
35
|
+
def _init_client(self, client: httpx.AsyncClient | None = None) -> Self:
|
38
36
|
client = client or httpx.AsyncClient(base_url=self.base_url)
|
39
37
|
HTTPXClientInstrumentor.instrument_client(client)
|
40
38
|
return client
|
41
39
|
|
42
|
-
async def __aenter__(self):
|
40
|
+
async def __aenter__(self) -> Self:
|
43
41
|
await self._client.__aenter__()
|
44
42
|
return self
|
45
43
|
|
@@ -48,7 +46,7 @@ class Client:
|
|
48
46
|
exc_type: type[BaseException] | None = None,
|
49
47
|
exc_value: BaseException | None = None,
|
50
48
|
traceback: TracebackType | None = None,
|
51
|
-
):
|
49
|
+
) -> None:
|
52
50
|
await self._client.__aexit__(exc_type, exc_value, traceback)
|
53
51
|
|
54
52
|
async def agents(self) -> AsyncIterator[Agent]:
|
@@ -63,31 +61,23 @@ class Client:
|
|
63
61
|
async def run_sync(self, *, agent: AgentName, input: Message) -> Run:
|
64
62
|
response = await self._client.post(
|
65
63
|
"/runs",
|
66
|
-
json=RunCreateRequest(
|
67
|
-
agent_name=agent, input=input, mode=RunMode.SYNC
|
68
|
-
).model_dump(),
|
64
|
+
json=RunCreateRequest(agent_name=agent, input=input, mode=RunMode.SYNC).model_dump(),
|
69
65
|
)
|
70
66
|
return RunCreateResponse.model_validate(response.json())
|
71
67
|
|
72
68
|
async def run_async(self, *, agent: AgentName, input: Message) -> Run:
|
73
69
|
response = await self._client.post(
|
74
70
|
"/runs",
|
75
|
-
json=RunCreateRequest(
|
76
|
-
agent_name=agent, input=input, mode=RunMode.ASYNC
|
77
|
-
).model_dump(),
|
71
|
+
json=RunCreateRequest(agent_name=agent, input=input, mode=RunMode.ASYNC).model_dump(),
|
78
72
|
)
|
79
73
|
return RunCreateResponse.model_validate(response.json())
|
80
74
|
|
81
|
-
async def run_stream(
|
82
|
-
self, *, agent: AgentName, input: Message
|
83
|
-
) -> AsyncIterator[RunEvent]:
|
75
|
+
async def run_stream(self, *, agent: AgentName, input: Message) -> AsyncIterator[RunEvent]:
|
84
76
|
async with aconnect_sse(
|
85
77
|
self._client,
|
86
78
|
"POST",
|
87
79
|
"/runs",
|
88
|
-
json=RunCreateRequest(
|
89
|
-
agent_name=agent, input=input, mode=RunMode.STREAM
|
90
|
-
).model_dump(),
|
80
|
+
json=RunCreateRequest(agent_name=agent, input=input, mode=RunMode.STREAM).model_dump(),
|
91
81
|
) as event_source:
|
92
82
|
async for event in self._validate_stream(event_source):
|
93
83
|
yield event
|
@@ -114,9 +104,7 @@ class Client:
|
|
114
104
|
)
|
115
105
|
return RunResumeResponse.model_validate(response.json())
|
116
106
|
|
117
|
-
async def run_resume_stream(
|
118
|
-
self, *, run_id: RunId, await_: AwaitResume
|
119
|
-
) -> AsyncIterator[RunEvent]:
|
107
|
+
async def run_resume_stream(self, *, run_id: RunId, await_: AwaitResume) -> AsyncIterator[RunEvent]:
|
120
108
|
async with aconnect_sse(
|
121
109
|
self._client,
|
122
110
|
"POST",
|
@@ -1,6 +1,7 @@
|
|
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
|
|
@@ -40,24 +41,19 @@ MessagePart = Union[TextMessagePart, ImageMessagePart, ArtifactMessagePart]
|
|
40
41
|
class Message(RootModel):
|
41
42
|
root: list[MessagePart]
|
42
43
|
|
43
|
-
def __init__(self, *items: MessagePart):
|
44
|
+
def __init__(self, *items: MessagePart) -> None:
|
44
45
|
super().__init__(root=list(items))
|
45
46
|
|
46
|
-
def __iter__(self):
|
47
|
+
def __iter__(self) -> Iterator[MessagePart]:
|
47
48
|
return iter(self.root)
|
48
49
|
|
49
|
-
def __getitem__(self, item):
|
50
|
-
return self.root[item]
|
51
|
-
|
52
50
|
def __add__(self, other: "Message") -> "Message":
|
53
51
|
if not isinstance(other, Message):
|
54
52
|
raise TypeError(f"Cannot concatenate Message with {type(other).__name__}")
|
55
53
|
return Message(*(self.root + other.root))
|
56
54
|
|
57
|
-
def __str__(self):
|
58
|
-
return "".join(
|
59
|
-
str(part) for part in self.root if isinstance(part, TextMessagePart)
|
60
|
-
)
|
55
|
+
def __str__(self) -> str:
|
56
|
+
return "".join(str(part) for part in self.root if isinstance(part, TextMessagePart))
|
61
57
|
|
62
58
|
|
63
59
|
AgentName = str
|
@@ -107,8 +103,8 @@ class Run(BaseModel):
|
|
107
103
|
|
108
104
|
def model_dump_json(
|
109
105
|
self,
|
110
|
-
**kwargs,
|
111
|
-
):
|
106
|
+
**kwargs: dict[str, Any],
|
107
|
+
) -> str:
|
112
108
|
return super().model_dump_json(
|
113
109
|
by_alias=True,
|
114
110
|
**kwargs,
|
@@ -128,8 +124,8 @@ class AwaitEvent(BaseModel):
|
|
128
124
|
|
129
125
|
def model_dump_json(
|
130
126
|
self,
|
131
|
-
**kwargs,
|
132
|
-
):
|
127
|
+
**kwargs: dict[str, Any],
|
128
|
+
) -> str:
|
133
129
|
return super().model_dump_json(
|
134
130
|
by_alias=True,
|
135
131
|
**kwargs,
|
@@ -1,11 +1,11 @@
|
|
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
11
|
from acp_sdk.server.context import Context
|
@@ -21,9 +21,7 @@ class Agent(abc.ABC):
|
|
21
21
|
return ""
|
22
22
|
|
23
23
|
@abc.abstractmethod
|
24
|
-
def run(
|
25
|
-
self, input: Message, *, context: Context
|
26
|
-
) -> AsyncGenerator[Message | Await, AwaitResume]:
|
24
|
+
def run(self, input: Message, *, context: Context) -> AsyncGenerator[Message | Await, AwaitResume]:
|
27
25
|
pass
|
28
26
|
|
29
27
|
async def session(self, session_id: SessionId | None) -> SessionId | None:
|
@@ -1,15 +1,16 @@
|
|
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
9
|
ACPError,
|
10
10
|
AnyModel,
|
11
11
|
Await,
|
12
12
|
AwaitEvent,
|
13
|
+
AwaitResume,
|
13
14
|
CancelledEvent,
|
14
15
|
CompletedEvent,
|
15
16
|
CreatedEvent,
|
@@ -19,17 +20,17 @@ from acp_sdk.models import (
|
|
19
20
|
Message,
|
20
21
|
MessageEvent,
|
21
22
|
Run,
|
22
|
-
AwaitResume,
|
23
23
|
RunEvent,
|
24
24
|
RunStatus,
|
25
25
|
)
|
26
|
+
from acp_sdk.server.agent import Agent
|
26
27
|
from acp_sdk.server.context import Context
|
27
28
|
|
28
29
|
logger = logging.getLogger("uvicorn.error")
|
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
|
@@ -130,4 +128,4 @@ class RunBundle:
|
|
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)
|
@@ -1,17 +1,16 @@
|
|
1
1
|
import asyncio
|
2
2
|
|
3
|
-
from acp_sdk.server.telemetry import configure_telemetry
|
4
|
-
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
|
5
|
-
|
6
3
|
from fastapi import FastAPI, HTTPException, status
|
7
4
|
from fastapi.responses import JSONResponse, StreamingResponse
|
5
|
+
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
|
8
6
|
|
9
|
-
from acp_sdk.server.agent import Agent
|
10
7
|
from acp_sdk.models import (
|
11
|
-
AgentName,
|
12
8
|
Agent as AgentModel,
|
13
|
-
|
9
|
+
)
|
10
|
+
from acp_sdk.models import (
|
11
|
+
AgentName,
|
14
12
|
AgentReadResponse,
|
13
|
+
AgentsListResponse,
|
15
14
|
Run,
|
16
15
|
RunCancelResponse,
|
17
16
|
RunCreateRequest,
|
@@ -23,7 +22,9 @@ from acp_sdk.models import (
|
|
23
22
|
RunResumeResponse,
|
24
23
|
RunStatus,
|
25
24
|
)
|
25
|
+
from acp_sdk.server.agent import Agent
|
26
26
|
from acp_sdk.server.bundle import RunBundle
|
27
|
+
from acp_sdk.server.telemetry import configure_telemetry
|
27
28
|
from acp_sdk.server.utils import stream_sse
|
28
29
|
|
29
30
|
|
@@ -34,36 +35,33 @@ def create_app(*agents: Agent) -> FastAPI:
|
|
34
35
|
FastAPIInstrumentor.instrument_app(app)
|
35
36
|
|
36
37
|
agents: dict[AgentName, Agent] = {agent.name: agent for agent in agents}
|
37
|
-
runs: dict[RunId, RunBundle] =
|
38
|
+
runs: dict[RunId, RunBundle] = {}
|
38
39
|
|
39
|
-
def find_run_bundle(run_id: RunId):
|
40
|
-
bundle = runs.get(run_id
|
40
|
+
def find_run_bundle(run_id: RunId) -> RunBundle:
|
41
|
+
bundle = runs.get(run_id)
|
41
42
|
if not bundle:
|
42
43
|
raise HTTPException(status_code=404, detail=f"Run {run_id} not found")
|
43
44
|
return bundle
|
44
45
|
|
45
|
-
def find_agent(agent_name: AgentName):
|
46
|
+
def find_agent(agent_name: AgentName) -> Agent:
|
46
47
|
agent = agents.get(agent_name, None)
|
47
48
|
if not agent:
|
48
49
|
raise HTTPException(status_code=404, detail=f"Agent {agent_name} not found")
|
49
50
|
return agent
|
50
51
|
|
51
52
|
@app.get("/agents")
|
52
|
-
async def
|
53
|
+
async def list_agents() -> AgentsListResponse:
|
53
54
|
return AgentsListResponse(
|
54
|
-
agents=[
|
55
|
-
AgentModel(name=agent.name, description=agent.description)
|
56
|
-
for agent in agents.values()
|
57
|
-
]
|
55
|
+
agents=[AgentModel(name=agent.name, description=agent.description) for agent in agents.values()]
|
58
56
|
)
|
59
57
|
|
60
58
|
@app.get("/agents/{name}")
|
61
|
-
async def
|
59
|
+
async def read_agent(name: AgentName) -> AgentReadResponse:
|
62
60
|
agent = find_agent(name)
|
63
61
|
return AgentModel(name=agent.name, description=agent.description)
|
64
62
|
|
65
63
|
@app.post("/runs")
|
66
|
-
async def
|
64
|
+
async def create_run(request: RunCreateRequest) -> RunCreateResponse:
|
67
65
|
agent = find_agent(request.agent_name)
|
68
66
|
bundle = RunBundle(
|
69
67
|
agent=agent,
|
@@ -94,12 +92,12 @@ def create_app(*agents: Agent) -> FastAPI:
|
|
94
92
|
raise NotImplementedError()
|
95
93
|
|
96
94
|
@app.get("/runs/{run_id}")
|
97
|
-
async def
|
95
|
+
async def read_run(run_id: RunId) -> RunReadResponse:
|
98
96
|
bundle = find_run_bundle(run_id)
|
99
97
|
return bundle.run
|
100
98
|
|
101
99
|
@app.post("/runs/{run_id}")
|
102
|
-
async def
|
100
|
+
async def resume_run(run_id: RunId, request: RunResumeRequest) -> RunResumeResponse:
|
103
101
|
bundle = find_run_bundle(run_id)
|
104
102
|
bundle.stream_queue = asyncio.Queue() # TODO improve
|
105
103
|
await bundle.await_queue.put(request.await_)
|
@@ -121,7 +119,7 @@ def create_app(*agents: Agent) -> FastAPI:
|
|
121
119
|
raise NotImplementedError()
|
122
120
|
|
123
121
|
@app.post("/runs/{run_id}/cancel")
|
124
|
-
async def
|
122
|
+
async def cancel_run(run_id: RunId) -> RunCancelResponse:
|
125
123
|
bundle = find_run_bundle(run_id)
|
126
124
|
if bundle.run.status.is_terminal:
|
127
125
|
raise HTTPException(
|
@@ -130,8 +128,6 @@ def create_app(*agents: Agent) -> FastAPI:
|
|
130
128
|
)
|
131
129
|
bundle.task.cancel()
|
132
130
|
bundle.run.status = RunStatus.CANCELLING
|
133
|
-
return JSONResponse(
|
134
|
-
status_code=status.HTTP_202_ACCEPTED, content=bundle.run.model_dump()
|
135
|
-
)
|
131
|
+
return JSONResponse(status_code=status.HTTP_202_ACCEPTED, content=bundle.run.model_dump())
|
136
132
|
|
137
133
|
return app
|
@@ -1,22 +1,23 @@
|
|
1
1
|
import logging
|
2
2
|
from importlib.metadata import version
|
3
|
+
from typing import Any
|
3
4
|
|
4
5
|
from opentelemetry import trace
|
6
|
+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
5
7
|
from opentelemetry.sdk.resources import (
|
6
|
-
Resource,
|
7
8
|
SERVICE_NAME,
|
8
9
|
SERVICE_NAMESPACE,
|
9
10
|
SERVICE_VERSION,
|
11
|
+
Resource,
|
10
12
|
)
|
11
13
|
from opentelemetry.sdk.trace import TracerProvider
|
12
14
|
from opentelemetry.sdk.trace.export import BatchSpanProcessor, SpanExportResult
|
13
|
-
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
14
15
|
|
15
16
|
logger = logging.getLogger("uvicorn.error")
|
16
17
|
|
17
18
|
|
18
19
|
class SilentOTLPSpanExporter(OTLPSpanExporter):
|
19
|
-
def export(self, spans):
|
20
|
+
def export(self, spans: Any) -> SpanExportResult:
|
20
21
|
try:
|
21
22
|
return super().export(spans)
|
22
23
|
except Exception as e:
|
@@ -24,7 +25,7 @@ class SilentOTLPSpanExporter(OTLPSpanExporter):
|
|
24
25
|
return SpanExportResult.FAILURE
|
25
26
|
|
26
27
|
|
27
|
-
def configure_telemetry():
|
28
|
+
def configure_telemetry() -> None:
|
28
29
|
current_provider = trace.get_tracer_provider()
|
29
30
|
|
30
31
|
# Detect default provider and override
|
@@ -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
|
-
)
|
@@ -1 +0,0 @@
|
|
1
|
-
from acp_sdk.client.client import Client
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|