Skip to content

Commit 8b08ac2

Browse files
committed
Merge remote-tracking branch 'origin/main' into pmohapatra-MCP-V1-V2-python
2 parents 8cc5e32 + f93147c commit 8b08ac2

60 files changed

Lines changed: 5544 additions & 2791 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -107,15 +107,13 @@ jobs:
107107
run: uv lock && uv sync --locked --all-extras --dev
108108

109109
- name: Verify centralized version constraints
110-
run: python scripts/verify_constraints.py
110+
run: uv run --frozen tox -e verify-constraints
111111

112112
- name: Check linting
113-
run: |
114-
uv run --frozen ruff check . --preview
113+
run: uv run --frozen tox -e lint
115114

116115
- name: Check formatting
117-
run: |
118-
uv run --frozen ruff format --check .
116+
run: uv run --frozen tox -e format
119117

120118
- name: Build package
121119
run: |
@@ -124,14 +122,12 @@ jobs:
124122
AGENT365_PYTHON_SDK_PACKAGE_VERSION: ${{ needs.version-number.outputs.PACKAGE_VERSION }}
125123

126124
- name: Run unit tests
127-
run: |
128-
uv run --frozen pytest tests/ -v --tb=short -m "not integration"
125+
run: uv run --frozen tox -e py3${{ matrix.python-version == '3.11' && '11' || '12' }}
129126

130127
- name: Run integration tests
131128
# Only run integration tests if secrets are available
132129
if: ${{ vars.RUN_INTEGRATION_TESTS == 'true' }}
133-
run: |
134-
uv run --frozen pytest -m integration -v --tb=short
130+
run: uv run --frozen tox -e integration
135131
env:
136132
AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }}
137133
AZURE_OPENAI_ENDPOINT: ${{ vars.AZURE_OPENAI_ENDPOINT }}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ dist/
7272
build/
7373
.eggs/
7474
.pytest_cache/
75+
.tox/
7576
_version.py
7677

7778
# Test coverage and reports

CLAUDE.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,31 @@ pytest tests/ --cov=libraries --cov-report=html -v
6161
- `unit`: Fast, mocked tests (default)
6262
- `integration`: Slow tests requiring real services/API keys
6363

64+
### Running with tox
65+
66+
```bash
67+
# Run all default environments (lint, format, unit tests on 3.11 + 3.12)
68+
uv run tox
69+
70+
# Run a specific environment
71+
uv run tox -e lint
72+
uv run tox -e format
73+
uv run tox -e py311
74+
uv run tox -e py312
75+
76+
# Run integration tests (requires env vars)
77+
uv run tox -e integration
78+
79+
# Verify centralized dependency constraints
80+
uv run tox -e verify-constraints
81+
82+
# Pass extra args to pytest
83+
uv run tox -e py311 -- -k "environment"
84+
85+
# List all available environments
86+
uv run tox list
87+
```
88+
6489
### Linting and Formatting
6590

