feat(engine): 添加MCP连接管理和工具集成功能
- 集成MCP连接管理器,支持MCP服务器连接 - 添加多种内置工具:ClarifyTool、CronTool、DelegateTool、ExecuteCodeTool、 PatchFileTool、ProcessTool、SendMessageTool、SpawnTool、TerminalTool、 TodoTool、WebFetchTool、WebSearchTool、WriteFileTool等 - 实现工具注册和装配功能 - 添加技能选择上下文参数 - 支持思考模式控制参数thinking_enabled feat(coordinator): 重构任务执行计划器参数命名 - 将learning_candidate_enabled重命名为allow_candidate_generation - 更新TeamGraphScheduler中的参数传递 - 修改LocalAgentRunner中的相关参数处理 - 更新README文档中的相应描述 refactor(context): 标准化工具调用参数格式 - 添加_json导入用于参数序列化 - 实现_provider_tool_calls方法标准化OpenAI兼容的工具调用载荷 - 修复工具调用中参数非字符串类型的序列化问题 refactor(session): 优化消息历史记录过滤逻辑 - 修改get_messages_as_conversation为基于运行状态过滤消息 - 排除未完成、失败或错误结束的运行记录 - 改进对话历史的可见性控制机制 fix(store): 修复FTS索引重建逻辑 - 添加异常处理防止FTS索引创建失败 - 实现_rebuild_fts_index方法重新构建全文搜索索引 - 优化索引触发器和表的维护流程
This commit is contained in:
@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
@ -64,6 +65,7 @@ class AgentLoop:
|
||||
self.profile = profile or AgentProfile()
|
||||
self.loader = loader or EngineLoader()
|
||||
self.loaded: EngineLoadResult | None = None
|
||||
self.runtime_services: dict[str, Any] = {}
|
||||
self._run_queue: asyncio.Queue[_DirectRunRequest | None] | None = None
|
||||
self._running = False
|
||||
self._stop_requested = False
|
||||
@ -190,6 +192,7 @@ class AgentLoop:
|
||||
user_id: str | None = None,
|
||||
title: str | None = None,
|
||||
execution_context: str | None = None,
|
||||
skill_selection_context: str | None = None,
|
||||
model: str | None = None,
|
||||
provider_name: str | None = None,
|
||||
api_key: str | None = None,
|
||||
@ -202,6 +205,9 @@ class AgentLoop:
|
||||
embedding_model: str | None = None,
|
||||
max_tokens: int | None = None,
|
||||
temperature: float | None = None,
|
||||
thinking_enabled: bool | None = None,
|
||||
include_skill_assembly: bool = True,
|
||||
include_tools: bool = True,
|
||||
max_tool_iterations: int | None = None,
|
||||
provider_bundle: ProviderBundle | None = None,
|
||||
parent_session_id: str | None = None,
|
||||
@ -210,7 +216,7 @@ class AgentLoop:
|
||||
attempt_index: int | None = None,
|
||||
pinned_skill_names: list[str] | None = None,
|
||||
pinned_skill_contexts: list[SkillContext] | None = None,
|
||||
learning_candidate_enabled: bool = False,
|
||||
allow_candidate_generation: bool = False,
|
||||
) -> AgentRunResult:
|
||||
"""跑通最小 direct run 主链。
|
||||
|
||||
@ -234,6 +240,7 @@ class AgentLoop:
|
||||
user_id=user_id,
|
||||
title=title,
|
||||
execution_context=execution_context,
|
||||
skill_selection_context=skill_selection_context,
|
||||
model=model,
|
||||
provider_name=provider_name,
|
||||
api_key=api_key,
|
||||
@ -246,6 +253,9 @@ class AgentLoop:
|
||||
embedding_model=embedding_model,
|
||||
max_tokens=max_tokens,
|
||||
temperature=temperature,
|
||||
thinking_enabled=thinking_enabled,
|
||||
include_skill_assembly=include_skill_assembly,
|
||||
include_tools=include_tools,
|
||||
max_tool_iterations=max_tool_iterations,
|
||||
provider_bundle=provider_bundle,
|
||||
parent_session_id=parent_session_id,
|
||||
@ -254,7 +264,7 @@ class AgentLoop:
|
||||
attempt_index=attempt_index,
|
||||
pinned_skill_names=pinned_skill_names,
|
||||
pinned_skill_contexts=pinned_skill_contexts,
|
||||
learning_candidate_enabled=learning_candidate_enabled,
|
||||
allow_candidate_generation=allow_candidate_generation,
|
||||
)
|
||||
|
||||
async def _process_direct_impl(
|
||||
@ -266,6 +276,7 @@ class AgentLoop:
|
||||
user_id: str | None = None,
|
||||
title: str | None = None,
|
||||
execution_context: str | None = None,
|
||||
skill_selection_context: str | None = None,
|
||||
model: str | None = None,
|
||||
provider_name: str | None = None,
|
||||
api_key: str | None = None,
|
||||
@ -278,6 +289,9 @@ class AgentLoop:
|
||||
embedding_model: str | None = None,
|
||||
max_tokens: int | None = None,
|
||||
temperature: float | None = None,
|
||||
thinking_enabled: bool | None = None,
|
||||
include_skill_assembly: bool = True,
|
||||
include_tools: bool = True,
|
||||
max_tool_iterations: int | None = None,
|
||||
provider_bundle: ProviderBundle | None = None,
|
||||
parent_session_id: str | None = None,
|
||||
@ -286,7 +300,7 @@ class AgentLoop:
|
||||
attempt_index: int | None = None,
|
||||
pinned_skill_names: list[str] | None = None,
|
||||
pinned_skill_contexts: list[SkillContext] | None = None,
|
||||
learning_candidate_enabled: bool = False,
|
||||
allow_candidate_generation: bool = False,
|
||||
) -> AgentRunResult:
|
||||
"""真正执行一轮 direct run 的内部实现。
|
||||
|
||||
@ -306,6 +320,10 @@ class AgentLoop:
|
||||
skills_loader = self._require_loaded("skills_loader")
|
||||
skill_assembler = self._require_loaded("skill_assembler")
|
||||
skill_learning_service = self._require_loaded("skill_learning_service")
|
||||
mcp_manager = getattr(loaded, "mcp_manager", None)
|
||||
if mcp_manager is not None:
|
||||
loaded.mcp_report = await mcp_manager.connect_all(tool_registry)
|
||||
loaded.tools = [spec.name for spec in tool_registry.list_specs()]
|
||||
|
||||
config = loaded.config
|
||||
configured_provider = config.resolve_provider_target(model=model, provider_name=provider_name)
|
||||
@ -357,6 +375,9 @@ class AgentLoop:
|
||||
"task_id": task_id,
|
||||
"task_mode": task_mode,
|
||||
"attempt_index": attempt_index,
|
||||
"thinking_enabled": thinking_enabled,
|
||||
"include_skill_assembly": include_skill_assembly,
|
||||
"skill_selection_context_present": bool(skill_selection_context),
|
||||
"parent_session_id": parent_session_id,
|
||||
"pinned_skill_names": list(pinned_skill_names or []),
|
||||
"pinned_skill_context_names": [skill.name for skill in pinned_skill_contexts or []],
|
||||
@ -396,19 +417,39 @@ class AgentLoop:
|
||||
if bundle.auxiliary_runtime is not None
|
||||
else bundle.main_runtime.model
|
||||
)
|
||||
assembled_skills = await skill_assembler.assemble(
|
||||
task_description=task,
|
||||
provider=skill_selector_provider,
|
||||
model=skill_selector_model,
|
||||
embedding_runtime=bundle.embedding_runtime,
|
||||
)
|
||||
activated_skills = self._merge_skill_contexts(
|
||||
[
|
||||
*(pinned_skill_contexts or []),
|
||||
*self._load_pinned_skill_contexts(skills_loader, pinned_skill_names or []),
|
||||
],
|
||||
assembled_skills.activated_skills,
|
||||
)
|
||||
pinned_skills = [
|
||||
*(pinned_skill_contexts or []),
|
||||
*self._load_pinned_skill_contexts(skills_loader, pinned_skill_names or []),
|
||||
]
|
||||
if not include_skill_assembly or thinking_enabled is False:
|
||||
activated_skills = self._merge_skill_contexts(pinned_skills, [])
|
||||
else:
|
||||
skill_query = skill_selection_context or task
|
||||
assembled_skills = await skill_assembler.assemble(
|
||||
task_description=skill_query,
|
||||
provider=skill_selector_provider,
|
||||
model=skill_selector_model,
|
||||
embedding_runtime=bundle.embedding_runtime,
|
||||
thinking_enabled=thinking_enabled,
|
||||
)
|
||||
for interaction in getattr(assembled_skills, "llm_interactions", []) or []:
|
||||
session_manager.append_message(
|
||||
resolved_session_id,
|
||||
run_id=resolved_run_id,
|
||||
role="system",
|
||||
event_type="skill_assembler_llm_interaction_snapshotted",
|
||||
event_payload=interaction,
|
||||
content=json.dumps(interaction, ensure_ascii=False, default=str),
|
||||
context_visible=False,
|
||||
source=source,
|
||||
title=title,
|
||||
model=skill_selector_model,
|
||||
user_id=user_id,
|
||||
)
|
||||
activated_skills = self._merge_skill_contexts(
|
||||
pinned_skills,
|
||||
assembled_skills.activated_skills,
|
||||
)
|
||||
skill_activation_messages = context_builder.build_skill_activation_messages(
|
||||
activated_skills
|
||||
)
|
||||
@ -444,14 +485,19 @@ class AgentLoop:
|
||||
user_id=user_id,
|
||||
)
|
||||
|
||||
selected_tool_specs = await tool_assembler.assemble(
|
||||
task_description=task,
|
||||
registry=tool_registry,
|
||||
skills_loader=skills_loader,
|
||||
activated_skills=activated_skills,
|
||||
embedding_runtime=bundle.embedding_runtime,
|
||||
top_k=10,
|
||||
)
|
||||
if not include_tools:
|
||||
selected_tool_specs = []
|
||||
elif thinking_enabled is False:
|
||||
selected_tool_specs = tool_registry.list_specs()
|
||||
else:
|
||||
selected_tool_specs = await tool_assembler.assemble(
|
||||
task_description=task,
|
||||
registry=tool_registry,
|
||||
skills_loader=skills_loader,
|
||||
activated_skills=activated_skills,
|
||||
embedding_runtime=bundle.embedding_runtime,
|
||||
top_k=10,
|
||||
)
|
||||
tool_schemas = tool_registry.export_selected_provider_schemas(selected_tool_specs)
|
||||
session_manager.append_message(
|
||||
resolved_session_id,
|
||||
@ -486,6 +532,25 @@ class AgentLoop:
|
||||
execution_context=execution_context,
|
||||
)
|
||||
context_result = context_builder.build_messages(build_input)
|
||||
if skill_selection_context:
|
||||
session_manager.append_message(
|
||||
resolved_session_id,
|
||||
run_id=resolved_run_id,
|
||||
role="system",
|
||||
event_type="skill_selection_context_snapshotted",
|
||||
event_payload={
|
||||
"skill_selection_context": skill_selection_context,
|
||||
"task_id": task_id,
|
||||
"task_mode": task_mode,
|
||||
"attempt_index": attempt_index,
|
||||
},
|
||||
content=skill_selection_context,
|
||||
context_visible=False,
|
||||
source=source,
|
||||
title=title,
|
||||
model=resolved_model,
|
||||
user_id=user_id,
|
||||
)
|
||||
session_manager.update_system_prompt(resolved_session_id, context_result.system_prompt)
|
||||
session_manager.append_message(
|
||||
resolved_session_id,
|
||||
@ -528,6 +593,9 @@ class AgentLoop:
|
||||
"memory_service": memory_service,
|
||||
"memory_store": memory_service.get_store(),
|
||||
"tool_registry": tool_registry,
|
||||
"skills_loader": skills_loader,
|
||||
"draft_service": getattr(loaded, "draft_service", None),
|
||||
**self.runtime_services,
|
||||
},
|
||||
metadata={
|
||||
"source": source,
|
||||
@ -541,13 +609,45 @@ class AgentLoop:
|
||||
final_model = bundle.main_runtime.model
|
||||
|
||||
while True:
|
||||
response = await provider.chat(
|
||||
messages=messages,
|
||||
tools=tool_schemas,
|
||||
chat_kwargs: dict[str, Any] = {
|
||||
"messages": messages,
|
||||
"tools": tool_schemas,
|
||||
"model": final_model,
|
||||
"max_tokens": resolved_max_tokens,
|
||||
"temperature": resolved_temperature,
|
||||
}
|
||||
if thinking_enabled is not None:
|
||||
chat_kwargs["thinking_enabled"] = thinking_enabled
|
||||
session_manager.append_message(
|
||||
resolved_session_id,
|
||||
run_id=resolved_run_id,
|
||||
role="system",
|
||||
event_type="llm_request_snapshotted",
|
||||
event_payload={
|
||||
"iteration": iterations,
|
||||
"provider_name": final_provider_name,
|
||||
"model": final_model,
|
||||
"messages": messages,
|
||||
"tools": tool_schemas,
|
||||
"max_tokens": resolved_max_tokens,
|
||||
"temperature": resolved_temperature,
|
||||
"thinking_enabled": thinking_enabled,
|
||||
},
|
||||
content=json.dumps(
|
||||
{
|
||||
"messages": messages,
|
||||
"tools": tool_schemas,
|
||||
},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
),
|
||||
context_visible=False,
|
||||
source=source,
|
||||
title=title,
|
||||
model=final_model,
|
||||
max_tokens=resolved_max_tokens,
|
||||
temperature=resolved_temperature,
|
||||
user_id=user_id,
|
||||
)
|
||||
response = await provider.chat(**chat_kwargs)
|
||||
final_provider_name = response.provider_name or final_provider_name
|
||||
final_model = response.model or final_model
|
||||
final_usage = self._merge_usage(final_usage, response.usage or {})
|
||||
@ -650,7 +750,7 @@ class AgentLoop:
|
||||
model=final_model,
|
||||
user_id=user_id,
|
||||
)
|
||||
self._record_skill_learning(
|
||||
self._record_run_receipts(
|
||||
skill_learning_service=skill_learning_service,
|
||||
session_manager=session_manager,
|
||||
session_id=resolved_session_id,
|
||||
@ -663,7 +763,7 @@ class AgentLoop:
|
||||
success=(final_finish_reason == "stop"),
|
||||
task_id=task_id,
|
||||
attempt_index=attempt_index,
|
||||
generate_candidates=learning_candidate_enabled,
|
||||
allow_candidate_generation=False,
|
||||
)
|
||||
return AgentRunResult(
|
||||
session_id=resolved_session_id,
|
||||
@ -703,7 +803,7 @@ class AgentLoop:
|
||||
usage=final_usage,
|
||||
task_id=task_id,
|
||||
)
|
||||
self._record_skill_learning(
|
||||
self._record_run_receipts(
|
||||
skill_learning_service=skill_learning_service,
|
||||
session_manager=session_manager,
|
||||
session_id=resolved_session_id,
|
||||
@ -716,7 +816,7 @@ class AgentLoop:
|
||||
success=False,
|
||||
task_id=task_id,
|
||||
attempt_index=attempt_index,
|
||||
generate_candidates=learning_candidate_enabled,
|
||||
allow_candidate_generation=False,
|
||||
)
|
||||
return result
|
||||
|
||||
@ -771,13 +871,16 @@ class AgentLoop:
|
||||
def _serialize_tool_calls(tool_calls: list[Any]) -> list[dict[str, Any]]:
|
||||
payload: list[dict[str, Any]] = []
|
||||
for tool_call in tool_calls:
|
||||
arguments = tool_call.arguments
|
||||
if not isinstance(arguments, str):
|
||||
arguments = json.dumps(arguments or {}, ensure_ascii=False, default=str)
|
||||
payload.append(
|
||||
{
|
||||
"id": tool_call.id,
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": tool_call.name,
|
||||
"arguments": tool_call.arguments,
|
||||
"arguments": arguments,
|
||||
},
|
||||
}
|
||||
)
|
||||
@ -877,7 +980,7 @@ class AgentLoop:
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _record_skill_learning(
|
||||
def _record_run_receipts(
|
||||
*,
|
||||
skill_learning_service: Any,
|
||||
session_manager: Any,
|
||||
@ -891,7 +994,7 @@ class AgentLoop:
|
||||
success: bool,
|
||||
task_id: str | None = None,
|
||||
attempt_index: int | None = None,
|
||||
generate_candidates: bool = False,
|
||||
allow_candidate_generation: bool = False,
|
||||
) -> None:
|
||||
run_record = RunRecord(
|
||||
run_id=run_id,
|
||||
@ -921,7 +1024,7 @@ class AgentLoop:
|
||||
try:
|
||||
candidates = skill_learning_service.collect_run_receipts(
|
||||
RunReceiptContext(run_record=run_record, effect_records=effect_records),
|
||||
generate_candidates=generate_candidates,
|
||||
generate_candidates=allow_candidate_generation,
|
||||
)
|
||||
except Exception as exc: # pragma: no cover - defensive hot-path guard
|
||||
session_manager.append_message(
|
||||
@ -948,7 +1051,7 @@ class AgentLoop:
|
||||
"run_record": run_record.to_dict(),
|
||||
"skill_effects": [item.to_dict() for item in effect_records],
|
||||
"learning_candidates": [candidate.to_dict() for candidate in candidates],
|
||||
"learning_candidate_enabled": generate_candidates,
|
||||
"candidate_generation_allowed": allow_candidate_generation,
|
||||
},
|
||||
content=f"Recorded {len(effect_records)} skill effect record(s).",
|
||||
context_visible=False,
|
||||
|
||||
Reference in New Issue
Block a user