2025-12-11 19:09:10 +08:00
|
|
|
|
"""
|
2025-12-11 21:14:32 +08:00
|
|
|
|
Recon Agent (信息收集层) - LLM 驱动版
|
2025-12-11 19:09:10 +08:00
|
|
|
|
|
2025-12-11 21:14:32 +08:00
|
|
|
|
LLM 是真正的大脑!
|
|
|
|
|
|
- LLM 决定收集什么信息
|
|
|
|
|
|
- LLM 决定使用哪个工具
|
|
|
|
|
|
- LLM 决定何时信息足够
|
|
|
|
|
|
- LLM 动态调整收集策略
|
|
|
|
|
|
|
|
|
|
|
|
类型: ReAct (真正的!)
|
2025-12-11 19:09:10 +08:00
|
|
|
|
"""
|
|
|
|
|
|
|
2025-12-11 23:29:04 +08:00
|
|
|
|
import asyncio
|
2025-12-11 21:14:32 +08:00
|
|
|
|
import json
|
2025-12-11 19:09:10 +08:00
|
|
|
|
import logging
|
2025-12-11 21:14:32 +08:00
|
|
|
|
import re
|
2025-12-11 19:09:10 +08:00
|
|
|
|
from typing import List, Dict, Any, Optional
|
2025-12-11 21:14:32 +08:00
|
|
|
|
from dataclasses import dataclass
|
2025-12-11 19:09:10 +08:00
|
|
|
|
|
|
|
|
|
|
from .base import BaseAgent, AgentConfig, AgentResult, AgentType, AgentPattern
|
2025-12-11 23:29:04 +08:00
|
|
|
|
from ..json_parser import AgentJsonParser
|
2025-12-11 19:09:10 +08:00
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-12-11 23:29:04 +08:00
|
|
|
|
RECON_SYSTEM_PROMPT = """你是 DeepAudit 的信息收集 Agent,负责在安全审计前收集项目信息。
|
2025-12-11 19:09:10 +08:00
|
|
|
|
|
2025-12-11 23:29:04 +08:00
|
|
|
|
## 你的职责
|
|
|
|
|
|
你专注于**信息收集**,为后续的漏洞分析提供基础数据:
|
|
|
|
|
|
1. 分析项目结构和目录布局
|
|
|
|
|
|
2. 识别技术栈(语言、框架、数据库)
|
|
|
|
|
|
3. 找出入口点(API、路由、用户输入处理)
|
|
|
|
|
|
4. 标记高风险区域(认证、数据库操作、文件处理)
|
|
|
|
|
|
5. 收集依赖信息
|
2025-12-11 19:09:10 +08:00
|
|
|
|
|
|
|
|
|
|
## 你可以使用的工具
|
|
|
|
|
|
|
2025-12-11 23:29:04 +08:00
|
|
|
|
### 文件系统工具
|
2025-12-11 21:14:32 +08:00
|
|
|
|
- **list_files**: 列出目录内容
|
|
|
|
|
|
参数: directory (str), recursive (bool), pattern (str), max_files (int)
|
|
|
|
|
|
|
|
|
|
|
|
- **read_file**: 读取文件内容
|
|
|
|
|
|
参数: file_path (str), start_line (int), end_line (int), max_lines (int)
|
|
|
|
|
|
|
|
|
|
|
|
- **search_code**: 代码关键字搜索
|
|
|
|
|
|
参数: keyword (str), max_results (int)
|
|
|
|
|
|
|
2025-12-11 23:29:04 +08:00
|
|
|
|
### 语义搜索工具
|
|
|
|
|
|
- **rag_query**: 语义代码搜索(如果可用)
|
|
|
|
|
|
参数: query (str), top_k (int)
|
|
|
|
|
|
|
|
|
|
|
|
## 注意
|
|
|
|
|
|
- 你只负责信息收集,不要进行漏洞分析
|
|
|
|
|
|
- 漏洞分析由 Analysis Agent 负责
|
|
|
|
|
|
- 专注于收集项目结构、技术栈、入口点等信息
|
2025-12-11 21:14:32 +08:00
|
|
|
|
|
|
|
|
|
|
## 工作方式
|
|
|
|
|
|
每一步,你需要输出:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
Thought: [分析当前状态,思考还需要什么信息]
|
|
|
|
|
|
Action: [工具名称]
|
|
|
|
|
|
Action Input: [JSON 格式的参数]
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
当你认为信息收集足够时,输出:
|
2025-12-11 19:09:10 +08:00
|
|
|
|
|
2025-12-11 21:14:32 +08:00
|
|
|
|
```
|
|
|
|
|
|
Thought: [总结收集到的信息]
|
|
|
|
|
|
Final Answer: [JSON 格式的收集结果]
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## Final Answer 格式
|
2025-12-11 19:09:10 +08:00
|
|
|
|
```json
|
|
|
|
|
|
{
|
2025-12-11 21:14:32 +08:00
|
|
|
|
"project_structure": {
|
|
|
|
|
|
"directories": [],
|
|
|
|
|
|
"config_files": [],
|
|
|
|
|
|
"total_files": 数量
|
|
|
|
|
|
},
|
2025-12-11 19:09:10 +08:00
|
|
|
|
"tech_stack": {
|
|
|
|
|
|
"languages": [],
|
|
|
|
|
|
"frameworks": [],
|
|
|
|
|
|
"databases": []
|
|
|
|
|
|
},
|
2025-12-11 21:14:32 +08:00
|
|
|
|
"entry_points": [
|
|
|
|
|
|
{"type": "描述", "file": "路径", "line": 行号}
|
|
|
|
|
|
],
|
|
|
|
|
|
"high_risk_areas": ["路径列表"],
|
|
|
|
|
|
"dependencies": {},
|
2025-12-11 19:09:10 +08:00
|
|
|
|
"initial_findings": []
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2025-12-11 21:14:32 +08:00
|
|
|
|
## 信息收集策略建议
|
|
|
|
|
|
1. 先 list_files 了解项目结构
|
|
|
|
|
|
2. 读取配置文件 (package.json, requirements.txt, go.mod 等) 识别技术栈
|
|
|
|
|
|
3. 搜索入口点模式 (routes, controllers, handlers)
|
|
|
|
|
|
4. 运行安全扫描发现初步问题
|
|
|
|
|
|
5. 根据发现继续深入
|
|
|
|
|
|
|
|
|
|
|
|
## 重要原则
|
|
|
|
|
|
1. **你是大脑** - 每一步都要思考,不要机械执行
|
|
|
|
|
|
2. **动态调整** - 根据发现调整策略
|
|
|
|
|
|
3. **效率优先** - 不要重复收集已有信息
|
|
|
|
|
|
4. **主动探索** - 发现有趣的东西要深入
|
|
|
|
|
|
|
|
|
|
|
|
现在开始收集项目信息!"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
|
class ReconStep:
|
|
|
|
|
|
"""信息收集步骤"""
|
|
|
|
|
|
thought: str
|
|
|
|
|
|
action: Optional[str] = None
|
|
|
|
|
|
action_input: Optional[Dict] = None
|
|
|
|
|
|
observation: Optional[str] = None
|
|
|
|
|
|
is_final: bool = False
|
|
|
|
|
|
final_answer: Optional[Dict] = None
|
2025-12-11 19:09:10 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ReconAgent(BaseAgent):
|
|
|
|
|
|
"""
|
2025-12-11 21:14:32 +08:00
|
|
|
|
信息收集 Agent - LLM 驱动版
|
2025-12-11 19:09:10 +08:00
|
|
|
|
|
2025-12-11 21:14:32 +08:00
|
|
|
|
LLM 全程参与,自主决定:
|
|
|
|
|
|
1. 收集什么信息
|
|
|
|
|
|
2. 使用什么工具
|
|
|
|
|
|
3. 何时足够
|
2025-12-11 19:09:10 +08:00
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
|
|
self,
|
|
|
|
|
|
llm_service,
|
|
|
|
|
|
tools: Dict[str, Any],
|
|
|
|
|
|
event_emitter=None,
|
|
|
|
|
|
):
|
|
|
|
|
|
config = AgentConfig(
|
|
|
|
|
|
name="Recon",
|
|
|
|
|
|
agent_type=AgentType.RECON,
|
|
|
|
|
|
pattern=AgentPattern.REACT,
|
|
|
|
|
|
max_iterations=15,
|
|
|
|
|
|
system_prompt=RECON_SYSTEM_PROMPT,
|
|
|
|
|
|
)
|
|
|
|
|
|
super().__init__(config, llm_service, tools, event_emitter)
|
2025-12-11 21:14:32 +08:00
|
|
|
|
|
|
|
|
|
|
self._conversation_history: List[Dict[str, str]] = []
|
|
|
|
|
|
self._steps: List[ReconStep] = []
|
|
|
|
|
|
|
2025-12-11 23:29:04 +08:00
|
|
|
|
|
2025-12-11 21:14:32 +08:00
|
|
|
|
|
|
|
|
|
|
def _parse_llm_response(self, response: str) -> ReconStep:
|
|
|
|
|
|
"""解析 LLM 响应"""
|
|
|
|
|
|
step = ReconStep(thought="")
|
|
|
|
|
|
|
|
|
|
|
|
# 提取 Thought
|
|
|
|
|
|
thought_match = re.search(r'Thought:\s*(.*?)(?=Action:|Final Answer:|$)', response, re.DOTALL)
|
|
|
|
|
|
if thought_match:
|
|
|
|
|
|
step.thought = thought_match.group(1).strip()
|
|
|
|
|
|
|
|
|
|
|
|
# 检查是否是最终答案
|
|
|
|
|
|
final_match = re.search(r'Final Answer:\s*(.*?)$', response, re.DOTALL)
|
|
|
|
|
|
if final_match:
|
|
|
|
|
|
step.is_final = True
|
2025-12-11 23:29:04 +08:00
|
|
|
|
answer_text = final_match.group(1).strip()
|
|
|
|
|
|
answer_text = re.sub(r'```json\s*', '', answer_text)
|
|
|
|
|
|
answer_text = re.sub(r'```\s*', '', answer_text)
|
|
|
|
|
|
# 使用增强的 JSON 解析器
|
|
|
|
|
|
step.final_answer = AgentJsonParser.parse(
|
|
|
|
|
|
answer_text,
|
|
|
|
|
|
default={"raw_answer": answer_text}
|
|
|
|
|
|
)
|
2025-12-11 21:14:32 +08:00
|
|
|
|
return step
|
|
|
|
|
|
|
|
|
|
|
|
# 提取 Action
|
|
|
|
|
|
action_match = re.search(r'Action:\s*(\w+)', response)
|
|
|
|
|
|
if action_match:
|
|
|
|
|
|
step.action = action_match.group(1).strip()
|
|
|
|
|
|
|
|
|
|
|
|
# 提取 Action Input
|
|
|
|
|
|
input_match = re.search(r'Action Input:\s*(.*?)(?=Thought:|Action:|Observation:|$)', response, re.DOTALL)
|
|
|
|
|
|
if input_match:
|
|
|
|
|
|
input_text = input_match.group(1).strip()
|
|
|
|
|
|
input_text = re.sub(r'```json\s*', '', input_text)
|
|
|
|
|
|
input_text = re.sub(r'```\s*', '', input_text)
|
2025-12-11 23:29:04 +08:00
|
|
|
|
# 使用增强的 JSON 解析器
|
|
|
|
|
|
step.action_input = AgentJsonParser.parse(
|
|
|
|
|
|
input_text,
|
|
|
|
|
|
default={"raw_input": input_text}
|
|
|
|
|
|
)
|
2025-12-11 21:14:32 +08:00
|
|
|
|
|
|
|
|
|
|
return step
|
|
|
|
|
|
|
2025-12-11 23:29:04 +08:00
|
|
|
|
|
2025-12-11 19:09:10 +08:00
|
|
|
|
|
|
|
|
|
|
async def run(self, input_data: Dict[str, Any]) -> AgentResult:
|
2025-12-11 21:14:32 +08:00
|
|
|
|
"""
|
|
|
|
|
|
执行信息收集 - LLM 全程参与!
|
|
|
|
|
|
"""
|
2025-12-11 19:09:10 +08:00
|
|
|
|
import time
|
|
|
|
|
|
start_time = time.time()
|
|
|
|
|
|
|
|
|
|
|
|
project_info = input_data.get("project_info", {})
|
|
|
|
|
|
config = input_data.get("config", {})
|
2025-12-11 21:14:32 +08:00
|
|
|
|
task = input_data.get("task", "")
|
|
|
|
|
|
task_context = input_data.get("task_context", "")
|
|
|
|
|
|
|
|
|
|
|
|
# 构建初始消息
|
|
|
|
|
|
initial_message = f"""请开始收集项目信息。
|
|
|
|
|
|
|
|
|
|
|
|
## 项目基本信息
|
|
|
|
|
|
- 名称: {project_info.get('name', 'unknown')}
|
|
|
|
|
|
- 根目录: {project_info.get('root', '.')}
|
|
|
|
|
|
|
|
|
|
|
|
## 任务上下文
|
|
|
|
|
|
{task_context or task or '进行全面的信息收集,为安全审计做准备。'}
|
|
|
|
|
|
|
|
|
|
|
|
## 可用工具
|
2025-12-11 23:29:04 +08:00
|
|
|
|
{self.get_tools_description()}
|
2025-12-11 21:14:32 +08:00
|
|
|
|
|
|
|
|
|
|
请开始你的信息收集工作。首先思考应该收集什么信息,然后选择合适的工具。"""
|
|
|
|
|
|
|
|
|
|
|
|
# 初始化对话历史
|
|
|
|
|
|
self._conversation_history = [
|
|
|
|
|
|
{"role": "system", "content": self.config.system_prompt},
|
|
|
|
|
|
{"role": "user", "content": initial_message},
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
self._steps = []
|
|
|
|
|
|
final_result = None
|
|
|
|
|
|
|
2025-12-11 23:29:04 +08:00
|
|
|
|
await self.emit_thinking("Recon Agent 启动,LLM 开始自主收集信息...")
|
2025-12-11 19:09:10 +08:00
|
|
|
|
|
|
|
|
|
|
try:
|
2025-12-11 21:14:32 +08:00
|
|
|
|
for iteration in range(self.config.max_iterations):
|
|
|
|
|
|
if self.is_cancelled:
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
|
|
self._iteration = iteration + 1
|
|
|
|
|
|
|
2025-12-11 23:29:04 +08:00
|
|
|
|
# 🔥 再次检查取消标志(在LLM调用之前)
|
|
|
|
|
|
if self.is_cancelled:
|
|
|
|
|
|
await self.emit_thinking("🛑 任务已取消,停止执行")
|
|
|
|
|
|
break
|
2025-12-11 21:14:32 +08:00
|
|
|
|
|
2025-12-11 23:29:04 +08:00
|
|
|
|
# 调用 LLM 进行思考和决策(使用基类统一方法)
|
|
|
|
|
|
try:
|
|
|
|
|
|
llm_output, tokens_this_round = await self.stream_llm_call(
|
|
|
|
|
|
self._conversation_history,
|
|
|
|
|
|
temperature=0.1,
|
|
|
|
|
|
max_tokens=2048,
|
|
|
|
|
|
)
|
|
|
|
|
|
except asyncio.CancelledError:
|
|
|
|
|
|
logger.info(f"[{self.name}] LLM call cancelled")
|
|
|
|
|
|
break
|
2025-12-11 21:14:32 +08:00
|
|
|
|
|
|
|
|
|
|
self._total_tokens += tokens_this_round
|
|
|
|
|
|
|
|
|
|
|
|
# 解析 LLM 响应
|
|
|
|
|
|
step = self._parse_llm_response(llm_output)
|
|
|
|
|
|
self._steps.append(step)
|
|
|
|
|
|
|
|
|
|
|
|
# 🔥 发射 LLM 思考内容事件 - 展示 LLM 在想什么
|
|
|
|
|
|
if step.thought:
|
|
|
|
|
|
await self.emit_llm_thought(step.thought, iteration + 1)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加 LLM 响应到历史
|
|
|
|
|
|
self._conversation_history.append({
|
|
|
|
|
|
"role": "assistant",
|
|
|
|
|
|
"content": llm_output,
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
# 检查是否完成
|
|
|
|
|
|
if step.is_final:
|
|
|
|
|
|
await self.emit_llm_decision("完成信息收集", "LLM 判断已收集足够信息")
|
|
|
|
|
|
await self.emit_llm_complete(
|
|
|
|
|
|
f"信息收集完成,共 {self._iteration} 轮思考",
|
|
|
|
|
|
self._total_tokens
|
|
|
|
|
|
)
|
|
|
|
|
|
final_result = step.final_answer
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
|
|
# 执行工具
|
|
|
|
|
|
if step.action:
|
|
|
|
|
|
# 🔥 发射 LLM 动作决策事件
|
|
|
|
|
|
await self.emit_llm_action(step.action, step.action_input or {})
|
|
|
|
|
|
|
2025-12-11 23:29:04 +08:00
|
|
|
|
observation = await self.execute_tool(
|
2025-12-11 21:14:32 +08:00
|
|
|
|
step.action,
|
|
|
|
|
|
step.action_input or {}
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
step.observation = observation
|
|
|
|
|
|
|
|
|
|
|
|
# 🔥 发射 LLM 观察事件
|
|
|
|
|
|
await self.emit_llm_observation(observation)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加观察结果到历史
|
|
|
|
|
|
self._conversation_history.append({
|
|
|
|
|
|
"role": "user",
|
|
|
|
|
|
"content": f"Observation:\n{observation}",
|
|
|
|
|
|
})
|
|
|
|
|
|
else:
|
|
|
|
|
|
# LLM 没有选择工具,提示它继续
|
|
|
|
|
|
await self.emit_llm_decision("继续思考", "LLM 需要更多信息")
|
|
|
|
|
|
self._conversation_history.append({
|
|
|
|
|
|
"role": "user",
|
|
|
|
|
|
"content": "请继续,选择一个工具执行,或者如果信息收集完成,输出 Final Answer。",
|
|
|
|
|
|
})
|
2025-12-11 19:09:10 +08:00
|
|
|
|
|
2025-12-11 21:14:32 +08:00
|
|
|
|
# 处理结果
|
2025-12-11 19:09:10 +08:00
|
|
|
|
duration_ms = int((time.time() - start_time) * 1000)
|
|
|
|
|
|
|
2025-12-11 21:14:32 +08:00
|
|
|
|
# 如果没有最终结果,从历史中汇总
|
|
|
|
|
|
if not final_result:
|
|
|
|
|
|
final_result = self._summarize_from_steps()
|
|
|
|
|
|
|
2025-12-11 23:29:04 +08:00
|
|
|
|
# 🔥 记录工作和洞察
|
|
|
|
|
|
self.record_work(f"完成项目信息收集,发现 {len(final_result.get('entry_points', []))} 个入口点")
|
|
|
|
|
|
self.record_work(f"识别技术栈: {final_result.get('tech_stack', {})}")
|
|
|
|
|
|
|
|
|
|
|
|
if final_result.get("high_risk_areas"):
|
|
|
|
|
|
self.add_insight(f"发现 {len(final_result['high_risk_areas'])} 个高风险区域需要重点分析")
|
|
|
|
|
|
if final_result.get("initial_findings"):
|
|
|
|
|
|
self.add_insight(f"初步发现 {len(final_result['initial_findings'])} 个潜在问题")
|
|
|
|
|
|
|
2025-12-11 19:09:10 +08:00
|
|
|
|
await self.emit_event(
|
|
|
|
|
|
"info",
|
2025-12-11 23:29:04 +08:00
|
|
|
|
f"Recon Agent 完成: {self._iteration} 轮迭代, {self._tool_calls} 次工具调用"
|
2025-12-11 19:09:10 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return AgentResult(
|
|
|
|
|
|
success=True,
|
2025-12-11 21:14:32 +08:00
|
|
|
|
data=final_result,
|
2025-12-11 19:09:10 +08:00
|
|
|
|
iterations=self._iteration,
|
|
|
|
|
|
tool_calls=self._tool_calls,
|
|
|
|
|
|
tokens_used=self._total_tokens,
|
|
|
|
|
|
duration_ms=duration_ms,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
2025-12-11 21:14:32 +08:00
|
|
|
|
logger.error(f"Recon Agent failed: {e}", exc_info=True)
|
2025-12-11 19:09:10 +08:00
|
|
|
|
return AgentResult(success=False, error=str(e))
|
|
|
|
|
|
|
2025-12-11 21:14:32 +08:00
|
|
|
|
def _summarize_from_steps(self) -> Dict[str, Any]:
|
|
|
|
|
|
"""从步骤中汇总结果"""
|
|
|
|
|
|
# 默认结果结构
|
2025-12-11 19:09:10 +08:00
|
|
|
|
result = {
|
2025-12-11 21:14:32 +08:00
|
|
|
|
"project_structure": {},
|
|
|
|
|
|
"tech_stack": {
|
|
|
|
|
|
"languages": [],
|
|
|
|
|
|
"frameworks": [],
|
|
|
|
|
|
"databases": [],
|
|
|
|
|
|
},
|
|
|
|
|
|
"entry_points": [],
|
|
|
|
|
|
"high_risk_areas": [],
|
2025-12-11 19:09:10 +08:00
|
|
|
|
"dependencies": {},
|
2025-12-11 21:14:32 +08:00
|
|
|
|
"initial_findings": [],
|
2025-12-11 19:09:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-11 21:14:32 +08:00
|
|
|
|
# 从步骤的观察结果中提取信息
|
|
|
|
|
|
for step in self._steps:
|
|
|
|
|
|
if step.observation:
|
|
|
|
|
|
# 尝试从观察中识别技术栈等信息
|
|
|
|
|
|
obs_lower = step.observation.lower()
|
|
|
|
|
|
|
|
|
|
|
|
if "package.json" in obs_lower:
|
|
|
|
|
|
result["tech_stack"]["languages"].append("JavaScript/TypeScript")
|
|
|
|
|
|
if "requirements.txt" in obs_lower or "setup.py" in obs_lower:
|
|
|
|
|
|
result["tech_stack"]["languages"].append("Python")
|
|
|
|
|
|
if "go.mod" in obs_lower:
|
|
|
|
|
|
result["tech_stack"]["languages"].append("Go")
|
|
|
|
|
|
|
|
|
|
|
|
# 识别框架
|
|
|
|
|
|
if "react" in obs_lower:
|
|
|
|
|
|
result["tech_stack"]["frameworks"].append("React")
|
|
|
|
|
|
if "django" in obs_lower:
|
|
|
|
|
|
result["tech_stack"]["frameworks"].append("Django")
|
|
|
|
|
|
if "fastapi" in obs_lower:
|
|
|
|
|
|
result["tech_stack"]["frameworks"].append("FastAPI")
|
|
|
|
|
|
if "express" in obs_lower:
|
|
|
|
|
|
result["tech_stack"]["frameworks"].append("Express")
|
2025-12-11 19:09:10 +08:00
|
|
|
|
|
2025-12-11 21:14:32 +08:00
|
|
|
|
# 去重
|
|
|
|
|
|
result["tech_stack"]["languages"] = list(set(result["tech_stack"]["languages"]))
|
|
|
|
|
|
result["tech_stack"]["frameworks"] = list(set(result["tech_stack"]["frameworks"]))
|
2025-12-11 19:09:10 +08:00
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
2025-12-11 21:14:32 +08:00
|
|
|
|
def get_conversation_history(self) -> List[Dict[str, str]]:
|
|
|
|
|
|
"""获取对话历史"""
|
|
|
|
|
|
return self._conversation_history
|
2025-12-11 19:09:10 +08:00
|
|
|
|
|
2025-12-11 21:14:32 +08:00
|
|
|
|
def get_steps(self) -> List[ReconStep]:
|
|
|
|
|
|
"""获取执行步骤"""
|
|
|
|
|
|
return self._steps
|