""" LocalAgent - Windows 本地 AI 执行助手 (MVP) ======================================== 配置说明 ======================================== 1. 复制 .env.example 为 .env 2. 在 .env 中填入你的 SiliconFlow API Key: LLM_API_KEY=sk-xxxxx ======================================== 运行方式 ======================================== 方式一:使用 Anaconda conda create -n localagent python=3.10 conda activate localagent pip install -r requirements.txt python main.py 方式二:直接运行(需已安装依赖) python main.py ======================================== 测试方法 ======================================== 1. 对话测试:输入 "今天天气怎么样" → 应识别为 chat 2. 执行测试: - 将测试文件放入 workspace/input 目录 - 输入 "把这些文件复制一份" → 应识别为 execution - 确认执行后,检查 workspace/output 目录 ======================================== """ import os import sys import tkinter as tk from tkinter import messagebox from pathlib import Path from typing import Optional from dotenv import load_dotenv import threading import queue # 确保项目根目录在 Python 路径中 PROJECT_ROOT = Path(__file__).parent ENV_PATH = PROJECT_ROOT / ".env" sys.path.insert(0, str(PROJECT_ROOT)) # 在导入其他模块之前先加载环境变量 load_dotenv(ENV_PATH) from llm.client import get_client, LLMClientError from llm.prompts import ( EXECUTION_PLAN_SYSTEM, EXECUTION_PLAN_USER, CODE_GENERATION_SYSTEM, CODE_GENERATION_USER ) from intent.classifier import classify_intent, IntentResult from intent.labels import CHAT, EXECUTION from safety.rule_checker import check_code_safety from safety.llm_reviewer import review_code_safety from executor.sandbox_runner import SandboxRunner, ExecutionResult from ui.chat_view import ChatView from ui.task_guide_view import TaskGuideView class LocalAgentApp: """ LocalAgent 主应用 职责: 1. 管理 UI 状态切换 2. 协调各模块工作流程 3. 处理用户交互 """ def __init__(self): self.workspace = PROJECT_ROOT / "workspace" self.runner = SandboxRunner(str(self.workspace)) # 当前任务状态 self.current_task: Optional[dict] = None # 线程通信队列 self.result_queue = queue.Queue() # 初始化 UI self._init_ui() def _init_ui(self): """初始化 UI""" self.root = tk.Tk() self.root.title("LocalAgent - 本地 AI 助手") self.root.geometry("800x700") self.root.configure(bg='#1e1e1e') # 设置窗口图标(如果有的话) try: self.root.iconbitmap(PROJECT_ROOT / "icon.ico") except: pass # 主容器 self.main_container = tk.Frame(self.root, bg='#1e1e1e') self.main_container.pack(fill=tk.BOTH, expand=True) # 聊天视图 self.chat_view = ChatView(self.main_container, self._on_user_input) # 任务引导视图(初始隐藏) self.task_view: Optional[TaskGuideView] = None # 定期检查后台任务结果 self._check_queue() def _check_queue(self): """检查后台任务队列""" try: while True: callback, args = self.result_queue.get_nowait() callback(*args) except queue.Empty: pass # 每 100ms 检查一次 self.root.after(100, self._check_queue) def _run_in_thread(self, func, callback, *args): """在后台线程运行函数,完成后回调""" def wrapper(): try: result = func(*args) self.result_queue.put((callback, (result, None))) except Exception as e: self.result_queue.put((callback, (None, e))) thread = threading.Thread(target=wrapper, daemon=True) thread.start() def _on_user_input(self, user_input: str): """处理用户输入""" # 显示用户消息 self.chat_view.add_message(user_input, 'user') self.chat_view.set_input_enabled(False) self.chat_view.add_message("正在分析您的需求...", 'system') # 在后台线程进行意图识别 self._run_in_thread( classify_intent, lambda result, error: self._on_intent_result(user_input, result, error), user_input ) def _on_intent_result(self, user_input: str, intent_result: Optional[IntentResult], error: Optional[Exception]): """意图识别完成回调""" if error: self.chat_view.add_message(f"意图识别失败: {str(error)}", 'error') self.chat_view.set_input_enabled(True) return if intent_result.label == CHAT: # 对话模式 self._handle_chat(user_input, intent_result) else: # 执行模式 self._handle_execution(user_input, intent_result) def _handle_chat(self, user_input: str, intent_result: IntentResult): """处理对话任务""" self.chat_view.add_message( f"识别为对话模式 (原因: {intent_result.reason})", 'system' ) self.chat_view.add_message("正在生成回复...", 'system') # 在后台线程调用 LLM def do_chat(): client = get_client() model = os.getenv("GENERATION_MODEL_NAME") return client.chat( messages=[{"role": "user", "content": user_input}], model=model, temperature=0.7, max_tokens=2048 ) self._run_in_thread( do_chat, self._on_chat_result ) def _on_chat_result(self, response: Optional[str], error: Optional[Exception]): """对话完成回调""" if error: self.chat_view.add_message(f"对话失败: {str(error)}", 'error') else: self.chat_view.add_message(response, 'assistant') self.chat_view.set_input_enabled(True) def _handle_execution(self, user_input: str, intent_result: IntentResult): """处理执行任务""" self.chat_view.add_message( f"识别为执行任务 (置信度: {intent_result.confidence:.0%})\n原因: {intent_result.reason}", 'system' ) self.chat_view.add_message("正在生成执行计划...", 'system') # 保存用户输入和意图结果 self.current_task = { 'user_input': user_input, 'intent_result': intent_result } # 在后台线程生成执行计划 self._run_in_thread( self._generate_execution_plan, self._on_plan_generated, user_input ) def _on_plan_generated(self, plan: Optional[str], error: Optional[Exception]): """执行计划生成完成回调""" if error: self.chat_view.add_message(f"生成执行计划失败: {str(error)}", 'error') self.chat_view.set_input_enabled(True) self.current_task = None return self.current_task['execution_plan'] = plan self.chat_view.add_message("正在生成执行代码...", 'system') # 在后台线程生成代码 self._run_in_thread( self._generate_code, self._on_code_generated, self.current_task['user_input'], plan ) def _on_code_generated(self, code: Optional[str], error: Optional[Exception]): """代码生成完成回调""" if error: self.chat_view.add_message(f"生成代码失败: {str(error)}", 'error') self.chat_view.set_input_enabled(True) self.current_task = None return self.current_task['code'] = code self.chat_view.add_message("正在进行安全检查...", 'system') # 硬规则检查(同步,很快) rule_result = check_code_safety(code) if not rule_result.passed: violations = "\n".join(f" • {v}" for v in rule_result.violations) self.chat_view.add_message( f"安全检查未通过,任务已取消:\n{violations}", 'error' ) self.chat_view.set_input_enabled(True) self.current_task = None return # 在后台线程进行 LLM 安全审查 self._run_in_thread( review_code_safety, self._on_safety_reviewed, self.current_task['user_input'], self.current_task['execution_plan'], code ) def _on_safety_reviewed(self, review_result, error: Optional[Exception]): """安全审查完成回调""" if error: self.chat_view.add_message(f"安全审查失败: {str(error)}", 'error') self.chat_view.set_input_enabled(True) self.current_task = None return if not review_result.passed: self.chat_view.add_message( f"安全审查未通过: {review_result.reason}", 'error' ) self.chat_view.set_input_enabled(True) self.current_task = None return self.chat_view.add_message("安全检查通过,请确认执行", 'system') # 显示任务引导视图 self._show_task_guide() def _generate_execution_plan(self, user_input: str) -> str: """生成执行计划""" client = get_client() model = os.getenv("GENERATION_MODEL_NAME") response = client.chat( messages=[ {"role": "system", "content": EXECUTION_PLAN_SYSTEM}, {"role": "user", "content": EXECUTION_PLAN_USER.format(user_input=user_input)} ], model=model, temperature=0.3, max_tokens=1024 ) return response def _generate_code(self, user_input: str, execution_plan: str) -> str: """生成执行代码""" client = get_client() model = os.getenv("GENERATION_MODEL_NAME") response = client.chat( messages=[ {"role": "system", "content": CODE_GENERATION_SYSTEM}, {"role": "user", "content": CODE_GENERATION_USER.format( user_input=user_input, execution_plan=execution_plan )} ], model=model, temperature=0.2, max_tokens=2048 ) # 提取代码块 code = self._extract_code(response) return code def _extract_code(self, response: str) -> str: """从 LLM 响应中提取代码""" import re # 尝试提取 ```python ... ``` 代码块 pattern = r'```python\s*(.*?)\s*```' matches = re.findall(pattern, response, re.DOTALL) if matches: return matches[0] # 尝试提取 ``` ... ``` 代码块 pattern = r'```\s*(.*?)\s*```' matches = re.findall(pattern, response, re.DOTALL) if matches: return matches[0] # 如果没有代码块,返回原始响应 return response def _show_task_guide(self): """显示任务引导视图""" if not self.current_task: return # 隐藏聊天视图 self.chat_view.get_frame().pack_forget() # 创建任务引导视图 self.task_view = TaskGuideView( self.main_container, on_execute=self._on_execute_task, on_cancel=self._on_cancel_task, workspace_path=self.workspace ) # 设置内容 self.task_view.set_intent_result( self.current_task['intent_result'].reason, self.current_task['intent_result'].confidence ) self.task_view.set_execution_plan(self.current_task['execution_plan']) # 显示 self.task_view.show() def _on_execute_task(self): """执行任务""" if not self.current_task: return self.task_view.set_buttons_enabled(False) # 在后台线程执行 def do_execute(): return self.runner.execute(self.current_task['code']) self._run_in_thread( do_execute, self._on_execution_complete ) def _on_execution_complete(self, result: Optional[ExecutionResult], error: Optional[Exception]): """执行完成回调""" if error: messagebox.showerror("执行错误", f"执行失败: {str(error)}") else: self._show_execution_result(result) # 刷新输出文件列表 if self.task_view: self.task_view.refresh_output() self._back_to_chat() def _show_execution_result(self, result: ExecutionResult): """显示执行结果""" if result.success: status = "执行成功" else: status = "执行失败" message = f"""{status} 任务 ID: {result.task_id} 耗时: {result.duration_ms} ms 日志文件: {result.log_path} 输出: {result.stdout if result.stdout else '(无输出)'} {f'错误信息: {result.stderr}' if result.stderr else ''} """ if result.success: messagebox.showinfo("执行结果", message) # 打开 output 目录 os.startfile(str(self.workspace / "output")) else: messagebox.showerror("执行结果", message) def _on_cancel_task(self): """取消任务""" self.current_task = None self._back_to_chat() def _back_to_chat(self): """返回聊天视图""" if self.task_view: self.task_view.hide() self.task_view = None self.chat_view.get_frame().pack(fill=tk.BOTH, expand=True, padx=10, pady=10) self.chat_view.set_input_enabled(True) self.current_task = None def run(self): """运行应用""" self.root.mainloop() def check_environment(): """检查运行环境""" load_dotenv(ENV_PATH) api_key = os.getenv("LLM_API_KEY") if not api_key or api_key == "your_api_key_here": print("=" * 50) print("错误: 未配置 LLM API Key") print("=" * 50) print() print("请按以下步骤配置:") print("1. 复制 .env.example 为 .env") print("2. 在 .env 中设置 LLM_API_KEY=你的API密钥") print() print("获取 API Key: https://siliconflow.cn") print("=" * 50) # 显示 GUI 错误提示 root = tk.Tk() root.withdraw() messagebox.showerror( "配置错误", "未配置 LLM API Key\n\n" "请按以下步骤配置:\n" "1. 复制 .env.example 为 .env\n" "2. 在 .env 中设置 LLM_API_KEY=你的API密钥\n\n" "获取 API Key: https://siliconflow.cn" ) root.destroy() return False return True def main(): """主入口""" print("=" * 50) print("LocalAgent - Windows 本地 AI 执行助手") print("=" * 50) # 检查环境 if not check_environment(): sys.exit(1) # 创建工作目录 workspace = PROJECT_ROOT / "workspace" (workspace / "input").mkdir(parents=True, exist_ok=True) (workspace / "output").mkdir(parents=True, exist_ok=True) (workspace / "logs").mkdir(parents=True, exist_ok=True) print(f"工作目录: {workspace}") print(f"输入目录: {workspace / 'input'}") print(f"输出目录: {workspace / 'output'}") print(f"日志目录: {workspace / 'logs'}") print("=" * 50) # 启动应用 app = LocalAgentApp() app.run() if __name__ == "__main__": main()