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.
Files changed (28) hide show
  1. {acp_sdk-1.0.0rc2 → acp_sdk-1.0.0rc3}/PKG-INFO +3 -5
  2. {acp_sdk-1.0.0rc2 → acp_sdk-1.0.0rc3}/README.md +2 -4
  3. {acp_sdk-1.0.0rc2 → acp_sdk-1.0.0rc3}/examples/servers/awaiting.py +2 -3
  4. {acp_sdk-1.0.0rc2 → acp_sdk-1.0.0rc3}/examples/servers/echo.py +2 -2
  5. {acp_sdk-1.0.0rc2 → acp_sdk-1.0.0rc3}/pyproject.toml +1 -1
  6. acp_sdk-1.0.0rc3/src/acp_sdk/__init__.py +1 -0
  7. {acp_sdk-1.0.0rc2 → acp_sdk-1.0.0rc3}/src/acp_sdk/client/client.py +17 -1
  8. acp_sdk-1.0.0rc3/src/acp_sdk/models/__init__.py +2 -0
  9. acp_sdk-1.0.0rc3/src/acp_sdk/models/errors.py +23 -0
  10. {acp_sdk-1.0.0rc2/src/acp_sdk → acp_sdk-1.0.0rc3/src/acp_sdk/models}/models.py +6 -4
  11. {acp_sdk-1.0.0rc2 → acp_sdk-1.0.0rc3}/src/acp_sdk/server/__init__.py +1 -0
  12. {acp_sdk-1.0.0rc2 → acp_sdk-1.0.0rc3}/src/acp_sdk/server/agent.py +5 -0
  13. {acp_sdk-1.0.0rc2 → acp_sdk-1.0.0rc3}/src/acp_sdk/server/bundle.py +4 -4
  14. acp_sdk-1.0.0rc3/src/acp_sdk/server/errors.py +54 -0
  15. acp_sdk-1.0.0rc3/src/acp_sdk/server/logging.py +16 -0
  16. {acp_sdk-1.0.0rc2 → acp_sdk-1.0.0rc3}/src/acp_sdk/server/server.py +35 -4
  17. acp_sdk-1.0.0rc3/src/acp_sdk/server/telemetry.py +52 -0
  18. acp_sdk-1.0.0rc2/src/acp_sdk/__init__.py +0 -0
  19. acp_sdk-1.0.0rc2/src/acp_sdk/server/telemetry.py +0 -46
  20. {acp_sdk-1.0.0rc2 → acp_sdk-1.0.0rc3}/.gitignore +0 -0
  21. {acp_sdk-1.0.0rc2 → acp_sdk-1.0.0rc3}/.python-version +0 -0
  22. {acp_sdk-1.0.0rc2 → acp_sdk-1.0.0rc3}/examples/clients/advanced.py +0 -0
  23. {acp_sdk-1.0.0rc2 → acp_sdk-1.0.0rc3}/examples/clients/simple.py +0 -0
  24. {acp_sdk-1.0.0rc2 → acp_sdk-1.0.0rc3}/examples/clients/stream.py +0 -0
  25. {acp_sdk-1.0.0rc2 → acp_sdk-1.0.0rc3}/src/acp_sdk/client/__init__.py +0 -0
  26. {acp_sdk-1.0.0rc2 → acp_sdk-1.0.0rc3}/src/acp_sdk/py.typed +0 -0
  27. {acp_sdk-1.0.0rc2 → acp_sdk-1.0.0rc3}/src/acp_sdk/server/context.py +0 -0
  28. {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.0rc2
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
- ## Example
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
- fastapi dev examples/servers/echo.py
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
- ## Example
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
- fastapi dev examples/servers/echo.py
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
- app = create_app(AwaitingAgent())
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, create_app
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
- app = create_app(EchoAgent())
29
+ serve(EchoAgent())
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "acp-sdk"
3
- version = "1.0.0rc2"
3
+ version = "1.0.0rc3"
4
4
  description = "Agent Communication Protocol SDK"
5
5
  readme = "README.md"
6
6
  authors = []
@@ -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) -> Self:
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,2 @@
1
+ from acp_sdk.models.errors import * # noqa: F403
2
+ from acp_sdk.models.models import * # noqa: F403
@@ -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
- class ACPError(BaseModel):
10
- code: str
11
- message: str
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: ACPError | None = None
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):
@@ -1,2 +1,3 @@
1
1
  from acp_sdk.server.agent import Agent as Agent
2
2
  from acp_sdk.server.server import create_app as create_app
3
+ from acp_sdk.server.server import serve as serve
@@ -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 = ACPError(code="unspecified", message=str(e))
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.telemetry import configure_telemetry
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=[AgentModel(name=agent.name, description=agent.description) for agent in agents.values()]
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