foxclaw 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +1 -8
- package/package.json +1 -1
- package/python/.env.example +8 -0
- package/python/.github/workflows/ci.yml +47 -2
- package/python/CHANGELOG.md +57 -0
- package/python/CHANGELOG_CN.md +57 -0
- package/python/README.md +11 -2
- package/python/README_CN.md +32 -20
- package/python/bench/bm25_audit.py +1 -14
- package/python/bench/bm25_regression.py +0 -1
- package/python/bench/bm25_stress.py +0 -4
- package/python/bench/cd_audit.py +0 -23
- package/python/bench/chaos.py +6 -8
- package/python/bench/e2e_real_models.py +0 -1
- package/python/bench/llm_retry_test.py +2 -9
- package/python/bench/load_test.py +5 -8
- package/python/bench/vector_ivf_upgrade.py +1 -7
- package/python/docs/RELEASE.md +4 -3
- package/python/docs/RUN_CN.md +2 -2
- package/python/evals/governance/cases.py +0 -37
- package/python/evals/governance/run_evals.py +5 -37
- package/python/examples/governance_middleware_demo.py +1 -6
- package/python/examples/langgraph_governance_demo.py +7 -11
- package/python/foxclaw/__init__.py +34 -152
- package/python/foxclaw/agents/base.py +65 -136
- package/python/foxclaw/agents/impl/_helpers.py +2 -10
- package/python/foxclaw/agents/impl/finance.py +4 -21
- package/python/foxclaw/agents/impl/office.py +7 -24
- package/python/foxclaw/agents/impl/supervisor.py +12 -79
- package/python/foxclaw/agents/mixins/_context.py +0 -23
- package/python/foxclaw/agents/mixins/augment.py +10 -16
- package/python/foxclaw/agents/mixins/llm.py +7 -6
- package/python/foxclaw/agents/mixins/memory.py +7 -1
- package/python/foxclaw/agents/mixins/preference.py +58 -8
- package/python/foxclaw/agents/mixins/prompt.py +0 -6
- package/python/foxclaw/agents/mixins/skillbox.py +6 -6
- package/python/foxclaw/agents/mixins/tool_exec.py +16 -42
- package/python/foxclaw/api/__init__.py +18 -77
- package/python/foxclaw/api/_state.py +43 -0
- package/python/foxclaw/api/{app.py → bootstrap.py} +5 -32
- package/python/foxclaw/api/middleware.py +5 -28
- package/python/foxclaw/api/routes/ahs.py +60 -73
- package/python/foxclaw/api/routes/attachments.py +5 -6
- package/python/foxclaw/api/routes/browser.py +7 -11
- package/python/foxclaw/api/routes/domain.py +18 -26
- package/python/foxclaw/api/routes/knowledge.py +3 -21
- package/python/foxclaw/api/routes/llm_admin.py +21 -33
- package/python/foxclaw/api/routes/memory.py +28 -12
- package/python/foxclaw/api/routes/orchestration.py +3 -5
- package/python/foxclaw/api/routes/recovery.py +4 -3
- package/python/foxclaw/api/routes/review.py +2 -11
- package/python/foxclaw/api/routes/search.py +3 -7
- package/python/foxclaw/api/routes/system.py +33 -49
- package/python/foxclaw/api/routes/task.py +31 -49
- package/python/foxclaw/api/routes/ws.py +2 -6
- package/python/foxclaw/api/spine.py +1 -4
- package/python/foxclaw/browser/action/action_executor.py +1 -5
- package/python/foxclaw/browser/action/action_types.py +0 -1
- package/python/foxclaw/browser/action/url_policy.py +1 -5
- package/python/foxclaw/browser/adapter/web_adapter.py +1 -1
- package/python/foxclaw/browser/driver/playwright_driver.py +20 -16
- package/python/foxclaw/browser/perception/dom_extractor.py +0 -1
- package/python/foxclaw/browser/perception/element_indexer.py +1 -2
- package/python/foxclaw/browser/service.py +7 -11
- package/python/foxclaw/capability/embedding_service.py +4 -6
- package/python/foxclaw/channels/__init__.py +9 -11
- package/python/foxclaw/channels/discord.py +4 -13
- package/python/foxclaw/channels/dispatcher.py +2 -27
- package/python/foxclaw/channels/feishu.py +10 -20
- package/python/foxclaw/channels/openclaw_bridge.py +4 -9
- package/python/foxclaw/channels/signing.py +9 -9
- package/python/foxclaw/channels/telegram.py +3 -4
- package/python/foxclaw/channels/weixin.py +7 -17
- package/python/foxclaw/cli.py +42 -13
- package/python/foxclaw/cognitive/reasoner.py +9 -10
- package/python/foxclaw/cognitive/state.py +18 -24
- package/python/foxclaw/core/audit_chain.py +1 -3
- package/python/foxclaw/core/audit_sink.py +3 -3
- package/python/foxclaw/core/compaction.py +8 -26
- package/python/foxclaw/core/config.py +2 -2
- package/python/foxclaw/core/confirm_nonce.py +6 -12
- package/python/foxclaw/core/envfile.py +2 -2
- package/python/foxclaw/core/failure_policy.py +17 -26
- package/python/foxclaw/core/gateway.py +6 -10
- package/python/foxclaw/core/governance_audit.py +3 -7
- package/python/foxclaw/core/idempotency.py +15 -22
- package/python/foxclaw/core/injection_guard.py +10 -29
- package/python/foxclaw/core/memory_adapter.py +30 -57
- package/python/foxclaw/core/metrics.py +4 -5
- package/python/foxclaw/core/model_router.py +36 -148
- package/python/foxclaw/core/paths.py +1 -1
- package/python/foxclaw/core/recovery.py +21 -34
- package/python/foxclaw/core/scheduler.py +23 -26
- package/python/foxclaw/core/secrets.py +0 -3
- package/python/foxclaw/core/sessions.py +4 -6
- package/python/foxclaw/core/settings.py +16 -32
- package/python/foxclaw/core/shared_write.py +11 -23
- package/python/foxclaw/core/steering_queue.py +12 -11
- package/python/foxclaw/core/system_bus.py +1 -1
- package/python/foxclaw/core/thought_chain.py +30 -32
- package/python/foxclaw/core/token_meter.py +9 -13
- package/python/foxclaw/core/tools.py +4 -12
- package/python/foxclaw/core/tracing.py +25 -40
- package/python/foxclaw/core/truth_pipeline.py +5 -9
- package/python/foxclaw/core/update_check.py +149 -0
- package/python/foxclaw/core/vec_recall_adapter.py +11 -37
- package/python/foxclaw/core/verify.py +6 -14
- package/python/foxclaw/core/web_search.py +4 -28
- package/python/foxclaw/finance/evidence.py +14 -18
- package/python/foxclaw/finance/fx.py +1 -3
- package/python/foxclaw/finance/graph.py +9 -18
- package/python/foxclaw/finance/models.py +20 -24
- package/python/foxclaw/finance/nlparse.py +12 -72
- package/python/foxclaw/finance/policy.py +8 -36
- package/python/foxclaw/finance/reasoner.py +3 -11
- package/python/foxclaw/finance/reasoning.py +13 -34
- package/python/foxclaw/finance/reconcile.py +5 -10
- package/python/foxclaw/finance/runtime.py +19 -46
- package/python/foxclaw/governance/middleware.py +13 -21
- package/python/foxclaw/harness/_base.py +2 -8
- package/python/foxclaw/harness/_policy.py +19 -38
- package/python/foxclaw/harness/drift.py +1 -13
- package/python/foxclaw/harness/event_bus.py +2 -5
- package/python/foxclaw/harness/harness.py +9 -97
- package/python/foxclaw/harness/loop_detection.py +11 -18
- package/python/foxclaw/itinerary/amap_client.py +2 -5
- package/python/foxclaw/itinerary/models.py +7 -7
- package/python/foxclaw/itinerary/reasoner.py +1 -6
- package/python/foxclaw/itinerary/reasoning.py +15 -21
- package/python/foxclaw/itinerary/runtime.py +8 -12
- package/python/foxclaw/kernel/capabilities.py +5 -6
- package/python/foxclaw/kernel/policy_gate.py +4 -10
- package/python/foxclaw/kernel/process_manager.py +17 -36
- package/python/foxclaw/kernel/scheduler.py +3 -9
- package/python/foxclaw/kernel/sentinel.py +13 -23
- package/python/foxclaw/kernel/sentinel_client.py +12 -15
- package/python/foxclaw/kernel/spine.py +9 -9
- package/python/foxclaw/knowledge/__init__.py +4 -20
- package/python/foxclaw/knowledge/truth_crystallizer.py +5 -8
- package/python/foxclaw/learning/knowledge_graph.py +4 -9
- package/python/foxclaw/learning/models.py +13 -13
- package/python/foxclaw/learning/reasoner.py +1 -4
- package/python/foxclaw/learning/runtime.py +19 -31
- package/python/foxclaw/learning/spaced_repetition.py +2 -7
- package/python/foxclaw/mcp_server.py +2 -3
- package/python/foxclaw/orchestration/__init__.py +16 -33
- package/python/foxclaw/pipeline/cognitive_pipeline.py +45 -97
- package/python/foxclaw/pipeline/stage_meta.py +21 -0
- package/python/foxclaw/pipeline/supervisor_stages.py +13 -42
- package/python/foxclaw/planning/llm_planner.py +3 -3
- package/python/foxclaw/planning/planner.py +6 -11
- package/python/foxclaw/search/agent_port.py +7 -11
- package/python/foxclaw/search/evidence.py +0 -4
- package/python/foxclaw/search/internal_search.py +3 -5
- package/python/foxclaw/search/models.py +11 -13
- package/python/foxclaw/search/planner.py +3 -3
- package/python/foxclaw/search/retriever.py +1 -1
- package/python/foxclaw/search/runtime.py +1 -8
- package/python/foxclaw/skills/__init__.py +46 -100
- package/python/foxclaw/skills/archive.py +111 -0
- package/python/foxclaw/skills/fetch.py +226 -0
- package/python/foxclaw/skills/hooks_spec.py +21 -25
- package/python/foxclaw/skills/install_cmd.py +216 -0
- package/python/foxclaw/skills/lifecycle.py +16 -42
- package/python/foxclaw/skills/markdown_skill.py +10 -9
- package/python/foxclaw/skills/mcp_client.py +5 -11
- package/python/foxclaw/skills/mcp_store.py +0 -3
- package/python/foxclaw/skills/permission.py +13 -48
- package/python/foxclaw/skills/pipeline.py +21 -53
- package/python/foxclaw/skills/policy.py +4 -10
- package/python/foxclaw/skills/review.py +3 -3
- package/python/frontend/deck/auth.js +0 -7
- package/python/frontend/deck/chat_model_picker.js +0 -54
- package/python/frontend/deck/domain.js +0 -20
- package/python/frontend/deck/main.js +0 -144
- package/python/frontend/deck/markdown.js +0 -4
- package/python/frontend/deck/settings.js +1 -18
- package/python/frontend/deck/state.js +0 -1
- package/python/frontend/deck/util.js +0 -2
- package/python/frontend/deck/visuals.js +0 -22
- package/python/frontend/planet/planet.js +0 -201
- package/python/frontend/v3/assets/index-CQSBdxk4.js +34 -0
- package/python/frontend/v3/assets/index-D5nIsjms.css +1 -0
- package/python/frontend/v3/index.html +3 -3
- package/python/frontend/widget_runtime.js +0 -43
- package/python/frontend/widgets.js +0 -57
- package/python/frontend_src/index.html +1 -1
- package/python/frontend_src/package-lock.json +2 -2
- package/python/frontend_src/package.json +1 -1
- package/python/frontend_src/src/App.vue +3 -7
- package/python/frontend_src/src/api.js +0 -7
- package/python/frontend_src/src/components/ChatDock.vue +27 -39
- package/python/frontend_src/src/components/DocViewer.vue +0 -9
- package/python/frontend_src/src/components/Inspector.vue +0 -6
- package/python/frontend_src/src/components/LeftRail.vue +60 -39
- package/python/frontend_src/src/components/MenuPopover.vue +9 -11
- package/python/frontend_src/src/components/MessageStream.vue +18 -30
- package/python/frontend_src/src/components/ModelPicker.vue +0 -5
- package/python/frontend_src/src/components/QuickIngest.vue +0 -6
- package/python/frontend_src/src/components/Stage.vue +71 -25
- package/python/frontend_src/src/components/ToolMenu.vue +0 -3
- package/python/frontend_src/src/components/TopBar.vue +38 -10
- package/python/frontend_src/src/components/panels/ActivityPanel.vue +0 -2
- package/python/frontend_src/src/components/panels/AgentsPanel.vue +2 -5
- package/python/frontend_src/src/components/panels/KnowledgePanel.vue +0 -16
- package/python/frontend_src/src/components/panels/MemoryPanel.vue +0 -1
- package/python/frontend_src/src/components/panels/PanelBox.vue +6 -7
- package/python/frontend_src/src/components/panels/ReviewPanel.vue +0 -16
- package/python/frontend_src/src/components/panels/SettingsPanel.vue +142 -38
- package/python/frontend_src/src/components/panels/TasksPanel.vue +0 -1
- package/python/frontend_src/src/components/panels/ToolsPanel.vue +125 -19
- package/python/frontend_src/src/lib/fmt.js +0 -4
- package/python/frontend_src/src/lib/i18n.js +53 -0
- package/python/frontend_src/src/lib/locales/en.js +173 -0
- package/python/frontend_src/src/lib/locales/zh.js +174 -0
- package/python/frontend_src/src/lib/md.js +0 -14
- package/python/frontend_src/src/main.js +2 -0
- package/python/frontend_src/src/stores/app.js +17 -32
- package/python/frontend_src/src/stores/chat.js +0 -34
- package/python/frontend_src/src/stores/model.js +0 -8
- package/python/frontend_src/src/stores/panels.js +0 -41
- package/python/frontend_src/src/stores/profile.js +31 -0
- package/python/frontend_src/src/stores/system.js +25 -0
- package/python/frontend_src/vite.config.js +0 -7
- package/python/pyproject.toml +6 -3
- package/python/requirements.txt +311 -0
- package/python/tests/conftest.py +0 -22
- package/python/tests/run_all.py +3 -66
- package/python/tests/test_agent_lifecycle.py +4 -38
- package/python/tests/test_ahs_p2.py +4 -25
- package/python/tests/test_anthropic_tools.py +1 -15
- package/python/tests/test_api.py +19 -9
- package/python/tests/test_api_auth.py +1 -5
- package/python/tests/test_attachments.py +9 -25
- package/python/tests/test_audit_chain.py +2 -7
- package/python/tests/test_audit_sink.py +0 -6
- package/python/tests/test_browser.py +2 -6
- package/python/tests/test_browser_request_guard.py +0 -4
- package/python/tests/test_capabilities.py +0 -6
- package/python/tests/test_channel_signing.py +2 -9
- package/python/tests/test_channels.py +1 -11
- package/python/tests/test_config_layering.py +0 -4
- package/python/tests/test_confirm_nonce.py +1 -12
- package/python/tests/test_context_compaction_wiring.py +1 -7
- package/python/tests/test_dispatch.py +0 -6
- package/python/tests/test_domain_evals.py +11 -80
- package/python/tests/test_domain_import.py +0 -6
- package/python/tests/test_domain_reasoners.py +0 -14
- package/python/tests/test_e2e.py +0 -17
- package/python/tests/test_emb_cache.py +7 -17
- package/python/tests/test_embedding_singleton.py +1 -3
- package/python/tests/test_evals_smoke.py +0 -24
- package/python/tests/test_event_spine.py +10 -18
- package/python/tests/test_evidence_semantic.py +1 -14
- package/python/tests/test_evidence_sse.py +3 -6
- package/python/tests/test_file_ingest.py +0 -11
- package/python/tests/test_finance.py +0 -9
- package/python/tests/test_finance_ledger.py +5 -12
- package/python/tests/test_finance_persist.py +8 -12
- package/python/tests/test_gateway.py +3 -22
- package/python/tests/test_governance.py +5 -52
- package/python/tests/test_governance_audit.py +4 -39
- package/python/tests/test_governance_middleware.py +1 -7
- package/python/tests/test_hkc.py +6 -24
- package/python/tests/test_hkc_index_build_manager.py +0 -33
- package/python/tests/test_hkc_manifest_reuse.py +4 -27
- package/python/tests/test_hkc_search_event_sync.py +3 -54
- package/python/tests/test_import_path_guard.py +0 -6
- package/python/tests/test_injection_guard.py +18 -36
- package/python/tests/test_integration.py +5 -29
- package/python/tests/test_itinerary.py +0 -3
- package/python/tests/test_kee_semantic.py +0 -5
- package/python/tests/test_kernel.py +1 -11
- package/python/tests/test_learning.py +3 -12
- package/python/tests/test_llm_admin.py +4 -19
- package/python/tests/test_llm_planner.py +1 -23
- package/python/tests/test_markdown_skills.py +3 -14
- package/python/tests/test_markdown_skills_openclaw.py +4 -9
- package/python/tests/test_mcp_connectors.py +3 -15
- package/python/tests/test_mcp_guardian_discovery.py +2 -6
- package/python/tests/test_mcp_no_memory.py +3 -3
- package/python/tests/test_mcp_policy_gate.py +0 -1
- package/python/tests/test_mcp_server.py +6 -9
- package/python/tests/test_memory_recall.py +1 -4
- package/python/tests/test_metrics.py +0 -8
- package/python/tests/test_multi_chain.py +1 -13
- package/python/tests/test_multimodal.py +5 -18
- package/python/tests/test_negotiation.py +3 -13
- package/python/tests/test_orchestration.py +1 -11
- package/python/tests/test_orchestration_decoupled.py +0 -6
- package/python/tests/test_p3_p5_hardening.py +3 -17
- package/python/tests/test_parallel_dispatch.py +4 -12
- package/python/tests/test_pipeline.py +17 -58
- package/python/tests/test_plan_visible.py +4 -4
- package/python/tests/test_planner.py +0 -7
- package/python/tests/test_policy_gate.py +2 -3
- package/python/tests/test_profile.py +67 -0
- package/python/tests/test_readme_claims.py +163 -0
- package/python/tests/test_reasoner.py +2 -17
- package/python/tests/test_release_check.py +2 -13
- package/python/tests/test_resume_checkpoint.py +7 -15
- package/python/tests/test_routing.py +2 -12
- package/python/tests/test_scheduler.py +7 -10
- package/python/tests/test_search.py +1 -11
- package/python/tests/test_secrets.py +4 -10
- package/python/tests/test_semantic.py +1 -10
- package/python/tests/test_sessions.py +3 -4
- package/python/tests/test_settings.py +2 -18
- package/python/tests/test_shadow_learning.py +3 -43
- package/python/tests/test_shared_write.py +0 -7
- package/python/tests/test_skill_taint_gate.py +1 -10
- package/python/tests/test_skills.py +2 -18
- package/python/tests/test_skills_fetch.py +282 -0
- package/python/tests/test_stats_and_events.py +2 -5
- package/python/tests/test_streaming.py +2 -7
- package/python/tests/test_supervisor_pipeline.py +4 -5
- package/python/tests/test_supervisor_triage.py +4 -10
- package/python/tests/test_taint.py +2 -7
- package/python/tests/test_task_stream.py +1 -12
- package/python/tests/test_thinking_sse.py +0 -1
- package/python/tests/test_tool_center.py +1 -17
- package/python/tests/test_tool_parallel.py +2 -2
- package/python/tests/test_tool_params.py +1 -5
- package/python/tests/test_tool_use.py +0 -13
- package/python/tests/test_toplevel_concurrency.py +3 -4
- package/python/tests/test_tracing.py +7 -41
- package/python/tests/test_truth_crystallizer.py +4 -6
- package/python/tests/test_truth_flow.py +2 -7
- package/python/tests/test_update_check.py +95 -0
- package/python/tests/test_verify_evidence.py +8 -21
- package/python/tests/test_version_sync.py +35 -6
- package/python/tests/test_web_trigger.py +0 -17
- package/python/tools/check_faiss_type.py +0 -1
- package/python/tools/check_log.ps1 +0 -8
- package/python/tools/mypy_baseline.txt +12 -4
- package/python/tools/mypy_ratchet.py +52 -6
- package/python/tools/pack.py +0 -5
- package/python/tools/release_check.py +7 -7
- package/python/tools/restart_foxclaw.ps1 +5 -8
- package/python/tools/shared_write_audit.py +1 -15
- package/python/tools/size_check.py +1 -1
- package/python/tools/strip_comments.py +228 -0
- package/python/frontend/v3/assets/index-BD7VPbMV.js +0 -33
- package/python/frontend/v3/assets/index-CAvnYWGH.css +0 -1
|
@@ -16,11 +16,12 @@ hooks / 流式(run_task_stream)/ stop_event 中断 / 统一 `_emit_execution` /
|
|
|
16
16
|
import asyncio
|
|
17
17
|
from datetime import datetime
|
|
18
18
|
|
|
19
|
+
from ..core.dispatch import dispatch_failed
|
|
19
20
|
from .cognitive_pipeline import PipelineContext, stage_recall # noqa: F401 (recall 复用默认 stage)
|
|
20
|
-
from
|
|
21
|
+
from .stage_meta import named_stage
|
|
21
22
|
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
@named_stage("meta_check")
|
|
24
25
|
async def sv_meta_check(ctx: PipelineContext) -> None:
|
|
25
26
|
agent = ctx.agent
|
|
26
27
|
if not agent._is_meta(ctx.task):
|
|
@@ -30,7 +31,6 @@ async def sv_meta_check(ctx: PipelineContext) -> None:
|
|
|
30
31
|
node_type="decision", conf=0.92,
|
|
31
32
|
)
|
|
32
33
|
subagents = getattr(agent, "_subagents", {})
|
|
33
|
-
# 拓扑 5 → 3:助手/行政已下放为指令技能,不再出现在团队介绍里。
|
|
34
34
|
sub_names = {
|
|
35
35
|
"finance": "财务 Agent(理财、收支、预算、风控)",
|
|
36
36
|
"office": "办公学习 Agent(学习、知识管理、研究、写作、日程与行程)",
|
|
@@ -57,11 +57,10 @@ async def sv_meta_check(ctx: PipelineContext) -> None:
|
|
|
57
57
|
)
|
|
58
58
|
ctx.result = {"dispatched_to": "supervisor", "sub_result": reply,
|
|
59
59
|
"self_answered": True}
|
|
60
|
-
ctx.extra["_meta_answered"] = True
|
|
61
|
-
sv_meta_check.stage_name = "meta_check"
|
|
60
|
+
ctx.extra["_meta_answered"] = True
|
|
62
61
|
|
|
63
62
|
|
|
64
|
-
|
|
63
|
+
@named_stage("schedule")
|
|
65
64
|
async def sv_schedule(ctx: PipelineContext) -> None:
|
|
66
65
|
if ctx.extra.get("_meta_answered"):
|
|
67
66
|
return
|
|
@@ -73,10 +72,9 @@ async def sv_schedule(ctx: PipelineContext) -> None:
|
|
|
73
72
|
+ (f" · 抢占({ctx.sched.preemption_reason})" if ctx.sched.preemptible else "")),
|
|
74
73
|
node_type="decision", conf=0.9,
|
|
75
74
|
)
|
|
76
|
-
sv_schedule.stage_name = "schedule"
|
|
77
75
|
|
|
78
76
|
|
|
79
|
-
|
|
77
|
+
@named_stage("route")
|
|
80
78
|
async def sv_route(ctx: PipelineContext) -> None:
|
|
81
79
|
if ctx.extra.get("_meta_answered"):
|
|
82
80
|
return
|
|
@@ -84,11 +82,6 @@ async def sv_route(ctx: PipelineContext) -> None:
|
|
|
84
82
|
target = agent._route(ctx.task, ctx.sched)
|
|
85
83
|
ctx.target = target
|
|
86
84
|
ctx.route = {"target": target, "reason": "主管关键词+调度意图分诊"}
|
|
87
|
-
# 无关键词命中 = 正面对话/泛化问题 → 主管**直接自答一次**,省掉"LLM 分诊 + 派发"的
|
|
88
|
-
# 第二次串行 LLM 调用(每次约 5s 网络往返,正面对话不值当)。只有命中领域关键词
|
|
89
|
-
# (财务/行政/办公/…)才派发给对应子 Agent。stub 下保持原路径(零额外调用、离线测试确定)。
|
|
90
|
-
# 权衡:少数本该派给领域 Agent 的"无关键词"任务会由主管自答——用户明确选速度优先。
|
|
91
|
-
# (本 stage 只做**决策**置标记;自答的 LLM 调用在 dispatch stage 执行。)
|
|
92
85
|
if not agent._has_keyword_hit(ctx.task) and not agent.router.is_stub:
|
|
93
86
|
agent._think(
|
|
94
87
|
content="无关键词命中 → 主管直接自答(省掉二次分诊派发,正面对话提速)",
|
|
@@ -100,16 +93,13 @@ async def sv_route(ctx: PipelineContext) -> None:
|
|
|
100
93
|
node_type="decision",
|
|
101
94
|
conf=0.9,
|
|
102
95
|
)
|
|
103
|
-
sv_route.stage_name = "route"
|
|
104
96
|
|
|
105
97
|
|
|
106
|
-
|
|
98
|
+
@named_stage("plan")
|
|
107
99
|
async def sv_plan(ctx: PipelineContext) -> None:
|
|
108
100
|
if ctx.extra.get("_meta_answered") or ctx.extra.get("_self_answer"):
|
|
109
101
|
return
|
|
110
102
|
agent = ctx.agent
|
|
111
|
-
# P0-1:LLM 规划含同步网络调用(call_tools),放线程池避免阻塞事件循环;
|
|
112
|
-
# 规则拆解路径是纯 CPU,放线程池开销可忽略、行为不变。
|
|
113
103
|
plan_obj, step_routes = await asyncio.to_thread(agent._plan_task, ctx.task, ctx.sched)
|
|
114
104
|
if plan_obj is not None:
|
|
115
105
|
routes_desc = " → ".join(f"{name}[{ag}]" for name, ag in step_routes)
|
|
@@ -117,23 +107,20 @@ async def sv_plan(ctx: PipelineContext) -> None:
|
|
|
117
107
|
content=f"planner 判定为多步任务({len(plan_obj.tasks)}步):{routes_desc}",
|
|
118
108
|
node_type="decision", conf=0.85,
|
|
119
109
|
)
|
|
120
|
-
# 若子任务跨不同 Agent,标记为多 Agent 顺序执行
|
|
121
110
|
distinct_agents = {ag for _, ag in step_routes}
|
|
122
111
|
ctx.multi_agent = len(distinct_agents) > 1
|
|
123
112
|
else:
|
|
124
113
|
ctx.multi_agent = False
|
|
125
114
|
ctx.plan_obj = plan_obj
|
|
126
115
|
ctx.step_routes = step_routes or []
|
|
127
|
-
sv_plan.stage_name = "plan"
|
|
128
116
|
|
|
129
117
|
|
|
130
|
-
|
|
118
|
+
@named_stage("dispatch")
|
|
131
119
|
async def sv_dispatch(ctx: PipelineContext) -> None:
|
|
132
120
|
agent = ctx.agent
|
|
133
121
|
if ctx.extra.get("_meta_answered"):
|
|
134
122
|
return
|
|
135
123
|
|
|
136
|
-
# 主管自答(route stage 的决策在此执行)
|
|
137
124
|
if ctx.extra.get("_self_answer"):
|
|
138
125
|
_msgs = [{"role": "user", "content": (
|
|
139
126
|
f"你是 Foxclaw 的主管 Agent(Supervisor),统筹整个智能体团队。"
|
|
@@ -141,9 +128,6 @@ async def sv_dispatch(ctx: PipelineContext) -> None:
|
|
|
141
128
|
f"问题作答,不要只做自我介绍、不要答非所问。若确属需要专门领域深办的任务,可"
|
|
142
129
|
f"简要引导用户去对应能力,但仍先给出有用的直接回应。"
|
|
143
130
|
)}]
|
|
144
|
-
# 联网增强:新闻/实时类(_web_augment 内 needs_search 命中)走百度搜索注入真实结果
|
|
145
|
-
# 再作答,避免主管凭空编造(系统 prompt 明令严禁模拟搜索)。纯闲聊 needs_search=False,
|
|
146
|
-
# 不搜、不减速。阻塞 I/O 放线程池(与 base._execute 同法),不堵 async 事件循环。
|
|
147
131
|
_msgs = await asyncio.to_thread(agent._web_augment, ctx.task, _msgs)
|
|
148
132
|
reply = await agent._call_llm(_msgs)
|
|
149
133
|
agent._store_memory(
|
|
@@ -157,7 +141,6 @@ async def sv_dispatch(ctx: PipelineContext) -> None:
|
|
|
157
141
|
|
|
158
142
|
subagents = getattr(agent, "_subagents", {})
|
|
159
143
|
|
|
160
|
-
# 多 Agent 执行:并行计划(无依赖)→ fork-join 并发;否则顺序执行后汇聚
|
|
161
144
|
if ctx.multi_agent and ctx.step_routes:
|
|
162
145
|
if getattr(ctx.plan_obj, "flow_type", "sequential") == "parallel":
|
|
163
146
|
agent._think(
|
|
@@ -173,58 +156,46 @@ async def sv_dispatch(ctx: PipelineContext) -> None:
|
|
|
173
156
|
subagents, ctx.context, ctx.memories)
|
|
174
157
|
return
|
|
175
158
|
|
|
176
|
-
# 单 Agent 分发
|
|
177
159
|
if ctx.target in subagents:
|
|
178
160
|
ctx.dispatch_result = await agent._dispatch_single(
|
|
179
161
|
ctx.task, ctx.target, ctx.sched, ctx.session_id,
|
|
180
162
|
subagents, ctx.context, ctx.memories, ctx.plan_obj)
|
|
181
163
|
return
|
|
182
164
|
|
|
183
|
-
# 子 Agent 不可用(未注册)→ 主管自处理
|
|
184
165
|
ctx.dispatch_result = await agent._dispatch_fallback(ctx.task, ctx.context)
|
|
185
|
-
sv_dispatch.stage_name = "dispatch"
|
|
186
166
|
|
|
187
167
|
|
|
188
|
-
|
|
168
|
+
@named_stage("replan")
|
|
189
169
|
async def sv_replan(ctx: PipelineContext) -> None:
|
|
190
170
|
if ctx.extra.get("_meta_answered") or ctx.extra.get("_self_answer"):
|
|
191
171
|
return
|
|
192
172
|
agent = ctx.agent
|
|
193
173
|
dr = ctx.dispatch_result or {}
|
|
194
174
|
if not dispatch_failed(dr):
|
|
195
|
-
return
|
|
175
|
+
return
|
|
196
176
|
if not agent._replan_enabled():
|
|
197
|
-
return
|
|
177
|
+
return
|
|
198
178
|
subagents = getattr(agent, "_subagents", {})
|
|
199
179
|
max_rounds = int((ctx.context or {}).get("replan_max_rounds", 2))
|
|
200
180
|
ctx.dispatch_result = await agent._replan_and_redo(
|
|
201
181
|
ctx.task, ctx.sched, ctx.session_id, subagents, ctx.context, ctx.memories,
|
|
202
182
|
dr, max_rounds=max_rounds)
|
|
203
|
-
sv_replan.stage_name = "replan"
|
|
204
183
|
|
|
205
184
|
|
|
206
|
-
|
|
185
|
+
@named_stage("aggregate")
|
|
207
186
|
async def sv_aggregate(ctx: PipelineContext) -> None:
|
|
208
|
-
# 清除内部短路标记,不外泄到返回体(它们会经 **ctx.extra 透传进 resp)
|
|
209
187
|
ctx.extra.pop("_meta_answered", None)
|
|
210
188
|
ctx.extra.pop("_self_answer", None)
|
|
211
189
|
dr = ctx.dispatch_result or {}
|
|
212
190
|
if not dr:
|
|
213
|
-
return
|
|
191
|
+
return
|
|
214
192
|
ctx.result = dr.get("result", ctx.result)
|
|
215
|
-
# 白名单透传(与原主管返回体逐字一致的顶层键);session_id/agent/thought_summary
|
|
216
|
-
# 由 base 模板统一组装,不从 dispatch_result 取。
|
|
217
193
|
for k in ("schedule", "plan", "process_id"):
|
|
218
194
|
if k in dr:
|
|
219
195
|
ctx.extra[k] = dr[k]
|
|
220
|
-
# 「未完成 ⇒ 不作为已完成真值沉淀」:任一子任务被 Harness 拦截 → 提升为顶层
|
|
221
|
-
# status=blocked(与 base 模板的 skill_status 提升同一机制),执行事件 blocked=True,
|
|
222
|
-
# 真值链不沉淀(呼应 P3-5)。成功/自答路径不带此键,行为逐字不变。
|
|
223
196
|
if isinstance(ctx.result, dict) and ctx.result.get("blocked"):
|
|
224
197
|
ctx.extra["skill_status"] = "blocked"
|
|
225
|
-
sv_aggregate.stage_name = "aggregate"
|
|
226
198
|
|
|
227
199
|
|
|
228
|
-
# 主管 pipeline 的 stage 序列(recall 复用默认实现:与旧 `self._recall_memory(task)` 逐字等价)
|
|
229
200
|
SUPERVISOR_STAGES = [stage_recall, sv_meta_check, sv_schedule, sv_route,
|
|
230
201
|
sv_plan, sv_dispatch, sv_replan, sv_aggregate]
|
|
@@ -152,9 +152,9 @@ def _invoke_plan_tool(router, system: str, user_content: str, agent_catalog: dic
|
|
|
152
152
|
return None
|
|
153
153
|
calls = res.get("tool_calls") or []
|
|
154
154
|
if not calls:
|
|
155
|
-
return None
|
|
155
|
+
return None
|
|
156
156
|
if calls[0].get("args_parse_failed"):
|
|
157
|
-
return None
|
|
157
|
+
return None
|
|
158
158
|
args = calls[0].get("args") or {}
|
|
159
159
|
steps = args.get("steps")
|
|
160
160
|
if not isinstance(steps, list):
|
|
@@ -168,7 +168,7 @@ def _invoke_plan_tool(router, system: str, user_content: str, agent_catalog: dic
|
|
|
168
168
|
continue
|
|
169
169
|
agent = str(s.get("agent") or "office").strip()
|
|
170
170
|
if agent not in agent_catalog:
|
|
171
|
-
agent = "office"
|
|
171
|
+
agent = "office"
|
|
172
172
|
dep = s.get("depends_on") or []
|
|
173
173
|
if not isinstance(dep, list):
|
|
174
174
|
dep = []
|
|
@@ -11,14 +11,11 @@
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
import re
|
|
13
13
|
from dataclasses import dataclass, field
|
|
14
|
+
from typing import Optional
|
|
14
15
|
|
|
15
|
-
# ── 单一真源:否定词 + 顺序/并行/列表 标记 ──────────────────────
|
|
16
16
|
NEGATIONS = ["不", "别", "无需", "不要", "取消", "甭", "don't", "no need", "without"]
|
|
17
|
-
# 顺序拆分信号(明确的顺序连接词;不用"和/并"等高风险常用字,避免误拆名词短语)
|
|
18
17
|
SPLIT_MARKERS = ["然后", "接着", "之后", "再", "最后", ";", ";"]
|
|
19
|
-
# 并行信号:出现这些"同时性"连接词 → 子任务彼此无依赖,可并行 fork
|
|
20
18
|
PARALLEL_TRIGGERS = ["同时", "分别", "并行", "一并"]
|
|
21
|
-
# 列表分隔符(仅在检出并行触发词时才当拆分点,避免误拆普通名词短语)
|
|
22
19
|
LIST_SEPS = ["、", ",", ",", ";", ";"]
|
|
23
20
|
|
|
24
21
|
|
|
@@ -55,15 +52,15 @@ class ScoredCandidate:
|
|
|
55
52
|
|
|
56
53
|
@dataclass
|
|
57
54
|
class TriggerRoute:
|
|
58
|
-
primary: str = None
|
|
55
|
+
primary: Optional[str] = None
|
|
59
56
|
confidence: float = 0.0
|
|
60
|
-
candidates: list = field(default_factory=list)
|
|
61
|
-
fallback: str = None
|
|
57
|
+
candidates: list = field(default_factory=list)
|
|
58
|
+
fallback: Optional[str] = None
|
|
62
59
|
needs_composition: bool = False
|
|
63
60
|
reason: str = ""
|
|
64
61
|
|
|
65
62
|
|
|
66
|
-
def trigger_route(task: str, candidates: list, fallback_id: str = None,
|
|
63
|
+
def trigger_route(task: str, candidates: list, fallback_id: str | None = None,
|
|
67
64
|
min_conf: float = 0.15, near_gap: float = 0.1) -> TriggerRoute:
|
|
68
65
|
"""触发词路由三步打分:否定过滤后的触发命中(0.8)+ 描述领域匹配(0.2),确定性排序。
|
|
69
66
|
这是 AHS 原 route 的算法,现作为三处共用的通用引擎。"""
|
|
@@ -75,17 +72,15 @@ def trigger_route(task: str, candidates: list, fallback_id: str = None,
|
|
|
75
72
|
triggers = c.triggers or []
|
|
76
73
|
if not triggers:
|
|
77
74
|
scored.append((c.id, 0.0)); continue
|
|
78
|
-
# 第1步:否定过滤后的触发命中数 → 命中即给强基础分,不被触发词总数稀释
|
|
79
75
|
hits = keyword_hits(task, triggers, negation=True)
|
|
80
76
|
hit_score = 0.0 if hits == 0 else min(0.6 + 0.15 * (hits - 1), 1.0)
|
|
81
|
-
# 第2步:领域匹配(描述里的词出现在任务中,粗略语义近似)
|
|
82
77
|
desc_words = [w for w in re.split(r"[\s,,、]", c.description or "") if len(w) >= 2]
|
|
83
78
|
domain_hit = sum(1 for w in desc_words if w in task)
|
|
84
79
|
domain_score = min(domain_hit / max(len(desc_words), 1), 1.0)
|
|
85
80
|
score = 0.8 * hit_score + 0.2 * domain_score
|
|
86
81
|
scored.append((c.id, round(score, 3)))
|
|
87
82
|
|
|
88
|
-
scored.sort(key=lambda x: (-x[1], x[0]))
|
|
83
|
+
scored.sort(key=lambda x: (-x[1], x[0]))
|
|
89
84
|
top_id, top_score = scored[0]
|
|
90
85
|
cands = [(i, s) for i, s in scored if s > 0]
|
|
91
86
|
|
|
@@ -29,7 +29,7 @@ def _score(query: str, text: str) -> int:
|
|
|
29
29
|
for term in re.split(r"[\s,,、;;::/]+", q):
|
|
30
30
|
if len(term) >= 2 and term in t:
|
|
31
31
|
score += 2
|
|
32
|
-
for i in range(len(q) - 1):
|
|
32
|
+
for i in range(len(q) - 1):
|
|
33
33
|
if q[i:i + 2] in t:
|
|
34
34
|
score += 1
|
|
35
35
|
return score
|
|
@@ -65,13 +65,12 @@ class AgentSourcePort:
|
|
|
65
65
|
try:
|
|
66
66
|
scored += gather(text, top_k)
|
|
67
67
|
except Exception:
|
|
68
|
-
continue
|
|
68
|
+
continue
|
|
69
69
|
scored.sort(key=lambda x: x[0], reverse=True)
|
|
70
70
|
return [ev for _, ev in scored[:max(top_k * 2, top_k)]]
|
|
71
71
|
|
|
72
|
-
# ── 1. 领域内核 ──────────────────────────────────────────
|
|
73
72
|
def _from_finance(self, text, top_k):
|
|
74
|
-
out = []
|
|
73
|
+
out: list[tuple[float, RawEvidence]] = []
|
|
75
74
|
rt = self.finance
|
|
76
75
|
if rt is None:
|
|
77
76
|
return out
|
|
@@ -101,7 +100,7 @@ class AgentSourcePort:
|
|
|
101
100
|
return out
|
|
102
101
|
|
|
103
102
|
def _from_learning(self, text, top_k):
|
|
104
|
-
out = []
|
|
103
|
+
out: list[tuple[float, RawEvidence]] = []
|
|
105
104
|
rt = self.learning
|
|
106
105
|
if rt is None:
|
|
107
106
|
return out
|
|
@@ -116,7 +115,7 @@ class AgentSourcePort:
|
|
|
116
115
|
return out
|
|
117
116
|
|
|
118
117
|
def _from_itinerary(self, text, top_k):
|
|
119
|
-
out = []
|
|
118
|
+
out: list[tuple[float, RawEvidence]] = []
|
|
120
119
|
rt = self.itinerary
|
|
121
120
|
if rt is None:
|
|
122
121
|
return out
|
|
@@ -130,9 +129,8 @@ class AgentSourcePort:
|
|
|
130
129
|
confidence=_conf(s))))
|
|
131
130
|
return out
|
|
132
131
|
|
|
133
|
-
# ── 2. ThoughtChain 推理链 ───────────────────────────────
|
|
134
132
|
def _from_thoughtchain(self, text, top_k):
|
|
135
|
-
out = []
|
|
133
|
+
out: list[tuple[float, RawEvidence]] = []
|
|
136
134
|
tc = self.thought_chain
|
|
137
135
|
if tc is None or not hasattr(tc, "search_nodes"):
|
|
138
136
|
return out
|
|
@@ -145,9 +143,8 @@ class AgentSourcePort:
|
|
|
145
143
|
confidence=_conf(max(s, 1)))))
|
|
146
144
|
return out
|
|
147
145
|
|
|
148
|
-
# ── 3. 各 Agent 的 HMR 记忆 ─────────────────────────────
|
|
149
146
|
def _from_memories(self, text, top_k):
|
|
150
|
-
out = []
|
|
147
|
+
out: list[tuple[float, RawEvidence]] = []
|
|
151
148
|
if self.hmr is None:
|
|
152
149
|
return out
|
|
153
150
|
for aid in (self.agents.keys() if self.agents else []):
|
|
@@ -167,7 +164,6 @@ class AgentSourcePort:
|
|
|
167
164
|
confidence=_conf(s))))
|
|
168
165
|
return out
|
|
169
166
|
|
|
170
|
-
# ── 4. 能力/状态目录 ─────────────────────────────────────
|
|
171
167
|
def _from_capabilities(self, text, top_k):
|
|
172
168
|
out = []
|
|
173
169
|
reg = self.skill_registry
|
|
@@ -24,7 +24,6 @@ from .models import RawEvidence, EvidenceGraph, EvidenceNode, EvidenceEdge
|
|
|
24
24
|
_SIMILARITY_SUPPORT = 0.6
|
|
25
25
|
_SIMILARITY_DUP = 0.92
|
|
26
26
|
|
|
27
|
-
# 否定词:ASCII 按词边界(防 not⊂another/notebook、no⊂know);中文按子串(中文无词边界)
|
|
28
27
|
_NEG_ASCII = ("not", "no longer", "never", "cannot", "isn't", "aren't", "won't")
|
|
29
28
|
_NEG_CJK = ("不是", "并非", "并不", "没有", "无法", "不会", "未能")
|
|
30
29
|
_NEG_ASCII_RE = re.compile(
|
|
@@ -37,9 +36,6 @@ def _has_neg(text: str) -> bool:
|
|
|
37
36
|
|
|
38
37
|
|
|
39
38
|
def _sem_model_name() -> str:
|
|
40
|
-
# 【P2·单 embedding 入口】解析链:与 harness.py 一致 ——
|
|
41
|
-
# 显式传参 > EMBEDDING_MODEL(单入口) > HKC_EMBEDDING_MODEL > FOXCLAW_EVIDENCE_MODEL > HKC_KEE_SEMANTIC_MODEL > MiniLM 兜底。
|
|
42
|
-
# 与 KEE 默认同名 → get_st_model 共享同一实例;可经环境变量换任意 ST 模型(模型无关)
|
|
43
39
|
return (os.environ.get("FOXCLAW_EVIDENCE_MODEL")
|
|
44
40
|
or os.environ.get("EMBEDDING_MODEL")
|
|
45
41
|
or os.environ.get("HKC_EMBEDDING_MODEL")
|
|
@@ -23,9 +23,9 @@ _UNGROUNDED_PENALTY = 0.15
|
|
|
23
23
|
|
|
24
24
|
class InternalSearchEngine:
|
|
25
25
|
def __init__(self, memory_port=None, knowledge_port=None, agent_port=None):
|
|
26
|
-
self.memory_port = memory_port
|
|
27
|
-
self.knowledge_port = knowledge_port
|
|
28
|
-
self.agent_port = agent_port
|
|
26
|
+
self.memory_port = memory_port
|
|
27
|
+
self.knowledge_port = knowledge_port
|
|
28
|
+
self.agent_port = agent_port
|
|
29
29
|
|
|
30
30
|
@staticmethod
|
|
31
31
|
def _safe_query(port, text: str, top_k: int) -> list[RawEvidence]:
|
|
@@ -46,8 +46,6 @@ class InternalSearchEngine:
|
|
|
46
46
|
memory_hits = self._safe_query(self.memory_port, text, top_k)
|
|
47
47
|
agent_hits = self._safe_query(self.agent_port, text, top_k)
|
|
48
48
|
|
|
49
|
-
# 知识候选用「内部一切可溯源证据」(记忆 + Agent 态势)交叉验证:找得到就 grounded,
|
|
50
|
-
# 找不到就做孤证惩罚。这样 HKC 权威知识与 Agent 实际持有的数据互为印证。
|
|
51
49
|
if knowledge_hits:
|
|
52
50
|
knowledge_hits = self._cross_link(knowledge_hits, memory_hits + agent_hits)
|
|
53
51
|
|
|
@@ -12,17 +12,16 @@ from typing import Any, Optional
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class RouteType(str, Enum):
|
|
15
|
-
INTERNAL = "HSR-I"
|
|
16
|
-
EXTERNAL = "HSR-E"
|
|
17
|
-
HYBRID = "HYBRID"
|
|
15
|
+
INTERNAL = "HSR-I"
|
|
16
|
+
EXTERNAL = "HSR-E"
|
|
17
|
+
HYBRID = "HYBRID"
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class SourceType(str, Enum):
|
|
21
|
-
MEMORY = "memory"
|
|
22
|
-
KNOWLEDGE = "knowledge"
|
|
23
|
-
AGENT = "agent"
|
|
24
|
-
|
|
25
|
-
WEB = "web" # 来自联网搜索(当前接的是百度 AI 搜索)
|
|
21
|
+
MEMORY = "memory"
|
|
22
|
+
KNOWLEDGE = "knowledge"
|
|
23
|
+
AGENT = "agent"
|
|
24
|
+
WEB = "web"
|
|
26
25
|
|
|
27
26
|
|
|
28
27
|
@dataclass
|
|
@@ -45,11 +44,10 @@ class RawEvidence:
|
|
|
45
44
|
id: str
|
|
46
45
|
fact: str
|
|
47
46
|
source: SourceType
|
|
48
|
-
origin: str
|
|
47
|
+
origin: str
|
|
49
48
|
confidence: float = 0.5
|
|
50
49
|
raw: Any = None
|
|
51
|
-
grounded_by: Optional[str] = None
|
|
52
|
-
# "这条知识由这条 HMR 记忆溯源支撑"
|
|
50
|
+
grounded_by: Optional[str] = None
|
|
53
51
|
|
|
54
52
|
|
|
55
53
|
@dataclass
|
|
@@ -66,7 +64,7 @@ class EvidenceNode:
|
|
|
66
64
|
class EvidenceEdge:
|
|
67
65
|
src: str
|
|
68
66
|
dst: str
|
|
69
|
-
relation: str
|
|
67
|
+
relation: str
|
|
70
68
|
|
|
71
69
|
|
|
72
70
|
@dataclass
|
|
@@ -103,7 +101,7 @@ class EvidenceGraph:
|
|
|
103
101
|
|
|
104
102
|
@dataclass
|
|
105
103
|
class StrategyDecision:
|
|
106
|
-
action: str
|
|
104
|
+
action: str
|
|
107
105
|
reason: str
|
|
108
106
|
next_steps: list[SubQuery] = field(default_factory=list)
|
|
109
107
|
|
|
@@ -7,7 +7,7 @@ Query Planner —— 决定路由(HSR-I / HSR-E / HYBRID),并把任务拆成
|
|
|
7
7
|
"""
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
from .models import SearchPlan, SubQuery, RouteType
|
|
10
|
-
from ..planning import planner as P
|
|
10
|
+
from ..planning import planner as P
|
|
11
11
|
|
|
12
12
|
_MEMORY_HINTS = ("我之前", "我们讨论过", "上次", "记得", "history", "之前说")
|
|
13
13
|
_HYBRID_HINTS = ("为什么", "对比", "分析", "综合", "评估")
|
|
@@ -28,7 +28,7 @@ class QueryPlanner:
|
|
|
28
28
|
steps = [SubQuery(id="s1", text=query)]
|
|
29
29
|
elif route == RouteType.EXTERNAL:
|
|
30
30
|
steps = self._decompose(query)
|
|
31
|
-
else:
|
|
31
|
+
else:
|
|
32
32
|
steps = [SubQuery(id="s1", text=query), *self._decompose(query, offset=2)]
|
|
33
33
|
|
|
34
34
|
return SearchPlan(
|
|
@@ -38,7 +38,7 @@ class QueryPlanner:
|
|
|
38
38
|
)
|
|
39
39
|
|
|
40
40
|
def _decompose(self, query: str, offset: int = 1) -> list[SubQuery]:
|
|
41
|
-
parts = P.split_on(query, P.LIST_SEPS)
|
|
41
|
+
parts = P.split_on(query, P.LIST_SEPS)
|
|
42
42
|
if len(parts) <= 1:
|
|
43
43
|
parts = [query]
|
|
44
44
|
return [SubQuery(id=f"s{offset + i}", text=p) for i, p in enumerate(parts)]
|
|
@@ -69,22 +69,16 @@ class SearchRuntime:
|
|
|
69
69
|
def __init__(self, hmr=None, knowledge_port=None, agent_port=None,
|
|
70
70
|
external_ports: list | None = None):
|
|
71
71
|
self.hmr = hmr
|
|
72
|
-
# knowledge_port 由 foxclaw/__init__.py 在启动时构造好传进来
|
|
73
|
-
# (进程内直连的 HKCReadPort)。这里不做任何自动连接/默认值——
|
|
74
|
-
# 连不连 HKC、怎么连,是 Foxclaw 启动流程的事,不是 SearchRuntime 的事。
|
|
75
72
|
self.knowledge_port = knowledge_port
|
|
76
|
-
# agent_port:内部搜索的「Agent」源(领域内核 + 推理链 + 各Agent记忆 + 能力/状态),
|
|
77
|
-
# 同样由 Foxclaw 启动时用各持有方引用构造好传进来。
|
|
78
73
|
self.agent_port = agent_port
|
|
79
74
|
self.planner = QueryPlanner()
|
|
80
75
|
self.fusion = EvidenceFusion()
|
|
81
76
|
self.strategy = SearchStrategy()
|
|
82
|
-
# 外部端口(联网)面向 AI/Agent:是证据/工具管线的一环,不是给人的搜索框。
|
|
83
77
|
self.external_ports = (
|
|
84
78
|
external_ports if external_ports is not None else [BaiduWebSearchPort()]
|
|
85
79
|
)
|
|
86
80
|
|
|
87
|
-
def search(self, query: str, agent_id: str = None, context: dict = None,
|
|
81
|
+
def search(self, query: str, agent_id: str | None = None, context: dict | None = None,
|
|
88
82
|
internal_only: bool = False) -> SearchResult:
|
|
89
83
|
"""唯一公开入口。给定 query,返回 SearchResult,过程中不写任何持久化状态。
|
|
90
84
|
|
|
@@ -101,7 +95,6 @@ class SearchRuntime:
|
|
|
101
95
|
trace: list[str] = []
|
|
102
96
|
plan = self.planner.plan(query, context)
|
|
103
97
|
if internal_only and plan.route == RouteType.EXTERNAL:
|
|
104
|
-
# 只查内部时,把纯外部路由拉回内部,保证内部三源(记忆/知识/Agent)被检索
|
|
105
98
|
plan.route = RouteType.INTERNAL
|
|
106
99
|
trace.append("[internal_only] 路由改写为 HSR-I(仅内部)")
|
|
107
100
|
trace.append(f"[plan] {plan.reasoning}")
|