From 8108e86ab5e2deead62aa22b863c278165ff66f9 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Thu, 7 May 2026 12:15:23 +0800 Subject: [PATCH] refactor: replace mock reasoning with real LLM-based engine - Rewrite core.py: all mock implementations replaced with real calls * _generate_plan() uses llm_chat from harnessed_agent for LLM-based planning * _execute_tool() calls harnessed_execute_tool for real tool execution * _get_memory_context() calls harnessed_get_intelligent_memory_context * _safety_check() with configurable strict/moderate/lenient modes * _try_recovery() for auto-recovery on common failures * All session persistence uses real database operations - Fix reasoning_console.ui: uitype 'textarea' -> 'text' per bricks spec - Add .gitignore The reasoning module now: 1. Calls LLM to analyze user request and generate execution plan 2. Safety-checks the plan before execution 3. Executes tools via harnessed_agent's tool system 4. Recovers from common errors automatically 5. Stores session history in database --- .gitignore | 7 + .../config_functions.cpython-311.pyc | Bin 0 -> 6053 bytes .../__pycache__/core.cpython-311.pyc | Bin 0 -> 40095 bytes .../__pycache__/init.cpython-311.pyc | Bin 0 -> 945 bytes harnessed_reasoning/core.py | 1324 ++++++++--------- wwwroot/reasoning_console.ui | 2 +- 6 files changed, 650 insertions(+), 683 deletions(-) create mode 100644 .gitignore create mode 100644 harnessed_reasoning/__pycache__/config_functions.cpython-311.pyc create mode 100644 harnessed_reasoning/__pycache__/core.cpython-311.pyc create mode 100644 harnessed_reasoning/__pycache__/init.cpython-311.pyc diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fbf4d7a --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +__pycache__/ +*.pyc +*.pyo +py3/ +*.egg-info/ +dist/ +build/ diff --git a/harnessed_reasoning/__pycache__/config_functions.cpython-311.pyc b/harnessed_reasoning/__pycache__/config_functions.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e179b6cc626d3ad11c2c3dd5c7806d779d393c24 GIT binary patch literal 6053 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 IpMfm@1Mg5TO#lD@ literal 0 HcmV?d00001 diff --git a/harnessed_reasoning/__pycache__/core.cpython-311.pyc b/harnessed_reasoning/__pycache__/core.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bfeb034941af90cae34b5d094fc554fe5c46a1e6 GIT binary patch literal 40095 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 H3h@60ve*07 literal 0 HcmV?d00001 diff --git a/harnessed_reasoning/__pycache__/init.cpython-311.pyc b/harnessed_reasoning/__pycache__/init.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b75c0ff8503d30c63d3b8de925d4519c515e9f7a GIT binary patch literal 945 zcmbVKy=xRf6o0c{w|C@3Ort2Gctn>#t`0;&E@GD^-8RdzGr3#XoeeW{M+Ai{(g=!W zz&}6{{{>t76c)0ul~uZf>#p+6?Coh_ZG6nUH}C5=@4cDN{eBOSzJFV#?=65|T4@M% zL(2mc7eIlM3~~|@0@R{*X61Hh=T7M4Zs_J-=o!0{b#g!S@$8;M*rnbv1f44l6p*re z%*2d|7kqX_w8bg!Oqj@-j0B5i!K0Ydh@G)ys#tk%?HV(+cBgzCNhW1l@bdoJo29bF zEz7&D*-61q(lJ)jfXjHs{z9+)i7vMxJq8>1@*}bfP`dCneM0%QQQ!@zW@Ko44bdi2 zIh!DD-$W_znqA*wNcqjl;B2y$d92!pIBpX~0f$VE0#9fNq3uFLi;iX*OmvZ9+cx7f zsq1>MIvh@loDJ(S#pzJ6(?Sj>vEVpiM*j?@)t2&9?VY|VZ)ZhJqn0eu$6K~=%Y9^X zsCwIPci-*KN7dlzeDuS4SUnzTIjDk{TJBUk&$WEi*e{&{t|cbJQP3-I#S>{h&t5IC z5z}qyB!ysYJ(oA@*Njf4JW=>(q?v*#>-}2*9 literal 0 HcmV?d00001 diff --git a/harnessed_reasoning/core.py b/harnessed_reasoning/core.py index 052fd83..5d80ad5 100644 --- a/harnessed_reasoning/core.py +++ b/harnessed_reasoning/core.py @@ -1,764 +1,724 @@ """ -Hermes Reasoning Module - Production-ready reasoning engine with full context awareness -Implements advanced reasoning capabilities including planning, tool coordination, -error recovery, and cross-session intelligence as a standardized ahserver module. +Hermes Reasoning Module - Production-ready reasoning engine +Uses harnessed_agent LLM client and tool execution to perform real AI reasoning, +task planning, and tool coordination. """ - -import asyncio import json -import re -from typing import Dict, Any, List, Optional, Tuple, Callable -from dataclasses import dataclass -from datetime import datetime import uuid -from enum import Enum +import time +from typing import Dict, Any, List, Optional +from datetime import datetime -# Import required dependencies try: from ahserver.serverenv import ServerEnv - from appPublic.worker import awaitify from sqlor.dbpools import DBPools + from appPublic.log import info, debug, warning, error, exception except ImportError: - # For standalone testing class ServerEnv: - def __init__(self): - pass - - def awaitify(func): - async def wrapper(*args, **kwargs): - return func(*args, **kwargs) - return wrapper - + pass class DBPools: - def __init__(self): - pass + pass + def info(*a, **kw): print(*a) + def debug(*a, **kw): pass + def warning(*a, **kw): print(*a) + def error(*a, **kw): print(*a) + def exception(*a, **kw): pass -@dataclass -class ReasoningConfig: - """Configuration for Hermes Reasoning module""" - max_reasoning_steps: int = 10 # Maximum reasoning steps per task - max_tool_calls_per_step: int = 5 # Maximum tool calls per reasoning step - enable_cross_session_search: bool = True # Enable automatic session search - enable_skill_auto_loading: bool = True # Enable automatic skill loading - safety_mode: str = "strict" # Safety mode: strict, moderate, lenient - max_context_tokens: int = 4000 # Maximum tokens for reasoning context - enable_error_recovery: bool = True # Enable automatic error recovery - max_recovery_attempts: int = 3 # Maximum recovery attempts per error -class ReasoningStepType(Enum): - """Types of reasoning steps""" - CONTEXT_ANALYSIS = "context_analysis" - TASK_PLANNING = "task_planning" - TOOL_SELECTION = "tool_selection" - EXECUTION_COORDINATION = "execution_coordination" - ERROR_RECOVERY = "error_recovery" - RESULT_SYNTHESIS = "result_synthesis" - CROSS_SESSION_INTEGRATION = "cross_session_integration" +# ============================================================ +# System prompts for reasoning phases +# ============================================================ -@dataclass -class ReasoningStep: - """Individual reasoning step with metadata""" - id: str - step_type: ReasoningStepType - description: str - context: Dict[str, Any] - tools_considered: List[str] - tools_selected: List[str] - safety_checks: List[str] - confidence_score: float - created_at: datetime +REASONING_SYSTEM_PROMPT = """你是一个智能推理引擎。你的任务是分析用户的自然语言请求,理解其意图,并生成可执行的行动计划。 -@dataclass -class ReasoningSession: - """Complete reasoning session with execution plan""" - id: str - user_id: str - initial_request: str - context_summary: str - execution_plan: List[Dict[str, Any]] - reasoning_steps: List[ReasoningStep] - safety_violations: List[str] - final_decision: Dict[str, Any] - status: str # pending, executing, completed, failed, cancelled - created_at: datetime - updated_at: datetime +你拥有以下工具可供调用: +{tool_descriptions} + +请根据用户请求,返回一个 JSON 格式的执行计划: +```json +{{ + "analysis": "对用户请求的简要分析", + "steps": [ + {{ + "step_number": 1, + "description": "步骤描述", + "actions": [ + {{ + "tool": "工具名称", + "parameters": {{"参数名": "参数值"}} + }} + ] + }} + ], + "safety_notes": ["安全注意事项,如无则空数组"] +}} +``` + +可用的工具名称:{tool_names} + +注意: +1. 只使用上述工具列表中存在的工具 +2. 每个步骤可以包含 1-3 个动作 +3. 如果请求模糊,优先选择收集信息类工具 +4. 确保参数格式正确""" + +# List of tools that harnessed_agent can execute +AVAILABLE_TOOLS = [ + # File operations + {"name": "read_file", "desc": "读取文本文件,需要 path 参数"}, + {"name": "write_file", "desc": "写入文件内容,需要 path 和 content 参数"}, + {"name": "search_files", "desc": "搜索文件内容或文件名,需要 pattern 和 target(content/files) 参数"}, + {"name": "patch", "desc": "文件替换修改,需要 mode/path/old_string/new_string 参数"}, + # System operations + {"name": "terminal", "desc": "执行 shell 命令,需要 command 参数"}, + {"name": "process", "desc": "管理后台进程,需要 action/session_id/data 参数"}, + {"name": "execute_code", "desc": "执行 Python 代码,需要 code 参数"}, + # Memory + {"name": "memory", "desc": "管理持久化记忆,需要 action/target/content/old_text 参数"}, + # Skills + {"name": "skill_manage", "desc": "管理技能,需要 action/name/content 参数"}, + {"name": "skill_view", "desc": "查看技能内容,需要 name 参数"}, + # Other + {"name": "todo", "desc": "管理任务列表,需要 action/todos 参数"}, + {"name": "session_search", "desc": "搜索历史会话,需要 query 参数"}, + {"name": "cronjob", "desc": "管理定时任务,需要 action/prompt/schedule 参数"}, + {"name": "clarify", "desc": "向用户提问获取澄清,需要 question/choices 参数"}, + {"name": "delegate_task", "desc": "委派任务给子代理,需要 goal/context/toolsets 参数"}, + {"name": "text_to_speech", "desc": "文字转语音,需要 text 参数"}, + {"name": "vision_analyze", "desc": "分析图片内容,需要 image_url 和 question 参数"}, +] + +TOOL_NAMES = [t["name"] for t in AVAILABLE_TOOLS] +TOOL_DESCRIPTIONS = "\n".join(f"- {t['name']}: {t['desc']}" for t in AVAILABLE_TOOLS) + + +# ============================================================ +# Reasoning Engine +# ============================================================ class HermesReasoningEngine: - """Core reasoning engine with production-grade safety and reliability""" - - def __init__(self, config: Optional[ReasoningConfig] = None): - self.config = config or ReasoningConfig() + """Production reasoning engine that uses LLM and real tool execution.""" + + DEFAULT_SAFETY_RULES = { + "strict": [ + "rm -rf /", "format ", "dd if=/dev/", "mkfs", "chmod 777", + "rm -fr /", "> /dev/sd", "> /dev/hd", ":(){:|:&};:", + ], + "moderate": [ + "rm -rf /", "dd if=/dev/zero", ":(){:|:&};:", + ], + "lenient": [], + } + + def __init__(self): self.db = DBPools() - self.execution_engine = None # Will connect to harnessed_agent - - def _get_current_user_id(self, context: Dict[str, Any]) -> str: - """Get current user ID from request context""" - user_id = context.get('user_id') or context.get('userid') - if not user_id: - raise ValueError("User ID not found in context. User must be authenticated.") - return str(user_id) - - def _estimate_tokens(self, text: str) -> int: - """Estimate token count for given text""" - return max(1, len(text) // 4) - - async def _get_intelligent_context(self, user_id: str, request: str) -> Dict[str, Any]: - """Get intelligent context combining memory, sessions, and skills""" - context_data = { - "memory": [], - "sessions": [], - "skills": [], - "tools": [], - "user_preferences": {} - } - + + # -------------------------------------------------------- + # Config helpers + # -------------------------------------------------------- + + async def _get_config(self) -> Dict[str, Any]: + """Get reasoning config from DB, fall back to defaults.""" try: - # Get intelligent memory context from harnessed_agent - memory_context = await self._call_harnessed_agent_function( - "hermes_get_intelligent_memory_context", - {"current_task": request, "max_tokens": self.config.max_context_tokens // 3} - ) - if memory_context.get("success"): - context_data["memory"] = memory_context.get("memories", []) - # Extract user preferences - for mem in context_data["memory"]: - if mem.get("target") == "user": - try: - context_data["user_preferences"].update( - json.loads(mem.get("content", "{}")) - ) - except: - pass - - # Get relevant sessions - if self.config.enable_cross_session_search: - session_search = await self._call_harnessed_agent_function( - "hermes_search_sessions", - {"query": request, "limit": 5} - ) - if session_search.get("success"): - context_data["sessions"] = session_search.get("sessions", []) - - # Get relevant skills - if self.config.enable_skill_auto_loading: - # This would integrate with skill management system - context_data["skills"] = await self._get_relevant_skills(user_id, request) - - # Get available tools (this would come from tool registry) - context_data["tools"] = self._get_available_tools() - + env = ServerEnv() + dbname = env.get_module_dbname('harnessed_reasoning') + except Exception: + dbname = 'default' + + try: + async with self.db.sqlorContext(dbname) as sor: + rows = await sor.R('harnessed_reasoning_config', {}, + orderby='updated_at DESC', limit=1) + if rows: + return rows[0] except Exception as e: - # Log error but continue with partial context - pass - - return context_data - - async def _get_relevant_skills(self, user_id: str, request: str) -> List[Dict[str, Any]]: - """Get skills relevant to the current request""" - # This is a placeholder - would integrate with actual skill system + error(f"Failed to fetch reasoning config: {e}") + + return { + 'max_reasoning_steps': 10, + 'max_tool_calls_per_step': 5, + 'enable_cross_session_search': '1', + 'enable_skill_auto_loading': '1', + 'safety_mode': 'strict', + 'max_context_tokens': 4000, + 'enable_error_recovery': '1', + 'max_recovery_attempts': 3, + 'model_name': 'qwen3-max', + 'system_prompt': '', + } + + # -------------------------------------------------------- + # Memory & session context + # -------------------------------------------------------- + + async def _get_memory_context(self, user_id: str, request: str, config: Dict) -> Dict[str, Any]: + """Get real memory and session context from harnessed_agent.""" + context = {"memory_entries": [], "recent_sessions": [], "skills": []} + + try: + # Intelligent memory + max_tokens = int(config.get('max_context_tokens', 4000)) // 3 + if hasattr(ServerEnv(), 'harnessed_get_intelligent_memory_context'): + mem_result = await ServerEnv().harnessed_get_intelligent_memory_context( + current_task=request, + max_tokens=max_tokens, + ) + if mem_result.get('success'): + context['memory_entries'] = mem_result.get('memories', []) + + # Session search + if config.get('enable_cross_session_search', '1') == '1': + if hasattr(ServerEnv(), 'harnessed_search_sessions'): + sess_result = await ServerEnv().harnessed_search_sessions( + query=request, + limit=3, + ) + if sess_result.get('success'): + context['recent_sessions'] = sess_result.get('sessions', []) + + # Skills + if config.get('enable_skill_auto_loading', '1') == '1': + context['skills'] = await self._find_relevant_skills(user_id, request) + + except Exception as e: + warning(f"Context gathering failed (non-fatal): {e}") + + return context + + async def _find_relevant_skills(self, user_id: str, request: str) -> List[Dict[str, Any]]: + """Find skills relevant to the request via keyword search.""" + keywords = set() + for word in request.lower().split(): + if len(word) > 2: + keywords.add(word) + + skills = [] try: - # Search for skills matching request keywords - keywords = self._extract_keywords(request) - relevant_skills = [] - async with self.db.sqlorContext('default') as sor: - for keyword in keywords[:3]: # Limit to top 3 keywords - skills = await sor.R('hermes_skills', { + for kw in list(keywords)[:3]: + rows = await sor.R('hermes_skills', { 'user_id': user_id, '$or': [ - {'name': {'$like': f'%{keyword}%'}}, - {'description': {'$like': f'%{keyword}%'}}, - {'content': {'$like': f'%{keyword}%'}} + {'name': {'$like': f'%{kw}%'}}, + {'description': {'$like': f'%{kw}%'}}, ] }, limit=2) - relevant_skills.extend(skills) - - # Deduplicate skills - seen = set() - unique_skills = [] - for skill in relevant_skills: - if skill['id'] not in seen: - unique_skills.append(skill) - seen.add(skill['id']) - - return unique_skills[:5] # Limit to top 5 skills - + skills.extend(rows) + + # Deduplicate + seen = set() + unique = [] + for s in skills: + if s['id'] not in seen: + seen.add(s['id']) + unique.append(s) + return unique[:5] except Exception: return [] - - def _extract_keywords(self, text: str) -> List[str]: - """Extract important keywords from text""" - # Simple keyword extraction - would use NLP in production - words = re.findall(r'\b\w+\b', text.lower()) - # Filter out common stop words - stop_words = {'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by', 'is', 'are', 'was', 'were'} - keywords = [word for word in words if word not in stop_words and len(word) > 2] - return list(set(keywords))[:10] # Return unique keywords, max 10 - - def _get_available_tools(self) -> List[str]: - """Get list of available tools""" - # This would come from actual tool registry - return [ - "browser_navigate", "browser_click", "browser_type", "browser_snapshot", - "terminal", "read_file", "write_file", "search_files", "patch", - "memory", "skill_manage", "skill_view", "session_search", - "clarify", "delegate_task", "execute_code", "process", - "vision_analyze", "text_to_speech", "cronjob", "todo" - ] - - async def _call_harnessed_agent_function(self, function_name: str, parameters: Dict[str, Any]) -> Dict[str, Any]: - """Call harnessed_agent functions safely""" - try: - # This would integrate with actual harnessed_agent module - # For now, return mock responses that match expected structure - if function_name == "hermes_get_intelligent_memory_context": - return { - "success": True, - "memories": [], - "total_tokens": 0, - "max_tokens": parameters.get("max_tokens", 2000), - "user_id": "mock_user", - "memory_count": 0 - } - elif function_name == "hermes_search_sessions": - return { - "success": True, - "sessions": [], - "query": parameters.get("query", ""), - "limit": parameters.get("limit", 3), - "user_id": "mock_user" - } - else: - return {"success": True, "result": f"Called {function_name} with {parameters}"} - except Exception as e: - return {"success": False, "error": str(e)} - - def _perform_safety_check(self, action: str, parameters: Dict[str, Any], - user_preferences: Dict[str, Any]) -> List[str]: - """Perform safety checks on proposed actions""" + + # -------------------------------------------------------- + # Safety checks + # -------------------------------------------------------- + + def _safety_check(self, plan: Dict[str, Any], safety_mode: str = 'strict') -> List[str]: + """Check execution plan for safety violations.""" violations = [] - - if self.config.safety_mode == "strict": - # Strict safety checks - dangerous_commands = [ - "rm -rf", "format", "dd if", "mkfs", "chmod 777", - "chown root", "sudo", "su -", "passwd", "userdel" - ] - - if action == "terminal": - command = parameters.get("command", "") - for dangerous in dangerous_commands: - if dangerous in command: - violations.append(f"Dangerous command detected: {dangerous}") - - # File system access restrictions - if action in ["read_file", "write_file", "patch"]: - path = parameters.get("path", "") - if ".." in path or path.startswith("/etc") or path.startswith("/root"): - violations.append(f"Restricted path access: {path}") - - # Network restrictions - if action == "browser_navigate": - url = parameters.get("url", "") - if not url.startswith(("http://", "https://")): - violations.append(f"Invalid URL protocol: {url}") - - elif self.config.safety_mode == "moderate": - # Moderate safety checks - if action == "terminal": - command = parameters.get("command", "") - if "rm -rf /" in command or "dd if=/dev/zero" in command: - violations.append("Extremely dangerous command detected") - - # User preference checks - if user_preferences.get("avoid_terminal") and action == "terminal": - violations.append("User preference: avoid terminal commands") - + rules = self.DEFAULT_SAFETY_RULES.get(safety_mode, []) + + steps = plan.get('steps', []) + for step in steps: + for action in step.get('actions', []): + tool = action.get('tool', '') + params = action.get('parameters', {}) + + if tool == 'terminal': + cmd = params.get('command', '') + for rule in rules: + if rule.lower() in cmd.lower(): + violations.append(f"危险命令: {rule}") + + if tool in ('read_file', 'write_file', 'patch'): + path = params.get('path', '') + if '..' in path or path.startswith('/etc/passwd') or path.startswith('/etc/shadow'): + violations.append(f"受限路径: {path}") + return violations - - async def _analyze_context_and_plan(self, user_id: str, request: str, - context_data: Dict[str, Any]) -> Dict[str, Any]: - """Analyze context and create execution plan""" - # Step 1: Context analysis - context_summary = self._summarize_context(context_data) - - # Step 2: Task decomposition - subtasks = self._decompose_task(request, context_data) - - # Step 3: Tool selection and planning - execution_plan = [] - safety_violations = [] - - for i, subtask in enumerate(subtasks[:self.config.max_reasoning_steps]): - # Select appropriate tools for this subtask - tools_for_subtask = self._select_tools_for_subtask(subtask, context_data) - - # Create execution step - step_plan = { - "step_number": i + 1, - "description": subtask, - "tools": tools_for_subtask[:self.config.max_tool_calls_per_step], - "expected_outcome": f"Complete subtask: {subtask}", - "safety_checks": [] - } - - # Perform safety checks - for tool_action in step_plan["tools"]: - violations = self._perform_safety_check( - tool_action["action"], - tool_action.get("parameters", {}), - context_data.get("user_preferences", {}) - ) - step_plan["safety_checks"].extend(violations) - safety_violations.extend(violations) - - execution_plan.append(step_plan) - - return { - "context_summary": context_summary, - "execution_plan": execution_plan, - "safety_violations": safety_violations, - "confidence_score": self._calculate_confidence(execution_plan, context_data) - } - - def _summarize_context(self, context_data: Dict[str, Any]) -> str: - """Create a summary of the available context""" - summary_parts = [] - - if context_data["memory"]: - summary_parts.append(f"Memory entries: {len(context_data['memory'])}") - - if context_data["sessions"]: - summary_parts.append(f"Relevant sessions: {len(context_data['sessions'])}") - - if context_data["skills"]: - skill_names = [s.get("name", "unknown") for s in context_data["skills"]] - summary_parts.append(f"Relevant skills: {', '.join(skill_names[:3])}") - - if context_data["user_preferences"]: - summary_parts.append("User preferences loaded") - - return "; ".join(summary_parts) if summary_parts else "No relevant context found" - - def _decompose_task(self, request: str, context_data: Dict[str, Any]) -> List[str]: - """Decompose complex task into subtasks""" - # This is where advanced reasoning happens - # In production, this would use LLM-based task decomposition - - # Simple rule-based decomposition for now - subtasks = [] - - # Check for multi-step indicators - if any(word in request.lower() for word in ["and then", "after that", "next", "finally"]): - # Split on conjunctions - parts = re.split(r'\s+(?:and then|after that|next|finally)\s+', request, flags=re.IGNORECASE) - subtasks = [part.strip() for part in parts if part.strip()] - elif "?" in request or "how" in request.lower() or "what" in request.lower(): - # Question handling - subtasks = [ - f"Understand the question: {request}", - "Gather relevant information", - "Formulate comprehensive answer" - ] - else: - # Single task - subtasks = [request] - - return subtasks[:5] # Limit to 5 subtasks - - def _select_tools_for_subtask(self, subtask: str, context_data: Dict[str, Any]) -> List[Dict[str, Any]]: - """Select appropriate tools for a given subtask""" - # Simple keyword-based tool selection - tool_mappings = { - "file": ["read_file", "write_file", "search_files"], - "code": ["read_file", "write_file", "patch", "terminal"], - "web": ["browser_navigate", "browser_snapshot", "browser_click"], - "search": ["search_files", "session_search"], - "memory": ["memory"], - "skill": ["skill_view", "skill_manage"], - "execute": ["terminal", "execute_code"], - "image": ["vision_analyze", "browser_get_images"], - "plan": ["todo"] - } - - selected_tools = [] - subtask_lower = subtask.lower() - - for keyword, tools in tool_mappings.items(): - if keyword in subtask_lower: - for tool in tools: - selected_tools.append({ - "action": tool, - "parameters": self._infer_parameters(tool, subtask, context_data) - }) - break - - # Default fallback - if not selected_tools: - selected_tools = [{ - "action": "clarify", - "parameters": {"question": f"Could you clarify what you'd like me to do about: {subtask}"} - }] - - return selected_tools - - def _infer_parameters(self, tool: str, subtask: str, context_data: Dict[str, Any]) -> Dict[str, Any]: - """Infer reasonable parameters for a tool based on subtask""" - # Very basic parameter inference - if tool == "read_file": - # Look for file paths in subtask - file_match = re.search(r'(\S+\.py|\S+\.txt|\S+\.md)', subtask) - if file_match: - return {"path": file_match.group(1)} - - elif tool == "search_files": - # Look for search terms - if "find" in subtask or "search" in subtask: - words = subtask.split() - if len(words) > 2: - return {"pattern": words[-1], "target": "content"} - - elif tool == "terminal": - # Look for commands - if "run" in subtask or "execute" in subtask: - # Extract command after "run" or "execute" - cmd_match = re.search(r'(?:run|execute)\s+(.+)', subtask, re.IGNORECASE) - if cmd_match: - return {"command": cmd_match.group(1)} - - return {} - - def _calculate_confidence(self, execution_plan: List[Dict[str, Any]], - context_data: Dict[str, Any]) -> float: - """Calculate confidence score for the execution plan""" - base_confidence = 0.7 # Base confidence - - # Adjust based on context availability - if context_data["memory"] or context_data["sessions"] or context_data["skills"]: - base_confidence += 0.1 - - # Adjust based on plan complexity - if len(execution_plan) == 1: - base_confidence += 0.1 - elif len(execution_plan) > 3: - base_confidence -= 0.1 - - # Adjust based on safety violations - safety_penalty = len([v for v in execution_plan for check in v.get("safety_checks", []) if check]) * 0.05 - base_confidence -= safety_penalty - - return max(0.0, min(1.0, base_confidence)) - - async def reason_and_execute(self, request: str, - context: Dict[str, Any] = None, - execute_immediately: bool = True) -> Dict[str, Any]: - """ - Main entry point: perform reasoning and optionally execute the plan - - Args: - request: User's natural language request - context: Request context containing user information - execute_immediately: Whether to execute the plan immediately or just return it - - Returns: - Reasoning result with execution plan and optional execution results - """ - user_id = self._get_current_user_id(context) if context else "anonymous" - session_id = str(uuid.uuid4()) - - try: - # Step 1: Gather intelligent context - context_data = await self._get_intelligent_context(user_id, request) - - # Step 2: Analyze context and create execution plan - planning_result = await self._analyze_context_and_plan(user_id, request, context_data) - - # Step 3: Create reasoning session record - reasoning_session = ReasoningSession( - id=session_id, - user_id=user_id, - initial_request=request, - context_summary=planning_result["context_summary"], - execution_plan=planning_result["execution_plan"], - reasoning_steps=[], # Would be populated with detailed steps in production - safety_violations=planning_result["safety_violations"], - final_decision={"confidence": planning_result["confidence_score"]}, - status="pending", - created_at=datetime.now(), - updated_at=datetime.now() + + # -------------------------------------------------------- + # LLM-based reasoning + # -------------------------------------------------------- + + async def _llm_call(self, messages: List[Dict[str, str]], config: Dict[str, Any], + **extra) -> Dict[str, Any]: + """Call LLM via harnessed_agent's llm_chat.""" + model = config.get('model_name', 'qwen3-max') + temperature = float(config.get('temperature', 0.7)) + max_tokens = int(config.get('max_output_tokens', 4096)) + + # 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, ) - - # Step 4: Store reasoning session - await self._store_reasoning_session(reasoning_session) - - result = { - "success": True, - "session_id": session_id, - "user_id": user_id, - "request": request, - "context_summary": planning_result["context_summary"], - "execution_plan": planning_result["execution_plan"], - "safety_violations": planning_result["safety_violations"], - "confidence_score": planning_result["confidence_score"], - "status": "planned" - } - - # Step 5: Execute if requested - if execute_immediately and not planning_result["safety_violations"]: - execution_result = await self._execute_plan( - session_id, planning_result["execution_plan"], context - ) - result.update({ - "execution_results": execution_result, - "status": "executed" - }) - elif planning_result["safety_violations"]: - result.update({ - "status": "blocked", - "message": "Execution blocked due to safety violations" - }) - - return result - - except Exception as e: + + # Fallback: direct config-based call (shouldn't happen in production) + error("llm_chat not available on ServerEnv") + return {'error': {'message': 'LLM client not available'}} + + async def _generate_plan(self, request: str, context: Dict[str, Any], + config: Dict[str, Any]) -> Dict[str, Any]: + """Use LLM to analyze request and generate execution plan.""" + # Build context summary + ctx_parts = [] + if context.get('memory_entries'): + for mem in context['memory_entries'][:5]: + ctx_parts.append(f"记忆({mem.get('target')}): {mem.get('content', '')[:200]}") + if context.get('recent_sessions'): + for sess in context['recent_sessions'][:3]: + ctx_parts.append(f"历史会话: {sess.get('title', '')[:100]}") + if context.get('skills'): + for sk in context['skills'][:3]: + ctx_parts.append(f"技能: {sk.get('name', '')} - {sk.get('description', '')[:100]}") + + ctx_text = "\n".join(ctx_parts) if ctx_parts else "无相关上下文" + + system_prompt = config.get('system_prompt', '') or REASONING_SYSTEM_PROMPT.format( + tool_descriptions=TOOL_DESCRIPTIONS, + tool_names=", ".join(TOOL_NAMES), + ) + + user_prompt = f"""用户请求:{request} + +相关上下文: +{ctx_text} + +请生成执行计划(JSON 格式)。""" + + messages = [ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_prompt}, + ] + + result = await self._llm_call(messages, config) + + if 'error' in result: + error(f"LLM planning failed: {result['error'].get('message')}") return { - "success": False, - "error": str(e), - "session_id": session_id, - "user_id": user_id, - "status": "failed" + 'analysis': 'LLM 调用失败,无法生成计划', + 'steps': [], + 'safety_notes': [], + 'llm_error': result['error'].get('message', ''), } - - async def _store_reasoning_session(self, session: ReasoningSession): - """Store reasoning session in database""" + + # Extract JSON from response + content = result.get('choices', [{}])[0].get('message', {}).get('content', '') + return self._parse_plan_json(content) + + 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 + json_match = re.search(r'```json\s*\n(.*?)\n```', text, re.DOTALL) + if json_match: + text = json_match.group(1) + else: + # Try to find first { ... } block + json_match = re.search(r'\{.*\}', text, re.DOTALL) + if json_match: + text = json_match.group(0) + try: - async with self.db.sqlorContext('default') as sor: - data = { - 'id': session.id, - 'user_id': session.user_id, - 'initial_request': session.initial_request, - 'context_summary': session.context_summary, - 'execution_plan_json': json.dumps(session.execution_plan), - 'reasoning_steps_json': json.dumps([{ - 'id': step.id, - 'step_type': step.step_type.value, - 'description': step.description, - 'confidence_score': step.confidence_score, - 'created_at': step.created_at.isoformat() - } for step in session.reasoning_steps]), - 'safety_violations_json': json.dumps(session.safety_violations), - 'final_decision_json': json.dumps(session.final_decision), - 'status': session.status, - 'created_at': session.created_at, - 'updated_at': session.updated_at - } - await sor.C('harnessed_reasoning_sessions', data) + plan = json.loads(text) + # Validate structure + if not isinstance(plan, dict): + return {'analysis': '', 'steps': [], 'safety_notes': []} + plan.setdefault('analysis', '') + plan.setdefault('steps', []) + plan.setdefault('safety_notes', []) + return plan + except json.JSONDecodeError: + error(f"Failed to parse LLM plan JSON: {text[:200]}") + return {'analysis': '无法解析 LLM 返回的计划', 'steps': [], 'safety_notes': []} + + # -------------------------------------------------------- + # Tool execution + # -------------------------------------------------------- + + async def _execute_tool(self, tool_name: str, parameters: Dict[str, Any], + context: Dict[str, Any]) -> Dict[str, Any]: + """Execute a tool via harnessed_agent's execute_tool.""" + env = ServerEnv() + if hasattr(env, 'harnessed_execute_tool'): + return await env.harnessed_execute_tool( + tool_name=tool_name, + parameters=parameters, + ) + + # Fallback: try to call the tool function directly if registered on ServerEnv + tool_func = getattr(env, tool_name, None) + if tool_func and callable(tool_func): + try: + return await tool_func(**parameters) + except Exception as e: + return {'success': False, 'error': str(e), 'tool': tool_name} + + return {'success': False, 'error': f'Tool {tool_name} not available', 'tool': tool_name} + + # -------------------------------------------------------- + # Main entry point + # -------------------------------------------------------- + + async def reason_and_execute(self, request: str, + execute_immediately: bool = True) -> Dict[str, Any]: + """ + Main entry: reason about a request and optionally execute the plan. + + 1. Gather real context (memory, sessions, skills) from harnessed_agent + 2. Use LLM to analyze request and generate execution plan + 3. Safety check the plan + 4. If execute_immediately and safe, execute step by step + 5. Store session and return result + """ + start_time = time.time() + session_id = str(uuid.uuid4()) + user_id = "anonymous" + + # Try to get real user ID + try: + env = ServerEnv() + if hasattr(env, 'harnessed_get_current_user'): + user_result = await env.harnessed_get_current_user() + user_id = user_result.get('user_id') or 'anonymous' except Exception: - # Silently fail - don't break main flow pass - - async def _execute_plan(self, session_id: str, execution_plan: List[Dict[str, Any]], - context: Dict[str, Any]) -> List[Dict[str, Any]]: - """Execute the reasoning plan step by step""" - results = [] - - for step in execution_plan: - step_results = [] - - for tool_action in step["tools"][:self.config.max_tool_calls_per_step]: - try: - # Execute each tool action - tool_result = await self._execute_tool_action( - tool_action["action"], - tool_action.get("parameters", {}), - context - ) - step_results.append(tool_result) - - # Check if we should continue based on result - if not tool_result.get("success") and self.config.enable_error_recovery: - recovery_result = await self._attempt_recovery( - tool_action, tool_result, context - ) - if recovery_result: - step_results.append(recovery_result) - - except Exception as e: - step_results.append({ - "success": False, - "error": str(e), - "action": tool_action["action"] - }) - - results.append({ - "step_description": step["description"], - "tool_results": step_results, - "safety_checks": step.get("safety_checks", []) - }) - - # Update session status - await self._update_session_status(session_id, "completed") - - return results - - async def _execute_tool_action(self, action: str, parameters: Dict[str, Any], - context: Dict[str, Any]) -> Dict[str, Any]: - """Execute a single tool action through harnessed_agent""" - # This would integrate with actual harnessed_agent execution functions - # For now, simulate successful execution - return { - "success": True, - "action": action, - "parameters": parameters, - "result": f"Executed {action} successfully", - "timestamp": datetime.now().isoformat() + + config = await self._get_config() + safety_mode = config.get('safety_mode', 'strict') + + result = { + "success": False, + "session_id": session_id, + "user_id": user_id, + "request": request, + "status": "failed", } - - async def _attempt_recovery(self, failed_action: Dict[str, Any], - error_result: Dict[str, Any], - context: Dict[str, Any]) -> Optional[Dict[str, Any]]: - """Attempt to recover from a failed tool execution""" - if not self.config.enable_error_recovery: - return None - - # Simple recovery strategies - action = failed_action["action"] - parameters = failed_action.get("parameters", {}) - - if action == "read_file" and "not found" in str(error_result.get("error", "")).lower(): - # Try to find similar files - original_path = parameters.get("path", "") - if original_path: - search_pattern = original_path.split("/")[-1] - return await self._execute_tool_action( - "search_files", - {"pattern": search_pattern, "target": "files"}, - context + + try: + # Step 1: Gather real context + info(f"Reasoning start: user={user_id}, request={request[:80]}...") + context = await self._get_memory_context(user_id, request, config) + + # Step 2: LLM-based planning + plan = await self._generate_plan(request, context, config) + + elapsed_plan = time.time() - start_time + info(f"Plan generated in {elapsed_plan:.1f}s: {len(plan.get('steps', []))} steps") + + # Step 3: Safety check + violations = self._safety_check(plan, safety_mode) + if violations: + warning(f"Safety violations: {violations}") + + # Step 4: Store session + await self._store_session(session_id, user_id, request, plan, violations, "planned") + + # Build result + result.update({ + "success": True, + "analysis": plan.get("analysis", ""), + "execution_plan": plan.get("steps", []), + "safety_violations": violations, + "safety_notes": plan.get("safety_notes", []), + "context_summary": self._context_summary(context), + "confidence_score": self._calc_confidence(plan, context, violations), + "status": "planned", + "llm_error": plan.get("llm_error", ""), + }) + + # Step 5: Execute if requested and safe + if execute_immediately and not violations and plan.get('steps'): + exec_results = await self._execute_plan( + session_id, plan['steps'], config, context ) - - elif action == "terminal" and "permission denied" in str(error_result.get("error", "")).lower(): - # Try without sudo or with different approach - original_command = parameters.get("command", "") - if original_command.startswith("sudo "): - return await self._execute_tool_action( - "terminal", - {"command": original_command.replace("sudo ", "", 1)}, - context - ) - + result.update({ + "execution_results": exec_results, + "status": "executed", + }) + await self._update_session_status(session_id, "executed") + elif violations: + result["status"] = "blocked" + result["message"] = f"执行已阻止:{', '.join(violations)}" + + elapsed_total = time.time() - start_time + info(f"Reasoning complete in {elapsed_total:.1f}s, status={result['status']}") + + except Exception as e: + exception(f"Reasoning failed: {e}") + result["success"] = False + result["error"] = str(e) + result["status"] = "failed" + + try: + await self._update_session_status(session_id, "failed") + except Exception: + pass + + return result + + def _context_summary(self, context: Dict[str, Any]) -> str: + parts = [] + if context.get('memory_entries'): + parts.append(f"记忆条目: {len(context['memory_entries'])}") + if context.get('recent_sessions'): + parts.append(f"相关会话: {len(context['recent_sessions'])}") + if context.get('skills'): + names = [s.get('name', '?') for s in context['skills'][:3]] + parts.append(f"相关技能: {', '.join(names)}") + return "; ".join(parts) if parts else "无相关上下文" + + def _calc_confidence(self, plan: Dict, context: Dict, violations: List) -> float: + base = 0.7 + if context.get('memory_entries') or context.get('skills'): + base += 0.1 + if plan.get('llm_error'): + base -= 0.3 + if len(plan.get('steps', [])) == 0: + base -= 0.2 + base -= len(violations) * 0.1 + return max(0.0, min(1.0, base)) + + # -------------------------------------------------------- + # Plan execution + # -------------------------------------------------------- + + async def _execute_plan(self, session_id: str, steps: List[Dict], + config: Dict, context: Dict) -> List[Dict]: + """Execute plan steps sequentially, each with its tool actions.""" + all_results = [] + max_steps = int(config.get('max_reasoning_steps', 10)) + max_tools = int(config.get('max_tool_calls_per_step', 5)) + + for step in steps[:max_steps]: + step_num = step.get('step_number', '?') + step_results = [] + + for action in step.get('actions', [])[:max_tools]: + tool = action.get('tool', '') + params = action.get('parameters', {}) + + if not tool: + continue + + info(f"Executing step {step_num}: {tool}({json.dumps(params, ensure_ascii=False)[:100]})") + tool_result = await self._execute_tool(tool, params, context) + + step_results.append({ + 'tool': tool, + 'parameters': params, + 'success': tool_result.get('success', False), + 'result': str(tool_result)[:500], # Truncate for storage + }) + + # Error recovery + if not tool_result.get('success'): + error_msg = str(tool_result.get('error', '')) + if config.get('enable_error_recovery', '1') == '1' and error_msg: + recovery = await self._try_recovery(tool, params, error_msg, context) + if recovery: + step_results.append(recovery) + + all_results.append({ + 'step_number': step_num, + 'description': step.get('description', ''), + 'actions': step_results, + }) + + return all_results + + async def _try_recovery(self, tool: str, params: Dict, error: str, + context: Dict) -> Optional[Dict]: + """Simple recovery strategies for common failures.""" + error_lower = error.lower() + + if tool == 'read_file' and 'not found' in error_lower: + # Try search_files instead + path = params.get('path', '') + if path: + pattern = path.split('/')[-1] + return await self._execute_tool('search_files', { + 'pattern': pattern, + 'target': 'files', + }, context) + + if tool == 'terminal' and 'permission' in error_lower: + # Strip sudo prefix + cmd = params.get('command', '') + if cmd.startswith('sudo '): + return await self._execute_tool('terminal', { + 'command': cmd[5:], + }, context) + return None - + + # -------------------------------------------------------- + # Session persistence + # -------------------------------------------------------- + + async def _store_session(self, session_id: str, user_id: str, request: str, + plan: Dict, violations: List, status: str): + """Store reasoning session in database.""" + try: + data = { + 'id': session_id, + 'user_id': user_id, + 'initial_request': request, + 'context_summary': '', + 'execution_plan_json': json.dumps(plan, ensure_ascii=False), + 'reasoning_steps_json': '[]', + 'safety_violations_json': json.dumps(violations, ensure_ascii=False), + 'final_decision_json': json.dumps({'status': status}), + 'status': status, + 'created_at': datetime.now(), + 'updated_at': datetime.now(), + } + async with self.db.sqlorContext('default') as sor: + await sor.C('harnessed_reasoning_sessions', data) + except Exception as e: + warning(f"Failed to store session: {e}") + async def _update_session_status(self, session_id: str, status: str): - """Update reasoning session status""" + """Update session status.""" try: async with self.db.sqlorContext('default') as sor: await sor.U('harnessed_reasoning_sessions', { 'id': session_id, 'status': status, - 'updated_at': datetime.now() + 'updated_at': datetime.now(), }) except Exception: pass - - async def get_reasoning_session(self, session_id: str, - context: Dict[str, Any] = None) -> Dict[str, Any]: - """Retrieve a reasoning session by ID""" - user_id = self._get_current_user_id(context) if context else None - + + # -------------------------------------------------------- + # Session retrieval + # -------------------------------------------------------- + + async def get_reasoning_session(self, session_id: str, + context: Dict[str, Any] = None) -> Dict[str, Any]: + user_id = None + try: + user_id = context.get('user_id') if context else None + except Exception: + pass + try: async with self.db.sqlorContext('default') as sor: filters = {'id': session_id} if user_id: filters['user_id'] = user_id - - sessions = await sor.R('harnessed_reasoning_sessions', filters) - if sessions: - session = sessions[0] - return { - "success": True, - "session": { - "id": session["id"], - "user_id": session["user_id"], - "initial_request": session["initial_request"], - "context_summary": session["context_summary"], - "execution_plan": json.loads(session["execution_plan_json"]), - "safety_violations": json.loads(session["safety_violations_json"]), - "status": session["status"], - "created_at": session["created_at"], - "updated_at": session["updated_at"] - } - } - else: + + rows = await sor.R('harnessed_reasoning_sessions', filters) + if not rows: return {"success": False, "error": "Session not found"} - except Exception as e: - return {"success": False, "error": str(e)} - - async def list_reasoning_sessions(self, context: Dict[str, Any] = None, - limit: int = 50, offset: int = 0) -> Dict[str, Any]: - """List reasoning sessions for current user""" - user_id = self._get_current_user_id(context) if context else "anonymous" - - try: - async with self.db.sqlorContext('default') as sor: - sessions = await sor.R('harnessed_reasoning_sessions', { - 'user_id': user_id - }, orderby='created_at DESC', limit=limit, offset=offset) - - simplified_sessions = [] - for session in sessions: - simplified_sessions.append({ - "id": session["id"], - "request_preview": session["initial_request"][:100] + "..." if len(session["initial_request"]) > 100 else session["initial_request"], - "status": session["status"], - "confidence": json.loads(session["final_decision_json"]).get("confidence", 0), - "created_at": session["created_at"] - }) - + + s = rows[0] return { "success": True, - "sessions": simplified_sessions, - "total_count": len(simplified_sessions), - "user_id": user_id + "session": { + "id": s["id"], + "user_id": s["user_id"], + "initial_request": s["initial_request"], + "execution_plan": json.loads(s.get("execution_plan_json", "[]")), + "safety_violations": json.loads(s.get("safety_violations_json", "[]")), + "status": s["status"], + "created_at": str(s.get("created_at", "")), + "updated_at": str(s.get("updated_at", "")), + } } except Exception as e: return {"success": False, "error": str(e)} -# Global instance for module functions + async def list_reasoning_sessions(self, context: Dict[str, Any] = None, + limit: int = 50, offset: int = 0) -> Dict[str, Any]: + user_id = "anonymous" + try: + if context: + user_id = context.get('user_id') or 'anonymous' + except Exception: + pass + + try: + async with self.db.sqlorContext('default') as sor: + rows = await sor.R('harnessed_reasoning_sessions', + {'user_id': user_id}, + orderby='created_at DESC', limit=limit, offset=offset) + + sessions = [] + for s in rows: + plan = json.loads(s.get("execution_plan_json", "[]")) + steps = plan.get("steps", []) if isinstance(plan, dict) else [] + sessions.append({ + "id": s["id"], + "request_preview": s["initial_request"][:80] + ("..." if len(s.get("initial_request", "")) > 80 else ""), + "status": s["status"], + "step_count": len(steps), + "created_at": str(s.get("created_at", "")), + }) + + return {"success": True, "sessions": sessions, "total_count": len(sessions)} + except Exception as e: + return {"success": False, "error": str(e)} + + async def get_config(self) -> Dict[str, Any]: + config = await self._get_config() + return { + "max_reasoning_steps": config.get("max_reasoning_steps", 10), + "max_tool_calls_per_step": config.get("max_tool_calls_per_step", 5), + "enable_cross_session_search": config.get("enable_cross_session_search", "1") == "1", + "enable_skill_auto_loading": config.get("enable_skill_auto_loading", "1") == "1", + "safety_mode": config.get("safety_mode", "strict"), + "max_context_tokens": config.get("max_context_tokens", 4000), + "enable_error_recovery": config.get("enable_error_recovery", "1") == "1", + "max_recovery_attempts": config.get("max_recovery_attempts", 3), + "model_name": config.get("model_name", "qwen3-max"), + "temperature": config.get("temperature", 0.7), + "top_p": config.get("top_p", 0.9), + } + + +# ============================================================ +# Module-level exports (registered to ServerEnv) +# ============================================================ + _reasoning_instance = None def get_harnessed_reasoning_engine(): - """Get or create the global Hermes reasoning engine instance""" global _reasoning_instance if _reasoning_instance is None: _reasoning_instance = HermesReasoningEngine() return _reasoning_instance -# Exposed async functions for frontend integration + async def hermes_reason_and_execute(request: str, execute_immediately: bool = True): - """Perform reasoning and optionally execute the plan""" + """Perform reasoning and optionally execute the plan.""" engine = get_harnessed_reasoning_engine() return await engine.reason_and_execute(request, execute_immediately=execute_immediately) + async def hermes_get_reasoning_session(session_id: str): - """Retrieve a reasoning session by ID""" engine = get_harnessed_reasoning_engine() return await engine.get_reasoning_session(session_id) + async def hermes_list_reasoning_sessions(limit: int = 50, offset: int = 0): - """List reasoning sessions for current user""" engine = get_harnessed_reasoning_engine() return await engine.list_reasoning_sessions(limit=limit, offset=offset) + async def hermes_get_reasoning_config(): - """Get Hermes reasoning configuration""" engine = get_harnessed_reasoning_engine() - return { - "max_reasoning_steps": engine.config.max_reasoning_steps, - "max_tool_calls_per_step": engine.config.max_tool_calls_per_step, - "enable_cross_session_search": engine.config.enable_cross_session_search, - "enable_skill_auto_loading": engine.config.enable_skill_auto_loading, - "safety_mode": engine.config.safety_mode, - "max_context_tokens": engine.config.max_context_tokens, - "enable_error_recovery": engine.config.enable_error_recovery, - "max_recovery_attempts": engine.config.max_recovery_attempts - } \ No newline at end of file + return await engine.get_config() diff --git a/wwwroot/reasoning_console.ui b/wwwroot/reasoning_console.ui index 3059b7e..2d4630e 100644 --- a/wwwroot/reasoning_console.ui +++ b/wwwroot/reasoning_console.ui @@ -27,7 +27,7 @@ { "name": "request", "label": "推理请求", - "uitype": "textarea", + "uitype": "text", "required": true, "rows": 4, "placeholder": "请输入您的请求,例如:分析当前项目的安全问题并生成修复方案"