acp-sdk 1.0.0rc2__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.0rc2 → acp_sdk-1.0.0rc3}/PKG-INFO +3 -5
- {acp_sdk-1.0.0rc2 → acp_sdk-1.0.0rc3}/README.md +2 -4
- {acp_sdk-1.0.0rc2 → acp_sdk-1.0.0rc3}/examples/servers/awaiting.py +2 -3
- {acp_sdk-1.0.0rc2 → acp_sdk-1.0.0rc3}/examples/servers/echo.py +2 -2
- {acp_sdk-1.0.0rc2 → acp_sdk-1.0.0rc3}/pyproject.toml +1 -1
- acp_sdk-1.0.0rc3/src/acp_sdk/__init__.py +1 -0
- {acp_sdk-1.0.0rc2 → acp_sdk-1.0.0rc3}/src/acp_sdk/client/client.py +17 -1
- 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.0rc2/src/acp_sdk → acp_sdk-1.0.0rc3/src/acp_sdk/models}/models.py +6 -4
- {acp_sdk-1.0.0rc2 → acp_sdk-1.0.0rc3}/src/acp_sdk/server/__init__.py +1 -0
- {acp_sdk-1.0.0rc2 → acp_sdk-1.0.0rc3}/src/acp_sdk/server/agent.py +5 -0
- {acp_sdk-1.0.0rc2 → acp_sdk-1.0.0rc3}/src/acp_sdk/server/bundle.py +4 -4
- 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.0rc2 → acp_sdk-1.0.0rc3}/src/acp_sdk/server/server.py +35 -4
- acp_sdk-1.0.0rc3/src/acp_sdk/server/telemetry.py +52 -0
- acp_sdk-1.0.0rc2/src/acp_sdk/__init__.py +0 -0
- acp_sdk-1.0.0rc2/src/acp_sdk/server/telemetry.py +0 -46
- {acp_sdk-1.0.0rc2 → acp_sdk-1.0.0rc3}/.gitignore +0 -0
- {acp_sdk-1.0.0rc2 → acp_sdk-1.0.0rc3}/.python-version +0 -0
- {acp_sdk-1.0.0rc2 → acp_sdk-1.0.0rc3}/examples/clients/advanced.py +0 -0
- {acp_sdk-1.0.0rc2 → acp_sdk-1.0.0rc3}/examples/clients/simple.py +0 -0
- {acp_sdk-1.0.0rc2 → acp_sdk-1.0.0rc3}/examples/clients/stream.py +0 -0
- {acp_sdk-1.0.0rc2 → acp_sdk-1.0.0rc3}/src/acp_sdk/client/__init__.py +0 -0
- {acp_sdk-1.0.0rc2 → acp_sdk-1.0.0rc3}/src/acp_sdk/py.typed +0 -0
- {acp_sdk-1.0.0rc2 → acp_sdk-1.0.0rc3}/src/acp_sdk/server/context.py +0 -0
- {acp_sdk-1.0.0rc2 → acp_sdk-1.0.0rc3}/src/acp_sdk/server/utils.py +0 -0
@@ -1,6 +1,6 @@
|
|
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
5
|
Requires-Python: <4.0,>=3.11
|
6
6
|
Requires-Dist: opentelemetry-api>=1.31.1
|
@@ -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
|
@@ -6,9 +6,8 @@ from acp_sdk.models import (
|
|
6
6
|
Message,
|
7
7
|
TextMessagePart,
|
8
8
|
)
|
9
|
-
from acp_sdk.server import Agent
|
9
|
+
from acp_sdk.server import Agent, serve
|
10
10
|
from acp_sdk.server.context import Context
|
11
|
-
from acp_sdk.server.server import create_app
|
12
11
|
|
13
12
|
|
14
13
|
class AwaitingAgent(Agent):
|
@@ -26,4 +25,4 @@ class AwaitingAgent(Agent):
|
|
26
25
|
yield Message(TextMessagePart(content=f"Thanks for {data}"))
|
27
26
|
|
28
27
|
|
29
|
-
|
28
|
+
serve(AwaitingAgent())
|
@@ -6,7 +6,7 @@ from acp_sdk.models import (
|
|
6
6
|
AwaitResume,
|
7
7
|
Message,
|
8
8
|
)
|
9
|
-
from acp_sdk.server import Agent,
|
9
|
+
from acp_sdk.server import Agent, serve
|
10
10
|
from acp_sdk.server.context import Context
|
11
11
|
|
12
12
|
|
@@ -26,4 +26,4 @@ class EchoAgent(Agent):
|
|
26
26
|
yield Message(part)
|
27
27
|
|
28
28
|
|
29
|
-
|
29
|
+
serve(EchoAgent())
|
@@ -0,0 +1 @@
|
|
1
|
+
from acp_sdk.models import * # noqa: F403
|
@@ -8,11 +8,13 @@ from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
|
|
8
8
|
from pydantic import TypeAdapter
|
9
9
|
|
10
10
|
from acp_sdk.models import (
|
11
|
+
ACPError,
|
11
12
|
Agent,
|
12
13
|
AgentName,
|
13
14
|
AgentReadResponse,
|
14
15
|
AgentsListResponse,
|
15
16
|
AwaitResume,
|
17
|
+
Error,
|
16
18
|
Message,
|
17
19
|
Run,
|
18
20
|
RunCancelResponse,
|
@@ -32,7 +34,7 @@ class Client:
|
|
32
34
|
|
33
35
|
self._client = self._init_client(client)
|
34
36
|
|
35
|
-
def _init_client(self, client: httpx.AsyncClient | None = None) ->
|
37
|
+
def _init_client(self, client: httpx.AsyncClient | None = None) -> httpx.AsyncClient:
|
36
38
|
client = client or httpx.AsyncClient(base_url=self.base_url)
|
37
39
|
HTTPXClientInstrumentor.instrument_client(client)
|
38
40
|
return client
|
@@ -51,11 +53,13 @@ class Client:
|
|
51
53
|
|
52
54
|
async def agents(self) -> AsyncIterator[Agent]:
|
53
55
|
response = await self._client.get("/agents")
|
56
|
+
self._raise_error(response)
|
54
57
|
for agent in AgentsListResponse.model_validate(response.json()).agents:
|
55
58
|
yield agent
|
56
59
|
|
57
60
|
async def agent(self, *, name: AgentName) -> Agent:
|
58
61
|
response = await self._client.get(f"/agents/{name}")
|
62
|
+
self._raise_error(response)
|
59
63
|
return AgentReadResponse.model_validate(response.json())
|
60
64
|
|
61
65
|
async def run_sync(self, *, agent: AgentName, input: Message) -> Run:
|
@@ -63,6 +67,7 @@ class Client:
|
|
63
67
|
"/runs",
|
64
68
|
json=RunCreateRequest(agent_name=agent, input=input, mode=RunMode.SYNC).model_dump(),
|
65
69
|
)
|
70
|
+
self._raise_error(response)
|
66
71
|
return RunCreateResponse.model_validate(response.json())
|
67
72
|
|
68
73
|
async def run_async(self, *, agent: AgentName, input: Message) -> Run:
|
@@ -70,6 +75,7 @@ class Client:
|
|
70
75
|
"/runs",
|
71
76
|
json=RunCreateRequest(agent_name=agent, input=input, mode=RunMode.ASYNC).model_dump(),
|
72
77
|
)
|
78
|
+
self._raise_error(response)
|
73
79
|
return RunCreateResponse.model_validate(response.json())
|
74
80
|
|
75
81
|
async def run_stream(self, *, agent: AgentName, input: Message) -> AsyncIterator[RunEvent]:
|
@@ -84,10 +90,12 @@ class Client:
|
|
84
90
|
|
85
91
|
async def run_status(self, *, run_id: RunId) -> Run:
|
86
92
|
response = await self._client.get(f"/runs/{run_id}")
|
93
|
+
self._raise_error(response)
|
87
94
|
return Run.model_validate(response.json())
|
88
95
|
|
89
96
|
async def run_cancel(self, *, run_id: RunId) -> Run:
|
90
97
|
response = await self._client.post(f"/runs/{run_id}/cancel")
|
98
|
+
self._raise_error(response)
|
91
99
|
return RunCancelResponse.model_validate(response.json())
|
92
100
|
|
93
101
|
async def run_resume_sync(self, *, run_id: RunId, await_: AwaitResume) -> Run:
|
@@ -95,6 +103,7 @@ class Client:
|
|
95
103
|
f"/runs/{run_id}",
|
96
104
|
json=RunResumeRequest(await_=await_, mode=RunMode.SYNC).model_dump(),
|
97
105
|
)
|
106
|
+
self._raise_error(response)
|
98
107
|
return RunResumeResponse.model_validate(response.json())
|
99
108
|
|
100
109
|
async def run_resume_async(self, *, run_id: RunId, await_: AwaitResume) -> Run:
|
@@ -102,6 +111,7 @@ class Client:
|
|
102
111
|
f"/runs/{run_id}",
|
103
112
|
json=RunResumeRequest(await_=await_, mode=RunMode.ASYNC).model_dump(),
|
104
113
|
)
|
114
|
+
self._raise_error(response)
|
105
115
|
return RunResumeResponse.model_validate(response.json())
|
106
116
|
|
107
117
|
async def run_resume_stream(self, *, run_id: RunId, await_: AwaitResume) -> AsyncIterator[RunEvent]:
|
@@ -121,3 +131,9 @@ class Client:
|
|
121
131
|
async for event in event_source.aiter_sse():
|
122
132
|
event = TypeAdapter(RunEvent).validate_json(event.data)
|
123
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)
|
@@ -5,10 +5,11 @@ from typing import Any, Literal, Union
|
|
5
5
|
|
6
6
|
from pydantic import AnyUrl, BaseModel, ConfigDict, Field, RootModel
|
7
7
|
|
8
|
+
from acp_sdk.models.errors import Error
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
10
|
+
|
11
|
+
class Metadata(BaseModel):
|
12
|
+
model_config = ConfigDict(extra="allow")
|
12
13
|
|
13
14
|
|
14
15
|
class AnyModel(BaseModel):
|
@@ -97,7 +98,7 @@ class Run(BaseModel):
|
|
97
98
|
status: RunStatus = RunStatus.CREATED
|
98
99
|
await_: Await | None = Field(None, alias="await")
|
99
100
|
output: Message | None = None
|
100
|
-
error:
|
101
|
+
error: Error | None = None
|
101
102
|
|
102
103
|
model_config = ConfigDict(populate_by_name=True)
|
103
104
|
|
@@ -205,6 +206,7 @@ class RunCancelResponse(Run):
|
|
205
206
|
class Agent(BaseModel):
|
206
207
|
name: str
|
207
208
|
description: str | None = None
|
209
|
+
metadata: Metadata = Metadata()
|
208
210
|
|
209
211
|
|
210
212
|
class AgentsListResponse(BaseModel):
|
@@ -8,6 +8,7 @@ from acp_sdk.models import (
|
|
8
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,6 +21,10 @@ 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
29
|
def run(self, input: Message, *, context: Context) -> AsyncGenerator[Message | Await, AwaitResume]:
|
25
30
|
pass
|
@@ -6,7 +6,6 @@ from opentelemetry import trace
|
|
6
6
|
from pydantic import ValidationError
|
7
7
|
|
8
8
|
from acp_sdk.models import (
|
9
|
-
ACPError,
|
10
9
|
AnyModel,
|
11
10
|
Await,
|
12
11
|
AwaitEvent,
|
@@ -14,6 +13,7 @@ from acp_sdk.models import (
|
|
14
13
|
CancelledEvent,
|
15
14
|
CompletedEvent,
|
16
15
|
CreatedEvent,
|
16
|
+
Error,
|
17
17
|
FailedEvent,
|
18
18
|
GenericEvent,
|
19
19
|
InProgressEvent,
|
@@ -23,10 +23,10 @@ from acp_sdk.models import (
|
|
23
23
|
RunEvent,
|
24
24
|
RunStatus,
|
25
25
|
)
|
26
|
+
from acp_sdk.models.errors import ErrorCode
|
26
27
|
from acp_sdk.server.agent import Agent
|
27
28
|
from acp_sdk.server.context import Context
|
28
|
-
|
29
|
-
logger = logging.getLogger("uvicorn.error")
|
29
|
+
from acp_sdk.server.logging import logger
|
30
30
|
|
31
31
|
|
32
32
|
class RunBundle:
|
@@ -122,7 +122,7 @@ class RunBundle:
|
|
122
122
|
await self.emit(CancelledEvent(run=self.run))
|
123
123
|
run_logger.info("Run cancelled")
|
124
124
|
except Exception as e:
|
125
|
-
self.run.error =
|
125
|
+
self.run.error = Error(code=ErrorCode.SERVER_ERROR, message=str(e))
|
126
126
|
self.run.status = RunStatus.FAILED
|
127
127
|
await self.emit(FailedEvent(run=self.run))
|
128
128
|
run_logger.exception("Run failed")
|
@@ -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,4 +1,5 @@
|
|
1
1
|
import asyncio
|
2
|
+
from typing import Any
|
2
3
|
|
3
4
|
from fastapi import FastAPI, HTTPException, status
|
4
5
|
from fastapi.responses import JSONResponse, StreamingResponse
|
@@ -22,21 +23,35 @@ from acp_sdk.models import (
|
|
22
23
|
RunResumeResponse,
|
23
24
|
RunStatus,
|
24
25
|
)
|
26
|
+
from acp_sdk.models.errors import ACPError
|
25
27
|
from acp_sdk.server.agent import Agent
|
26
28
|
from acp_sdk.server.bundle import RunBundle
|
27
|
-
from acp_sdk.server.
|
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
|
28
39
|
from acp_sdk.server.utils import stream_sse
|
29
40
|
|
30
41
|
|
31
42
|
def create_app(*agents: Agent) -> FastAPI:
|
32
43
|
app = FastAPI(title="acp-agents")
|
33
44
|
|
34
|
-
configure_telemetry()
|
35
45
|
FastAPIInstrumentor.instrument_app(app)
|
36
46
|
|
37
47
|
agents: dict[AgentName, Agent] = {agent.name: agent for agent in agents}
|
38
48
|
runs: dict[RunId, RunBundle] = {}
|
39
49
|
|
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
|
+
|
40
55
|
def find_run_bundle(run_id: RunId) -> RunBundle:
|
41
56
|
bundle = runs.get(run_id)
|
42
57
|
if not bundle:
|
@@ -52,13 +67,16 @@ def create_app(*agents: Agent) -> FastAPI:
|
|
52
67
|
@app.get("/agents")
|
53
68
|
async def list_agents() -> AgentsListResponse:
|
54
69
|
return AgentsListResponse(
|
55
|
-
agents=[
|
70
|
+
agents=[
|
71
|
+
AgentModel(name=agent.name, description=agent.description, metadata=agent.metadata)
|
72
|
+
for agent in agents.values()
|
73
|
+
]
|
56
74
|
)
|
57
75
|
|
58
76
|
@app.get("/agents/{name}")
|
59
77
|
async def read_agent(name: AgentName) -> AgentReadResponse:
|
60
78
|
agent = find_agent(name)
|
61
|
-
return AgentModel(name=agent.name, description=agent.description)
|
79
|
+
return AgentModel(name=agent.name, description=agent.description, metadata=agent.metadata)
|
62
80
|
|
63
81
|
@app.post("/runs")
|
64
82
|
async def create_run(request: RunCreateRequest) -> RunCreateResponse:
|
@@ -131,3 +149,16 @@ def create_app(*agents: Agent) -> FastAPI:
|
|
131
149
|
return JSONResponse(status_code=status.HTTP_202_ACCEPTED, content=bundle.run.model_dump())
|
132
150
|
|
133
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))
|
File without changes
|
@@ -1,46 +0,0 @@
|
|
1
|
-
import logging
|
2
|
-
from importlib.metadata import version
|
3
|
-
from typing import Any
|
4
|
-
|
5
|
-
from opentelemetry import trace
|
6
|
-
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
7
|
-
from opentelemetry.sdk.resources import (
|
8
|
-
SERVICE_NAME,
|
9
|
-
SERVICE_NAMESPACE,
|
10
|
-
SERVICE_VERSION,
|
11
|
-
Resource,
|
12
|
-
)
|
13
|
-
from opentelemetry.sdk.trace import TracerProvider
|
14
|
-
from opentelemetry.sdk.trace.export import BatchSpanProcessor, SpanExportResult
|
15
|
-
|
16
|
-
logger = logging.getLogger("uvicorn.error")
|
17
|
-
|
18
|
-
|
19
|
-
class SilentOTLPSpanExporter(OTLPSpanExporter):
|
20
|
-
def export(self, spans: Any) -> SpanExportResult:
|
21
|
-
try:
|
22
|
-
return super().export(spans)
|
23
|
-
except Exception as e:
|
24
|
-
logger.warning(f"OpenTelemetry Exporter failed silently: {e}")
|
25
|
-
return SpanExportResult.FAILURE
|
26
|
-
|
27
|
-
|
28
|
-
def configure_telemetry() -> None:
|
29
|
-
current_provider = trace.get_tracer_provider()
|
30
|
-
|
31
|
-
# Detect default provider and override
|
32
|
-
if isinstance(current_provider, trace.ProxyTracerProvider):
|
33
|
-
provider = TracerProvider(
|
34
|
-
resource=Resource(
|
35
|
-
attributes={
|
36
|
-
SERVICE_NAME: "acp-server",
|
37
|
-
SERVICE_NAMESPACE: "acp",
|
38
|
-
SERVICE_VERSION: version("acp-sdk"),
|
39
|
-
}
|
40
|
-
)
|
41
|
-
)
|
42
|
-
|
43
|
-
processor = BatchSpanProcessor(SilentOTLPSpanExporter())
|
44
|
-
provider.add_span_processor(processor)
|
45
|
-
|
46
|
-
trace.set_tracer_provider(provider)
|
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
|