Skip to content

Commit abf5a5d

Browse files
committed
feat: 对齐 Python 编排上下文与降级逻辑
1 parent 6d86525 commit abf5a5d

8 files changed

Lines changed: 166 additions & 62 deletions

File tree

secbot_agent/core/agents/base.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ def get_conversation_history(self, limit: Optional[int] = None) -> List[AgentMes
103103
def clear_memory(self):
104104
"""清空记忆"""
105105
self._conversation.clear()
106+
self.messages = [
107+
AgentMessage(role="system", content=self.system_prompt)
108+
] if self.system_prompt else []
106109
if hasattr(self.memory, "clear_all") and callable(self.memory.clear_all):
107110
# MemoryManager.clear_all 为 async,此处仅清空对话列表;持久记忆由调用方按需清空
108111
pass
@@ -121,4 +124,3 @@ def update_system_prompt(self, new_prompt: str):
121124
self.messages.insert(0, AgentMessage(role="system", content=new_prompt))
122125

123126
self.log.bind(event="stage_end").info(f"智能体 {self.name} 的系统提示词已更新")
124-

secbot_agent/core/agents/coordinator_agent.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ async def execute_todo(
136136
iteration: int = 1,
137137
get_root_password=None,
138138
emit_events: bool = True,
139+
context_block: str = "",
139140
) -> Dict[str, Any]:
140141
"""
141142
单步执行入口:根据 Todo.agent_hint / resource 路由到专职子 Agent。
@@ -165,6 +166,7 @@ async def execute_todo(
165166
iteration=iteration,
166167
get_root_password=get_root_password,
167168
emit_events=emit_events,
169+
context_block=context_block,
168170
)
169171

170172
# 为结果打上 agent 标签,便于 Summary / 前端区分
@@ -332,4 +334,3 @@ def _select_sub_agent(
332334

333335

334336
__all__ = ["CoordinatorAgent"]
335-

secbot_agent/core/agents/intent_router.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"分类(共 6 类):\n"
3131
"1) small_talk:闲聊、感谢、表情、确认(\"\"/\"ok\"/\"😊\")、纯礼貌。不需要工具、不需要安全知识。\n"
3232
"2) meta:询问 secbot 自身能力、改设置、查看历史/工具列表/会话状态。\n"
33-
"3) qa:安全知识、概念、原理、提问类(\"什么是 SSRF?\"/\"DNS 解析过程?\")。不需要执行工具\n"
33+
"3) qa:安全知识、概念、原理、提问类(\"什么是 SSRF?\"/\"DNS 解析过程?\")。通常不需要执行工具;但若用户明确在问“最新/近期/当前”的漏洞或安全动态,仍归为 qa,由问答层决定是否做只读实时检索\n"
3434
"4) clarify_needed:用户想做任务,但关键参数缺失(目标缺、模糊指代、范围不清),必须先追问。\n"
3535
"5) task_simple:任务意图明确,且显然 1 步可解(\"再跑一遍上次的端口扫描\"/\"对 1.2.3.4 ping 一下\"),跳过复杂规划。\n"
3636
"6) task_complex:任务,需要规划、并行/串行多步工具调用、可能要生成报告。\n\n"
@@ -273,8 +273,8 @@ async def classify(
273273
lc_messages.append(HumanMessage(content=content))
274274
lc_messages.append(HumanMessage(content=user_prompt))
275275

276-
self._ensure_llm()
277276
try:
277+
self._ensure_llm()
278278
import asyncio
279279

280280
resp = await asyncio.wait_for(

secbot_agent/core/agents/tool_calling_agent.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,16 @@ def __init__(
8787
self._model_override: Optional[str] = None
8888

8989
self._sync_base_url_and_model()
90-
self.llm = _create_llm(
91-
provider=self._provider_override or settings.llm_provider,
92-
model=self._model_override,
93-
)
90+
try:
91+
self.llm = _create_llm(
92+
provider=self._provider_override or settings.llm_provider,
93+
model=self._model_override,
94+
)
95+
except Exception as e:
96+
self.llm = None
97+
logger.bind(agent=self.name, event="agent_error", attempt=1).warning(
98+
f"LLM 初始化失败,保留 Agent 基础能力: {e}"
99+
)
94100
logger.bind(agent=self.name, event="stage_start", attempt=1).info(
95101
f"推理后端: {self._provider_override or settings.llm_provider}, 模型: {self.model}"
96102
)
@@ -104,7 +110,7 @@ def __init__(
104110
langchain_tools = [LangChainToolWrapper(tool) for tool in self.tools]
105111

106112
# 使用 LangChain 1.1+ 的新 API
107-
if langchain_tools:
113+
if langchain_tools and self.llm is not None:
108114
self.tools_dict = {tool.name: tool for tool in langchain_tools}
109115
# 配置或运行时发现模型不支持 tools 时,不绑定工具
110116
supports_tools = getattr(settings, "llm_tools_supported", True)
@@ -136,7 +142,7 @@ def __init__(
136142
self.llm_with_tools = self.llm
137143
self.tools_dict = {}
138144
self.use_bind_tools = False
139-
logger.bind(agent=self.name, event="agent_error", attempt=1).warning("没有提供工具,工具调用智能体将无法使用工具调用功能")
145+
logger.bind(agent=self.name, event="agent_error", attempt=1).warning("没有可用 LLM 或工具,工具调用智能体将仅保留基础会话能力")
140146

141147
def _sync_base_url_and_model(self) -> None:
142148
"""根据当前 provider/model 覆盖更新 base_url 和 model(用于显示)。"""

secbot_agent/core/executor.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,14 @@ def __init__(
3737
planner,
3838
event_bus: EventBus,
3939
get_root_password: Optional[Callable] = None,
40+
context_block: str = "",
4041
):
4142
self.plan_result = plan_result
4243
self.agent = agent
4344
self.planner = planner
4445
self.event_bus = event_bus
4546
self.get_root_password = get_root_password
47+
self.context_block = context_block
4648
self._layer_results: Dict[str, Any] = {}
4749

4850
def _get_todo_by_id(self, todo_id: str) -> Optional[TodoItem]:
@@ -241,6 +243,7 @@ async def _execute_single_todo(
241243
iteration=iteration,
242244
get_root_password=self.get_root_password,
243245
emit_events=emit_events,
246+
context_block=self.context_block,
244247
)
245248
duration_ms = int((time.perf_counter() - started) * 1000)
246249
if result.get("success"):

secbot_agent/core/patterns/security_react.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,7 @@ async def process(self, user_input: str, on_event=None, **kwargs) -> str:
611611
self._skip_report = skip_report # 供 handle_accept 使用
612612
# 当前计划步骤(由 SessionManager 传入),用于 prompt 中提示“未完成不输出 Final Answer”
613613
self._current_todos = kwargs.get("todos") or []
614+
self._runtime_context_block = (kwargs.get("context_block") or "").strip()
614615
# 需要 root 时询问密码的回调(由交互层传入)
615616
self._get_root_password = kwargs.get("get_root_password")
616617

@@ -1138,6 +1139,13 @@ async def _plan(self, user_input: str, on_event=None) -> str:
11381139
"""规划阶段:分析任务,制定执行计划"""
11391140
tools_desc = self._get_tools_description()
11401141
context_block = get_agent_context_block()
1142+
runtime_context = getattr(self, "_runtime_context_block", "")
1143+
if runtime_context:
1144+
context_block = (
1145+
f"{context_block}\n"
1146+
f"## 会话检索上下文(由 ContextAssembler 注入)\n"
1147+
f"{runtime_context}\n"
1148+
)
11411149

11421150
planning_prompt = f"""你是一个安全测试专家。请分析用户请求,制定详细的执行计划。
11431151
@@ -1703,6 +1711,7 @@ async def execute_todo(
17031711
iteration: int = 1,
17041712
get_root_password=None,
17051713
emit_events: bool = True,
1714+
context_block: str = "",
17061715
) -> Dict[str, Any]:
17071716
"""
17081717
单步执行:根据 todo 的 tool_hint 执行对应工具,供 TaskExecutor 分层调度使用。
@@ -1756,6 +1765,8 @@ async def execute_todo(
17561765

17571766
# 使用 LLM 提取参数
17581767
context_str = ""
1768+
if context_block:
1769+
context_str += f"\n- request_context: {context_block[:1200]}"
17591770
if context:
17601771
for k, v in context.items():
17611772
if v is not None:

secbot_agent/core/session.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ async def _maybe_handle_conversational_intent(
257257
ans = (intent.direct_response or "").strip()
258258
if not ans:
259259
ans = (
260-
"收到~有需要执行的安全任务随时说"
260+
"收到~有需要执行的安全任务随时说!"
261261
if intent.intent == "small_talk"
262262
else "我会尽量帮你查清楚。"
263263
)
@@ -375,20 +375,38 @@ async def handle_message(
375375
import os
376376

377377
at = agent_type or self.get_current_agent_type()
378+
sid = self.current_session.id if self.current_session else "default"
378379

379380
# ---- 强制 Q&A(mode=ask)----
380381
if force_qa:
381382
await self.event_bus.emit_simple_async(
382383
EventType.TASK_PHASE, phase="done", detail=""
383384
)
384-
response = await self.qa_agent.answer(user_input)
385+
if self.context_assembler and self.current_session:
386+
ctx = await self.context_assembler.build(
387+
query=user_input,
388+
session=self.current_session,
389+
session_id=sid,
390+
agent_type="qa",
391+
model_name=model_name,
392+
)
393+
await self._emit_context_usage(ctx.debug)
394+
hist = [
395+
{"role": m.role.value, "content": m.content}
396+
for m in self.current_session.messages
397+
]
398+
response = await self.qa_agent.answer_with_context(
399+
user_input, hist, ctx.context_block
400+
)
401+
else:
402+
response = await self.qa_agent.answer(user_input)
385403
if self.current_session:
386404
self.current_session.add_message(MessageRole.ASSISTANT, response)
405+
await self._persist_turn(user_input, response, "qa")
387406
return response
388407

389408
# ---- IntentRouter + 可选 Explore + 预算上下文 ----
390409
_context_block = ""
391-
sid = self.current_session.id if self.current_session else "default"
392410
focus_kw: List[str] = []
393411
unresolved_snap: List[str] = []
394412
if self.context_assembler:
@@ -532,6 +550,7 @@ def event_bridge(event_type: str, data: dict):
532550
skip_planning=True,
533551
skip_report=True,
534552
todos=[],
553+
context_block=_context_block,
535554
get_root_password=getattr(self, "get_root_password", None),
536555
)
537556
else:
@@ -541,6 +560,7 @@ def event_bridge(event_type: str, data: dict):
541560
skip_planning=True,
542561
skip_report=True,
543562
todos=[],
563+
context_block=_context_block,
544564
get_root_password=getattr(self, "get_root_password", None),
545565
)
546566

@@ -678,6 +698,7 @@ async def _do_execute():
678698
planner=self.planner,
679699
event_bus=self.event_bus,
680700
get_root_password=getattr(self, "get_root_password", None),
701+
context_block=_context_block,
681702
)
682703
return await executor.run(user_input, on_event=event_bridge)
683704
else:
@@ -687,6 +708,7 @@ async def _do_execute():
687708
skip_planning=True,
688709
skip_report=True,
689710
todos=todos_snapshot,
711+
context_block=_context_block,
690712
get_root_password=getattr(self, "get_root_password", None),
691713
)
692714
return resp
@@ -749,6 +771,7 @@ async def _do_execute():
749771
planner=self.planner,
750772
event_bus=self.event_bus,
751773
get_root_password=getattr(self, "get_root_password", None),
774+
context_block=_context_block,
752775
)
753776
if lock is not None:
754777
async with lock:

0 commit comments

Comments
 (0)