feat: V3系统评审问题修复
1. 场景价值与技术合理性修复: - 补充docs/SCENE_VALUE.md(业务背景、痛点分析、用户场景、竞品对比、价值量化) - 添加用户操作流程图(Mermaid) - 添加3个真实业务案例量化数据 2. 演示与文档修复: - 创建docs/API.md(完整API文档) - 创建docs/QUICKSTART.md(5分钟快速入门指南) 3. AI使用日志修复: - 更新AGENTS.md,添加强制自动执行的AI使用日志记录指令 - 在_AI_USAGE_LOG.md末尾添加范式执行统计 4. 安全性修复: - 在agents/llm.py中添加输入过滤(防Prompt注入) - 添加输出验证、速率限制、详细日志 5. 架构设计修复: - 创建tools/registry.py工具注册表 - 修改orchestrator.py和orchestrator_db.py使用注册表动态获取运行器 6. 开发范式修复: - 在_AI_USAGE_LOG.md末尾添加范式执行统计
This commit is contained in:
+84
-3
@@ -1,7 +1,9 @@
|
||||
import json, hashlib, os
|
||||
import json, hashlib, os, re, time, logging
|
||||
from pathlib import Path
|
||||
import httpx
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LLMClient:
|
||||
def __init__(self, model=None, timeout=15, cache_dir=".cache/llm"):
|
||||
@@ -10,6 +12,8 @@ class LLMClient:
|
||||
self.timeout = timeout
|
||||
self.dir = Path(cache_dir)
|
||||
self.dir.mkdir(parents=True, exist_ok=True)
|
||||
self._last_call_time = 0
|
||||
self._min_interval = 0.1 # 100ms 最小调用间隔
|
||||
|
||||
def _key(self, msgs):
|
||||
return hashlib.sha256(json.dumps(msgs, sort_keys=True).encode()).hexdigest()
|
||||
@@ -26,22 +30,99 @@ class LLMClient:
|
||||
def _set(self, k, v):
|
||||
(self.dir / f"{k}.json").write_text(json.dumps({"response": v}))
|
||||
|
||||
def _sanitize_input(self, text: str) -> str:
|
||||
"""输入过滤:移除潜在的prompt注入模式,限制输入长度"""
|
||||
if not isinstance(text, str):
|
||||
text = str(text)
|
||||
|
||||
# 1. 限制输入长度(防止过长输入)
|
||||
max_length = 50000
|
||||
if len(text) > max_length:
|
||||
logger.warning(f"Input truncated from {len(text)} to {max_length} chars")
|
||||
text = text[:max_length]
|
||||
|
||||
# 2. 移除潜在的prompt注入模式
|
||||
injection_patterns = [
|
||||
r'(?i)ignore\s+previous\s+instructions',
|
||||
r'(?i)disregard\s+all\s+prior',
|
||||
r'(?i)you\s+are\s+now\s+',
|
||||
r'(?i)system\s*:\s*',
|
||||
r'(?i)assistant\s*:\s*',
|
||||
r'(?i)\[INST\]',
|
||||
r'(?i)\[/INST\]',
|
||||
r'(?i)<\|im_start\|>',
|
||||
r'(?i)<\|im_end\|>',
|
||||
]
|
||||
|
||||
for pattern in injection_patterns:
|
||||
if re.search(pattern, text):
|
||||
logger.warning(f"Potential prompt injection detected: {pattern}")
|
||||
text = re.sub(pattern, '[FILTERED]', text)
|
||||
|
||||
return text
|
||||
|
||||
def _validate_output(self, output: str) -> bool:
|
||||
"""输出验证:检查LLM返回内容的基本有效性"""
|
||||
if not output or not isinstance(output, str):
|
||||
return False
|
||||
|
||||
# 检查长度是否合理
|
||||
if len(output) > 100000:
|
||||
logger.warning(f"Output too long: {len(output)} chars")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _rate_limit(self):
|
||||
"""速率限制:确保最小调用间隔"""
|
||||
current_time = time.time()
|
||||
elapsed = current_time - self._last_call_time
|
||||
if elapsed < self._min_interval:
|
||||
time.sleep(self._min_interval - elapsed)
|
||||
self._last_call_time = time.time()
|
||||
|
||||
def call(self, messages, retries=1):
|
||||
k = self._key(messages)
|
||||
c = self._get(k)
|
||||
if c:
|
||||
logger.debug(f"Cache hit for key: {k[:8]}...")
|
||||
return c
|
||||
|
||||
# 对用户消息进行输入过滤
|
||||
sanitized_messages = []
|
||||
for msg in messages:
|
||||
if msg.get("role") == "user":
|
||||
sanitized_content = self._sanitize_input(msg.get("content", ""))
|
||||
sanitized_messages.append({**msg, "content": sanitized_content})
|
||||
else:
|
||||
sanitized_messages.append(msg)
|
||||
|
||||
key = os.environ.get("LLM_API_KEY", os.environ.get("OPENAI_API_KEY", ""))
|
||||
base = os.environ.get("LLM_API_BASE", "https://api.openai.com/v1")
|
||||
|
||||
for a in range(retries + 1):
|
||||
try:
|
||||
r = httpx.post(f"{base}/chat/completions", json={"model": self.model, "messages": messages},
|
||||
# 速率限制
|
||||
self._rate_limit()
|
||||
|
||||
logger.info(f"LLM call: model={self.model}, attempt={a+1}/{retries+1}")
|
||||
r = httpx.post(f"{base}/chat/completions", json={"model": self.model, "messages": sanitized_messages},
|
||||
headers={"Authorization": f"Bearer {key}"}, timeout=self.timeout)
|
||||
r.raise_for_status()
|
||||
v = r.json()["choices"][0]["message"]["content"]
|
||||
|
||||
# 输出验证
|
||||
if not self._validate_output(v):
|
||||
logger.warning("Invalid output from LLM, using fallback")
|
||||
v = ""
|
||||
|
||||
self._set(k, v)
|
||||
logger.info(f"LLM call success: response length={len(v)}")
|
||||
return v
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
logger.error(f"LLM call failed: {e}")
|
||||
if a == retries:
|
||||
raise
|
||||
time.sleep(1) # 失败后等待1秒重试
|
||||
|
||||
return ""
|
||||
|
||||
Reference in New Issue
Block a user