Skip to content

Commit f93147c

Browse files
CopilotnikhilNavanikhilc-microsoft
authored
Add /otlp/ segment to observability exporter URL paths (#225)
* Add /otlp/ segment to observability exporter URL paths Agent-Logs-Url: https://github.com/microsoft/Agent365-python/sessions/15f00c02-8b53-473e-857f-ad6ba3a9e1bb Co-authored-by: nikhilNava <211831449+nikhilNava@users.noreply.github.com> * add change log and bump version * address pr comment --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: nikhilNava <211831449+nikhilNava@users.noreply.github.com> Co-authored-by: Nikhil Navakiran <nikhil.navakiran@gmail.com> Co-authored-by: Nikhil Chitlur Navakiran (from Dev Box) <nikhilc@microsoft.com>
1 parent 8b94a1d commit f93147c

5 files changed

Lines changed: 57 additions & 16 deletions

File tree

libraries/microsoft-agents-a365-observability-core/CHANGELOG.md

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,45 @@
22

33
All notable changes to this package will be documented in this file.
44

5-
## [Unreleased]
5+
## [0.3.0]
6+
7+
### Breaking Changes
8+
9+
- **New permission required: `Agent365.Observability.OtelWrite`** — The observability exporter now requires this scope as both a delegated and application permission on your agent blueprint. See [Upgrade Instructions](#upgrade-instructions-observability-permission-for-existing-agents) below.
10+
11+
---
12+
13+
### Upgrade Instructions: Observability Permission for Existing Agents
14+
15+
Existing agent blueprints need `Agent365.Observability.OtelWrite` granted as both a **delegated permission** and an **application permission**. Choose either option below.
16+
17+
#### Option A — Agent 365 CLI (requires both config files)
18+
19+
Requires `a365.config.json` and `a365.generated.config.json` in your config directory, a Global Administrator account, and [Agent 365 CLI v1.1.139-preview](https://www.nuget.org/packages/Microsoft.Agents.A365.DevTools.Cli/1.1.139-preview) or later.
20+
21+
```
22+
a365 setup admin --config-dir "<path-to-config-dir>"
23+
```
24+
25+
This grants all missing permissions including the new Observability scopes.
26+
27+
#### Option B — Entra Portal (no config files required)
28+
29+
Requires Global Administrator access to the blueprint app registration.
30+
31+
1. Go to **Entra portal** > **App registrations** > select your Blueprint app
32+
2. Go to **API permissions** > **Add a permission** > **APIs my organization uses** > search for `9b975845-388f-4429-889e-eab1ef63949c`
33+
3. Select **Delegated permissions** > check `Agent365.Observability.OtelWrite` > **Add permissions**
34+
4. Repeat step 2–3, this time select **Application permissions** > check `Agent365.Observability.OtelWrite` > **Add permissions**
35+
5. Click **Grant admin consent** and confirm
36+
37+
Both `Agent365.Observability.OtelWrite` (Delegated) and `Agent365.Observability.OtelWrite` (Application) should show **Granted** status.
38+
39+
> **Note:** If your agent is autonomous, you only need the **Application permission**. The delegated permission is required for agents that authenticate via a user session.
40+
41+
---
42+
43+
## [0.2.1.dev46]
644

745
### Breaking Changes
846

libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/exporters/agent365_exporter.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ class _Agent365Exporter(SpanExporter):
4646
Agent 365 span exporter for Agent 365:
4747
* Partitions spans by (tenantId, agentId)
4848
* Builds OTLP-like JSON: resourceSpans -> scopeSpans -> spans
49-
* POSTs per group to https://{endpoint}/maven/agent365/agents/{agentId}/traces?api-version=1
49+
* POSTs per group to https://{endpoint}/observability/tenants/{tenantId}/otlp/agents/{agentId}/traces?api-version=1
50+
* or, when use_s2s_endpoint is True, https://{endpoint}/observabilityService/tenants/{tenantId}/otlp/agents/{agentId}/traces?api-version=1
5051
* Adds Bearer token via token_resolver(agentId, tenantId)
5152
"""
5253

libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/exporters/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,9 +221,9 @@ def build_export_url(
221221
The fully constructed export URL with path and query parameters.
222222
"""
223223
endpoint_path = (
224-
f"/observabilityService/tenants/{tenant_id}/agents/{agent_id}/traces"
224+
f"/observabilityService/tenants/{tenant_id}/otlp/agents/{agent_id}/traces"
225225
if use_s2s_endpoint
226-
else f"/observability/tenants/{tenant_id}/agents/{agent_id}/traces"
226+
else f"/observability/tenants/{tenant_id}/otlp/agents/{agent_id}/traces"
227227
)
228228

229229
parsed = urlparse(endpoint)

tests/observability/core/test_agent365_exporter.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ def test_export_success(self):
124124

125125
self.assertIn(DEFAULT_ENDPOINT_URL, url)
126126
self.assertIn(
127-
"/observability/tenants/test-tenant-123/agents/test-agent-456/traces", url
127+
"/observability/tenants/test-tenant-123/otlp/agents/test-agent-456/traces", url
128128
)
129129
self.assertEqual(headers["authorization"], "Bearer test_token_123")
130130
self.assertEqual(headers["content-type"], "application/json")
@@ -237,10 +237,11 @@ def test_s2s_endpoint_path_when_enabled(self):
237237

238238
self.assertIn(DEFAULT_ENDPOINT_URL, url)
239239
self.assertIn(
240-
"/observabilityService/tenants/test-tenant-123/agents/test-agent-456/traces", url
240+
"/observabilityService/tenants/test-tenant-123/otlp/agents/test-agent-456/traces",
241+
url,
241242
)
242243
self.assertNotIn(
243-
"/observability/tenants/test-tenant-123/agents/test-agent-456/traces", url
244+
"/observability/tenants/test-tenant-123/otlp/agents/test-agent-456/traces", url
244245
)
245246
self.assertEqual(headers["authorization"], "Bearer test_token_123")
246247
self.assertEqual(headers["content-type"], "application/json")
@@ -269,10 +270,11 @@ def test_default_endpoint_path_when_s2s_disabled(self):
269270

270271
self.assertIn(DEFAULT_ENDPOINT_URL, url)
271272
self.assertIn(
272-
"/observability/tenants/test-tenant-123/agents/test-agent-456/traces", url
273+
"/observability/tenants/test-tenant-123/otlp/agents/test-agent-456/traces", url
273274
)
274275
self.assertNotIn(
275-
"/observabilityService/tenants/test-tenant-123/agents/test-agent-456/traces", url
276+
"/observabilityService/tenants/test-tenant-123/otlp/agents/test-agent-456/traces",
277+
url,
276278
)
277279
self.assertEqual(headers["authorization"], "Bearer test_token_123")
278280
self.assertEqual(headers["content-type"], "application/json")
@@ -318,7 +320,7 @@ def test_export_logging(self, mock_logger):
318320
unittest.mock.call.debug("Found 1 identity groups with 2 total spans to export"),
319321
# Should log endpoint being used at DEBUG (default endpoint)
320322
unittest.mock.call.debug(
321-
f"Exporting 2 spans to endpoint: {DEFAULT_ENDPOINT_URL}/observability/tenants/test-tenant-123/agents/test-agent-456/traces?api-version=1 "
323+
f"Exporting 2 spans to endpoint: {DEFAULT_ENDPOINT_URL}/observability/tenants/test-tenant-123/otlp/agents/test-agent-456/traces?api-version=1 "
322324
"(tenant: test-tenant-123, agent: test-agent-456)"
323325
),
324326
# Should log token resolution success at DEBUG
@@ -391,7 +393,7 @@ def test_export_uses_domain_override_when_env_var_set(self):
391393
args, kwargs = mock_post.call_args
392394
url, body, headers = args
393395

394-
expected_url = f"https://{override_domain}/observability/tenants/test-tenant-123/agents/test-agent-456/traces?api-version=1"
396+
expected_url = f"https://{override_domain}/observability/tenants/test-tenant-123/otlp/agents/test-agent-456/traces?api-version=1"
395397
self.assertEqual(url, expected_url)
396398

397399
def test_export_uses_default_endpoint_when_no_override(self):
@@ -420,7 +422,7 @@ def test_export_uses_default_endpoint_when_no_override(self):
420422
args, kwargs = mock_post.call_args
421423
url, body, headers = args
422424

423-
expected_url = f"{DEFAULT_ENDPOINT_URL}/observability/tenants/test-tenant-123/agents/test-agent-456/traces?api-version=1"
425+
expected_url = f"{DEFAULT_ENDPOINT_URL}/observability/tenants/test-tenant-123/otlp/agents/test-agent-456/traces?api-version=1"
424426
self.assertEqual(url, expected_url)
425427

426428
def test_export_ignores_empty_domain_override(self):
@@ -474,7 +476,7 @@ def test_export_uses_valid_url_override_with_https(self):
474476
args, kwargs = mock_post.call_args
475477
url, body, headers = args
476478

477-
expected_url = "https://override.example.com/observability/tenants/test-tenant-123/agents/test-agent-456/traces?api-version=1"
479+
expected_url = "https://override.example.com/observability/tenants/test-tenant-123/otlp/agents/test-agent-456/traces?api-version=1"
478480
self.assertEqual(url, expected_url)
479481

480482
def test_export_uses_valid_url_override_with_http(self):
@@ -502,7 +504,7 @@ def test_export_uses_valid_url_override_with_http(self):
502504
args, kwargs = mock_post.call_args
503505
url, body, headers = args
504506

505-
expected_url = "http://localhost:8080/observability/tenants/test-tenant-123/agents/test-agent-456/traces?api-version=1"
507+
expected_url = "http://localhost:8080/observability/tenants/test-tenant-123/otlp/agents/test-agent-456/traces?api-version=1"
506508
self.assertEqual(url, expected_url)
507509

508510
def test_export_uses_valid_domain_override_with_port(self):
@@ -530,7 +532,7 @@ def test_export_uses_valid_domain_override_with_port(self):
530532
args, kwargs = mock_post.call_args
531533
url, body, headers = args
532534

533-
expected_url = "https://example.com:8080/observability/tenants/test-tenant-123/agents/test-agent-456/traces?api-version=1"
535+
expected_url = "https://example.com:8080/observability/tenants/test-tenant-123/otlp/agents/test-agent-456/traces?api-version=1"
534536
self.assertEqual(url, expected_url)
535537

536538
def test_export_ignores_invalid_domain_with_protocol(self):

versioning/TARGET-VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.2.1
1+
0.3.0

0 commit comments

Comments
 (0)