Skip to content

Commit 789ed50

Browse files
fix: Resolve async client usage and improve error handling in DeepSeek fallback
- Fix critical async client usage by importing and using AsyncOpenAI for async methods - Add proper exception chaining with 'from' clauses and detailed logging - Implement JSON parsing error handling with specific exception types - Replace hardcoded JSON structure prompts with dynamic schema generation - Enhance fallback mechanism robustness for DeepSeek compatibility Fixes identified issues: - Lines 168 & 181: Async methods now use proper AsyncOpenAI clients - Lines 124 & 164: Exception handling now chains errors properly - Lines 158-160 & 192-194: JSON parsing wrapped in try-catch blocks - Lines 119 & 145: Dynamic prompt generation from Pydantic schemas Co-authored-by: MervinPraison <MervinPraison@users.noreply.github.com>
1 parent b65d955 commit 789ed50

1 file changed

Lines changed: 48 additions & 14 deletions

File tree

  • src/praisonai-agents/praisonaiagents/process

src/praisonai-agents/praisonaiagents/process/process.py

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from ..main import display_error, client
99
import csv
1010
import os
11+
from openai import AsyncOpenAI
1112

1213
class LoopItems(BaseModel):
1314
items: List[Any]
@@ -116,12 +117,24 @@ async def _get_manager_instructions_with_fallback_async(self, manager_task, mana
116117
logging.info(f"Structured output failed: {e}, falling back to JSON mode...")
117118
# Fallback to regular JSON mode
118119
try:
119-
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>\"}"
120+
# Generate JSON structure description from Pydantic model
121+
try:
122+
schema = ManagerInstructions.model_json_schema()
123+
props_desc = ", ".join([f'"{k}": <{v.get("type", "any")}>' for k, v in schema.get('properties', {}).items()])
124+
required_props = schema.get('required', [])
125+
required_desc = f" (required: {', '.join(f'\"{p}\"' for p in required_props)})" if required_props else ""
126+
json_structure_desc = "{" + props_desc + "}"
127+
enhanced_prompt = manager_prompt + f"\n\nIMPORTANT: Respond with valid JSON only, using this exact structure: {json_structure_desc}{required_desc}"
128+
except Exception as schema_error:
129+
logging.warning(f"Could not generate schema for ManagerInstructions: {schema_error}. Using hardcoded prompt.")
130+
# Fallback to hardcoded prompt if schema generation fails
131+
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>\"}"
132+
120133
return await self._get_json_response_async(manager_task, enhanced_prompt, ManagerInstructions)
121134
except Exception as fallback_error:
122135
error_msg = f"Both structured output and JSON fallback failed: {fallback_error}"
123-
logging.error(error_msg)
124-
raise Exception(error_msg)
136+
logging.error(error_msg, exc_info=True)
137+
raise Exception(error_msg) from fallback_error
125138

126139
def _get_manager_instructions_with_fallback(self, manager_task, manager_prompt, ManagerInstructions):
127140
"""Sync version of getting manager instructions with fallback"""
@@ -142,7 +155,18 @@ def _get_manager_instructions_with_fallback(self, manager_task, manager_prompt,
142155
logging.info(f"Structured output failed: {e}, falling back to JSON mode...")
143156
# Fallback to regular JSON mode
144157
try:
145-
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>\"}"
158+
# Generate JSON structure description from Pydantic model
159+
try:
160+
schema = ManagerInstructions.model_json_schema()
161+
props_desc = ", ".join([f'"{k}": <{v.get("type", "any")}>' for k, v in schema.get('properties', {}).items()])
162+
required_props = schema.get('required', [])
163+
required_desc = f" (required: {', '.join(f'\"{p}\"' for p in required_props)})" if required_props else ""
164+
json_structure_desc = "{" + props_desc + "}"
165+
enhanced_prompt = manager_prompt + f"\n\nIMPORTANT: Respond with valid JSON only, using this exact structure: {json_structure_desc}{required_desc}"
166+
except Exception as schema_error:
167+
logging.warning(f"Could not generate schema for ManagerInstructions: {schema_error}. Using hardcoded prompt.")
168+
# Fallback to hardcoded prompt if schema generation fails
169+
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>\"}"
146170

147171
manager_response = client.chat.completions.create(
148172
model=self.manager_llm,
@@ -155,17 +179,22 @@ def _get_manager_instructions_with_fallback(self, manager_task, manager_prompt,
155179
)
156180

157181
# Parse JSON and validate with Pydantic
158-
json_content = manager_response.choices[0].message.content
159-
parsed_json = json.loads(json_content)
160-
return ManagerInstructions(**parsed_json)
182+
try:
183+
json_content = manager_response.choices[0].message.content
184+
parsed_json = json.loads(json_content)
185+
return ManagerInstructions(**parsed_json)
186+
except (json.JSONDecodeError, ValueError) as e:
187+
raise Exception(f"Failed to parse JSON response: {json_content}") from e
161188
except Exception as fallback_error:
162189
error_msg = f"Both structured output and JSON fallback failed: {fallback_error}"
163-
logging.error(error_msg)
164-
raise Exception(error_msg)
190+
logging.error(error_msg, exc_info=True)
191+
raise Exception(error_msg) from fallback_error
165192

166193
async def _get_structured_response_async(self, manager_task, manager_prompt, ManagerInstructions):
167194
"""Async version of structured response"""
168-
manager_response = await client.beta.chat.completions.parse(
195+
# Create an async client instance for this async method
196+
async_client = AsyncOpenAI()
197+
manager_response = await async_client.beta.chat.completions.parse(
169198
model=self.manager_llm,
170199
messages=[
171200
{"role": "system", "content": manager_task.description},
@@ -178,7 +207,9 @@ async def _get_structured_response_async(self, manager_task, manager_prompt, Man
178207

179208
async def _get_json_response_async(self, manager_task, enhanced_prompt, ManagerInstructions):
180209
"""Async version of JSON fallback response"""
181-
manager_response = await client.chat.completions.create(
210+
# Create an async client instance for this async method
211+
async_client = AsyncOpenAI()
212+
manager_response = await async_client.chat.completions.create(
182213
model=self.manager_llm,
183214
messages=[
184215
{"role": "system", "content": manager_task.description},
@@ -189,9 +220,12 @@ async def _get_json_response_async(self, manager_task, enhanced_prompt, ManagerI
189220
)
190221

191222
# Parse JSON and validate with Pydantic
192-
json_content = manager_response.choices[0].message.content
193-
parsed_json = json.loads(json_content)
194-
return ManagerInstructions(**parsed_json)
223+
try:
224+
json_content = manager_response.choices[0].message.content
225+
parsed_json = json.loads(json_content)
226+
return ManagerInstructions(**parsed_json)
227+
except (json.JSONDecodeError, ValueError) as e:
228+
raise Exception(f"Failed to parse JSON response: {json_content}") from e
195229

196230

197231
async def aworkflow(self) -> AsyncGenerator[str, None]:

0 commit comments

Comments
 (0)