From c36477c9cb241b8db39d46041d206a869754198f Mon Sep 17 00:00:00 2001 From: yumoqing Date: Thu, 7 May 2026 14:51:44 +0800 Subject: [PATCH] fix: improve LLM call error handling and plan parsing - Add detailed logging to _llm_call method - Improve _parse_plan_json to handle more LLM response formats - Show LLM error messages in reasoning_submit.dspy - Better error handling and fallback for JSON parsing --- harnessed_reasoning/core.py | 62 ++++++++++++++++++++++++------- wwwroot/api/reasoning_submit.dspy | 14 ++++++- 2 files changed, 60 insertions(+), 16 deletions(-) diff --git a/harnessed_reasoning/core.py b/harnessed_reasoning/core.py index 5d80ad5..e518c44 100644 --- a/harnessed_reasoning/core.py +++ b/harnessed_reasoning/core.py @@ -258,17 +258,23 @@ class HermesReasoningEngine: # Use harnessed_agent's llm_chat if available env = ServerEnv() if hasattr(env, 'llm_chat'): - return await env.llm_chat( - messages=messages, - model=model, - temperature=temperature, - max_tokens=max_tokens, - **extra, - ) - - # Fallback: direct config-based call (shouldn't happen in production) - error("llm_chat not available on ServerEnv") - return {'error': {'message': 'LLM client not available'}} + info(f"Calling llm_chat: model={model}, temp={temperature}, max_tokens={max_tokens}") + try: + result = await env.llm_chat( + messages=messages, + model=model, + temperature=temperature, + max_tokens=max_tokens, + **extra, + ) + info(f"llm_chat returned: success={'error' not in result}") + return result + except Exception as e: + error(f"llm_chat exception: {e}") + return {'error': {'message': f'llm_chat exception: {str(e)}', 'type': 'exception'}} + else: + error("llm_chat not available on ServerEnv") + return {'error': {'message': 'LLM client not available (llm_chat not registered)', 'type': 'configuration_error'}} async def _generate_plan(self, request: str, context: Dict[str, Any], config: Dict[str, Any]) -> Dict[str, Any]: @@ -321,8 +327,9 @@ class HermesReasoningEngine: def _parse_plan_json(self, text: str) -> Dict[str, Any]: """Extract and parse JSON plan from LLM response.""" - # Try to find JSON block import re + + # Try to find JSON block in code fence json_match = re.search(r'```json\s*\n(.*?)\n```', text, re.DOTALL) if json_match: text = json_match.group(1) @@ -340,10 +347,37 @@ class HermesReasoningEngine: plan.setdefault('analysis', '') plan.setdefault('steps', []) plan.setdefault('safety_notes', []) + + # Validate steps structure + valid_steps = [] + for i, step in enumerate(plan.get('steps', [])): + if isinstance(step, dict): + step.setdefault('step_number', i + 1) + step.setdefault('description', '') + step.setdefault('actions', []) + valid_steps.append(step) + elif isinstance(step, str): + # Handle case where LLM returns list of strings + valid_steps.append({ + 'step_number': i + 1, + 'description': step, + 'actions': [] + }) + plan['steps'] = valid_steps + return plan except json.JSONDecodeError: - error(f"Failed to parse LLM plan JSON: {text[:200]}") - return {'analysis': '无法解析 LLM 返回的计划', 'steps': [], 'safety_notes': []} + error(f"Failed to parse LLM plan JSON: {text[:500]}") + # Fallback: create a simple plan from the response text + return { + 'analysis': text[:200], + 'steps': [{ + 'step_number': 1, + 'description': text[:100], + 'actions': [] + }], + 'safety_notes': [] + } # -------------------------------------------------------- # Tool execution diff --git a/wwwroot/api/reasoning_submit.dspy b/wwwroot/api/reasoning_submit.dspy index 25e0820..952c585 100644 --- a/wwwroot/api/reasoning_submit.dspy +++ b/wwwroot/api/reasoning_submit.dspy @@ -21,31 +21,41 @@ try: if reasoning_result.get('success'): # Build result widget showing reasoning output + analysis = reasoning_result.get('analysis', '无') + llm_error = reasoning_result.get('llm_error', '') + plan_items = [] for step in reasoning_result.get('execution_plan', []): plan_items.append(f"步骤{step.get('step_number', '?')}: {step.get('description', '')}") plan_text = '\n'.join(plan_items) if plan_items else '无执行计划' safety_text = '\n'.join(reasoning_result.get('safety_violations', [])) or '无安全风险' + status = reasoning_result.get('status', 'unknown') summary = ( f"请求: {request_text}\n\n" + f"分析: {analysis}\n\n" f"上下文: {reasoning_result.get('context_summary', '无')}\n\n" f"置信度: {reasoning_result.get('confidence_score', 0):.0%}\n\n" f"执行计划:\n{plan_text}\n\n" f"安全检查:\n{safety_text}\n\n" - f"状态: {reasoning_result.get('status', 'unknown')}" + f"状态: {status}" ) + # Show LLM error if any + if llm_error: + summary += f"\n\nLLM 错误: {llm_error}" + if reasoning_result.get('execution_results'): summary += f"\n\n执行结果:\n{json.dumps(reasoning_result['execution_results'], ensure_ascii=False, indent=2)}" + msg_type = 'success' if not llm_error else 'warning' result = { 'widgettype': 'Message', 'options': { 'title': '推理完成', 'message': summary, - 'type': 'success' + 'type': msg_type } } else: