Skip to content

Commit 7405bf6

Browse files
Merge pull request #540 from MervinPraison/claude/issue-240-20250528_161140
fix: Add DeepSeek compatibility fallback for hierarchical process mode
2 parents 50c349f + 789ed50 commit 7405bf6

1 file changed

Lines changed: 128 additions & 26 deletions

File tree

  • src/praisonai-agents/praisonaiagents/process

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

Lines changed: 128 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import logging
22
import asyncio
3+
import json
34
from typing import Dict, Optional, List, Any, AsyncGenerator
45
from pydantic import BaseModel, ConfigDict
56
from ..agent.agent import Agent
67
from ..task.task import Task
78
from ..main import display_error, client
89
import csv
910
import os
11+
from openai import AsyncOpenAI
1012

1113
class LoopItems(BaseModel):
1214
model_config = ConfigDict(arbitrary_types_allowed=True)
@@ -106,6 +108,126 @@ def _find_next_not_started_task(self) -> Optional[Task]:
106108
logging.debug(f"Fallback attempt {fallback_attempts}: No 'not started' task found within retry limit.")
107109
return None # Return None if no task found after all attempts
108110

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+
109231

110232
async def aworkflow(self) -> AsyncGenerator[str, None]:
111233
"""Async version of workflow method"""
@@ -496,26 +618,13 @@ class ManagerInstructions(BaseModel):
496618
try:
497619
logging.info("Requesting manager instructions...")
498620
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
507623
)
508624
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
517627
)
518-
parsed_instructions = manager_response.choices[0].message.parsed
519628
logging.info(f"Manager instructions: {parsed_instructions}")
520629
except Exception as e:
521630
display_error(f"Manager parse error: {e}")
@@ -1110,16 +1219,9 @@ class ManagerInstructions(BaseModel):
11101219

11111220
try:
11121221
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
11211224
)
1122-
parsed_instructions = manager_response.choices[0].message.parsed
11231225
logging.info(f"Manager instructions: {parsed_instructions}")
11241226
except Exception as e:
11251227
display_error(f"Manager parse error: {e}")

0 commit comments

Comments
 (0)