feat: enhance LocalAgent configuration and UI components

- Updated .env.example to provide clearer configuration instructions and API key setup.
- Removed debug_env.py as it was no longer needed.
- Refactored main.py to streamline application initialization and workspace setup.
- Introduced a new HistoryManager for managing task execution history.
- Enhanced UI components in chat_view.py and task_guide_view.py to improve user interaction and code preview functionality.
- Added loading indicators and improved task history display in the UI.
- Implemented unit tests for history management and intent classification.
This commit is contained in:
Mimikko-zeus
2026-01-07 10:29:13 +08:00
parent 1ba5f0f7d6
commit 0a92355bfb
18 changed files with 2144 additions and 557 deletions

View File

@@ -2,13 +2,15 @@
LLM 统一调用客户端
所有模型通过 SiliconFlow API 调用
支持流式和非流式两种模式
支持自动重试机制
"""
import os
import json
import time
import requests
from pathlib import Path
from typing import Optional, Generator, Callable
from typing import Optional, Generator, Callable, List, Dict, Any
from dotenv import load_dotenv
# 获取项目根目录
@@ -40,29 +42,78 @@ class LLMClient:
model="Qwen/Qwen2.5-7B-Instruct"
):
print(chunk, end="", flush=True)
特性:
- 自动重试网络错误时自动重试默认3次
- 指数退避:重试间隔逐渐增加
"""
def __init__(self):
# 重试配置
DEFAULT_MAX_RETRIES = 3
DEFAULT_RETRY_DELAY = 1.0 # 初始重试延迟(秒)
DEFAULT_RETRY_BACKOFF = 2.0 # 退避倍数
def __init__(self, max_retries: int = DEFAULT_MAX_RETRIES):
load_dotenv(ENV_PATH)
self.api_url = os.getenv("LLM_API_URL")
self.api_key = os.getenv("LLM_API_KEY")
self.max_retries = max_retries
if not self.api_url:
raise LLMClientError("未配置 LLM_API_URL请检查 .env 文件")
if not self.api_key or self.api_key == "your_api_key_here":
raise LLMClientError("未配置有效的 LLM_API_KEY请检查 .env 文件")
def _should_retry(self, exception: Exception) -> bool:
"""判断是否应该重试"""
# 网络连接错误、超时错误可以重试
if isinstance(exception, (requests.exceptions.ConnectionError,
requests.exceptions.Timeout)):
return True
# 服务器错误5xx可以重试
if isinstance(exception, LLMClientError):
error_msg = str(exception)
if "状态码: 5" in error_msg or "502" in error_msg or "503" in error_msg or "504" in error_msg:
return True
return False
def _do_request_with_retry(
self,
request_func: Callable,
operation_name: str = "请求"
):
"""带重试的请求执行"""
last_exception = None
for attempt in range(self.max_retries + 1):
try:
return request_func()
except Exception as e:
last_exception = e
# 判断是否应该重试
if attempt < self.max_retries and self._should_retry(e):
delay = self.DEFAULT_RETRY_DELAY * (self.DEFAULT_RETRY_BACKOFF ** attempt)
print(f"[重试] {operation_name}失败,{delay:.1f}秒后重试 ({attempt + 1}/{self.max_retries})...")
time.sleep(delay)
continue
else:
raise
# 所有重试都失败
raise last_exception
def chat(
self,
messages: list[dict],
messages: List[Dict[str, str]],
model: str,
temperature: float = 0.7,
max_tokens: int = 1024,
timeout: int = 180
) -> str:
"""
调用 LLM 进行对话(非流式)
调用 LLM 进行对话(非流式,带自动重试
Args:
messages: 消息列表
@@ -74,60 +125,63 @@ class LLMClient:
Returns:
LLM 生成的文本内容
"""
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
payload = {
"model": model,
"messages": messages,
"stream": False,
"temperature": temperature,
"max_tokens": max_tokens
}
try:
response = requests.post(
self.api_url,
headers=headers,
json=payload,
timeout=timeout
)
except requests.exceptions.Timeout:
raise LLMClientError(f"请求超时({timeout}秒),请检查网络连接或稍后重试")
except requests.exceptions.ConnectionError:
raise LLMClientError("网络连接失败,请检查网络设置")
except requests.exceptions.RequestException as e:
raise LLMClientError(f"网络请求异常: {str(e)}")
if response.status_code != 200:
error_msg = f"API 返回错误 (状态码: {response.status_code})"
def do_request():
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
payload = {
"model": model,
"messages": messages,
"stream": False,
"temperature": temperature,
"max_tokens": max_tokens
}
try:
error_detail = response.json()
if "error" in error_detail:
error_msg += f": {error_detail['error']}"
except:
error_msg += f": {response.text[:200]}"
raise LLMClientError(error_msg)
response = requests.post(
self.api_url,
headers=headers,
json=payload,
timeout=timeout
)
except requests.exceptions.Timeout:
raise LLMClientError(f"请求超时({timeout}秒),请检查网络连接或稍后重试")
except requests.exceptions.ConnectionError:
raise LLMClientError("网络连接失败,请检查网络设置")
except requests.exceptions.RequestException as e:
raise LLMClientError(f"网络请求异常: {str(e)}")
if response.status_code != 200:
error_msg = f"API 返回错误 (状态码: {response.status_code})"
try:
error_detail = response.json()
if "error" in error_detail:
error_msg += f": {error_detail['error']}"
except:
error_msg += f": {response.text[:200]}"
raise LLMClientError(error_msg)
try:
result = response.json()
content = result["choices"][0]["message"]["content"]
return content
except (KeyError, IndexError, TypeError) as e:
raise LLMClientError(f"解析 API 响应失败: {str(e)}")
try:
result = response.json()
content = result["choices"][0]["message"]["content"]
return content
except (KeyError, IndexError, TypeError) as e:
raise LLMClientError(f"解析 API 响应失败: {str(e)}")
return self._do_request_with_retry(do_request, "LLM调用")
def chat_stream(
self,
messages: list[dict],
messages: List[Dict[str, str]],
model: str,
temperature: float = 0.7,
max_tokens: int = 2048,
timeout: int = 180
) -> Generator[str, None, None]:
"""
调用 LLM 进行对话(流式)
调用 LLM 进行对话(流式,带自动重试
Args:
messages: 消息列表
@@ -139,43 +193,49 @@ class LLMClient:
Yields:
逐个返回生成的文本片段
"""
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
payload = {
"model": model,
"messages": messages,
"stream": True,
"temperature": temperature,
"max_tokens": max_tokens
}
try:
response = requests.post(
self.api_url,
headers=headers,
json=payload,
timeout=timeout,
stream=True
)
except requests.exceptions.Timeout:
raise LLMClientError(f"请求超时({timeout}秒),请检查网络连接或稍后重试")
except requests.exceptions.ConnectionError:
raise LLMClientError("网络连接失败,请检查网络设置")
except requests.exceptions.RequestException as e:
raise LLMClientError(f"网络请求异常: {str(e)}")
if response.status_code != 200:
error_msg = f"API 返回错误 (状态码: {response.status_code})"
def do_request():
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
payload = {
"model": model,
"messages": messages,
"stream": True,
"temperature": temperature,
"max_tokens": max_tokens
}
try:
error_detail = response.json()
if "error" in error_detail:
error_msg += f": {error_detail['error']}"
except:
error_msg += f": {response.text[:200]}"
raise LLMClientError(error_msg)
response = requests.post(
self.api_url,
headers=headers,
json=payload,
timeout=timeout,
stream=True
)
except requests.exceptions.Timeout:
raise LLMClientError(f"请求超时({timeout}秒),请检查网络连接或稍后重试")
except requests.exceptions.ConnectionError:
raise LLMClientError("网络连接失败,请检查网络设置")
except requests.exceptions.RequestException as e:
raise LLMClientError(f"网络请求异常: {str(e)}")
if response.status_code != 200:
error_msg = f"API 返回错误 (状态码: {response.status_code})"
try:
error_detail = response.json()
if "error" in error_detail:
error_msg += f": {error_detail['error']}"
except:
error_msg += f": {response.text[:200]}"
raise LLMClientError(error_msg)
return response
# 流式请求的重试只在建立连接阶段
response = self._do_request_with_retry(do_request, "流式LLM调用")
# 解析 SSE 流
for line in response.iter_lines():
@@ -197,7 +257,7 @@ class LLMClient:
def chat_stream_collect(
self,
messages: list[dict],
messages: List[Dict[str, str]],
model: str,
temperature: float = 0.7,
max_tokens: int = 2048,

View File

@@ -108,7 +108,8 @@ import shutil
from pathlib import Path
# 工作目录(固定,不要修改)
WORKSPACE = Path(__file__).parent
# 代码保存在 workspace/codes/ 目录,向上一级是 workspace
WORKSPACE = Path(__file__).parent.parent
INPUT_DIR = WORKSPACE / "input"
OUTPUT_DIR = WORKSPACE / "output"