LocalMate / app /agent /reasoning.py
Cuong2004's picture
prompt
9e98b5a
"""ReAct Reasoning Module - Prompts and parsing for multi-step reasoning."""
import json
import re
from dataclasses import dataclass
from typing import Any
from app.shared.logger import agent_logger
from app.shared.prompts import (
REACT_SYSTEM_PROMPT,
TOOL_PURPOSES,
)
# Re-export for backward compatibility
__all__ = ["REACT_SYSTEM_PROMPT", "ReasoningResult", "parse_reasoning_response", "build_reasoning_prompt", "get_tool_purpose"]
@dataclass
class ReasoningResult:
"""Result from LLM reasoning step."""
thought: str
action: str
action_input: dict
raw_response: str
parse_error: str | None = None
def parse_reasoning_response(response: str) -> ReasoningResult:
"""
Parse LLM response into thought/action/action_input.
Handles various formats:
- Clean JSON
- JSON in markdown code blocks
- Partial/malformed JSON
"""
raw = response.strip()
# Try to extract JSON from code blocks
json_match = re.search(r'```(?:json)?\s*(\{.*?\})\s*```', raw, re.DOTALL)
if json_match:
raw = json_match.group(1)
# Try to find JSON object
json_start = raw.find('{')
json_end = raw.rfind('}')
if json_start != -1 and json_end != -1:
raw = raw[json_start:json_end + 1]
try:
data = json.loads(raw)
return ReasoningResult(
thought=data.get("thought", ""),
action=data.get("action", "finish"),
action_input=data.get("action_input", {}),
raw_response=response,
)
except json.JSONDecodeError as e:
agent_logger.error(f"Failed to parse reasoning response", e)
# Fallback: try to extract key fields with regex
thought_match = re.search(r'"thought"\s*:\s*"([^"]*)"', raw)
action_match = re.search(r'"action"\s*:\s*"([^"]*)"', raw)
thought = thought_match.group(1) if thought_match else "Parse error"
action = action_match.group(1) if action_match else "finish"
return ReasoningResult(
thought=thought,
action=action,
action_input={},
raw_response=response,
parse_error=str(e),
)
def build_reasoning_prompt(
query: str,
context_summary: str,
previous_steps: list[dict],
image_url: str | None = None,
) -> str:
"""Build the prompt for the next reasoning step."""
# Previous steps summary with FULL observations
steps_text = ""
if previous_steps:
steps_text = "\n**Các bước đã thực hiện và KẾT QUẢ:**\n"
for step in previous_steps:
action = step.get('action', 'unknown')
thought = step.get('thought', '')[:100]
observation = step.get('observation', [])
steps_text += f"\n📍 **Step {step['step']}**: {thought}...\n"
steps_text += f" Action: `{action}`\n"
# Show detailed observation data
if action == "get_location_coordinates" and observation:
if isinstance(observation, dict):
lat = observation.get('lat', 'N/A')
lng = observation.get('lng', 'N/A')
steps_text += f" ✅ Kết quả: lat={lat}, lng={lng}\n"
steps_text += f" ⚠️ ĐÃ CÓ TỌA ĐỘ - KHÔNG CẦN GỌI LẠI get_location_coordinates\n"
elif action == "find_nearby_places" and observation:
if isinstance(observation, list) and len(observation) > 0:
steps_text += f" ✅ Tìm được {len(observation)} địa điểm:\n"
for i, place in enumerate(observation[:5], 1):
if isinstance(place, dict):
name = place.get('name', 'Unknown')
dist = place.get('distance_km', 'N/A')
rating = place.get('rating', 'N/A')
steps_text += f" {i}. {name} ({dist}km, ⭐{rating})\n"
else:
steps_text += f" {i}. {place}\n"
if len(observation) > 5:
steps_text += f" ... và {len(observation) - 5} địa điểm khác\n"
steps_text += f" ⚠️ ĐÃ CÓ DANH SÁCH - KHÔNG CẦN GỌI LẠI find_nearby_places\n"
elif action == "retrieve_context_text" and observation:
if isinstance(observation, list) and len(observation) > 0:
steps_text += f" ✅ Tìm được {len(observation)} kết quả text:\n"
for i, item in enumerate(observation[:3], 1):
if isinstance(item, dict):
name = item.get('name', 'Unknown')
steps_text += f" {i}. {name}\n"
else:
steps_text += f" {i}. {item}\n"
steps_text += f" ⚠️ ĐÃ CÓ KẾT QUẢ TEXT - KHÔNG CẦN GỌI LẠI retrieve_context_text\n"
elif observation:
result_count = len(observation) if isinstance(observation, list) else 1
steps_text += f" ✅ Kết quả: {result_count} items\n"
steps_text += "\n**⚠️ QUAN TRỌNG:** Nếu đã có đủ thông tin từ các bước trên → action = 'finish'\n"
# Image context
image_text = ""
if image_url:
image_text = "\n**Lưu ý:** User đã gửi kèm ảnh. Có thể dùng retrieve_similar_visuals nếu cần.\n"
prompt = f"""**Câu hỏi của user:** {query}
{image_text}
{context_summary}
{steps_text}
**Bước tiếp theo là gì?**
Trả lời theo format JSON:
```json
{{
"thought": "...",
"action": "tool_name hoặc finish",
"action_input": {{...}}
}}
```"""
return prompt
def get_tool_purpose(action: str) -> str:
"""Get human-readable purpose for a tool."""
return TOOL_PURPOSES.get(action, action)