feat: 四层架构数据分析 Agent
- Layer 1 Planner: 意图规划,将问题转为结构化分析计划 - Layer 2 Explorer: 自适应探索循环,多轮迭代动态生成 SQL - Layer 3 InsightEngine: 异常检测 + 主动洞察 - Layer 4 ContextManager: 多轮对话上下文记忆 安全设计:AI 只看 Schema + 聚合结果,不接触原始数据。 支持任意 OpenAI 兼容 API(OpenAI / Ollama / DeepSeek / vLLM)
This commit is contained in:
134
context.py
Normal file
134
context.py
Normal file
@@ -0,0 +1,134 @@
|
||||
"""
|
||||
Layer 4: 上下文管理器
|
||||
管理多轮对话的分析上下文,让后续问题可以引用之前的发现。
|
||||
"""
|
||||
import json
|
||||
import time
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Optional
|
||||
|
||||
from explorer import ExplorationStep
|
||||
from 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}: "
|
||||
finding += ", ".join(
|
||||
f"{k}={v}" for k, v in top_row.items() if k.lower() not in ("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 insight in self.insights[:3]:
|
||||
parts.append(f" - {insight}")
|
||||
|
||||
return "\n".join(parts)
|
||||
|
||||
def to_reference_text(self) -> str:
|
||||
"""生成供 LLM 使用的上下文文本"""
|
||||
return (
|
||||
f"## 之前的分析\n\n"
|
||||
f"### 问题\n{self.question}\n\n"
|
||||
f"### 摘要\n{self.summary()}\n\n"
|
||||
f"### 详细发现\n"
|
||||
+ "\n".join(
|
||||
f"- {step.purpose}: {step.row_count} 行结果"
|
||||
for step in self.steps if step.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
|
||||
|
||||
# 取最近 2 轮分析的摘要
|
||||
recent = self.sessions[-2:]
|
||||
parts = []
|
||||
for session in recent:
|
||||
parts.append(session.to_reference_text())
|
||||
|
||||
return "\n\n---\n\n".join(parts)
|
||||
|
||||
def get_history_summary(self) -> str:
|
||||
"""获取所有历史的摘要"""
|
||||
if not self.sessions:
|
||||
return "(无历史分析)"
|
||||
|
||||
lines = [f"共 {len(self.sessions)} 次分析:"]
|
||||
for i, session in enumerate(self.sessions, 1):
|
||||
ts = time.strftime("%H:%M", time.localtime(session.timestamp))
|
||||
lines.append(f" {i}. [{ts}] {session.question}")
|
||||
if session.insights:
|
||||
for insight in session.insights[:2]:
|
||||
lines.append(f" {insight}")
|
||||
return "\n".join(lines)
|
||||
|
||||
def clear(self):
|
||||
"""清空历史"""
|
||||
self.sessions.clear()
|
||||
Reference in New Issue
Block a user