ag2 0.9.4__py3-none-any.whl → 0.9.5__py3-none-any.whl

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.

Potentially problematic release.


This version of ag2 might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ag2
3
- Version: 0.9.4
3
+ Version: 0.9.5
4
4
  Summary: A programming framework for agentic AI
5
5
  Project-URL: Homepage, https://ag2.ai/
6
6
  Project-URL: Documentation, https://docs.ag2.ai
@@ -1,6 +1,6 @@
1
1
  autogen/__init__.py,sha256=8JVF68yM6D3qsY2ZXLOwX0C7QK2td45Veka3d5zAGHA,2108
2
2
  autogen/browser_utils.py,sha256=tkNiADQuO27htXvj1FcQfGvw02Qjn-L-l5A0MJhc8lY,13264
3
- autogen/code_utils.py,sha256=hDMSbYiqroQp1PdEpMwatQ16cibElFG0UuB6r4c107A,23900
3
+ autogen/code_utils.py,sha256=7-u4a0O24c0P3vfag5R7lCPze7uFjFDVCktOxESD87A,24118
4
4
  autogen/doc_utils.py,sha256=RwKfLUKAnRLYLFI_ffiko6Y7NWG0fxEzpBQjJJOc4D0,1046
5
5
  autogen/exception_utils.py,sha256=YpuaczyoZ4wHFhyv1a_UA9CpUN2KKIU4johIRm_mOaQ,2486
6
6
  autogen/formatting_utils.py,sha256=JfENmuHOmDQEQSUPOMvc8e4jAaJ2CJyppp4Fw9ePPDc,2122
@@ -14,7 +14,7 @@ autogen/retrieve_utils.py,sha256=R3Yp5d8dH4o9ayLZrGn4rCjIaY4glOHIiyQjwClmdi8,200
14
14
  autogen/runtime_logging.py,sha256=yCmZODvwqYR91m8lX3Q4SoPcY-DK48NF4m56CP6Om3c,4692
15
15
  autogen/token_count_utils.py,sha256=n4wTFVNHwrfjZkrErFr8kNig2K-YCGgMLWsjDRS9D6g,10797
16
16
  autogen/types.py,sha256=qu-7eywhakW2AxQ5lYisLLeIg45UoOW-b3ErIuyRTuw,1000
17
- autogen/version.py,sha256=zukvVxK3WY_ypC-a8Iy7A_baGAoqr3cT-lhYYnSMhhM,193
17
+ autogen/version.py,sha256=THVipdL6_RedIuOtoczdX7_iJxpvPiU7gkSD-mvqhIo,193
18
18
  autogen/_website/__init__.py,sha256=c8B9TpO07x9neD0zsJWj6AaEdlcP-WvxrvVOGWLtamk,143
19
19
  autogen/_website/generate_api_references.py,sha256=yKqyeSP_NE27wwLYWsZbTYRceEoxzNxPXqn6vsIzEvk,14789
20
20
  autogen/_website/generate_mkdocs.py,sha256=TkmLnUDv1Ms5cGClXPmenA8nxmwg4kR0E-FHCVjw_og,43246
@@ -159,7 +159,7 @@ autogen/agentchat/realtime/experimental/function_observer.py,sha256=M0cXXJNoBQ8s
159
159
  autogen/agentchat/realtime/experimental/realtime_agent.py,sha256=i8rxU-Tjk2Pz-WOZJ5PuRymaMvVLH66lqH2LJ85PxLM,5713
160
160
  autogen/agentchat/realtime/experimental/realtime_events.py,sha256=zmRr3pwPJpme5VZEADIz5vg9IZoT3Z1NAc3vt1RdWLk,1083
161
161
  autogen/agentchat/realtime/experimental/realtime_observer.py,sha256=nTouVj5-il0q2_P2LTpyb4pnHqyfwP5MJh_QmMJF3e8,3061
