agent-framework-openai 1.2.0__tar.gz → 1.2.2__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-openai
3
- Version: 1.2.0
3
+ Version: 1.2.2
4
4
  Summary: OpenAI integrations for Microsoft Agent Framework.
5
5
  Author-email: Microsoft <af-support@microsoft.com>
6
6
  Requires-Python: >=3.10
@@ -16,7 +16,7 @@ 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.2.0,<2
19
+ Requires-Dist: agent-framework-core>=1.2.2,<2
20
20
  Requires-Dist: openai>=1.99.0,<3
21
21
  Project-URL: homepage, https://aka.ms/agent-framework
22
22
  Project-URL: issues, https://github.com/microsoft/agent-framework/issues
@@ -241,6 +241,85 @@ OpenAIChatOptionsT = TypeVar(
241
241
  # endregion
242
242
 
243
243
 
244
+ # region Helpers
245
+
246
+
247
+ def _annotations_to_output_text(annotations: Sequence[Annotation] | None) -> list[dict[str, Any]]:
248
+ """Convert framework `Annotation` objects to Responses API `output_text` annotation dicts.
249
+
250
+ Citations from `file_search`, `code_interpreter` file paths, and url citations all collapse
251
+ to `Annotation(type="citation", ...)` in the framework. The original API form is recovered
252
+ here so assistant messages roundtrip cleanly through history forwarding.
253
+
254
+ Each Responses API annotation dict carries at most one `start_index`/`end_index` pair, so an
255
+ `Annotation` with multiple `annotated_regions` is fanned out into one entry per region.
256
+ Regions missing valid integer span bounds are skipped.
257
+ """
258
+ if not annotations:
259
+ return []
260
+ out: list[dict[str, Any]] = []
261
+ for annotation in annotations:
262
+ if annotation.get("type") != "citation":
263
+ continue
264
+ props = annotation.get("additional_properties") or {}
265
+ regions = annotation.get("annotated_regions") or []
266
+ file_id = annotation.get("file_id")
267
+ url = annotation.get("url")
268
+ title = annotation.get("title")
269
+ container_id = props.get("container_id")
270
+
271
+ if container_id and file_id:
272
+ for region in regions:
273
+ start = region.get("start_index")
274
+ end = region.get("end_index")
275
+ if not (isinstance(start, int) and isinstance(end, int)):
276
+ continue
277
+ entry: dict[str, Any] = {
278
+ "type": "container_file_citation",
279
+ "container_id": container_id,
280
+ "file_id": file_id,
281
+ "start_index": start,
282
+ "end_index": end,
283
+ }
284
+ if url:
285
+ entry["filename"] = url
286
+ out.append(entry)
287
+ elif url and not file_id and regions:
288
+ for region in regions:
289
+ start = region.get("start_index")
290
+ end = region.get("end_index")
291
+ if not (isinstance(start, int) and isinstance(end, int)):
292
+ continue
293
+ out.append({
294
+ "type": "url_citation",
295
+ "url": url,
296
+ "title": title or "",
297
+ "start_index": start,
298
+ "end_index": end,
299
+ })
300
+ elif file_id and url:
301
+ entry = {
302
+ "type": "file_citation",
303
+ "file_id": file_id,
304
+ "filename": url,
305
+ }
306
+ if (idx := props.get("index")) is not None:
307
+ entry["index"] = idx
308
+ out.append(entry)
309
+ elif file_id:
310
+ entry = {
311
+ "type": "file_path",
312
+ "file_id": file_id,
313
+ }
314
+ if (idx := props.get("index")) is not None:
315
+ entry["index"] = idx
316
+ out.append(entry)
317
+ return out
318
+
319
+
320
+ # endregion
321
+
322
+
244
323
  # region ResponsesClient
245
324
 
246
325
 
@@ -1374,7 +1453,7 @@ class RawOpenAIChatClient( # type: ignore[misc]
1374
1453
  return {
1375
1454
  "type": "output_text",
1376
1455
  "text": content.text,
1377
- "annotations": [],
1456
+ "annotations": _annotations_to_output_text(getattr(content, "annotations", None)),
1378
1457
  }
1379
1458
  return {
1380
1459
  "type": "input_text",
@@ -1522,6 +1601,13 @@ class RawOpenAIChatClient( # type: ignore[misc]
1522
1601
  "approve": content.approved,
1523
1602
  }
1524
1603
  case "hosted_file":
1604
+ # `input_file` is an input-only content type in the Responses API and is rejected
1605
+ # inside an assistant message. Hosted-file content on an assistant message
1606
+ # represents a citation produced by a hosted tool (e.g., file_search) and cannot be
1607
+ # meaningfully replayed as input — drop it. The accompanying text annotations carry
1608
+ # the citation context for round-tripping.
1609
+ if role == "assistant":
1610
+ return {}
1525
1611
  return {
1526
1612
  "type": "input_file",
1527
1613
  "file_id": content.file_id,
@@ -2502,45 +2588,63 @@ class RawOpenAIChatClient( # type: ignore[misc]
2502
2588
 
2503
2589
  ann_type = _get_ann_value("type")
2504
2590
  ann_file_id = _get_ann_value("file_id")
2591
+ # Hosted-file citations attach as text annotations (matching the non-streaming path)
2592
+ # so they don't roundtrip as standalone `input_file` items in assistant history.
2505
2593
  if ann_type == "file_path":
2506
2594
  if ann_file_id:
2595
+ annotation_obj = Annotation(
2596
+ type="citation",
2597
+ file_id=str(ann_file_id),
2598
+ additional_properties={
2599
+ "annotation_index": event.annotation_index,
2600
+ "index": _get_ann_value("index"),
2601
+ },
2602
+ raw_representation=annotation,
2603
+ )
2507
2604
  contents.append(
2508
- Content.from_hosted_file(
2509
- file_id=str(ann_file_id),
2510
- additional_properties={
2511
- "annotation_index": event.annotation_index,
2512
- "index": _get_ann_value("index"),
2513
- },
2514
- raw_representation=event,
2515
- )
2605
+ Content.from_text(text="", annotations=[annotation_obj], raw_representation=event)
2516
2606
  )
2517
2607
  elif ann_type == "file_citation":
2518
2608
  if ann_file_id:
2609
+ ann_filename = _get_ann_value("filename")
2610
+ annotation_obj = Annotation(
2611
+ type="citation",
2612
+ file_id=str(ann_file_id),
2613
+ url=ann_filename,
2614
+ additional_properties={
2615
+ "annotation_index": event.annotation_index,
2616
+ "index": _get_ann_value("index"),
2617
+ },
2618
+ raw_representation=annotation,
2619
+ )
2519
2620
  contents.append(
2520
- Content.from_hosted_file(
2521
- file_id=str(ann_file_id),
2522
- additional_properties={
2523
- "annotation_index": event.annotation_index,
2524
- "filename": _get_ann_value("filename"),
2525
- "index": _get_ann_value("index"),
2526
- },
2527
- raw_representation=event,
2528
- )
2621
+ Content.from_text(text="", annotations=[annotation_obj], raw_representation=event)
2529
2622
  )
2530
2623
  elif ann_type == "container_file_citation":
2531
2624
  if ann_file_id:
2625
+ ann_filename = _get_ann_value("filename")
2626
+ ann_start = _get_ann_value("start_index")
2627
+ ann_end = _get_ann_value("end_index")
2628
+ annotation_obj = Annotation(
2629
+ type="citation",
2630
+ file_id=str(ann_file_id),
2631
+ url=ann_filename,
2632
+ additional_properties={
2633
+ "annotation_index": event.annotation_index,
2634
+ "container_id": _get_ann_value("container_id"),
2635
+ },
2636
+ raw_representation=annotation,
2637
+ )
2638
+ if ann_start is not None and ann_end is not None:
2639
+ annotation_obj["annotated_regions"] = [
2640
+ TextSpanRegion(
2641
+ type="text_span",
2642
+ start_index=ann_start,
2643
+ end_index=ann_end,
2644
+ )
2645
+ ]
2532
2646
  contents.append(
2533
- Content.from_hosted_file(
2534
- file_id=str(ann_file_id),
2535
- additional_properties={
2536
- "annotation_index": event.annotation_index,
2537
- "container_id": _get_ann_value("container_id"),
2538
- "filename": _get_ann_value("filename"),
2539
- "start_index": _get_ann_value("start_index"),
2540
- "end_index": _get_ann_value("end_index"),
2541
- },
2542
- raw_representation=event,
2543
- )
2647
+ Content.from_text(text="", annotations=[annotation_obj], raw_representation=event)
2544
2648
  )
2545
2649
  elif ann_type == "url_citation":
2546
2650
  ann_url = _get_ann_value("url")
@@ -4,7 +4,7 @@ description = "OpenAI 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.2.0"
7
+ version = "1.2.2"
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,7 +23,7 @@ classifiers = [
23
23
  "Typing :: Typed",
24
24
  ]
25
25
  dependencies = [
26
- "agent-framework-core>=1.2.0,<2",
26
+ "agent-framework-core>=1.2.2,<2",
27
27
  "openai>=1.99.0,<3",
28
28
  ]
29
29