6691
```bash
@@ -241,7 +266,8 @@ Place it before imports with one blank line after.
241266
The `.github/workflows/ci.yml` pipeline:
242267
- Runs on pushes to `main` and `release/*` branches
243268
- Tests both Python 3.11 and 3.12
244-
- Executes: lint check → format check → build → unit tests → integration tests (if secrets available)
269+
- Uses **tox** (via `uv run --frozen tox -e <env>`) to run lint, format, test, and constraint verification steps
270+
- Executes: verify-constraints → lint → format → build → unit tests → integration tests (if secrets available)
245271
- Only publishes packages on `release/*` branches when SDK changes detected
246272
- Uses git-based versioning (tags on release branches = official versions, others = dev versions)
247273

libraries/microsoft-agents-a365-notifications/pyproject.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
[build-system]
2-
requires = ["setuptools>=68", "wheel", "tzdata"]
3-
build-backend = "setuptools.build_meta"
2+
requires = ["setuptools>=68", "wheel", "tzdata", "tomlkit", "packaging"]
3+
build-backend = "build_backend"
4+
backend-path = ["../../versioning/helper"]
45

56
[project]
67
name = "microsoft-agents-a365-notifications"

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/__init__.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,37 @@
1919
unregister_span_enricher,
2020
)
2121
from .exporters.spectra_exporter_options import SpectraExporterOptions
22-
from .inference_call_details import InferenceCallDetails, ServiceEndpoint
22+
from .inference_call_details import InferenceCallDetails
23+
from .models.service_endpoint import ServiceEndpoint
2324
from .inference_operation_type import InferenceOperationType
2425
from .inference_scope import InferenceScope
2526
from .invoke_agent_details import InvokeAgentScopeDetails
2627
from .invoke_agent_scope import InvokeAgentScope
2728
from .middleware.baggage_builder import BaggageBuilder
2829
from .models.caller_details import CallerDetails
30+
from .models.messages import (
31+
BlobPart,
32+
ChatMessage,
33+
FilePart,
34+
FinishReason,
35+
GenericPart,
36+
InputMessages,
37+
InputMessagesParam,
38+
MessagePart,
39+
MessageRole,
40+
Modality,
41+
OutputMessage,
42+
OutputMessages,
43+
OutputMessagesParam,
44+
ReasoningPart,
45+
ServerToolCallPart,
46+
ServerToolCallResponsePart,
47+
TextPart,
48+
ToolCallRequestPart,
49+
ToolCallResponsePart,
50+
UriPart,
51+
)
52+
from .models.response import Response
2953
from .models.user_details import UserDetails
3054
from .opentelemetry_scope import OpenTelemetryScope
3155
from .request import Request
@@ -70,12 +94,34 @@
7094
"ToolCallDetails",
7195
"Channel",
7296
"Request",
97+
"Response",
7398
"SpanDetails",
7499
"InferenceCallDetails",
75100
"ServiceEndpoint",
76101
# Enums
77102
"InferenceOperationType",
78103
"ToolType",
104+
# OTEL gen-ai message format types
105+
"MessageRole",
106+
"FinishReason",
107+
"Modality",
108+
"TextPart",
109+
"ToolCallRequestPart",
110+
"ToolCallResponsePart",
111+
"ReasoningPart",
112+
"BlobPart",
113+
"FilePart",
114+
"UriPart",
115+
"ServerToolCallPart",
116+
"ServerToolCallResponsePart",
117+
"GenericPart",
118+
"MessagePart",
119+
"ChatMessage",
120+
"OutputMessage",
121+
"InputMessages",
122+
"OutputMessages",
123+
"InputMessagesParam",
124+
"OutputMessagesParam",
79125
# Utility functions
80126
"extract_context_from_headers",
81127
"get_traceparent",

libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/agent_details.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,6 @@ class AgentDetails:
3838

3939
provider_name: Optional[str] = None
4040
"""The provider name (e.g., openai, anthropic)."""
41+
42+
agent_version: Optional[str] = None
43+
"""Optional version of the agent (e.g., "1.0.0", "2025-05-01")."""

libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
GEN_AI_AGENT_ID_KEY = "gen_ai.agent.id"
3939
GEN_AI_AGENT_NAME_KEY = "gen_ai.agent.name"
4040
GEN_AI_AGENT_DESCRIPTION_KEY = "gen_ai.agent.description"
41+
GEN_AI_AGENT_VERSION_KEY = "gen_ai.agent.version"
4142
GEN_AI_AGENT_PLATFORM_ID_KEY = "microsoft.a365.agent.platform.id"
4243
GEN_AI_AGENT_THOUGHT_PROCESS_KEY = "microsoft.a365.agent.thought.process"
4344
GEN_AI_CONVERSATION_ID_KEY = "gen_ai.conversation.id"
@@ -73,6 +74,7 @@
7374
GEN_AI_CALLER_AGENT_ID_KEY = "microsoft.a365.caller.agent.id"
7475
GEN_AI_CALLER_AGENT_APPLICATION_ID_KEY = "microsoft.a365.caller.agent.blueprint.id"
7576
GEN_AI_CALLER_AGENT_PLATFORM_ID_KEY = "microsoft.a365.caller.agent.platform.id"
77+
GEN_AI_CALLER_AGENT_VERSION_KEY = "microsoft.a365.caller.agent.version"
7678

7779
# Agent-specific dimensions
7880
AGENT_ID_KEY = "gen_ai.agent.id"

libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/execute_tool_scope.py

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
GEN_AI_CONVERSATION_ID_KEY,
1313
GEN_AI_TOOL_ARGS_KEY,
1414
GEN_AI_TOOL_CALL_ID_KEY,
15+
GEN_AI_TOOL_CALL_RESULT_KEY,
1516
GEN_AI_TOOL_DESCRIPTION_KEY,
1617
GEN_AI_TOOL_NAME_KEY,
1718
GEN_AI_TOOL_TYPE_KEY,
@@ -21,12 +22,12 @@
2122
USER_ID_KEY,
2223
USER_NAME_KEY,
2324
)
25+
from .utils import safe_json_dumps, validate_and_normalize_ip
2426
from .models.user_details import UserDetails
2527
from .opentelemetry_scope import OpenTelemetryScope
2628
from .request import Request
2729
from .span_details import SpanDetails
2830
from .tool_call_details import ToolCallDetails
29-
from .utils import validate_and_normalize_ip
3031

3132

3233
class ExecuteToolScope(OpenTelemetryScope):
@@ -77,25 +78,26 @@ def __init__(
7778
user_details: Optional human user details
7879
span_details: Optional span configuration (parent context, timing, kind)
7980
"""
80-
kind = SpanKind.INTERNAL
81-
parent_context = None
82-
start_time = None
83-
end_time = None
84-
if span_details is not None:
85-
if span_details.span_kind is not None:
86-
kind = span_details.span_kind
87-
parent_context = span_details.parent_context
88-
start_time = span_details.start_time
89-
end_time = span_details.end_time
81+
# spanKind defaults to INTERNAL; allow override via span_details
82+
resolved_span_details = (
83+
SpanDetails(
84+
span_kind=span_details.span_kind
85+
if span_details and span_details.span_kind
86+
else SpanKind.INTERNAL,
87+
parent_context=span_details.parent_context if span_details else None,
88+
start_time=span_details.start_time if span_details else None,
89+
end_time=span_details.end_time if span_details else None,
90+
span_links=span_details.span_links if span_details else None,
91+
)
92+
if span_details
93+
else SpanDetails(span_kind=SpanKind.INTERNAL)
94+
)
9095

9196
super().__init__(
92-
kind=kind,
9397
operation_name=EXECUTE_TOOL_OPERATION_NAME,
9498
activity_name=f"{EXECUTE_TOOL_OPERATION_NAME} {details.tool_name}",
9599
agent_details=agent_details,
96-
parent_context=parent_context,
97-
start_time=start_time,
98-
end_time=end_time,
100+
span_details=resolved_span_details,
99101
)
100102

101103
# Extract details
@@ -107,7 +109,9 @@ def __init__(
107109
endpoint = details.endpoint
108110

109111
self.set_tag_maybe(GEN_AI_TOOL_NAME_KEY, tool_name)
110-
self.set_tag_maybe(GEN_AI_TOOL_ARGS_KEY, arguments)
112+
if arguments is not None:
113+
serialized = safe_json_dumps(arguments) if isinstance(arguments, dict) else arguments
114+
self.set_tag_maybe(GEN_AI_TOOL_ARGS_KEY, serialized)
111115
self.set_tag_maybe(GEN_AI_TOOL_TYPE_KEY, tool_type)
112116
self.set_tag_maybe(GEN_AI_TOOL_CALL_ID_KEY, tool_call_id)
113117
self.set_tag_maybe(GEN_AI_TOOL_DESCRIPTION_KEY, description)
@@ -133,13 +137,15 @@ def __init__(
133137
validate_and_normalize_ip(user_details.user_client_ip),
134138
)
135139

136-
def record_response(self, response: str) -> None:
137-
"""Records response information for telemetry tracking.
140+
def record_response(self, result: dict[str, object] | str) -> None:
141+
"""Record the tool call result for telemetry tracking.
138142
139-
Note: This method is intentionally a no-op as GEN_AI_EVENT_CONTENT was removed.
140-
The method is kept for interface compatibility.
143+
Per OTEL spec, the result is expected to be an object. If a string
144+
is provided, it is recorded as-is (JSON string fallback). If a dict
145+
is provided, it is serialized to JSON.
141146
142147
Args:
143-
response: The response to record
148+
result: Tool call result as a structured dict or JSON string
144149
"""
145-
pass
150+
serialized = safe_json_dumps(result) if isinstance(result, dict) else result
151+
self.set_tag_maybe(GEN_AI_TOOL_CALL_RESULT_KEY, serialized)

libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/execution_type.py

Lines changed: 0 additions & 14 deletions
This file was deleted.

0 commit comments

Comments
 (0)