162
- autogen/agentchat/realtime/experimental/realtime_swarm.py,sha256=ENR7URgzaa4roTTLPAbUr44TT9FznG57kAU_0PIb34s,17809
162
+ autogen/agentchat/realtime/experimental/realtime_swarm.py,sha256=sy7Vwt7Ztuu4LMnZQ_RjASY0KWUNARJev41INXqY7pc,17880
163
163
  autogen/agentchat/realtime/experimental/websockets.py,sha256=bj9b5eq80L3KlGWPP6nn7uyfT_Z47kQqtIRbQkeE5SI,667
164
164
  autogen/agentchat/realtime/experimental/audio_adapters/__init__.py,sha256=rd0pEy91LYq0JMvIk8Fv7ZKIQLK7oZbVdgVAwNZDCmQ,315
165
165
  autogen/agentchat/realtime/experimental/audio_adapters/twilio_audio_adapter.py,sha256=-O10rpqPKZKxZO58rQOxPnwECe-RQJoSUTU_K8i0A98,6110
@@ -296,7 +296,7 @@ autogen/oai/__init__.py,sha256=BIwnV6wtHmKgIM4IUdymfPfpdNqos5P7BfRv-7_QL9A,1680
296
296
  autogen/oai/anthropic.py,sha256=AK_Bbcc5I-PqAvoiwB0LaO1ZLugh_wQNAOIFcj7eTtk,29174
297
297
  autogen/oai/bedrock.py,sha256=8AYWZVsDkJS2HmQ0ggoUqlKV_a4H_ON8rks4VW2Jrxk,25719
298
298
  autogen/oai/cerebras.py,sha256=8hiSBq88l2yTXUJPV7AvGXRCtwvW0Y9hIYUnYK2S2os,12462
299
- autogen/oai/client.py,sha256=BB_6Heny6_7lq8q7ZAPKohHAK63zs9UGzRoUknTxjYY,65051
299
+ autogen/oai/client.py,sha256=nXPRAiUrO5MoGGK2CjKG8TNKfuJxRCDdGVHKQGKk0H4,66957
300
300
  autogen/oai/client_utils.py,sha256=lVbHyff7OnpdM-tXskC23xLdFccj2AalTdWA4DxzxS4,7543
301
301
  autogen/oai/cohere.py,sha256=pRcQWjbzKbZ1RfC1vk9WGjgndwjHbIaOVoKEYdV2L6c,19421
302
302
  autogen/oai/gemini.py,sha256=bc_RQwtoGi7DnwQwPie7ZdvsqpD1AjYwzjVwnIKP39U,43168
@@ -304,6 +304,7 @@ autogen/oai/gemini_types.py,sha256=i4wT8ytD2cO9nCUWm2FKLKVVxyd31wMhOpnrvnIiKIc,5
304
304
  autogen/oai/groq.py,sha256=pQWtaAY_AjT30XKbZNHXDzWsawBys3yFWlfy6K4Nqr8,12431
305
305
  autogen/oai/mistral.py,sha256=SlOYPdnNLHuTEHBGCzsemG9sLEgphdUukRurERdMsvI,12677
306
306
  autogen/oai/ollama.py,sha256=t0fIgDCoIfsQZ3hhpseam5N-fbpI7-fw82bG55mA8nU,29103
307
+ autogen/oai/openai_responses.py,sha256=W847OTGzlDbII8sYWIXTOc9NEPAM59XmLWvcYbC6bRo,17811
307
308
  autogen/oai/openai_utils.py,sha256=4kEu50WeTGGG2uh1fOeMxRIZkEoov-YkkTgx2n5DhkM,38131
308
309
  autogen/oai/together.py,sha256=Sj4LOk9RrBG3Bb5IVsrjBYz-hDECCyCgofsCdtk6PSM,14867
309
310
  autogen/oai/oai_models/__init__.py,sha256=cILDaaCCvSC3aAX85iLwE1RCpNEokA9925Zse5hX2K4,549
@@ -405,8 +406,8 @@ autogen/agentchat/contrib/captainagent/tools/math/modular_inverse_sum.py,sha256=
405
406
  autogen/agentchat/contrib/captainagent/tools/math/simplify_mixed_numbers.py,sha256=iqgpFJdyBHPPNCqkehSIbeuV8Rabr2eDMilT23Wx7PI,1687
