agent-framework-devui 1.0.0b251007__py3-none-any.whl → 1.0.0b251028__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 agent-framework-devui might be problematic. Click here for more details.
- agent_framework_devui/_conversations.py +473 -0
- agent_framework_devui/_discovery.py +295 -325
- agent_framework_devui/_executor.py +115 -246
- agent_framework_devui/_mapper.py +747 -88
- agent_framework_devui/_server.py +275 -240
- agent_framework_devui/_utils.py +150 -1
- agent_framework_devui/models/__init__.py +21 -10
- agent_framework_devui/models/_discovery_models.py +1 -2
- agent_framework_devui/models/_openai_custom.py +103 -83
- agent_framework_devui/ui/assets/index-CE4pGoXh.css +1 -0
- agent_framework_devui/ui/assets/index-D_Y1oSGu.js +577 -0
- agent_framework_devui/ui/index.html +2 -2
- agent_framework_devui-1.0.0b251028.dist-info/METADATA +321 -0
- agent_framework_devui-1.0.0b251028.dist-info/RECORD +23 -0
- agent_framework_devui/ui/assets/index-D0SfShuZ.js +0 -445
- agent_framework_devui/ui/assets/index-WsCIE0bH.css +0 -1
- agent_framework_devui-1.0.0b251007.dist-info/METADATA +0 -172
- agent_framework_devui-1.0.0b251007.dist-info/RECORD +0 -22
- {agent_framework_devui-1.0.0b251007.dist-info → agent_framework_devui-1.0.0b251028.dist-info}/WHEEL +0 -0
- {agent_framework_devui-1.0.0b251007.dist-info → agent_framework_devui-1.0.0b251028.dist-info}/entry_points.txt +0 -0
- {agent_framework_devui-1.0.0b251007.dist-info → agent_framework_devui-1.0.0b251028.dist-info}/licenses/LICENSE +0 -0
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
import json
|
|
6
6
|
import logging
|
|
7
7
|
import os
|
|
8
|
-
import uuid
|
|
9
8
|
from collections.abc import AsyncGenerator
|
|
10
|
-
from typing import Any
|
|
9
|
+
from typing import Any
|
|
11
10
|
|
|
12
|
-
from agent_framework import
|
|
11
|
+
from agent_framework import AgentProtocol
|
|
13
12
|
|
|
13
|
+
from ._conversations import ConversationStore, InMemoryConversationStore
|
|
14
14
|
from ._discovery import EntityDiscovery
|
|
15
15
|
from ._mapper import MessageMapper
|
|
16
16
|
from ._tracing import capture_traces
|
|
@@ -29,21 +29,26 @@ class EntityNotFoundError(Exception):
|
|
|
29
29
|
class AgentFrameworkExecutor:
|
|
30
30
|
"""Executor for Agent Framework entities - agents and workflows."""
|
|
31
31
|
|
|
32
|
-
def __init__(
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
entity_discovery: EntityDiscovery,
|
|
35
|
+
message_mapper: MessageMapper,
|
|
36
|
+
conversation_store: ConversationStore | None = None,
|
|
37
|
+
):
|
|
33
38
|
"""Initialize Agent Framework executor.
|
|
34
39
|
|
|
35
40
|
Args:
|
|
36
41
|
entity_discovery: Entity discovery instance
|
|
37
42
|
message_mapper: Message mapper instance
|
|
43
|
+
conversation_store: Optional conversation store (defaults to in-memory)
|
|
38
44
|
"""
|
|
39
45
|
self.entity_discovery = entity_discovery
|
|
40
46
|
self.message_mapper = message_mapper
|
|
41
47
|
self._setup_tracing_provider()
|
|
42
48
|
self._setup_agent_framework_tracing()
|
|
43
49
|
|
|
44
|
-
#
|
|
45
|
-
self.
|
|
46
|
-
self.agent_threads: dict[str, list[str]] = {} # agent_id -> thread_ids
|
|
50
|
+
# Use provided conversation store or default to in-memory
|
|
51
|
+
self.conversation_store = conversation_store or InMemoryConversationStore()
|
|
47
52
|
|
|
48
53
|
def _setup_tracing_provider(self) -> None:
|
|
49
54
|
"""Set up our own TracerProvider so we can add processors."""
|
|
@@ -83,199 +88,6 @@ class AgentFrameworkExecutor:
|
|
|
83
88
|
else:
|
|
84
89
|
logger.debug("ENABLE_OTEL not set, skipping observability setup")
|
|
85
90
|
|
|
86
|
-
# Thread Management Methods
|
|
87
|
-
def create_thread(self, agent_id: str) -> str:
|
|
88
|
-
"""Create new thread for agent."""
|
|
89
|
-
thread_id = f"thread_{uuid.uuid4().hex[:8]}"
|
|
90
|
-
thread = AgentThread()
|
|
91
|
-
|
|
92
|
-
self.thread_storage[thread_id] = thread
|
|
93
|
-
|
|
94
|
-
if agent_id not in self.agent_threads:
|
|
95
|
-
self.agent_threads[agent_id] = []
|
|
96
|
-
self.agent_threads[agent_id].append(thread_id)
|
|
97
|
-
|
|
98
|
-
return thread_id
|
|
99
|
-
|
|
100
|
-
def get_thread(self, thread_id: str) -> AgentThread | None:
|
|
101
|
-
"""Get AgentThread by ID."""
|
|
102
|
-
return self.thread_storage.get(thread_id)
|
|
103
|
-
|
|
104
|
-
def list_threads_for_agent(self, agent_id: str) -> list[str]:
|
|
105
|
-
"""List thread IDs for agent."""
|
|
106
|
-
return self.agent_threads.get(agent_id, [])
|
|
107
|
-
|
|
108
|
-
def get_agent_for_thread(self, thread_id: str) -> str | None:
|
|
109
|
-
"""Find which agent owns this thread."""
|
|
110
|
-
for agent_id, thread_ids in self.agent_threads.items():
|
|
111
|
-
if thread_id in thread_ids:
|
|
112
|
-
return agent_id
|
|
113
|
-
return None
|
|
114
|
-
|
|
115
|
-
def delete_thread(self, thread_id: str) -> bool:
|
|
116
|
-
"""Delete thread."""
|
|
117
|
-
if thread_id not in self.thread_storage:
|
|
118
|
-
return False
|
|
119
|
-
|
|
120
|
-
for _agent_id, thread_ids in self.agent_threads.items():
|
|
121
|
-
if thread_id in thread_ids:
|
|
122
|
-
thread_ids.remove(thread_id)
|
|
123
|
-
break
|
|
124
|
-
|
|
125
|
-
del self.thread_storage[thread_id]
|
|
126
|
-
return True
|
|
127
|
-
|
|
128
|
-
async def get_thread_messages(self, thread_id: str) -> list[dict[str, Any]]:
|
|
129
|
-
"""Get messages from a thread's message store, preserving all content types for UI display."""
|
|
130
|
-
thread = self.get_thread(thread_id)
|
|
131
|
-
if not thread or not thread.message_store:
|
|
132
|
-
return []
|
|
133
|
-
|
|
134
|
-
try:
|
|
135
|
-
# Get AgentFramework ChatMessage objects from thread
|
|
136
|
-
af_messages = await thread.message_store.list_messages()
|
|
137
|
-
|
|
138
|
-
ui_messages = []
|
|
139
|
-
for i, af_msg in enumerate(af_messages):
|
|
140
|
-
# Extract role value (handle enum)
|
|
141
|
-
role = af_msg.role.value if hasattr(af_msg.role, "value") else str(af_msg.role)
|
|
142
|
-
|
|
143
|
-
# Skip tool/function messages - only show user and assistant messages
|
|
144
|
-
if role not in ["user", "assistant"]:
|
|
145
|
-
continue
|
|
146
|
-
|
|
147
|
-
# Extract all user-facing content (text, images, files, etc.)
|
|
148
|
-
display_contents = self._extract_display_contents(af_msg.contents)
|
|
149
|
-
|
|
150
|
-
# Skip messages with no displayable content
|
|
151
|
-
if not display_contents:
|
|
152
|
-
continue
|
|
153
|
-
|
|
154
|
-
# Extract usage information if present
|
|
155
|
-
usage_data = None
|
|
156
|
-
for content in af_msg.contents:
|
|
157
|
-
content_type = getattr(content, "type", None)
|
|
158
|
-
if content_type == "usage":
|
|
159
|
-
details = getattr(content, "details", None)
|
|
160
|
-
if details:
|
|
161
|
-
usage_data = {
|
|
162
|
-
"total_tokens": getattr(details, "total_token_count", 0) or 0,
|
|
163
|
-
"prompt_tokens": getattr(details, "input_token_count", 0) or 0,
|
|
164
|
-
"completion_tokens": getattr(details, "output_token_count", 0) or 0,
|
|
165
|
-
}
|
|
166
|
-
break
|
|
167
|
-
|
|
168
|
-
ui_message = {
|
|
169
|
-
"id": af_msg.message_id or f"restored-{i}",
|
|
170
|
-
"role": role,
|
|
171
|
-
"contents": display_contents,
|
|
172
|
-
"timestamp": __import__("datetime").datetime.now().isoformat(),
|
|
173
|
-
"author_name": af_msg.author_name,
|
|
174
|
-
"message_id": af_msg.message_id,
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
# Add usage data if available
|
|
178
|
-
if usage_data:
|
|
179
|
-
ui_message["usage"] = usage_data
|
|
180
|
-
|
|
181
|
-
ui_messages.append(ui_message)
|
|
182
|
-
|
|
183
|
-
logger.info(f"Restored {len(ui_messages)} display messages for thread {thread_id}")
|
|
184
|
-
return ui_messages
|
|
185
|
-
|
|
186
|
-
except Exception as e:
|
|
187
|
-
logger.error(f"Error getting thread messages: {e}")
|
|
188
|
-
import traceback
|
|
189
|
-
|
|
190
|
-
logger.error(traceback.format_exc())
|
|
191
|
-
return []
|
|
192
|
-
|
|
193
|
-
def _extract_display_contents(self, contents: list[Any]) -> list[dict[str, Any]]:
|
|
194
|
-
"""Extract all user-facing content (text, images, files, etc.) from message contents.
|
|
195
|
-
|
|
196
|
-
Filters out internal mechanics like function calls/results while preserving
|
|
197
|
-
all content types that should be displayed in the UI.
|
|
198
|
-
"""
|
|
199
|
-
display_contents = []
|
|
200
|
-
|
|
201
|
-
for content in contents:
|
|
202
|
-
content_type = getattr(content, "type", None)
|
|
203
|
-
|
|
204
|
-
# Text content
|
|
205
|
-
if content_type == "text":
|
|
206
|
-
text = getattr(content, "text", "")
|
|
207
|
-
|
|
208
|
-
# Handle double-encoded JSON from user messages
|
|
209
|
-
if text.startswith('{"role":'):
|
|
210
|
-
try:
|
|
211
|
-
import json
|
|
212
|
-
|
|
213
|
-
parsed = json.loads(text)
|
|
214
|
-
if parsed.get("contents"):
|
|
215
|
-
for sub_content in parsed["contents"]:
|
|
216
|
-
if sub_content.get("type") == "text":
|
|
217
|
-
display_contents.append({"type": "text", "text": sub_content.get("text", "")})
|
|
218
|
-
except Exception:
|
|
219
|
-
display_contents.append({"type": "text", "text": text})
|
|
220
|
-
else:
|
|
221
|
-
display_contents.append({"type": "text", "text": text})
|
|
222
|
-
|
|
223
|
-
# Data content (images, files, PDFs, etc.)
|
|
224
|
-
elif content_type == "data":
|
|
225
|
-
display_contents.append({
|
|
226
|
-
"type": "data",
|
|
227
|
-
"uri": getattr(content, "uri", ""),
|
|
228
|
-
"media_type": getattr(content, "media_type", None),
|
|
229
|
-
})
|
|
230
|
-
|
|
231
|
-
# URI content (external links to images/files)
|
|
232
|
-
elif content_type == "uri":
|
|
233
|
-
display_contents.append({
|
|
234
|
-
"type": "uri",
|
|
235
|
-
"uri": getattr(content, "uri", ""),
|
|
236
|
-
"media_type": getattr(content, "media_type", None),
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
# Skip function_call, function_result, and other internal content types
|
|
240
|
-
|
|
241
|
-
return display_contents
|
|
242
|
-
|
|
243
|
-
async def serialize_thread(self, thread_id: str) -> dict[str, Any] | None:
|
|
244
|
-
"""Serialize thread state for persistence."""
|
|
245
|
-
thread = self.get_thread(thread_id)
|
|
246
|
-
if not thread:
|
|
247
|
-
return None
|
|
248
|
-
|
|
249
|
-
try:
|
|
250
|
-
# Use AgentThread's built-in serialization
|
|
251
|
-
serialized_state = await thread.serialize()
|
|
252
|
-
|
|
253
|
-
# Add our metadata
|
|
254
|
-
agent_id = self.get_agent_for_thread(thread_id)
|
|
255
|
-
serialized_state["metadata"] = {"agent_id": agent_id, "thread_id": thread_id}
|
|
256
|
-
|
|
257
|
-
return serialized_state
|
|
258
|
-
|
|
259
|
-
except Exception as e:
|
|
260
|
-
logger.error(f"Error serializing thread {thread_id}: {e}")
|
|
261
|
-
return None
|
|
262
|
-
|
|
263
|
-
async def deserialize_thread(self, thread_id: str, agent_id: str, serialized_state: dict[str, Any]) -> bool:
|
|
264
|
-
"""Deserialize thread state from persistence."""
|
|
265
|
-
try:
|
|
266
|
-
thread = await AgentThread.deserialize(serialized_state)
|
|
267
|
-
# Store the restored thread
|
|
268
|
-
self.thread_storage[thread_id] = thread
|
|
269
|
-
if agent_id not in self.agent_threads:
|
|
270
|
-
self.agent_threads[agent_id] = []
|
|
271
|
-
self.agent_threads[agent_id].append(thread_id)
|
|
272
|
-
|
|
273
|
-
return True
|
|
274
|
-
|
|
275
|
-
except Exception as e:
|
|
276
|
-
logger.error(f"Error deserializing thread {thread_id}: {e}")
|
|
277
|
-
return False
|
|
278
|
-
|
|
279
91
|
async def discover_entities(self) -> list[EntityInfo]:
|
|
280
92
|
"""Discover all available entities.
|
|
281
93
|
|
|
@@ -357,9 +169,11 @@ class AgentFrameworkExecutor:
|
|
|
357
169
|
Raw Agent Framework events and trace events
|
|
358
170
|
"""
|
|
359
171
|
try:
|
|
360
|
-
# Get entity info
|
|
172
|
+
# Get entity info
|
|
361
173
|
entity_info = self.get_entity_info(entity_id)
|
|
362
|
-
|
|
174
|
+
|
|
175
|
+
# Trigger lazy loading (will return from cache if already loaded)
|
|
176
|
+
entity_obj = await self.entity_discovery.load_entity(entity_id)
|
|
363
177
|
|
|
364
178
|
if not entity_obj:
|
|
365
179
|
raise EntityNotFoundError(f"Entity object for '{entity_id}' not found")
|
|
@@ -390,7 +204,7 @@ class AgentFrameworkExecutor:
|
|
|
390
204
|
yield {"type": "error", "message": str(e), "entity_id": entity_id}
|
|
391
205
|
|
|
392
206
|
async def _execute_agent(
|
|
393
|
-
self, agent:
|
|
207
|
+
self, agent: AgentProtocol, request: AgentFrameworkRequest, trace_collector: Any
|
|
394
208
|
) -> AsyncGenerator[Any, None]:
|
|
395
209
|
"""Execute Agent Framework agent with trace collection and optional thread support.
|
|
396
210
|
|
|
@@ -403,40 +217,73 @@ class AgentFrameworkExecutor:
|
|
|
403
217
|
Agent update events and trace events
|
|
404
218
|
"""
|
|
405
219
|
try:
|
|
220
|
+
# Emit agent lifecycle start event
|
|
221
|
+
from .models._openai_custom import AgentStartedEvent
|
|
222
|
+
|
|
223
|
+
yield AgentStartedEvent()
|
|
224
|
+
|
|
406
225
|
# Convert input to proper ChatMessage or string
|
|
407
226
|
user_message = self._convert_input_to_chat_message(request.input)
|
|
408
227
|
|
|
409
|
-
# Get thread
|
|
228
|
+
# Get thread from conversation parameter (OpenAI standard!)
|
|
410
229
|
thread = None
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
thread = self.get_thread(
|
|
230
|
+
conversation_id = request.get_conversation_id()
|
|
231
|
+
if conversation_id:
|
|
232
|
+
thread = self.conversation_store.get_thread(conversation_id)
|
|
414
233
|
if thread:
|
|
415
|
-
logger.debug(f"Using existing
|
|
234
|
+
logger.debug(f"Using existing conversation: {conversation_id}")
|
|
416
235
|
else:
|
|
417
|
-
logger.warning(f"
|
|
236
|
+
logger.warning(f"Conversation {conversation_id} not found, proceeding without thread")
|
|
418
237
|
|
|
419
238
|
if isinstance(user_message, str):
|
|
420
239
|
logger.debug(f"Executing agent with text input: {user_message[:100]}...")
|
|
421
240
|
else:
|
|
422
241
|
logger.debug(f"Executing agent with multimodal ChatMessage: {type(user_message)}")
|
|
242
|
+
# Check if agent supports streaming
|
|
243
|
+
if hasattr(agent, "run_stream") and callable(agent.run_stream):
|
|
244
|
+
# Use Agent Framework's native streaming with optional thread
|
|
245
|
+
if thread:
|
|
246
|
+
async for update in agent.run_stream(user_message, thread=thread):
|
|
247
|
+
for trace_event in trace_collector.get_pending_events():
|
|
248
|
+
yield trace_event
|
|
249
|
+
|
|
250
|
+
yield update
|
|
251
|
+
else:
|
|
252
|
+
async for update in agent.run_stream(user_message):
|
|
253
|
+
for trace_event in trace_collector.get_pending_events():
|
|
254
|
+
yield trace_event
|
|
255
|
+
|
|
256
|
+
yield update
|
|
257
|
+
elif hasattr(agent, "run") and callable(agent.run):
|
|
258
|
+
# Non-streaming agent - use run() and yield complete response
|
|
259
|
+
logger.info("Agent lacks run_stream(), using run() method (non-streaming)")
|
|
260
|
+
if thread:
|
|
261
|
+
response = await agent.run(user_message, thread=thread)
|
|
262
|
+
else:
|
|
263
|
+
response = await agent.run(user_message)
|
|
423
264
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
for trace_event in trace_collector.get_pending_events():
|
|
428
|
-
yield trace_event
|
|
265
|
+
# Yield trace events before response
|
|
266
|
+
for trace_event in trace_collector.get_pending_events():
|
|
267
|
+
yield trace_event
|
|
429
268
|
|
|
430
|
-
|
|
269
|
+
# Yield the complete response (mapper will convert to streaming events)
|
|
270
|
+
yield response
|
|
431
271
|
else:
|
|
432
|
-
|
|
433
|
-
for trace_event in trace_collector.get_pending_events():
|
|
434
|
-
yield trace_event
|
|
272
|
+
raise ValueError("Agent must implement either run() or run_stream() method")
|
|
435
273
|
|
|
436
|
-
|
|
274
|
+
# Emit agent lifecycle completion event
|
|
275
|
+
from .models._openai_custom import AgentCompletedEvent
|
|
276
|
+
|
|
277
|
+
yield AgentCompletedEvent()
|
|
437
278
|
|
|
438
279
|
except Exception as e:
|
|
439
280
|
logger.error(f"Error in agent execution: {e}")
|
|
281
|
+
# Emit agent lifecycle failure event
|
|
282
|
+
from .models._openai_custom import AgentFailedEvent
|
|
283
|
+
|
|
284
|
+
yield AgentFailedEvent(error=e)
|
|
285
|
+
|
|
286
|
+
# Still yield the error for backward compatibility
|
|
440
287
|
yield {"type": "error", "message": f"Agent execution error: {e!s}"}
|
|
441
288
|
|
|
442
289
|
async def _execute_workflow(
|
|
@@ -453,14 +300,9 @@ class AgentFrameworkExecutor:
|
|
|
453
300
|
Workflow events and trace events
|
|
454
301
|
"""
|
|
455
302
|
try:
|
|
456
|
-
# Get input data
|
|
457
|
-
input_data
|
|
458
|
-
|
|
459
|
-
input_data = request.extra_body.input_data
|
|
460
|
-
logger.debug(f"Using structured input_data from extra_body: {type(input_data)}")
|
|
461
|
-
else:
|
|
462
|
-
input_data = request.input
|
|
463
|
-
logger.debug(f"Using input field as fallback: {type(input_data)}")
|
|
303
|
+
# Get input data directly from request.input field
|
|
304
|
+
input_data = request.input
|
|
305
|
+
logger.debug(f"Using input field: {type(input_data)}")
|
|
464
306
|
|
|
465
307
|
# Parse input based on workflow's expected input type
|
|
466
308
|
parsed_input = await self._parse_workflow_input(workflow, input_data)
|
|
@@ -483,6 +325,9 @@ class AgentFrameworkExecutor:
|
|
|
483
325
|
def _convert_input_to_chat_message(self, input_data: Any) -> Any:
|
|
484
326
|
"""Convert OpenAI Responses API input to Agent Framework ChatMessage or string.
|
|
485
327
|
|
|
328
|
+
Handles various input formats including text, images, files, and multimodal content.
|
|
329
|
+
Falls back to string extraction for simple cases.
|
|
330
|
+
|
|
486
331
|
Args:
|
|
487
332
|
input_data: OpenAI ResponseInputParam (List[ResponseInputItemParam])
|
|
488
333
|
|
|
@@ -512,6 +357,9 @@ class AgentFrameworkExecutor:
|
|
|
512
357
|
) -> Any:
|
|
513
358
|
"""Convert OpenAI ResponseInputParam to Agent Framework ChatMessage.
|
|
514
359
|
|
|
360
|
+
Processes text, images, files, and other content types from OpenAI format
|
|
361
|
+
to Agent Framework ChatMessage with appropriate content objects.
|
|
362
|
+
|
|
515
363
|
Args:
|
|
516
364
|
input_items: List of OpenAI ResponseInputItemParam objects (dicts or objects)
|
|
517
365
|
ChatMessage: ChatMessage class for creating chat messages
|
|
@@ -597,6 +445,40 @@ class AgentFrameworkExecutor:
|
|
|
597
445
|
elif file_url:
|
|
598
446
|
contents.append(DataContent(uri=file_url, media_type=media_type))
|
|
599
447
|
|
|
448
|
+
elif content_type == "function_approval_response":
|
|
449
|
+
# Handle function approval response (DevUI extension)
|
|
450
|
+
try:
|
|
451
|
+
from agent_framework import FunctionApprovalResponseContent, FunctionCallContent
|
|
452
|
+
|
|
453
|
+
request_id = content_item.get("request_id", "")
|
|
454
|
+
approved = content_item.get("approved", False)
|
|
455
|
+
function_call_data = content_item.get("function_call", {})
|
|
456
|
+
|
|
457
|
+
# Create FunctionCallContent from the function_call data
|
|
458
|
+
function_call = FunctionCallContent(
|
|
459
|
+
call_id=function_call_data.get("id", ""),
|
|
460
|
+
name=function_call_data.get("name", ""),
|
|
461
|
+
arguments=function_call_data.get("arguments", {}),
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
# Create FunctionApprovalResponseContent with correct signature
|
|
465
|
+
approval_response = FunctionApprovalResponseContent(
|
|
466
|
+
approved, # positional argument
|
|
467
|
+
id=request_id, # keyword argument 'id', NOT 'request_id'
|
|
468
|
+
function_call=function_call, # FunctionCallContent object
|
|
469
|
+
)
|
|
470
|
+
contents.append(approval_response)
|
|
471
|
+
logger.info(
|
|
472
|
+
f"Added FunctionApprovalResponseContent: id={request_id}, "
|
|
473
|
+
f"approved={approved}, call_id={function_call.call_id}"
|
|
474
|
+
)
|
|
475
|
+
except ImportError:
|
|
476
|
+
logger.warning(
|
|
477
|
+
"FunctionApprovalResponseContent not available in agent_framework"
|
|
478
|
+
)
|
|
479
|
+
except Exception as e:
|
|
480
|
+
logger.error(f"Failed to create FunctionApprovalResponseContent: {e}")
|
|
481
|
+
|
|
600
482
|
# Handle other OpenAI input item types as needed
|
|
601
483
|
# (tool calls, function results, etc.)
|
|
602
484
|
|
|
@@ -687,23 +569,6 @@ class AgentFrameworkExecutor:
|
|
|
687
569
|
|
|
688
570
|
return start_executor, message_types
|
|
689
571
|
|
|
690
|
-
def _select_primary_input_type(self, message_types: list[Any]) -> Any | None:
|
|
691
|
-
"""Choose the most user-friendly input type for workflow kick-off."""
|
|
692
|
-
if not message_types:
|
|
693
|
-
return None
|
|
694
|
-
|
|
695
|
-
preferred = (str, dict)
|
|
696
|
-
|
|
697
|
-
for candidate in preferred:
|
|
698
|
-
for message_type in message_types:
|
|
699
|
-
if message_type is candidate:
|
|
700
|
-
return candidate
|
|
701
|
-
origin = get_origin(message_type)
|
|
702
|
-
if origin is candidate:
|
|
703
|
-
return candidate
|
|
704
|
-
|
|
705
|
-
return message_types[0]
|
|
706
|
-
|
|
707
572
|
def _parse_structured_workflow_input(self, workflow: Any, input_data: dict[str, Any]) -> Any:
|
|
708
573
|
"""Parse structured input data for workflow execution.
|
|
709
574
|
|
|
@@ -728,7 +593,9 @@ class AgentFrameworkExecutor:
|
|
|
728
593
|
return input_data
|
|
729
594
|
|
|
730
595
|
# Get the first (primary) input type
|
|
731
|
-
|
|
596
|
+
from ._utils import select_primary_input_type
|
|
597
|
+
|
|
598
|
+
input_type = select_primary_input_type(message_types)
|
|
732
599
|
if input_type is None:
|
|
733
600
|
logger.debug("Could not select primary input type for workflow - using raw dict")
|
|
734
601
|
return input_data
|
|
@@ -764,7 +631,9 @@ class AgentFrameworkExecutor:
|
|
|
764
631
|
return raw_input
|
|
765
632
|
|
|
766
633
|
# Get the first (primary) input type
|
|
767
|
-
|
|
634
|
+
from ._utils import select_primary_input_type
|
|
635
|
+
|
|
636
|
+
input_type = select_primary_input_type(message_types)
|
|
768
637
|
if input_type is None:
|
|
769
638
|
logger.debug("Could not select primary input type for workflow - using raw string")
|
|
770
639
|
return raw_input
|