SQLite 持久连接 — sandbox 不再每次查询开关连接,改为 __init__ 时建连、close() 时释放
Explorer 的 system prompt 明确告知 sandbox 规则 — "每条 SQL 必须包含聚合函数或 LIMIT",减少 LLM 生成违规 SQL 浪费轮次 LLM 客户端单例 — 所有组件共享一个 openai.OpenAI 实例,不再各建各的 sanitize 顺序修复 — 小样本抑制放在 float round 之前,避免被 round 干扰 quick_detect 从 O(n²) 改为 O(n) — 按列聚合一次,加去重,不再对每行重复算整列统计 历史上下文实际生效 — get_context_for 的结果现在会注入到 Explorer 的初始 prompt 里,多轮分析时 LLM 能看到之前的发现
This commit is contained in:
80
layers/context.py
Normal file
80
layers/context.py
Normal file
@@ -0,0 +1,80 @@
|
||||
"""
|
||||
Layer 4: 上下文管理器
|
||||
"""
|
||||
import time
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
from layers.explorer import ExplorationStep
|
||||
from layers.insights import Insight
|
||||
|
||||
|
||||
@dataclass
|
||||
class AnalysisSession:
|
||||
"""一次分析的完整记录"""
|
||||
question: str
|
||||
plan: dict
|
||||
steps: list[ExplorationStep]
|
||||
insights: list[Insight]
|
||||
report: str
|
||||
timestamp: float = field(default_factory=time.time)
|
||||
|
||||
def summary(self) -> str:
|
||||
parts = [f"**问题**: {self.question}"]
|
||||
if self.plan:
|
||||
parts.append(f"**分析类型**: {self.plan.get('analysis_type', 'unknown')}")
|
||||
parts.append(f"**维度**: {', '.join(self.plan.get('dimensions', []))}")
|
||||
key_findings = []
|
||||
for step in self.steps:
|
||||
if step.success and step.rows:
|
||||
top_row = step.rows[0] if step.rows else {}
|
||||
finding = f"{step.purpose}: " + ", ".join(f"{k}={v}" for k, v in top_row.items() if k.lower() != "id")
|
||||
key_findings.append(finding)
|
||||
if key_findings:
|
||||
parts.append("**核心发现**:")
|
||||
for f in key_findings[:5]:
|
||||
parts.append(f" - {f}")
|
||||
if self.insights:
|
||||
parts.append("**洞察**:")
|
||||
for i in self.insights[:3]:
|
||||
parts.append(f" - {i}")
|
||||
return "\n".join(parts)
|
||||
|
||||
def to_reference_text(self) -> str:
|
||||
return (
|
||||
f"## 之前的分析\n### 问题\n{self.question}\n### 摘要\n{self.summary()}\n### 发现\n"
|
||||
+ "\n".join(f"- {s.purpose}: {s.row_count} 行" for s in self.steps if s.success)
|
||||
)
|
||||
|
||||
|
||||
class ContextManager:
|
||||
"""上下文管理器"""
|
||||
|
||||
def __init__(self, max_history: int = 10):
|
||||
self.sessions: list[AnalysisSession] = []
|
||||
self.max_history = max_history
|
||||
|
||||
def add_session(self, question: str, plan: dict, steps: list[ExplorationStep],
|
||||
insights: list[Insight], report: str) -> AnalysisSession:
|
||||
session = AnalysisSession(question=question, plan=plan, steps=steps, insights=insights, report=report)
|
||||
self.sessions.append(session)
|
||||
if len(self.sessions) > self.max_history:
|
||||
self.sessions = self.sessions[-self.max_history:]
|
||||
return session
|
||||
|
||||
def get_context_for(self, new_question: str) -> Optional[str]:
|
||||
if not self.sessions:
|
||||
return None
|
||||
return "\n\n---\n\n".join(s.to_reference_text() for s in self.sessions[-2:])
|
||||
|
||||
def get_history_summary(self) -> str:
|
||||
if not self.sessions:
|
||||
return "(无历史分析)"
|
||||
lines = [f"共 {len(self.sessions)} 次分析:"]
|
||||
for i, s in enumerate(self.sessions, 1):
|
||||
ts = time.strftime("%H:%M", time.localtime(s.timestamp))
|
||||
lines.append(f" {i}. [{ts}] {s.question}")
|
||||
return "\n".join(lines)
|
||||
|
||||
def clear(self):
|
||||
self.sessions.clear()
|
||||
Reference in New Issue
Block a user