406
407
  autogen/agentchat/contrib/captainagent/tools/math/sum_of_digit_factorials.py,sha256=-6T5r6Er4mONPldRxv3F9tLoE7Og3qmeSeTC7Du_tTg,596
407
408
  autogen/agentchat/contrib/captainagent/tools/math/sum_of_primes_below.py,sha256=Xig7K3A3DRnbv-UXfyo5bybGZUQYAQsltthfTYW5eV8,509
408
- ag2-0.9.4.dist-info/METADATA,sha256=YnCG5kxaDTMI72s4N3OPKiuuepUwQVYLdKjgte3l6ZA,35268
409
- ag2-0.9.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
410
- ag2-0.9.4.dist-info/licenses/LICENSE,sha256=GEFQVNayAR-S_rQD5l8hPdgvgyktVdy4Bx5-v90IfRI,11384
411
- ag2-0.9.4.dist-info/licenses/NOTICE.md,sha256=07iCPQGbth4pQrgkSgZinJGT5nXddkZ6_MGYcBd2oiY,1134
412
- ag2-0.9.4.dist-info/RECORD,,
409
+ ag2-0.9.5.dist-info/METADATA,sha256=K4Gz-eYvrw_xTPz7UV0WPlKiR5LluSMxvV0PGJZUWpA,35268
410
+ ag2-0.9.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
411
+ ag2-0.9.5.dist-info/licenses/LICENSE,sha256=GEFQVNayAR-S_rQD5l8hPdgvgyktVdy4Bx5-v90IfRI,11384
412
+ ag2-0.9.5.dist-info/licenses/NOTICE.md,sha256=07iCPQGbth4pQrgkSgZinJGT5nXddkZ6_MGYcBd2oiY,1134
413
+ ag2-0.9.5.dist-info/RECORD,,
@@ -359,6 +359,8 @@ class SwarmableRealtimeAgent(SwarmableAgent):
359
359
  description=None,
360
360
  silent=None,
361
361
  )
362
+ self.input_guardrails = []
363
+ self.output_guardrails = []
362
364
 
363
365
  def reset_answer(self) -> None:
364
366
  """Reset the answer event."""
