agent-framework-openai 1.2.2__tar.gz → 1.3.0__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_openai-1.2.2 → agent_framework_openai-1.3.0}/PKG-INFO +2 -2
- {agent_framework_openai-1.2.2 → agent_framework_openai-1.3.0}/agent_framework_openai/_chat_client.py +153 -2
- {agent_framework_openai-1.2.2 → agent_framework_openai-1.3.0}/agent_framework_openai/_chat_completion_client.py +9 -0
- {agent_framework_openai-1.2.2 → agent_framework_openai-1.3.0}/pyproject.toml +2 -2
- {agent_framework_openai-1.2.2 → agent_framework_openai-1.3.0}/LICENSE +0 -0
- {agent_framework_openai-1.2.2 → agent_framework_openai-1.3.0}/README.md +0 -0
- {agent_framework_openai-1.2.2 → agent_framework_openai-1.3.0}/agent_framework_openai/__init__.py +0 -0
- {agent_framework_openai-1.2.2 → agent_framework_openai-1.3.0}/agent_framework_openai/_embedding_client.py +0 -0
- {agent_framework_openai-1.2.2 → agent_framework_openai-1.3.0}/agent_framework_openai/_exceptions.py +0 -0
- {agent_framework_openai-1.2.2 → agent_framework_openai-1.3.0}/agent_framework_openai/_shared.py +0 -0
- {agent_framework_openai-1.2.2 → agent_framework_openai-1.3.0}/agent_framework_openai/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-framework-openai
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
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.
|
|
19
|
+
Requires-Dist: agent-framework-core>=1.3.0,<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
|
{agent_framework_openai-1.2.2 → agent_framework_openai-1.3.0}/agent_framework_openai/_chat_client.py
RENAMED
|
@@ -121,6 +121,14 @@ OPENAI_LOCAL_SHELL_COMMAND_PARTS_KEY = "openai.local_shell_command_parts"
|
|
|
121
121
|
OPENAI_SHELL_OUTPUT_TYPE_SHELL_CALL = "shell_call_output"
|
|
122
122
|
OPENAI_SHELL_OUTPUT_TYPE_LOCAL_SHELL_CALL = "local_shell_call_output"
|
|
123
123
|
|
|
124
|
+
# Internal marker emitted by `_prepare_content_for_openai` for an
|
|
125
|
+
# `mcp_server_tool_result` Content. The Responses API expects an `mcp_call`
|
|
126
|
+
# input item to carry both arguments and output as one item, so result
|
|
127
|
+
# Contents cannot be serialized standalone. `_prepare_messages_for_openai`
|
|
128
|
+
# coalesces these markers into the most recent matching `mcp_call` input
|
|
129
|
+
# item before returning, dropping any that are unmatched.
|
|
130
|
+
_AF_MCP_PENDING_OUTPUT_KEY = "__af_pending_mcp_result__"
|
|
131
|
+
|
|
124
132
|
|
|
125
133
|
class OpenAIContinuationToken(ContinuationToken):
|
|
126
134
|
"""Continuation token for OpenAI Responses API background operations."""
|
|
@@ -196,6 +204,11 @@ class OpenAIChatOptions(ChatOptions[ResponseFormatT], Generic[ResponseFormatT],
|
|
|
196
204
|
"""Configuration for reasoning models (gpt-5, o-series).
|
|
197
205
|
See: https://platform.openai.com/docs/guides/reasoning"""
|
|
198
206
|
|
|
207
|
+
verbosity: Literal["low", "medium", "high"]
|
|
208
|
+
"""Output verbosity for GPT-5 family models. Lower values yield shorter responses.
|
|
209
|
+
Translated to ``text.verbosity`` when sent to the Responses API.
|
|
210
|
+
See: https://developers.openai.com/cookbook/examples/gpt-5/gpt-5_new_params_and_tools#1-verbosity-parameter"""
|
|
211
|
+
|
|
199
212
|
safety_identifier: str
|
|
200
213
|
"""A stable identifier for detecting policy violations.
|
|
201
214
|
Recommend hashing username/email to avoid sending identifying info."""
|
|
@@ -654,7 +667,16 @@ class RawOpenAIChatClient( # type: ignore[misc]
|
|
|
654
667
|
response = await client.responses.retrieve(continuation_token["response_id"])
|
|
655
668
|
except Exception as ex:
|
|
656
669
|
self._handle_request_error(ex)
|
|
657
|
-
|
|
670
|
+
chat_response = self._parse_response_from_openai(response, options=validated_options)
|
|
671
|
+
# Once the background response completes, drop the continuation_token from
|
|
672
|
+
# the caller's options dict. FunctionInvocationLayer reuses the same dict
|
|
673
|
+
# across tool-loop iterations, so leaving it in place makes the next iteration
|
|
674
|
+
# retrieve the same completed response again instead of POSTing tool results
|
|
675
|
+
# (issue #5394). Keep `background` so subsequent iterations still create
|
|
676
|
+
# background responses.
|
|
677
|
+
if chat_response.continuation_token is None and isinstance(options, dict):
|
|
678
|
+
options.pop("continuation_token", None)
|
|
679
|
+
return chat_response
|
|
658
680
|
client, run_options, validated_options = await self._prepare_request(messages, options)
|
|
659
681
|
try:
|
|
660
682
|
if "text_format" in run_options:
|
|
@@ -1296,6 +1318,12 @@ class RawOpenAIChatClient( # type: ignore[misc]
|
|
|
1296
1318
|
"type": "function",
|
|
1297
1319
|
"name": func_name,
|
|
1298
1320
|
}
|
|
1321
|
+
elif mode == "auto" and (allowed := tool_mode.get("allowed_tools")) is not None:
|
|
1322
|
+
run_options["tool_choice"] = {
|
|
1323
|
+
"type": "allowed_tools",
|
|
1324
|
+
"mode": "auto",
|
|
1325
|
+
"tools": [{"type": "function", "name": name} for name in allowed],
|
|
1326
|
+
}
|
|
1299
1327
|
else:
|
|
1300
1328
|
run_options["tool_choice"] = mode
|
|
1301
1329
|
else:
|
|
@@ -1308,6 +1336,11 @@ class RawOpenAIChatClient( # type: ignore[misc]
|
|
|
1308
1336
|
response_format, text_config = self._prepare_response_and_text_format(
|
|
1309
1337
|
response_format=response_format, text_config=text_config
|
|
1310
1338
|
)
|
|
1339
|
+
# The Responses API nests verbosity under ``text.verbosity``; surface it as a
|
|
1340
|
+
# top-level option for parity with ``reasoning`` and translate here.
|
|
1341
|
+
if (verbosity := run_options.pop("verbosity", None)) is not None:
|
|
1342
|
+
text_config = dict(text_config) if text_config else {}
|
|
1343
|
+
text_config["verbosity"] = verbosity
|
|
1311
1344
|
if text_config:
|
|
1312
1345
|
run_options["text"] = text_config
|
|
1313
1346
|
if response_format:
|
|
@@ -1357,7 +1390,10 @@ class RawOpenAIChatClient( # type: ignore[misc]
|
|
|
1357
1390
|
for message in chat_messages
|
|
1358
1391
|
]
|
|
1359
1392
|
# Flatten the list of lists into a single list
|
|
1360
|
-
|
|
1393
|
+
flat = list(chain.from_iterable(list_of_list))
|
|
1394
|
+
# Coalesce hosted-MCP result markers onto matching mcp_call input
|
|
1395
|
+
# items (drop unmatched). See `_AF_MCP_PENDING_OUTPUT_KEY`.
|
|
1396
|
+
return self._coalesce_pending_mcp_results(flat)
|
|
1361
1397
|
|
|
1362
1398
|
def _prepare_message_for_openai(
|
|
1363
1399
|
self,
|
|
@@ -1422,6 +1458,18 @@ class RawOpenAIChatClient( # type: ignore[misc]
|
|
|
1422
1458
|
)
|
|
1423
1459
|
if prepared:
|
|
1424
1460
|
all_messages.append(prepared)
|
|
1461
|
+
case "mcp_server_tool_call" | "mcp_server_tool_result":
|
|
1462
|
+
# Hosted MCP call/result contents serialize as a single
|
|
1463
|
+
# top-level mcp_call input item; the result side emits an
|
|
1464
|
+
# internal marker that `_prepare_messages_for_openai`
|
|
1465
|
+
# coalesces onto the matching call (or drops if unmatched).
|
|
1466
|
+
prepared_mcp = self._prepare_content_for_openai(
|
|
1467
|
+
message.role,
|
|
1468
|
+
content,
|
|
1469
|
+
replays_local_storage=replays_local_storage,
|
|
1470
|
+
)
|
|
1471
|
+
if prepared_mcp:
|
|
1472
|
+
all_messages.append(prepared_mcp)
|
|
1425
1473
|
case _:
|
|
1426
1474
|
prepared_content = self._prepare_content_for_openai(
|
|
1427
1475
|
message.role,
|
|
@@ -1600,6 +1648,24 @@ class RawOpenAIChatClient( # type: ignore[misc]
|
|
|
1600
1648
|
"approval_request_id": content.id,
|
|
1601
1649
|
"approve": content.approved,
|
|
1602
1650
|
}
|
|
1651
|
+
case "mcp_server_tool_call":
|
|
1652
|
+
if not content.call_id:
|
|
1653
|
+
return {}
|
|
1654
|
+
return {
|
|
1655
|
+
"type": "mcp_call",
|
|
1656
|
+
"id": content.call_id,
|
|
1657
|
+
"server_label": content.server_name or "",
|
|
1658
|
+
"name": content.tool_name or "",
|
|
1659
|
+
"arguments": self._stringify_mcp_arguments(content.arguments),
|
|
1660
|
+
}
|
|
1661
|
+
case "mcp_server_tool_result":
|
|
1662
|
+
if not content.call_id:
|
|
1663
|
+
return {}
|
|
1664
|
+
return {
|
|
1665
|
+
_AF_MCP_PENDING_OUTPUT_KEY: True,
|
|
1666
|
+
"call_id": content.call_id,
|
|
1667
|
+
"output": self._stringify_mcp_output(content.output),
|
|
1668
|
+
}
|
|
1603
1669
|
case "hosted_file":
|
|
1604
1670
|
# `input_file` is an input-only content type in the Responses API and is rejected
|
|
1605
1671
|
# inside an assistant message. Hosted-file content on an assistant message
|
|
@@ -1675,6 +1741,91 @@ class RawOpenAIChatClient( # type: ignore[misc]
|
|
|
1675
1741
|
"""Join shell commands into a single executable command string."""
|
|
1676
1742
|
return "\n".join(command for command in commands if command).strip()
|
|
1677
1743
|
|
|
1744
|
+
@staticmethod
|
|
1745
|
+
def _stringify_mcp_arguments(arguments: Any) -> str:
|
|
1746
|
+
"""Render hosted-MCP tool-call arguments as a JSON string for the Responses API."""
|
|
1747
|
+
if arguments is None:
|
|
1748
|
+
return ""
|
|
1749
|
+
if isinstance(arguments, str):
|
|
1750
|
+
return arguments
|
|
1751
|
+
try:
|
|
1752
|
+
return json.dumps(arguments)
|
|
1753
|
+
except (TypeError, ValueError):
|
|
1754
|
+
return str(arguments)
|
|
1755
|
+
|
|
1756
|
+
@staticmethod
|
|
1757
|
+
def _stringify_mcp_output(output: Any) -> str:
|
|
1758
|
+
"""Render a hosted-MCP tool-call result into the string `mcp_call.output` field.
|
|
1759
|
+
|
|
1760
|
+
Accepts a string, a list of text-bearing Content objects (the form
|
|
1761
|
+
the chat client produces when parsing an `mcp_call` Responses item),
|
|
1762
|
+
or any other value. List entries that are dicts with the canonical
|
|
1763
|
+
MCP text-content shape (`{"text": "..."}`) are unwrapped to their
|
|
1764
|
+
text. Anything else falls back to JSON encoding rather than Python
|
|
1765
|
+
`repr`, so the wire payload stays parseable for downstream callers.
|
|
1766
|
+
"""
|
|
1767
|
+
if output is None:
|
|
1768
|
+
return ""
|
|
1769
|
+
if isinstance(output, str):
|
|
1770
|
+
return output
|
|
1771
|
+
if isinstance(output, Sequence) and not isinstance(output, (str, bytes, bytearray)):
|
|
1772
|
+
# cast is for pyright (reportUnknownVariableType); mypy considers
|
|
1773
|
+
# it redundant after the isinstance narrowing.
|
|
1774
|
+
entries = cast(Sequence[Any], output) # type: ignore[redundant-cast]
|
|
1775
|
+
parts: list[str] = []
|
|
1776
|
+
for entry in entries:
|
|
1777
|
+
if isinstance(entry, str):
|
|
1778
|
+
parts.append(entry)
|
|
1779
|
+
continue
|
|
1780
|
+
text = getattr(entry, "text", None)
|
|
1781
|
+
if isinstance(text, str):
|
|
1782
|
+
parts.append(text)
|
|
1783
|
+
continue
|
|
1784
|
+
if isinstance(entry, Mapping):
|
|
1785
|
+
mapping_text = cast(Any, entry).get("text")
|
|
1786
|
+
if isinstance(mapping_text, str):
|
|
1787
|
+
parts.append(mapping_text)
|
|
1788
|
+
continue
|
|
1789
|
+
parts.append(json.dumps(entry, default=str))
|
|
1790
|
+
return "".join(parts)
|
|
1791
|
+
return json.dumps(output, default=str)
|
|
1792
|
+
|
|
1793
|
+
@staticmethod
|
|
1794
|
+
def _coalesce_pending_mcp_results(items: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
1795
|
+
"""Merge pending hosted-MCP result markers onto matching mcp_call input items.
|
|
1796
|
+
|
|
1797
|
+
See `_AF_MCP_PENDING_OUTPUT_KEY`. The Responses API expects a single
|
|
1798
|
+
`mcp_call` input item carrying both `arguments` and `output`, so a
|
|
1799
|
+
result Content cannot be its own input item. Any unmatched markers
|
|
1800
|
+
are dropped (debug-logged); surfacing them as standalone items
|
|
1801
|
+
would produce the orphan `function_call_output` / `mcp_call_output`
|
|
1802
|
+
the API rejects.
|
|
1803
|
+
"""
|
|
1804
|
+
out: list[dict[str, Any]] = []
|
|
1805
|
+
for item in items:
|
|
1806
|
+
if item.get(_AF_MCP_PENDING_OUTPUT_KEY):
|
|
1807
|
+
target_call_id = item.get("call_id")
|
|
1808
|
+
target = next(
|
|
1809
|
+
(
|
|
1810
|
+
existing
|
|
1811
|
+
for existing in reversed(out)
|
|
1812
|
+
if existing.get("type") == "mcp_call" and existing.get("id") == target_call_id
|
|
1813
|
+
),
|
|
1814
|
+
None,
|
|
1815
|
+
)
|
|
1816
|
+
if target is not None:
|
|
1817
|
+
if target.get("output") is None:
|
|
1818
|
+
target["output"] = item.get("output")
|
|
1819
|
+
else:
|
|
1820
|
+
logger.debug(
|
|
1821
|
+
"Dropping orphan mcp_server_tool_result for call_id=%s; "
|
|
1822
|
+
"no matching mcp_call appeared in input.",
|
|
1823
|
+
target_call_id,
|
|
1824
|
+
)
|
|
1825
|
+
continue
|
|
1826
|
+
out.append(item)
|
|
1827
|
+
return out
|
|
1828
|
+
|
|
1678
1829
|
@staticmethod
|
|
1679
1830
|
def _serialize_provider_payload(value: Any) -> Any:
|
|
1680
1831
|
"""Convert OpenAI SDK objects into JSON-serializable Python values."""
|
|
@@ -145,6 +145,9 @@ class OpenAIChatCompletionOptions(ChatOptions[ResponseModelT], Generic[ResponseM
|
|
|
145
145
|
logprobs: bool
|
|
146
146
|
top_logprobs: int
|
|
147
147
|
prediction: Prediction
|
|
148
|
+
verbosity: Literal["low", "medium", "high"]
|
|
149
|
+
"""Output verbosity for GPT-5 family models. Lower values yield shorter responses.
|
|
150
|
+
See: https://developers.openai.com/cookbook/examples/gpt-5/gpt-5_new_params_and_tools#1-verbosity-parameter"""
|
|
148
151
|
|
|
149
152
|
|
|
150
153
|
OpenAIChatCompletionOptionsT = TypeVar(
|
|
@@ -662,6 +665,12 @@ class RawOpenAIChatCompletionClient( # type: ignore[misc]
|
|
|
662
665
|
"type": "function",
|
|
663
666
|
"function": {"name": func_name},
|
|
664
667
|
}
|
|
668
|
+
elif mode in ("auto", "required") and tool_mode.get("allowed_tools") is not None:
|
|
669
|
+
logger.warning(
|
|
670
|
+
"allowed_tools is not supported by the Chat Completions API; "
|
|
671
|
+
"the setting will be ignored. Use OpenAIChatClient (Responses API) instead."
|
|
672
|
+
)
|
|
673
|
+
run_options["tool_choice"] = mode
|
|
665
674
|
else:
|
|
666
675
|
run_options["tool_choice"] = mode
|
|
667
676
|
|
|
@@ -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.
|
|
7
|
+
version = "1.3.0"
|
|
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.
|
|
26
|
+
"agent-framework-core>=1.3.0,<2",
|
|
27
27
|
"openai>=1.99.0,<3",
|
|
28
28
|
]
|
|
29
29
|
|
|
File without changes
|
|
File without changes
|
{agent_framework_openai-1.2.2 → agent_framework_openai-1.3.0}/agent_framework_openai/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{agent_framework_openai-1.2.2 → agent_framework_openai-1.3.0}/agent_framework_openai/_exceptions.py
RENAMED
|
File without changes
|
{agent_framework_openai-1.2.2 → agent_framework_openai-1.3.0}/agent_framework_openai/_shared.py
RENAMED
|
File without changes
|
{agent_framework_openai-1.2.2 → agent_framework_openai-1.3.0}/agent_framework_openai/py.typed
RENAMED
|
File without changes
|