agent-framework-foundry 1.7.0__tar.gz → 1.8.1__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.
- {agent_framework_foundry-1.7.0 → agent_framework_foundry-1.8.1}/PKG-INFO +4 -4
- {agent_framework_foundry-1.7.0 → agent_framework_foundry-1.8.1}/agent_framework_foundry/__init__.py +2 -0
- {agent_framework_foundry-1.7.0 → agent_framework_foundry-1.8.1}/agent_framework_foundry/_agent.py +22 -6
- {agent_framework_foundry-1.7.0 → agent_framework_foundry-1.8.1}/agent_framework_foundry/_foundry_evals.py +242 -30
- {agent_framework_foundry-1.7.0 → agent_framework_foundry-1.8.1}/pyproject.toml +4 -4
- {agent_framework_foundry-1.7.0 → agent_framework_foundry-1.8.1}/LICENSE +0 -0
- {agent_framework_foundry-1.7.0 → agent_framework_foundry-1.8.1}/README.md +0 -0
- {agent_framework_foundry-1.7.0 → agent_framework_foundry-1.8.1}/agent_framework_foundry/_chat_client.py +0 -0
- {agent_framework_foundry-1.7.0 → agent_framework_foundry-1.8.1}/agent_framework_foundry/_embedding_client.py +0 -0
- {agent_framework_foundry-1.7.0 → agent_framework_foundry-1.8.1}/agent_framework_foundry/_memory_provider.py +0 -0
- {agent_framework_foundry-1.7.0 → agent_framework_foundry-1.8.1}/agent_framework_foundry/_oauth_helpers.py +0 -0
- {agent_framework_foundry-1.7.0 → agent_framework_foundry-1.8.1}/agent_framework_foundry/_to_prompt_agent.py +0 -0
- {agent_framework_foundry-1.7.0 → agent_framework_foundry-1.8.1}/agent_framework_foundry/_tools.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-framework-foundry
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.8.1
|
|
4
4
|
Summary: Microsoft Foundry integrations for Microsoft Agent Framework.
|
|
5
5
|
Author-email: Microsoft <af-support@microsoft.com>
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -16,10 +16,10 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.14
|
|
17
17
|
Classifier: Typing :: Typed
|
|
18
18
|
License-File: LICENSE
|
|
19
|
-
Requires-Dist: agent-framework-core>=1.
|
|
20
|
-
Requires-Dist: agent-framework-openai>=1.
|
|
19
|
+
Requires-Dist: agent-framework-core>=1.8.1,<2
|
|
20
|
+
Requires-Dist: agent-framework-openai>=1.8.1,<2
|
|
21
21
|
Requires-Dist: azure-ai-inference>=1.0.0b9,<1.0.0b10
|
|
22
|
-
Requires-Dist: azure-ai-projects>=2.
|
|
22
|
+
Requires-Dist: azure-ai-projects>=2.2.0,<3.0
|
|
23
23
|
Project-URL: homepage, https://aka.ms/agent-framework
|
|
24
24
|
Project-URL: issues, https://github.com/microsoft/agent-framework/issues
|
|
25
25
|
Project-URL: release_notes, https://github.com/microsoft/agent-framework/releases?q=tag%3Apython-1&expanded=true
|
{agent_framework_foundry-1.7.0 → agent_framework_foundry-1.8.1}/agent_framework_foundry/__init__.py
RENAMED
|
@@ -12,6 +12,7 @@ from ._embedding_client import (
|
|
|
12
12
|
)
|
|
13
13
|
from ._foundry_evals import (
|
|
14
14
|
FoundryEvals,
|
|
15
|
+
GeneratedEvaluatorRef,
|
|
15
16
|
evaluate_foundry_target,
|
|
16
17
|
evaluate_traces,
|
|
17
18
|
)
|
|
@@ -33,6 +34,7 @@ __all__ = [
|
|
|
33
34
|
"FoundryEmbeddingSettings",
|
|
34
35
|
"FoundryEvals",
|
|
35
36
|
"FoundryMemoryProvider",
|
|
37
|
+
"GeneratedEvaluatorRef",
|
|
36
38
|
"RawFoundryAgent",
|
|
37
39
|
"RawFoundryAgentChatClient",
|
|
38
40
|
"RawFoundryChatClient",
|
{agent_framework_foundry-1.7.0 → agent_framework_foundry-1.8.1}/agent_framework_foundry/_agent.py
RENAMED
|
@@ -57,8 +57,6 @@ if TYPE_CHECKING:
|
|
|
57
57
|
from agent_framework import (
|
|
58
58
|
Agent,
|
|
59
59
|
AgentRunInputs,
|
|
60
|
-
ChatAndFunctionMiddlewareTypes,
|
|
61
|
-
ContextProvider,
|
|
62
60
|
MiddlewareTypes,
|
|
63
61
|
ToolTypes,
|
|
64
62
|
)
|
|
@@ -193,6 +191,7 @@ class RawFoundryAgentChatClient( # type: ignore[misc]
|
|
|
193
191
|
compaction_strategy: CompactionStrategy | None = None,
|
|
194
192
|
tokenizer: TokenizerProtocol | None = None,
|
|
195
193
|
additional_properties: dict[str, Any] | None = None,
|
|
194
|
+
timeout: float | None = None,
|
|
196
195
|
) -> None:
|
|
197
196
|
"""Initialize a raw Foundry Agent client.
|
|
198
197
|
|
|
@@ -213,6 +212,8 @@ class RawFoundryAgentChatClient( # type: ignore[misc]
|
|
|
213
212
|
compaction_strategy: Optional per-client compaction override.
|
|
214
213
|
tokenizer: Optional tokenizer for compaction strategies.
|
|
215
214
|
additional_properties: Additional properties stored on the client instance.
|
|
215
|
+
timeout: HTTP timeout in seconds for requests. When not provided, the
|
|
216
|
+
OpenAI SDK default is used (connect: 5s, total: 600s).
|
|
216
217
|
"""
|
|
217
218
|
settings = load_settings(
|
|
218
219
|
FoundryAgentSettings,
|
|
@@ -262,8 +263,11 @@ class RawFoundryAgentChatClient( # type: ignore[misc]
|
|
|
262
263
|
openai_client_kwargs["default_headers"] = dict(default_headers)
|
|
263
264
|
if allow_preview:
|
|
264
265
|
openai_client_kwargs["agent_name"] = self.agent_name
|
|
266
|
+
openai_client = self.project_client.get_openai_client(**openai_client_kwargs)
|
|
267
|
+
if timeout is not None:
|
|
268
|
+
openai_client = openai_client.with_options(timeout=timeout)
|
|
265
269
|
super().__init__(
|
|
266
|
-
async_client=
|
|
270
|
+
async_client=openai_client,
|
|
267
271
|
default_headers=default_headers,
|
|
268
272
|
instruction_role=instruction_role,
|
|
269
273
|
compaction_strategy=compaction_strategy,
|
|
@@ -353,6 +357,7 @@ class RawFoundryAgentChatClient( # type: ignore[misc]
|
|
|
353
357
|
if _uses_foundry_agent_session(conversation_id):
|
|
354
358
|
run_options.pop("previous_response_id", None)
|
|
355
359
|
run_options.pop("conversation", None)
|
|
360
|
+
run_options.pop("model", None)
|
|
356
361
|
extra_body["agent_session_id"] = conversation_id
|
|
357
362
|
# Non-preview Prompt/Hosted Agent calls need agent_reference in the request body to
|
|
358
363
|
# tell the Responses API which Foundry agent (and version) is in use, since ``model``
|
|
@@ -368,7 +373,6 @@ class RawFoundryAgentChatClient( # type: ignore[misc]
|
|
|
368
373
|
# Strip tools from request body - Foundry API rejects requests with both
|
|
369
374
|
# agent endpoint and tools present. FunctionTools are invoked client-side
|
|
370
375
|
# by the function invocation layer, not sent to the service.
|
|
371
|
-
run_options.pop("model", None)
|
|
372
376
|
if not self.allow_preview:
|
|
373
377
|
run_options.pop("tools", None)
|
|
374
378
|
run_options.pop("tool_choice", None)
|
|
@@ -507,10 +511,10 @@ class _FoundryAgentChatClient( # type: ignore[misc]
|
|
|
507
511
|
.. code-block:: python
|
|
508
512
|
|
|
509
513
|
from agent_framework import Agent
|
|
510
|
-
from agent_framework.foundry import
|
|
514
|
+
from agent_framework.foundry import FoundryAgent
|
|
511
515
|
from azure.identity import AzureCliCredential
|
|
512
516
|
|
|
513
|
-
client =
|
|
517
|
+
client = FoundryAgent(
|
|
514
518
|
project_endpoint="https://your-project.services.ai.azure.com",
|
|
515
519
|
agent_name="my-prompt-agent",
|
|
516
520
|
agent_version="1",
|
|
@@ -539,6 +543,7 @@ class _FoundryAgentChatClient( # type: ignore[misc]
|
|
|
539
543
|
additional_properties: dict[str, Any] | None = None,
|
|
540
544
|
middleware: (Sequence[ChatAndFunctionMiddlewareTypes] | None) = None,
|
|
541
545
|
function_invocation_configuration: FunctionInvocationConfiguration | None = None,
|
|
546
|
+
timeout: float | None = None,
|
|
542
547
|
) -> None:
|
|
543
548
|
"""Initialize a Foundry Agent client with full middleware support.
|
|
544
549
|
|
|
@@ -558,6 +563,8 @@ class _FoundryAgentChatClient( # type: ignore[misc]
|
|
|
558
563
|
additional_properties: Additional properties stored on the client instance.
|
|
559
564
|
middleware: Optional sequence of middleware.
|
|
560
565
|
function_invocation_configuration: Optional function invocation configuration.
|
|
566
|
+
timeout: HTTP timeout in seconds for requests. When not provided, the
|
|
567
|
+
OpenAI SDK default is used (connect: 5s, total: 600s).
|
|
561
568
|
"""
|
|
562
569
|
super().__init__(
|
|
563
570
|
project_endpoint=project_endpoint,
|
|
@@ -575,6 +582,7 @@ class _FoundryAgentChatClient( # type: ignore[misc]
|
|
|
575
582
|
additional_properties=additional_properties,
|
|
576
583
|
middleware=middleware,
|
|
577
584
|
function_invocation_configuration=function_invocation_configuration,
|
|
585
|
+
timeout=timeout,
|
|
578
586
|
)
|
|
579
587
|
|
|
580
588
|
|
|
@@ -627,6 +635,7 @@ class RawFoundryAgent( # type: ignore[misc]
|
|
|
627
635
|
compaction_strategy: CompactionStrategy | None = None,
|
|
628
636
|
tokenizer: TokenizerProtocol | None = None,
|
|
629
637
|
additional_properties: Mapping[str, Any] | None = None,
|
|
638
|
+
timeout: float | None = None,
|
|
630
639
|
) -> None:
|
|
631
640
|
"""Initialize a Foundry Agent.
|
|
632
641
|
|
|
@@ -659,6 +668,8 @@ class RawFoundryAgent( # type: ignore[misc]
|
|
|
659
668
|
compaction_strategy: Optional agent-level in-run compaction override.
|
|
660
669
|
tokenizer: Optional agent-level tokenizer override.
|
|
661
670
|
additional_properties: Additional properties stored on the local agent wrapper.
|
|
671
|
+
timeout: HTTP timeout in seconds for requests. When not provided, the
|
|
672
|
+
OpenAI SDK default is used (connect: 5s, total: 600s).
|
|
662
673
|
"""
|
|
663
674
|
# Create the client
|
|
664
675
|
actual_client_type = client_type or _FoundryAgentChatClient
|
|
@@ -677,6 +688,7 @@ class RawFoundryAgent( # type: ignore[misc]
|
|
|
677
688
|
"default_headers": default_headers,
|
|
678
689
|
"env_file_path": env_file_path,
|
|
679
690
|
"env_file_encoding": env_file_encoding,
|
|
691
|
+
"timeout": timeout,
|
|
680
692
|
}
|
|
681
693
|
if function_invocation_configuration is not None:
|
|
682
694
|
if not issubclass(actual_client_type, FunctionInvocationLayer):
|
|
@@ -914,6 +926,7 @@ class FoundryAgent( # type: ignore[misc]
|
|
|
914
926
|
compaction_strategy: CompactionStrategy | None = None,
|
|
915
927
|
tokenizer: TokenizerProtocol | None = None,
|
|
916
928
|
additional_properties: Mapping[str, Any] | None = None,
|
|
929
|
+
timeout: float | None = None,
|
|
917
930
|
) -> None:
|
|
918
931
|
"""Initialize a Foundry Agent with full middleware and telemetry.
|
|
919
932
|
|
|
@@ -960,6 +973,8 @@ class FoundryAgent( # type: ignore[misc]
|
|
|
960
973
|
compaction_strategy: Optional agent-level in-run compaction override.
|
|
961
974
|
tokenizer: Optional agent-level tokenizer override.
|
|
962
975
|
additional_properties: Additional properties stored on the local agent wrapper.
|
|
976
|
+
timeout: HTTP timeout in seconds for requests. When not provided, the
|
|
977
|
+
OpenAI SDK default is used (connect: 5s, total: 600s).
|
|
963
978
|
"""
|
|
964
979
|
super().__init__(
|
|
965
980
|
project_endpoint=project_endpoint,
|
|
@@ -985,4 +1000,5 @@ class FoundryAgent( # type: ignore[misc]
|
|
|
985
1000
|
compaction_strategy=compaction_strategy,
|
|
986
1001
|
tokenizer=tokenizer,
|
|
987
1002
|
additional_properties=additional_properties,
|
|
1003
|
+
timeout=timeout,
|
|
988
1004
|
)
|
|
@@ -28,8 +28,9 @@ from __future__ import annotations
|
|
|
28
28
|
|
|
29
29
|
import asyncio
|
|
30
30
|
import logging
|
|
31
|
-
from collections.abc import Sequence
|
|
32
|
-
from
|
|
31
|
+
from collections.abc import Iterable, Sequence
|
|
32
|
+
from dataclasses import dataclass
|
|
33
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
33
34
|
|
|
34
35
|
from agent_framework._evaluation import (
|
|
35
36
|
AgentEvalConverter,
|
|
@@ -39,6 +40,7 @@ from agent_framework._evaluation import (
|
|
|
39
40
|
EvalItemResult,
|
|
40
41
|
EvalResults,
|
|
41
42
|
EvalScoreResult,
|
|
43
|
+
RubricScore,
|
|
42
44
|
)
|
|
43
45
|
from agent_framework._feature_stage import ExperimentalFeature, experimental
|
|
44
46
|
from openai import AsyncOpenAI
|
|
@@ -51,6 +53,54 @@ if TYPE_CHECKING:
|
|
|
51
53
|
|
|
52
54
|
logger = logging.getLogger(__name__)
|
|
53
55
|
|
|
56
|
+
|
|
57
|
+
# region Generated rubric evaluator references
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@experimental(feature_id=ExperimentalFeature.EVALS)
|
|
61
|
+
@dataclass(frozen=True)
|
|
62
|
+
class GeneratedEvaluatorRef:
|
|
63
|
+
"""A reference to a rubric evaluator that already exists in Foundry.
|
|
64
|
+
|
|
65
|
+
Pass instances of this class to :class:`FoundryEvals` to score items
|
|
66
|
+
with a pre-existing rubric evaluator (manually authored or
|
|
67
|
+
auto-generated through the Foundry portal). agent-framework is a
|
|
68
|
+
consumer here: it does not create or modify the evaluator definition;
|
|
69
|
+
it only references the persisted version by name.
|
|
70
|
+
|
|
71
|
+
Pinning ``version`` is strongly recommended so evaluation runs are
|
|
72
|
+
reproducible. ``version=None`` resolves to whichever version is
|
|
73
|
+
current at execution time; :class:`FoundryEvals` emits a warning when
|
|
74
|
+
a versionless reference is used. CI gates should always pass a
|
|
75
|
+
concrete version.
|
|
76
|
+
|
|
77
|
+
Attributes:
|
|
78
|
+
name: Evaluator name as stored in the Foundry project (for
|
|
79
|
+
example ``"reservation-policy-rubric"``). Distinct from
|
|
80
|
+
built-in evaluators such as ``"builtin.relevance"``.
|
|
81
|
+
version: Pinned evaluator version. ``None`` means "latest" —
|
|
82
|
+
this is discouraged for CI/repro and :class:`FoundryEvals`
|
|
83
|
+
will emit a warning when used.
|
|
84
|
+
display_name: Optional human-readable name used in result
|
|
85
|
+
summaries. Defaults to ``name`` when unset.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
name: str
|
|
89
|
+
version: str | None = None
|
|
90
|
+
display_name: str | None = None
|
|
91
|
+
|
|
92
|
+
@classmethod
|
|
93
|
+
def latest(cls, name: str, *, display_name: str | None = None) -> GeneratedEvaluatorRef:
|
|
94
|
+
"""Construct a versionless reference (resolves to the latest version at run time).
|
|
95
|
+
|
|
96
|
+
Discouraged for reproducible runs. Prefer the constructor with
|
|
97
|
+
an explicit ``version`` so CI and replay evaluations stay stable
|
|
98
|
+
when the evaluator is updated in Foundry.
|
|
99
|
+
"""
|
|
100
|
+
return cls(name=name, version=None, display_name=display_name)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# endregion
|
|
54
104
|
# Agent evaluators that accept query/response as conversation arrays.
|
|
55
105
|
# Maintained manually — check https://learn.microsoft.com/en-us/azure/ai-studio/how-to/develop/evaluate-sdk
|
|
56
106
|
# for the latest evaluator list. These are the evaluators that need conversation-format input.
|
|
@@ -166,7 +216,7 @@ def _resolve_evaluator(name: str) -> str:
|
|
|
166
216
|
|
|
167
217
|
|
|
168
218
|
def _build_testing_criteria(
|
|
169
|
-
evaluators: Sequence[str],
|
|
219
|
+
evaluators: Sequence[str | GeneratedEvaluatorRef],
|
|
170
220
|
model: str,
|
|
171
221
|
*,
|
|
172
222
|
include_data_mapping: bool = False,
|
|
@@ -175,7 +225,9 @@ def _build_testing_criteria(
|
|
|
175
225
|
"""Build ``testing_criteria`` for ``evals.create()``.
|
|
176
226
|
|
|
177
227
|
Args:
|
|
178
|
-
evaluators: Evaluator names
|
|
228
|
+
evaluators: Evaluator names (built-in shorts / fully-qualified
|
|
229
|
+
``builtin.*`` names) or :class:`GeneratedEvaluatorRef`
|
|
230
|
+
instances for generated rubric evaluators.
|
|
179
231
|
model: Model deployment for the LLM judge.
|
|
180
232
|
include_data_mapping: Whether to include field-level data mapping
|
|
181
233
|
(required for the JSONL data source, not needed for response-based).
|
|
@@ -183,7 +235,38 @@ def _build_testing_criteria(
|
|
|
183
235
|
definitions.
|
|
184
236
|
"""
|
|
185
237
|
criteria: list[dict[str, Any]] = []
|
|
186
|
-
for
|
|
238
|
+
for entry_spec in evaluators:
|
|
239
|
+
if isinstance(entry_spec, GeneratedEvaluatorRef):
|
|
240
|
+
short = entry_spec.display_name or entry_spec.name
|
|
241
|
+
ref_entry: dict[str, Any] = {
|
|
242
|
+
"type": "azure_ai_evaluator",
|
|
243
|
+
"name": short,
|
|
244
|
+
"evaluator_name": entry_spec.name,
|
|
245
|
+
"initialization_parameters": {"deployment_name": model},
|
|
246
|
+
}
|
|
247
|
+
if entry_spec.version is not None:
|
|
248
|
+
ref_entry["evaluator_version"] = entry_spec.version
|
|
249
|
+
else:
|
|
250
|
+
logger.warning(
|
|
251
|
+
"GeneratedEvaluatorRef '%s' has no pinned version; the eval run "
|
|
252
|
+
"will resolve to whichever version is current at execution time. "
|
|
253
|
+
"Pin the version for reproducible runs.",
|
|
254
|
+
entry_spec.name,
|
|
255
|
+
)
|
|
256
|
+
if include_data_mapping:
|
|
257
|
+
# Rubric evaluators accept conversation arrays like agent
|
|
258
|
+
# evaluators, plus tool_definitions when items are tool-aware.
|
|
259
|
+
ref_mapping: dict[str, str] = {
|
|
260
|
+
"query": "{{item.query_messages}}",
|
|
261
|
+
"response": "{{item.response_messages}}",
|
|
262
|
+
}
|
|
263
|
+
if include_tool_definitions:
|
|
264
|
+
ref_mapping["tool_definitions"] = "{{item.tool_definitions}}"
|
|
265
|
+
ref_entry["data_mapping"] = ref_mapping
|
|
266
|
+
criteria.append(ref_entry)
|
|
267
|
+
continue
|
|
268
|
+
|
|
269
|
+
name = entry_spec
|
|
187
270
|
qualified = _resolve_evaluator(name)
|
|
188
271
|
short = name if not name.startswith("builtin.") else name.split(".")[-1]
|
|
189
272
|
|
|
@@ -247,9 +330,9 @@ def _build_item_schema(
|
|
|
247
330
|
|
|
248
331
|
|
|
249
332
|
def _resolve_default_evaluators(
|
|
250
|
-
evaluators: Sequence[str] | None,
|
|
333
|
+
evaluators: Sequence[str | GeneratedEvaluatorRef] | None,
|
|
251
334
|
items: Sequence[EvalItem | dict[str, Any]] | None = None,
|
|
252
|
-
) -> list[str]:
|
|
335
|
+
) -> list[str | GeneratedEvaluatorRef]:
|
|
253
336
|
"""Resolve evaluators, applying defaults when ``None``.
|
|
254
337
|
|
|
255
338
|
Defaults to relevance + coherence + task_adherence. Automatically adds
|
|
@@ -258,7 +341,7 @@ def _resolve_default_evaluators(
|
|
|
258
341
|
if evaluators is not None:
|
|
259
342
|
return list(evaluators)
|
|
260
343
|
|
|
261
|
-
result = list(_DEFAULT_EVALUATORS)
|
|
344
|
+
result: list[str | GeneratedEvaluatorRef] = list(_DEFAULT_EVALUATORS)
|
|
262
345
|
if items is not None:
|
|
263
346
|
has_tools = any((item.tools if isinstance(item, EvalItem) else item.get("tool_definitions")) for item in items)
|
|
264
347
|
if has_tools:
|
|
@@ -267,14 +350,24 @@ def _resolve_default_evaluators(
|
|
|
267
350
|
|
|
268
351
|
|
|
269
352
|
def _filter_tool_evaluators(
|
|
270
|
-
evaluators: list[str],
|
|
353
|
+
evaluators: list[str | GeneratedEvaluatorRef],
|
|
271
354
|
items: Sequence[EvalItem | dict[str, Any]],
|
|
272
|
-
) -> list[str]:
|
|
273
|
-
"""Remove tool evaluators if no items have tool definitions.
|
|
355
|
+
) -> list[str | GeneratedEvaluatorRef]:
|
|
356
|
+
"""Remove tool evaluators if no items have tool definitions.
|
|
357
|
+
|
|
358
|
+
Generated rubric evaluators are tool-aware but not tool-required; they
|
|
359
|
+
are preserved regardless of whether items carry tool definitions.
|
|
360
|
+
"""
|
|
274
361
|
has_tools = any((item.tools if isinstance(item, EvalItem) else item.get("tool_definitions")) for item in items)
|
|
275
362
|
if has_tools:
|
|
276
363
|
return evaluators
|
|
277
|
-
|
|
364
|
+
|
|
365
|
+
def _is_tool_only(spec: str | GeneratedEvaluatorRef) -> bool:
|
|
366
|
+
if isinstance(spec, GeneratedEvaluatorRef):
|
|
367
|
+
return False
|
|
368
|
+
return _resolve_evaluator(spec) in _TOOL_EVALUATORS
|
|
369
|
+
|
|
370
|
+
filtered = [e for e in evaluators if not _is_tool_only(e)]
|
|
278
371
|
if not filtered:
|
|
279
372
|
raise ValueError(
|
|
280
373
|
f"All requested evaluators {evaluators} require tool definitions, "
|
|
@@ -282,7 +375,7 @@ def _filter_tool_evaluators(
|
|
|
282
375
|
"or choose evaluators that do not require tools."
|
|
283
376
|
)
|
|
284
377
|
if len(filtered) < len(evaluators):
|
|
285
|
-
removed = [e for e in evaluators if
|
|
378
|
+
removed = [e for e in evaluators if _is_tool_only(e)]
|
|
286
379
|
logger.info("Removed tool evaluators %s (no items have tools)", removed)
|
|
287
380
|
return filtered
|
|
288
381
|
|
|
@@ -354,6 +447,114 @@ def _extract_per_evaluator(run: RunRetrieveResponse) -> dict[str, dict[str, int]
|
|
|
354
447
|
return per_eval
|
|
355
448
|
|
|
356
449
|
|
|
450
|
+
_RUBRIC_DIMENSION_KEYS: tuple[str, ...] = ("dimension_scores", "rubric_scores")
|
|
451
|
+
"""Property keys that may carry per-dimension rubric breakdowns.
|
|
452
|
+
|
|
453
|
+
The published Foundry rubric-evaluator output format uses
|
|
454
|
+
``properties.dimension_scores`` (see the Microsoft Learn "Rubric
|
|
455
|
+
evaluators" reference). Earlier preview builds and some SDK shapes
|
|
456
|
+
used ``rubric_scores``; we accept both for defensive forward/backward
|
|
457
|
+
compatibility.
|
|
458
|
+
"""
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def _parse_dimension_entries(raw: Any) -> list[RubricScore]:
|
|
462
|
+
"""Parse a raw list-like payload into ``RubricScore`` instances.
|
|
463
|
+
|
|
464
|
+
Returns an empty list when ``raw`` is falsy, not iterable, or
|
|
465
|
+
contains no well-formed entries.
|
|
466
|
+
"""
|
|
467
|
+
if not raw:
|
|
468
|
+
return []
|
|
469
|
+
try:
|
|
470
|
+
raw_iter: Iterable[Any] = iter(raw)
|
|
471
|
+
except TypeError:
|
|
472
|
+
return []
|
|
473
|
+
|
|
474
|
+
parsed: list[RubricScore] = []
|
|
475
|
+
for raw_entry in raw_iter:
|
|
476
|
+
entry: Any = raw_entry
|
|
477
|
+
try:
|
|
478
|
+
rid: Any
|
|
479
|
+
score_val: Any
|
|
480
|
+
applicable: Any
|
|
481
|
+
weight: Any
|
|
482
|
+
reason: Any
|
|
483
|
+
if isinstance(entry, dict):
|
|
484
|
+
entry_any = cast("dict[str, Any]", entry)
|
|
485
|
+
rid = entry_any.get("id")
|
|
486
|
+
score_val = entry_any.get("score")
|
|
487
|
+
applicable = entry_any.get("applicable")
|
|
488
|
+
weight = entry_any.get("weight")
|
|
489
|
+
reason = entry_any.get("reason", "")
|
|
490
|
+
else:
|
|
491
|
+
rid = getattr(entry, "id", None)
|
|
492
|
+
score_val = getattr(entry, "score", None)
|
|
493
|
+
applicable = getattr(entry, "applicable", None)
|
|
494
|
+
weight = getattr(entry, "weight", None)
|
|
495
|
+
reason = getattr(entry, "reason", "") or ""
|
|
496
|
+
if rid is None or weight is None or applicable is None:
|
|
497
|
+
continue
|
|
498
|
+
parsed.append(
|
|
499
|
+
RubricScore(
|
|
500
|
+
id=str(rid),
|
|
501
|
+
score=int(score_val) if isinstance(score_val, (int, float)) else None,
|
|
502
|
+
applicable=bool(applicable),
|
|
503
|
+
weight=int(weight),
|
|
504
|
+
reason=str(reason) if reason is not None else "",
|
|
505
|
+
)
|
|
506
|
+
)
|
|
507
|
+
except (TypeError, ValueError):
|
|
508
|
+
logger.debug("Skipping malformed rubric dimension entry: %s", cast("Any", entry), exc_info=True)
|
|
509
|
+
return parsed
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
def _extract_rubric_scores(sample: Any) -> list[RubricScore] | None:
|
|
513
|
+
"""Extract typed ``RubricScore`` instances from an evaluator's raw sample payload.
|
|
514
|
+
|
|
515
|
+
Foundry rubric evaluators include a per-dimension breakdown under
|
|
516
|
+
``properties.dimension_scores`` on each result (preview builds used
|
|
517
|
+
``rubric_scores``; both keys are accepted, with the canonical
|
|
518
|
+
``dimension_scores`` taking priority). The exact location may
|
|
519
|
+
vary across SDK versions, so this helper accepts a few shapes:
|
|
520
|
+
|
|
521
|
+
* The SDK ``sample`` object exposes
|
|
522
|
+
``properties.dimension_scores`` / ``properties.rubric_scores``.
|
|
523
|
+
* The ``sample`` is a dict containing the same under
|
|
524
|
+
``properties.<key>``.
|
|
525
|
+
* The ``sample`` is a dict with ``dimension_scores`` /
|
|
526
|
+
``rubric_scores`` at the top level.
|
|
527
|
+
|
|
528
|
+
Returns ``None`` when no rubric scores are present (i.e. the
|
|
529
|
+
evaluator was not a rubric evaluator).
|
|
530
|
+
"""
|
|
531
|
+
if sample is None:
|
|
532
|
+
return None
|
|
533
|
+
|
|
534
|
+
containers: list[Any] = []
|
|
535
|
+
properties: Any = getattr(sample, "properties", None)
|
|
536
|
+
if properties is not None:
|
|
537
|
+
containers.append(properties)
|
|
538
|
+
if isinstance(sample, dict):
|
|
539
|
+
sample_any = cast("dict[str, Any]", sample)
|
|
540
|
+
props_dict: Any = sample_any.get("properties")
|
|
541
|
+
if props_dict is not None and props_dict is not properties:
|
|
542
|
+
containers.append(props_dict)
|
|
543
|
+
containers.append(sample_any)
|
|
544
|
+
|
|
545
|
+
for container in containers:
|
|
546
|
+
for key in _RUBRIC_DIMENSION_KEYS:
|
|
547
|
+
raw: Any = None
|
|
548
|
+
if isinstance(container, dict):
|
|
549
|
+
raw = cast("dict[str, Any]", container).get(key)
|
|
550
|
+
elif hasattr(container, key):
|
|
551
|
+
raw = getattr(container, key, None)
|
|
552
|
+
parsed = _parse_dimension_entries(raw)
|
|
553
|
+
if parsed:
|
|
554
|
+
return parsed
|
|
555
|
+
return None
|
|
556
|
+
|
|
557
|
+
|
|
357
558
|
async def _fetch_output_items(
|
|
358
559
|
client: AsyncOpenAI,
|
|
359
560
|
eval_id: str,
|
|
@@ -377,12 +578,15 @@ async def _fetch_output_items(
|
|
|
377
578
|
# Extract per-evaluator scores
|
|
378
579
|
scores: list[EvalScoreResult] = []
|
|
379
580
|
for r in oi.results or []:
|
|
581
|
+
sample = r.sample
|
|
582
|
+
dimensions = _extract_rubric_scores(sample)
|
|
380
583
|
scores.append(
|
|
381
584
|
EvalScoreResult(
|
|
382
585
|
name=r.name,
|
|
383
586
|
score=r.score,
|
|
384
587
|
passed=r.passed,
|
|
385
|
-
sample=
|
|
588
|
+
sample=sample,
|
|
589
|
+
dimensions=dimensions,
|
|
386
590
|
)
|
|
387
591
|
)
|
|
388
592
|
|
|
@@ -394,15 +598,18 @@ async def _fetch_output_items(
|
|
|
394
598
|
output_text: str | None = None
|
|
395
599
|
response_id: str | None = None
|
|
396
600
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
601
|
+
# mypy infers oi.sample as dict[str, object] | None, but the
|
|
602
|
+
# OpenAI SDK actually returns a typed Sample model. Cast to Any so
|
|
603
|
+
# both type checkers accept the attribute access pattern.
|
|
604
|
+
oi_sample: Any = oi.sample
|
|
605
|
+
if oi_sample is not None:
|
|
606
|
+
err = oi_sample.error
|
|
607
|
+
if err is not None and (err.code or err.message):
|
|
401
608
|
error_code = err.code or None
|
|
402
609
|
error_message = err.message or None
|
|
403
610
|
|
|
404
|
-
usage =
|
|
405
|
-
if usage is not None and usage.total_tokens:
|
|
611
|
+
usage = oi_sample.usage
|
|
612
|
+
if usage is not None and usage.total_tokens:
|
|
406
613
|
token_usage = {
|
|
407
614
|
"prompt_tokens": usage.prompt_tokens,
|
|
408
615
|
"completion_tokens": usage.completion_tokens,
|
|
@@ -411,13 +618,13 @@ async def _fetch_output_items(
|
|
|
411
618
|
}
|
|
412
619
|
|
|
413
620
|
# Extract input/output text
|
|
414
|
-
if
|
|
415
|
-
parts = [si.content for si in
|
|
621
|
+
if oi_sample.input:
|
|
622
|
+
parts = [si.content for si in oi_sample.input if si.role == "user"]
|
|
416
623
|
if parts:
|
|
417
624
|
input_text = " ".join(parts)
|
|
418
625
|
|
|
419
|
-
if
|
|
420
|
-
parts = [so.content or "" for so in
|
|
626
|
+
if oi_sample.output:
|
|
627
|
+
parts = [so.content or "" for so in oi_sample.output if so.role == "assistant"]
|
|
421
628
|
if parts:
|
|
422
629
|
output_text = " ".join(parts)
|
|
423
630
|
|
|
@@ -472,7 +679,7 @@ async def _evaluate_via_responses_impl(
|
|
|
472
679
|
*,
|
|
473
680
|
client: AsyncOpenAI,
|
|
474
681
|
response_ids: Sequence[str],
|
|
475
|
-
evaluators: list[str],
|
|
682
|
+
evaluators: list[str | GeneratedEvaluatorRef],
|
|
476
683
|
model: str,
|
|
477
684
|
eval_name: str,
|
|
478
685
|
poll_interval: float,
|
|
@@ -573,8 +780,11 @@ class FoundryEvals:
|
|
|
573
780
|
(from ``azure.ai.projects.aio``). Provide this or *client*.
|
|
574
781
|
model: Model deployment name for the evaluator LLM judge.
|
|
575
782
|
Resolved from ``client.model`` when omitted.
|
|
576
|
-
evaluators: Evaluator
|
|
577
|
-
|
|
783
|
+
evaluators: Evaluator specifications. Entries may be built-in
|
|
784
|
+
short names (e.g. ``"relevance"``), fully-qualified
|
|
785
|
+
``"builtin.*"`` names, or :class:`GeneratedEvaluatorRef`
|
|
786
|
+
instances for previously generated rubric evaluators. When
|
|
787
|
+
``None`` (default), uses smart defaults based on item data.
|
|
578
788
|
conversation_split: How to split multi-turn conversations into
|
|
579
789
|
query/response halves. Defaults to ``LAST_TURN``. Pass a
|
|
580
790
|
``ConversationSplit`` enum value or a custom callable — see
|
|
@@ -623,7 +833,7 @@ class FoundryEvals:
|
|
|
623
833
|
client: FoundryChatClient | None = None,
|
|
624
834
|
project_client: AIProjectClient | None = None,
|
|
625
835
|
model: str | None = None,
|
|
626
|
-
evaluators: Sequence[str] | None = None,
|
|
836
|
+
evaluators: Sequence[str | GeneratedEvaluatorRef] | None = None,
|
|
627
837
|
conversation_split: ConversationSplitter = ConversationSplit.LAST_TURN,
|
|
628
838
|
poll_interval: float = 5.0,
|
|
629
839
|
timeout: float = 180.0,
|
|
@@ -642,7 +852,9 @@ class FoundryEvals:
|
|
|
642
852
|
"Model is required. Pass model= explicitly or use a FoundryChatClient that has a model configured."
|
|
643
853
|
)
|
|
644
854
|
self._model = resolved_model
|
|
645
|
-
self._evaluators
|
|
855
|
+
self._evaluators: list[str | GeneratedEvaluatorRef] | None = (
|
|
856
|
+
list(evaluators) if evaluators is not None else None
|
|
857
|
+
)
|
|
646
858
|
self._conversation_split = conversation_split
|
|
647
859
|
self._poll_interval = poll_interval
|
|
648
860
|
self._timeout = timeout
|
|
@@ -678,7 +890,7 @@ class FoundryEvals:
|
|
|
678
890
|
async def _evaluate_via_dataset(
|
|
679
891
|
self,
|
|
680
892
|
items: Sequence[EvalItem],
|
|
681
|
-
evaluators: list[str],
|
|
893
|
+
evaluators: list[str | GeneratedEvaluatorRef],
|
|
682
894
|
eval_name: str,
|
|
683
895
|
) -> EvalResults:
|
|
684
896
|
"""Evaluate using JSONL dataset upload path."""
|
|
@@ -4,7 +4,7 @@ description = "Microsoft Foundry integrations for Microsoft Agent Framework."
|
|
|
4
4
|
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.8.1"
|
|
8
8
|
license-files = ["LICENSE"]
|
|
9
9
|
urls.homepage = "https://aka.ms/agent-framework"
|
|
10
10
|
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
|
|
@@ -23,10 +23,10 @@ classifiers = [
|
|
|
23
23
|
"Typing :: Typed",
|
|
24
24
|
]
|
|
25
25
|
dependencies = [
|
|
26
|
-
"agent-framework-core>=1.
|
|
27
|
-
"agent-framework-openai>=1.
|
|
26
|
+
"agent-framework-core>=1.8.1,<2",
|
|
27
|
+
"agent-framework-openai>=1.8.1,<2",
|
|
28
28
|
"azure-ai-inference>=1.0.0b9,<1.0.0b10",
|
|
29
|
-
"azure-ai-projects>=2.
|
|
29
|
+
"azure-ai-projects>=2.2.0,<3.0",
|
|
30
30
|
]
|
|
31
31
|
|
|
32
32
|
[tool.uv]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agent_framework_foundry-1.7.0 → agent_framework_foundry-1.8.1}/agent_framework_foundry/_tools.py
RENAMED
|
File without changes
|