autogen/code_utils.py CHANGED
@@ -72,18 +72,20 @@ def content_str(content: Union[str, list[Union[UserMessageTextContentPart, UserM
72
72
  if not isinstance(content, list):
73
73
  raise TypeError(f"content must be None, str, or list, but got {type(content)}")
74
74
 
75
- rst = ""
75
+ rst = []
76
76
  for item in content:
77
77
  if not isinstance(item, dict):
78
78
  raise TypeError("Wrong content format: every element should be dict if the content is a list.")
79
79
  assert "type" in item, "Wrong content format. Missing 'type' key in content's dict."
80
- if item["type"] == "text":
81
- rst += item["text"]
82
- elif item["type"] == "image_url":
83
- rst += "<image>"
80
+ if item["type"] in ["text", "input_text"]:
81
+ rst.append(item["text"])
82
+ elif item["type"] in ["image_url", "input_image"]:
83
+ rst.append("<image>")
84
+ elif item["type"] in ["function", "tool_call", "tool_calls"]:
85
+ rst.append("<function>" if "name" not in item else f"<function: {item['name']}>")
84
86
  else:
85
87
  raise ValueError(f"Wrong content format: unknown type {item['type']} within the content")
86
- return rst
88
+ return "\n".join(rst)
87
89
 
88
90
 
89
91
  def infer_lang(code: str) -> str:
autogen/oai/client.py CHANGED
@@ -51,6 +51,8 @@ if openai_result.is_successful:
51
51
  from openai.types.completion import Completion
52
52
  from openai.types.completion_usage import CompletionUsage
53
53
 
54
+ from autogen.oai.openai_responses import OpenAIResponsesClient
55
+
54
56
  if openai.__version__ >= "1.1.0":
55
57
  TOOL_ENABLED = True
56
58
  ERROR = None
@@ -314,7 +316,7 @@ class ModelClient(Protocol):
314
316
  class ModelClientResponseProtocol(Protocol):
315
317
  class Choice(Protocol):
316
318
  class Message(Protocol):
317
- content: Optional[str]
319
+ content: Optional[str] | Optional[dict[str, Any]]
318
320
 
319
321
  message: Message
320
322
 
@@ -952,6 +954,15 @@ class OpenAIWrapper:
952
954
  raise ImportError("Please install `boto3` to use the Amazon Bedrock API.")
953
955
  client = BedrockClient(response_format=response_format, **openai_config)
954
956
  self._clients.append(client)
957
+ elif api_type is not None and api_type.startswith("responses"):
958
+ # OpenAI Responses API (stateful). Reuse the same OpenAI SDK but call the `/responses` endpoint via the new client.
959
+ @require_optional_import("openai>=1.66.2", "openai")
960
+ def create_responses_client() -> "OpenAI":
961
+ client = OpenAI(**openai_config)
962
+ self._clients.append(OpenAIResponsesClient(client, response_format=response_format))
963
+ return client
964
+
965
+ client = create_responses_client()
955
966
  else:
956
967
 
957
968
  @require_optional_import("openai>=1.66.2", "openai")
@@ -1442,3 +1453,35 @@ class OpenAIWrapper:
1442
1453
  A list of text, or a list of ChatCompletion objects if function_call/tool_calls are present.
1443
1454
  """
1444
1455
  return response.message_retrieval_function(response)
1456
+
1457
+
1458
+ # -----------------------------------------------------------------------------
1459
+ # New: Responses API config entry (OpenAI-hosted preview endpoint)
1460
+ # -----------------------------------------------------------------------------
1461
+
1462
+
1463
+ @register_llm_config
1464
+ class OpenAIResponsesLLMConfigEntry(OpenAILLMConfigEntry):
1465
+ """LLMConfig entry for the OpenAI Responses API (stateful, tool-enabled).
1466
+
1467
+ This reuses all the OpenAI fields but changes *api_type* so the wrapper can
1468
+ route traffic to the `client.responses` endpoint instead of
1469
+ `chat.completions`. It inherits everything else – including reasoning
1470
+ fields – from *OpenAILLMConfigEntry* so users can simply set
1471
+
1472
+ ```python
1473
+ {
1474
+ "api_type": "responses", # <-- key differentiator
1475
+ "model": "o3", # reasoning model
1476
+ "reasoning_effort": "medium", # low / medium / high
1477
+ "stream": True,
1478
+ }
1479
+ ```
1480
+ """
1481
+
1482
+ api_type: Literal["responses"] = "responses"
1483
+ tool_choice: Optional[Literal["none", "auto", "required"]] = "auto"
1484
+ built_in_tools: Optional[list[str]] = None
1485
+
1486
+ def create_client(self) -> "ModelClient": # pragma: no cover
1487
+ raise NotImplementedError("Handled via OpenAIWrapper._register_default_client")
@@ -0,0 +1,426 @@
1
+ # Copyright (c) 2023 - 2025, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ import copy
6
+ import warnings
7
+ from typing import TYPE_CHECKING, Any, Tuple, Union
8
+
9
+ from pydantic import BaseModel
10
+
11
+ from autogen.code_utils import content_str
12
+ from autogen.import_utils import optional_import_block, require_optional_import
13
+
14
+ if TYPE_CHECKING:
15
+ from autogen.oai.client import ModelClient, OpenAI, OpenAILLMConfigEntry
16
+ else:
17
+ # Import at runtime to avoid circular import
18
+ OpenAILLMConfigEntry = None
19
+ ModelClient = None
20
+ OpenAI = None
21
+
22
+ with optional_import_block() as openai_result:
23
+ from openai.types.responses.response import Response
24
+ from openai.types.responses.response_output_item import ImageGenerationCall
25
+
26
+ # Image Costs
27
+ # Pricing per image (in USD)
28
+ PRICING = {
29
+ "gpt-image-1": {
30
+ "low": {"1024x1024": 0.011, "1024x1536": 0.016, "1536x1024": 0.016},
31
+ "medium": {"1024x1024": 0.042, "1024x1536": 0.063, "1536x1024": 0.063},
32
+ "high": {"1024x1024": 0.167, "1024x1536": 0.25, "1536x1024": 0.25},
33
+ },
34
+ "dall-e-3": {
35
+ "standard": {"1024x1024": 0.040, "1024x1792": 0.080, "1792x1024": 0.080},
36
+ "hd": {"1024x1024": 0.080, "1024x1792": 0.120, "1792x1024": 0.120},
37
+ },
38
+ "dall-e-2": {"standard": {"1024x1024": 0.020, "512x512": 0.018, "256x256": 0.016}},
39
+ }
40
+
41
+ # Valid sizes for each model
42
+ VALID_SIZES = {
43
+ "gpt-image-1": ["1024x1024", "1024x1536", "1536x1024"],
44
+ "dall-e-3": ["1024x1024", "1024x1792", "1792x1024"],
45
+ "dall-e-2": ["1024x1024", "512x512", "256x256"],
46
+ }
47
+
48
+
49
+ def calculate_openai_image_cost(
50
+ model: str = "gpt-image-1", size: str = "1024x1024", quality: str = "high"
51
+ ) -> Tuple[float, str]:
52
+ """
53
+ Calculate the cost for a single image generation.
54
+
55
+ Args:
56
+ model: Model name ("gpt-image-1", "dall-e-3" or "dall-e-2")
57
+ size: Image size (e.g., "1024x1024", "1024x1536")
58
+ quality: Quality setting:
59
+ - For gpt-image-1: "low", "medium", or "high"
60
+ - For dall-e-3: "standard" or "hd"
61
+ - For dall-e-2: "standard" only
62
+
63
+ Returns:
64
+ Tuple of (cost, error_message)
65
+ """
66
+ # Normalize inputs
67
+ model = model.lower()
68
+ quality = quality.lower()
69
+
70
+ # Validate model
71
+ if model not in PRICING:
72
+ return 0.0, f"Invalid model: {model}. Valid models: {list(PRICING.keys())}"
73
+
74
+ # Validate size
75
+ if size not in VALID_SIZES[model]:
76
+ return 0.0, f"Invalid size {size} for {model}. Valid sizes: {VALID_SIZES[model]}"
77
+
78
+ # Get the cost based on model type
79
+ try:
80
+ if model == "gpt-image-1" or model == "dall-e-3":
81
+ cost = PRICING[model][quality][size]
82
+ elif model == "dall-e-2":
83
+ cost = PRICING[model]["standard"][size]
84
+ else:
85
+ return 0.0, f"Model {model} not properly configured"
86
+
87
+ return cost, None
88
+
89
+ except KeyError:
90
+ return 0.0, f"Invalid quality '{quality}' for {model}"
91
+
92
+
93
+ def _get_base_class():
94
+ """Lazy import OpenAILLMConfigEntry to avoid circular imports."""
95
+ from autogen.oai.client import OpenAILLMConfigEntry
96
+
97
+ return OpenAILLMConfigEntry
98
+
99
+
100
+ # -----------------------------------------------------------------------------
101
+ # OpenAI Client that calls the /responses endpoint
102
+ # -----------------------------------------------------------------------------
103
+ @require_optional_import("openai", "openai")
104
+ class OpenAIResponsesClient:
105
+ """Minimal implementation targeting the experimental /responses endpoint.
106
+
107
+ We purposefully keep the surface small - *create*, *message_retrieval*,
108
+ *cost* and *get_usage* - enough for ConversableAgent to operate. Anything
109
+ that the new endpoint does natively (web_search, file_search, image
110
+ generation, function calling, etc.) is transparently passed through by the
111
+ OpenAI SDK so we don't replicate logic here.
112
+ """
113
+
114
+ def __init__(
115
+ self,
116
+ client: "OpenAI",
117
+ response_format: Union[BaseModel, dict[str, Any], None] = None,
118
+ ):
119
+ self._oai_client = client # plain openai.OpenAI instance
120
+ self.response_format = response_format # kept for parity but unused for now
121
+
122
+ # Initialize the image generation parameters
123
+ self.image_output_params = {
124
+ "quality": None, # "high" or "low"
125
+ "background": None, # "white" or "black" or "transparent"
126
+ "size": None, # "1024x1024" or "1024x1792" or "1792x1024"
127
+ "output_format": "png", # "png", "jpg" or "jpeg" or "webp"
128
+ "output_compression": None, # 0-100 if output_format is "jpg" or "jpeg" or "webp"
129
+ }
130
+ self.previous_response_id = None
131
+
132
+ # Image costs are calculated manually (rather than off returned information)
133
+ self.image_costs = 0
134
+
135
+ # ------------------------------------------------------------------ helpers
136
+ # responses objects embed usage similarly to chat completions
137
+ @staticmethod
138
+ def _usage_dict(resp) -> dict:
139
+ usage_obj = getattr(resp, "usage", None) or {}
140
+
141
+ # Convert pydantic/BaseModel usage objects to dict for uniform access
142
+ if hasattr(usage_obj, "model_dump"):
143
+ usage = usage_obj.model_dump()
144
+ elif isinstance(usage_obj, dict):
145
+ usage = usage_obj
146
+ else: # fallback - unknown structure
147
+ usage = {}
148
+
149
+ output_tokens_details = usage.get("output_tokens_details", {})
150
+
151
+ return {
152
+ "prompt_tokens": usage.get("input_tokens", 0),
153
+ "completion_tokens": usage.get("output_tokens", 0),
154
+ "total_tokens": usage.get("total_tokens", 0),
155
+ "cost": getattr(resp, "cost", 0),
156
+ "model": getattr(resp, "model", ""),
157
+ "reasoning_tokens": output_tokens_details.get("reasoning_tokens", 0),
158
+ }
159
+
160
+ def _add_image_cost(self, response: "Response") -> None:
161
+ """Add image cost to self._image_costs when an image is generated"""
162
+ for output in response.output:
163
+ if (
164
+ isinstance(output, ImageGenerationCall)
165
+ and hasattr(response.output[0], "model_extra")
166
+ and response.output[0].model_extra
167
+ ):
168
+ extra_fields = output.model_extra
169
+
170
+ image_cost, image_error = calculate_openai_image_cost(
171
+ model="gpt-image-1",
172
+ size=extra_fields.get("size", "1024x1536"),
173
+ quality=extra_fields.get("quality", "high"),
174
+ )
175
+
176
+ if not image_error and image_cost:
177
+ self.image_costs += image_cost
178
+
179
+ def _get_delta_messages(self, messages: list[dict[str, Any]]) -> list[dict[str, Any]]:
180
+ """Get the delta messages from the messages."""
181
+ delta_messages = []
182
+ for m in messages[::-1]:
183
+ contents = m.get("content")
184
+ is_last_completed_response = False
185
+ if isinstance(contents, list):
186
+ for c in contents:
187
+ if "status" in c and c.get("status") == "completed":
188
+ is_last_completed_response = True
189
+ break
190
+ elif isinstance(contents, str):
191
+ is_last_completed_response = "status" in m and m.get("status") == "completed"
192
+
193
+ if is_last_completed_response:
194
+ break
195
+ delta_messages.append(m)
196
+ return delta_messages[::-1]
197
+
198
+ def create(self, params: dict[str, Any]) -> "Response":
199
+ """Invoke `client.responses.create() or .parse()`.
200
+
201
+ If the caller provided a classic *messages* array we convert it to the
202
+ *input* format expected by the Responses API.
203
+ """
204
+ params = params.copy()
205
+
206
+ image_generation_tool_params = {"type": "image_generation"}
207
+ web_search_tool_params = {"type": "web_search_preview"}
208
+
209
+ if self.previous_response_id is not None and "previous_response_id" not in params:
210
+ params["previous_response_id"] = self.previous_response_id
211
+
212
+ # Back-compat: transform messages → input if needed ------------------
213
+ if "messages" in params and "input" not in params:
214
+ msgs = self._get_delta_messages(params.pop("messages"))
215
+ input_items = []
216
+ for m in msgs[::-1]: # reverse the list to get the last item first
217
+ role = m.get("role", "user")
218
+ # First, we need to convert the content to the Responses API format
219
+ content = m.get("content")
220
+ blocks = []
221
+ if role != "tool":
222
+ if isinstance(content, list):
223
+ for c in content:
224
+ if c.get("type") in ["input_text", "text"]:
225
+ blocks.append({"type": "input_text", "text": c.get("text")})
226
+ elif c.get("type") == "input_image":
227
+ blocks.append({"type": "input_image", "image_url": c.get("image_url")})
228
+ elif c.get("type") == "image_params":
229
+ for k, v in c.get("image_params", {}).items():
230
+ if k in self.image_output_params:
231
+ image_generation_tool_params[k] = v
232
+ else:
233
+ raise ValueError(f"Invalid content type: {c.get('type')}")
234
+ else:
235
+ blocks.append({"type": "input_text", "text": content})
236
+ input_items.append({"role": role, "content": blocks})
237
+
238
+ else:
239
+ if input_items:
240
+ break
241
+ # tool call response is the last item in the list
242
+ content = content_str(m.get("content"))
243
+ input_items.append({
244
+ "type": "function_call_output",
245
+ "call_id": m.get("tool_call_id", None),
246
+ "output": content,
247
+ })
248
+ break
249
+ params["input"] = input_items[::-1]
250
+
251
+ # Initialize tools list
252
+ tools_list = []
253
+ # Back-compat: add default tools
254
+ built_in_tools = params.pop("built_in_tools", [])
255
+ if built_in_tools:
256
+ if "image_generation" in built_in_tools:
257
+ tools_list.append(image_generation_tool_params)
258
+ if "web_search" in built_in_tools:
259
+ tools_list.append(web_search_tool_params)
260
+
261
+ if "tools" in params:
262
+ for tool in params["tools"]:
263
+ tool_item = {"type": "function"}
264
+ if "function" in tool:
265
+ tool_item |= tool["function"]
266
+ tools_list.append(tool_item)
267
+ params["tools"] = tools_list
268
+ params["tool_choice"] = "auto"
269
+
270
+ # Ensure we don't mix legacy params that Responses doesn't accept
271
+ if params.get("stream") and params.get("background"):
272
+ warnings.warn(
273
+ "Streaming a background response may introduce latency.",
274
+ UserWarning,
275
+ )
276
+
277
+ # ------------------------------------------------------------------
278
+ # Structured output handling - mimic OpenAIClient behaviour
279
+ # ------------------------------------------------------------------
280
+
281
+ if self.response_format is not None or "response_format" in params:
282
+
283
+ def _create_or_parse(**kwargs):
284
+ # For structured output we must convert dict / pydantic model
285
+ # into the JSON-schema body expected by the API.
286
+ if "stream" in kwargs:
287
+ kwargs.pop("stream") # Responses API rejects stream with RF for now
288
+
289
+ rf = kwargs.get("response_format", self.response_format)
290
+
291
+ if isinstance(rf, dict):
292
+ from autogen.oai.client import _ensure_strict_json_schema
293
+
294
+ kwargs["text_format"] = {
295
+ "type": "json_schema",
296
+ "json_schema": {
297
+ "schema": _ensure_strict_json_schema(rf, path=(), root=rf),
298
+ "name": "response_format",
299
+ "strict": True,
300
+ },
301
+ }
302
+ else:
303
+ # pydantic.BaseModel subclass
304
+ from autogen.oai.client import type_to_response_format_param
305
+
306
+ kwargs["text_format"] = type_to_response_format_param(rf)
307
+ if "response_format" in kwargs:
308
+ kwargs["text_format"] = kwargs.pop("response_format")
309
+
310
+ try:
311
+ return self._oai_client.responses.parse(**kwargs)
312
+ except TypeError as e:
313
+ # Older openai-python versions may not yet expose the
314
+ # text_format parameter on the Responses endpoint.
315
+ if "text_format" in str(e) and "unexpected" in str(e):
316
+ warnings.warn(
317
+ "Installed openai-python version doesn't support "
318
+ "`response_format` for the Responses API. "
319
+ "Falling back to raw text output.",
320
+ UserWarning,
321
+ )
322
+ kwargs.pop("text_format", None)
323
+ return self._oai_client.responses.create(**kwargs)
324
+
325
+ response = _create_or_parse(**params)
326
+ self.previous_response_id = response.id
327
+ return response
328
+
329
+ # No structured output
330
+ response = self._oai_client.responses.create(**params)
331
+ self.previous_response_id = response.id
332
+
333
+ # Accumulate image costs
334
+ self._add_image_cost(response)
335
+
336
+ return response
337
+
338
+ def message_retrieval(
339
+ self, response
340
+ ) -> Union[list[str], list["ModelClient.ModelClientResponseProtocol.Choice.Message"]]:
341
+ output = getattr(response, "output", [])
342
+ content = [] # list[dict[str, Union[str, dict[str, Any]]]]]
343
+ tool_calls = []
344
+ for item in output:
345
+ # Convert pydantic objects to plain dicts for uniform handling
346
+ if hasattr(item, "model_dump"):
347
+ item = item.model_dump()
348
+
349
+ item_type = item.get("type")
350
+
351
+ # ------------------------------------------------------------------
352
+ # 1) Normal messages
353
+ # ------------------------------------------------------------------
354
+ if item_type == "message":
355
+ new_item = copy.deepcopy(item)
356
+ new_item["type"] = "text"
357
+ new_item["role"] = "assistant"
358
+ blocks = item.get("content", [])
359
+ if len(blocks) == 1 and blocks[0].get("type") == "output_text":
360
+ new_item["text"] = blocks[0]["text"]
361
+ if "content" in new_item:
362
+ del new_item["content"]
363
+ content.append(new_item)
364
+ continue
365
+
366
+ # ------------------------------------------------------------------
367
+ # 2) Custom function calls
368
+ # ------------------------------------------------------------------
369
+ if item_type == "function_call":
370
+ tool_calls.append({
371
+ "id": item.get("call_id", None),
372
+ "function": {
373
+ "name": item.get("name", None),
374
+ "arguments": item.get("arguments"),
375
+ },
376
+ "type": "function_call",
377
+ })
378
+ continue
379
+
380
+ # ------------------------------------------------------------------
381
+ # 3) Built-in tool calls
382
+ # ------------------------------------------------------------------
383
+ if item_type and item_type.endswith("_call"):
384
+ tool_name = item_type.replace("_call", "")
385
+ tool_call_args = {
386
+ "id": item.get("id"),
387
+ "role": "tool_calls",
388
+ "type": "tool_call", # Responses API currently routes via function-like tools
389
+ "name": tool_name,
390
+ }
391
+ if tool_name == "image_generation":
392
+ for k in self.image_output_params:
393
+ if k in item:
394
+ tool_call_args[k] = item[k]
395
+ encoded_base64_result = item.get("result", "")
396
+ tool_call_args["content"] = encoded_base64_result
397
+ # add image_url for image input back to oai response api.
398
+ output_format = self.image_output_params["output_format"]
399
+ tool_call_args["image_url"] = f"data:image/{output_format};base64,{encoded_base64_result}"
400
+ elif tool_name == "web_search":
401
+ pass
402
+ else:
403
+ raise ValueError(f"Invalid tool name: {tool_name}")
404
+ content.append(tool_call_args)
405
+ continue
406
+
407
+ # ------------------------------------------------------------------
408
+ # 4) Fallback - store raw dict so information isn't lost
409
+ # ------------------------------------------------------------------
410
+ content.append(item)
411
+
412
+ return [
413
+ {
414
+ "role": "assistant",
415
+ "id": response.id,
416
+ "content": content if content else None,
417
+ "tool_calls": tool_calls,
418
+ }
419
+ ]
420
+
421
+ def cost(self, response):
422
+ return self._usage_dict(response).get("cost", 0) + self.image_costs
423
+
424
+ @staticmethod
425
+ def get_usage(response):
426
+ return OpenAIResponsesClient._usage_dict(response)
autogen/version.py CHANGED
@@ -4,4 +4,4 @@
4
4
 
5
5
  __all__ = ["__version__"]
6
6
 
7
- __version__ = "0.9.4"
7
+ __version__ = "0.9.5"
File without changes