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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-framework-foundry
3
- Version: 1.7.0
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.7.0,<2
20
- Requires-Dist: agent-framework-openai>=1.7.0,<2
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.1.0,<3.0
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
@@ -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",
@@ -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=self.project_client.get_openai_client(**openai_client_kwargs),
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 FoundryAgentClient
514
+ from agent_framework.foundry import FoundryAgent
511
515
  from azure.identity import AzureCliCredential
512
516
 
513
- client = FoundryAgentClient(
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 typing import TYPE_CHECKING, Any
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 name in evaluators:
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
- filtered = [e for e in evaluators if _resolve_evaluator(e) not in _TOOL_EVALUATORS]
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 _resolve_evaluator(e) in _TOOL_EVALUATORS]
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=r.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
- sample = oi.sample
398
- if sample is not None: # pyright: ignore[reportUnnecessaryComparison]
399
- err = sample.error
400
- if err is not None and (err.code or err.message): # pyright: ignore[reportUnnecessaryComparison]
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 = sample.usage
405
- if usage is not None and usage.total_tokens: # pyright: ignore[reportUnnecessaryComparison]
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 sample.input:
415
- parts = [si.content for si in sample.input if si.role == "user"]
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 sample.output:
420
- parts = [so.content or "" for so in sample.output if so.role == "assistant"]
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 names (e.g. ``["relevance", "tool_call_accuracy"]``).
577
- When ``None`` (default), uses smart defaults based on item data.
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 = list(evaluators) if evaluators is not None else None
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.0"
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.7.0,<2",
27
- "agent-framework-openai>=1.7.0,<2",
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.1.0,<3.0",
29
+ "azure-ai-projects>=2.2.0,<3.0",
30
30
  ]
31
31
 
32
32
  [tool.uv]