|
1 | 1 | import logging |
2 | 2 | import asyncio |
| 3 | +import json |
3 | 4 | from typing import Dict, Optional, List, Any, AsyncGenerator |
4 | 5 | from pydantic import BaseModel, ConfigDict |
5 | 6 | from ..agent.agent import Agent |
6 | 7 | from ..task.task import Task |
7 | 8 | from ..main import display_error, client |
8 | 9 | import csv |
9 | 10 | import os |
| 11 | +from openai import AsyncOpenAI |
10 | 12 |
|
11 | 13 | class LoopItems(BaseModel): |
12 | 14 | model_config = ConfigDict(arbitrary_types_allowed=True) |
@@ -106,6 +108,126 @@ def _find_next_not_started_task(self) -> Optional[Task]: |
106 | 108 | logging.debug(f"Fallback attempt {fallback_attempts}: No 'not started' task found within retry limit.") |
107 | 109 | return None # Return None if no task found after all attempts |
108 | 110 |
|
| 111 | + async def _get_manager_instructions_with_fallback_async(self, manager_task, manager_prompt, ManagerInstructions): |
| 112 | + """Async version of getting manager instructions with fallback""" |
| 113 | + try: |
| 114 | + # First try structured output (OpenAI compatible) |
| 115 | + logging.info("Attempting structured output...") |
| 116 | + return await self._get_structured_response_async(manager_task, manager_prompt, ManagerInstructions) |
| 117 | + except Exception as e: |
| 118 | + logging.info(f"Structured output failed: {e}, falling back to JSON mode...") |
| 119 | + # Fallback to regular JSON mode |
| 120 | + try: |
| 121 | + # Generate JSON structure description from Pydantic model |
| 122 | + try: |
| 123 | + schema = ManagerInstructions.model_json_schema() |
| 124 | + props_desc = ", ".join([f'"{k}": <{v.get("type", "any")}>' for k, v in schema.get('properties', {}).items()]) |
| 125 | + required_props = schema.get('required', []) |
| 126 | + required_desc = f" (required: {', '.join(f'\"{p}\"' for p in required_props)})" if required_props else "" |
| 127 | + json_structure_desc = "{" + props_desc + "}" |
| 128 | + enhanced_prompt = manager_prompt + f"\n\nIMPORTANT: Respond with valid JSON only, using this exact structure: {json_structure_desc}{required_desc}" |
| 129 | + except Exception as schema_error: |
| 130 | + logging.warning(f"Could not generate schema for ManagerInstructions: {schema_error}. Using hardcoded prompt.") |
| 131 | + # Fallback to hardcoded prompt if schema generation fails |
| 132 | + enhanced_prompt = manager_prompt + "\n\nIMPORTANT: Respond with valid JSON only, using this exact structure: {\"task_id\": <int>, \"agent_name\": \"<string>\", \"action\": \"<execute or stop>\"}" |
| 133 | + |
| 134 | + return await self._get_json_response_async(manager_task, enhanced_prompt, ManagerInstructions) |
| 135 | + except Exception as fallback_error: |
| 136 | + error_msg = f"Both structured output and JSON fallback failed: {fallback_error}" |
| 137 | + logging.error(error_msg, exc_info=True) |
| 138 | + raise Exception(error_msg) from fallback_error |
| 139 | + |
| 140 | + def _get_manager_instructions_with_fallback(self, manager_task, manager_prompt, ManagerInstructions): |
| 141 | + """Sync version of getting manager instructions with fallback""" |
| 142 | + try: |
| 143 | + # First try structured output (OpenAI compatible) |
| 144 | + logging.info("Attempting structured output...") |
| 145 | + manager_response = client.beta.chat.completions.parse( |
| 146 | + model=self.manager_llm, |
| 147 | + messages=[ |
| 148 | + {"role": "system", "content": manager_task.description}, |
| 149 | + {"role": "user", "content": manager_prompt} |
| 150 | + ], |
| 151 | + temperature=0.7, |
| 152 | + response_format=ManagerInstructions |
| 153 | + ) |
| 154 | + return manager_response.choices[0].message.parsed |
| 155 | + except Exception as e: |
| 156 | + logging.info(f"Structured output failed: {e}, falling back to JSON mode...") |
| 157 | + # Fallback to regular JSON mode |
| 158 | + try: |
| 159 | + # Generate JSON structure description from Pydantic model |
| 160 | + try: |
| 161 | + schema = ManagerInstructions.model_json_schema() |
| 162 | + props_desc = ", ".join([f'"{k}": <{v.get("type", "any")}>' for k, v in schema.get('properties', {}).items()]) |
| 163 | + required_props = schema.get('required', []) |
| 164 | + required_desc = f" (required: {', '.join(f'\"{p}\"' for p in required_props)})" if required_props else "" |
| 165 | + json_structure_desc = "{" + props_desc + "}" |
| 166 | + enhanced_prompt = manager_prompt + f"\n\nIMPORTANT: Respond with valid JSON only, using this exact structure: {json_structure_desc}{required_desc}" |
| 167 | + except Exception as schema_error: |
| 168 | + logging.warning(f"Could not generate schema for ManagerInstructions: {schema_error}. Using hardcoded prompt.") |
| 169 | + # Fallback to hardcoded prompt if schema generation fails |
| 170 | + enhanced_prompt = manager_prompt + "\n\nIMPORTANT: Respond with valid JSON only, using this exact structure: {\"task_id\": <int>, \"agent_name\": \"<string>\", \"action\": \"<execute or stop>\"}" |
| 171 | + |
| 172 | + manager_response = client.chat.completions.create( |
| 173 | + model=self.manager_llm, |
| 174 | + messages=[ |
| 175 | + {"role": "system", "content": manager_task.description}, |
| 176 | + {"role": "user", "content": enhanced_prompt} |
| 177 | + ], |
| 178 | + temperature=0.7, |
| 179 | + response_format={"type": "json_object"} |
| 180 | + ) |
| 181 | + |
| 182 | + # Parse JSON and validate with Pydantic |
| 183 | + try: |
| 184 | + json_content = manager_response.choices[0].message.content |
| 185 | + parsed_json = json.loads(json_content) |
| 186 | + return ManagerInstructions(**parsed_json) |
| 187 | + except (json.JSONDecodeError, ValueError) as e: |
| 188 | + raise Exception(f"Failed to parse JSON response: {json_content}") from e |
| 189 | + except Exception as fallback_error: |
| 190 | + error_msg = f"Both structured output and JSON fallback failed: {fallback_error}" |
| 191 | + logging.error(error_msg, exc_info=True) |
| 192 | + raise Exception(error_msg) from fallback_error |
| 193 | + |
| 194 | + async def _get_structured_response_async(self, manager_task, manager_prompt, ManagerInstructions): |
| 195 | + """Async version of structured response""" |
| 196 | + # Create an async client instance for this async method |
| 197 | + async_client = AsyncOpenAI() |
| 198 | + manager_response = await async_client.beta.chat.completions.parse( |
| 199 | + model=self.manager_llm, |
| 200 | + messages=[ |
| 201 | + {"role": "system", "content": manager_task.description}, |
| 202 | + {"role": "user", "content": manager_prompt} |
| 203 | + ], |
| 204 | + temperature=0.7, |
| 205 | + response_format=ManagerInstructions |
| 206 | + ) |
| 207 | + return manager_response.choices[0].message.parsed |
| 208 | + |
| 209 | + async def _get_json_response_async(self, manager_task, enhanced_prompt, ManagerInstructions): |
| 210 | + """Async version of JSON fallback response""" |
| 211 | + # Create an async client instance for this async method |
| 212 | + async_client = AsyncOpenAI() |
| 213 | + manager_response = await async_client.chat.completions.create( |
| 214 | + model=self.manager_llm, |
| 215 | + messages=[ |
| 216 | + {"role": "system", "content": manager_task.description}, |
| 217 | + {"role": "user", "content": enhanced_prompt} |
| 218 | + ], |
| 219 | + temperature=0.7, |
| 220 | + response_format={"type": "json_object"} |
| 221 | + ) |
| 222 | + |
| 223 | + # Parse JSON and validate with Pydantic |
| 224 | + try: |
| 225 | + json_content = manager_response.choices[0].message.content |
| 226 | + parsed_json = json.loads(json_content) |
| 227 | + return ManagerInstructions(**parsed_json) |
| 228 | + except (json.JSONDecodeError, ValueError) as e: |
| 229 | + raise Exception(f"Failed to parse JSON response: {json_content}") from e |
| 230 | + |
109 | 231 |
|
110 | 232 | async def aworkflow(self) -> AsyncGenerator[str, None]: |
111 | 233 | """Async version of workflow method""" |
@@ -496,26 +618,13 @@ class ManagerInstructions(BaseModel): |
496 | 618 | try: |
497 | 619 | logging.info("Requesting manager instructions...") |
498 | 620 | if manager_task.async_execution: |
499 | | - manager_response = await client.beta.chat.completions.parse( |
500 | | - model=self.manager_llm, |
501 | | - messages=[ |
502 | | - {"role": "system", "content": manager_task.description}, |
503 | | - {"role": "user", "content": manager_prompt} |
504 | | - ], |
505 | | - temperature=0.7, |
506 | | - response_format=ManagerInstructions |
| 621 | + parsed_instructions = await self._get_manager_instructions_with_fallback_async( |
| 622 | + manager_task, manager_prompt, ManagerInstructions |
507 | 623 | ) |
508 | 624 | else: |
509 | | - manager_response = client.beta.chat.completions.parse( |
510 | | - model=self.manager_llm, |
511 | | - messages=[ |
512 | | - {"role": "system", "content": manager_task.description}, |
513 | | - {"role": "user", "content": manager_prompt} |
514 | | - ], |
515 | | - temperature=0.7, |
516 | | - response_format=ManagerInstructions |
| 625 | + parsed_instructions = self._get_manager_instructions_with_fallback( |
| 626 | + manager_task, manager_prompt, ManagerInstructions |
517 | 627 | ) |
518 | | - parsed_instructions = manager_response.choices[0].message.parsed |
519 | 628 | logging.info(f"Manager instructions: {parsed_instructions}") |
520 | 629 | except Exception as e: |
521 | 630 | display_error(f"Manager parse error: {e}") |
@@ -1110,16 +1219,9 @@ class ManagerInstructions(BaseModel): |
1110 | 1219 |
|
1111 | 1220 | try: |
1112 | 1221 | logging.info("Requesting manager instructions...") |
1113 | | - manager_response = client.beta.chat.completions.parse( |
1114 | | - model=self.manager_llm, |
1115 | | - messages=[ |
1116 | | - {"role": "system", "content": manager_task.description}, |
1117 | | - {"role": "user", "content": manager_prompt} |
1118 | | - ], |
1119 | | - temperature=0.7, |
1120 | | - response_format=ManagerInstructions |
| 1222 | + parsed_instructions = self._get_manager_instructions_with_fallback( |
| 1223 | + manager_task, manager_prompt, ManagerInstructions |
1121 | 1224 | ) |
1122 | | - parsed_instructions = manager_response.choices[0].message.parsed |
1123 | 1225 | logging.info(f"Manager instructions: {parsed_instructions}") |
1124 | 1226 | except Exception as e: |
1125 | 1227 | display_error(f"Manager parse error: {e}") |
|
0 commit comments