foxclaw 0.1.2 → 0.1.4
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/.github/workflows/release.yml +5 -0
- package/python/CHANGELOG.md +100 -0
- package/python/CHANGELOG_CN.md +100 -0
- package/python/README.md +17 -2
- package/python/README_CN.md +38 -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/CONTENT_GUARD.md +189 -0
- package/python/docs/CONTENT_GUARD_CN.md +189 -0
- package/python/docs/RELEASE.md +78 -37
- 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 +74 -152
- package/python/foxclaw/agents/base.py +82 -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 +23 -3
- package/python/foxclaw/agents/mixins/preference.py +58 -8
- package/python/foxclaw/agents/mixins/prompt.py +59 -12
- package/python/foxclaw/agents/mixins/skillbox.py +6 -6
- package/python/foxclaw/agents/mixins/tool_exec.py +16 -42
- package/python/foxclaw/agents/rules/finance/RULES.md +131 -0
- package/python/foxclaw/api/__init__.py +18 -77
- package/python/foxclaw/api/_state.py +43 -0
- package/python/foxclaw/api/bootstrap.py +114 -0
- 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 +50 -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/behavior/__init__.py +20 -0
- package/python/foxclaw/behavior/behavior.yaml +62 -0
- package/python/foxclaw/behavior/spec.py +287 -0
- 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/content_guard.py +341 -0
- 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 +53 -60
- package/python/foxclaw/core/metrics.py +4 -5
- package/python/foxclaw/core/model_router.py +74 -150
- 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/sensitive_write.py +283 -0
- 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 +18 -201
- package/python/frontend/v3/assets/index-JKfHlb-a.css +1 -0
- package/python/frontend/v3/assets/index-tN90dpUH.js +34 -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 +42 -3
- 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 +16 -44
- 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 +17 -3
- package/python/requirements.txt +311 -0
- package/python/tests/conftest.py +0 -22
- package/python/tests/run_all.py +4 -66
- package/python/tests/test_agent_lifecycle.py +4 -38
- package/python/tests/test_agent_rules.py +81 -0
- 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_behavior_spec.py +198 -0
- 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_conflict_dismiss.py +143 -0
- package/python/tests/test_content_guard.py +177 -0
- 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_sensitive_write.py +181 -0
- 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/build_content_lexicon.py +218 -0
- package/python/tools/bundle_web.py +98 -0
- 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/recheck_conflicts.py +156 -0
- package/python/tools/release_check.py +24 -15
- 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/vendor/hkc/hkc_api/routes/ability.py +31 -0
- package/python/vendor/hkc/hkc_api/schemas.py +11 -0
- package/python/vendor/hkc/hkc_kee/kee.py +124 -3
- package/python/vendor/hkc/hkc_sdk/base.py +22 -0
- package/python/vendor/hkc/hkc_sdk/direct_client.py +10 -0
- package/python/vendor/hkc/hkc_sdk/http_client.py +19 -0
- package/python/foxclaw/api/app.py +0 -123
- package/python/frontend/v3/assets/index-BD7VPbMV.js +0 -33
- package/python/frontend/v3/assets/index-CAvnYWGH.css +0 -1
|
@@ -23,40 +23,23 @@ import urllib.error
|
|
|
23
23
|
from . import settings
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
# 触发联网搜索的关键词:问到实时/时效性信息时才搜
|
|
27
|
-
# ── 是否需要联网:话题信号(强) vs 时间词(弱) vs 个人数据(否决)──────────────
|
|
28
|
-
# 教训:曾把纯时间词(今天/本月/最近…)也当触发,结果「分析一下本月支出」这类**问自己账本**
|
|
29
|
-
# 的任务被误判为需要联网 → 白跑一次 ~22s 的付费深度搜索。而 Foxclaw 的领域(个人财务/
|
|
30
|
-
# 行程/学习)天生满是时间词,这不是个别误判,是**一整类合法任务**在白交时间和钱。
|
|
31
|
-
# 现在:时间词单独**不足以**触发;必须出现"外部世界话题";且个人数据一律否决。
|
|
32
|
-
# (与既有的 now⊂knowledge / current⊂concurrent 词边界修复同源:都是过度触发。)
|
|
33
|
-
|
|
34
|
-
# 强触发:出现即说明要的是外部实时信息
|
|
35
26
|
TOPIC_TRIGGERS = [
|
|
36
27
|
"版本", "几个版本", "第几", "价格", "多少钱", "汇率", "股价", "新闻",
|
|
37
28
|
"发布", "上线", "更新到", "迭代", "排行", "排名", "天气",
|
|
38
|
-
# 问"某产品/技术的模型/型号/出没出/更没更"——都是外部时效信息,过去漏搜→编造。
|
|
39
|
-
# 只留明确指向外部产品的词;"有哪些/可用/支持"太宽(会误伤"我有哪些任务"),不加。
|
|
40
29
|
"模型", "型号", "出了没", "发布没", "更新没", "新版",
|
|
41
30
|
"latest", "news", "version", "price", "models",
|
|
42
31
|
]
|
|
43
|
-
# 弱信号:纯时间词。单独出现不触发(个人数据问题天生带这些词)
|
|
44
32
|
TIME_WORDS = [
|
|
45
33
|
"最新", "现在", "目前", "今天", "今日", "昨天", "本周", "本月", "今年",
|
|
46
34
|
"最近", "当前", "today", "current", "now",
|
|
47
35
|
]
|
|
48
|
-
# 否决:问的是用户自己的账/行程/学习/记忆 —— 这些答案在本地领域内核里,不在网上。
|
|
49
|
-
# 注意别放"我的"这类过宽的词:「我的股票现在多少钱」是真需要联网的。
|
|
50
36
|
PERSONAL_MARKERS = [
|
|
51
37
|
"支出", "收入", "预算", "交易", "账单", "花了", "消费", "记账", "结余", "余额",
|
|
52
38
|
"行程", "日程", "出行", "复习", "知识点", "学习计划", "记忆",
|
|
53
39
|
]
|
|
54
40
|
|
|
55
|
-
SEARCH_TRIGGERS = TOPIC_TRIGGERS
|
|
41
|
+
SEARCH_TRIGGERS = TOPIC_TRIGGERS
|
|
56
42
|
|
|
57
|
-
# 中文触发词按「子串」匹配(中文无词边界);英文触发词按「词边界」匹配,
|
|
58
|
-
# 否则短词会误命中常见词:"now"⊂"knowledge"、"current"⊂"concurrent"、
|
|
59
|
-
# "now"⊂"known" —— 在这个满口 knowledge 的项目里会频繁无谓地打百度。
|
|
60
43
|
_CJK_TRIGGERS = [k for k in SEARCH_TRIGGERS if not k.isascii()]
|
|
61
44
|
_ASCII_TRIGGERS = [k.lower() for k in SEARCH_TRIGGERS if k.isascii()]
|
|
62
45
|
_ASCII_RE = (
|
|
@@ -85,16 +68,12 @@ def needs_search(query: str) -> bool:
|
|
|
85
68
|
return False
|
|
86
69
|
q = query.lower()
|
|
87
70
|
if is_personal_query(q):
|
|
88
|
-
return False
|
|
89
|
-
# 自我模型/身份问题(你是什么模型 / 你现在是哪个模型 …)→ 由 ModelRouter 运行时注入本地即答,不联网。
|
|
90
|
-
# 否则会因含"模型"触发词白跑一次 ~20s 百度搜索。"你"与"模型"相邻(≤8字)才算自问;
|
|
91
|
-
# "你帮我查 X 的模型"(相距远)仍走搜索。
|
|
71
|
+
return False
|
|
92
72
|
if re.search(r"你[^,。?!]{0,8}模型", q):
|
|
93
73
|
return False
|
|
94
74
|
if any(k in q for k in _CJK_TRIGGERS):
|
|
95
|
-
return True
|
|
75
|
+
return True
|
|
96
76
|
return bool(_ASCII_RE and _ASCII_RE.search(q))
|
|
97
|
-
# 注:只含时间词(今天/本月/最近…)而无话题 → 不触发(见上方 TIME_WORDS 说明)
|
|
98
77
|
|
|
99
78
|
|
|
100
79
|
def _key() -> str:
|
|
@@ -139,7 +118,7 @@ def _search_answer_inner(query: str, key: str) -> dict:
|
|
|
139
118
|
"stream": False,
|
|
140
119
|
"model": _model(),
|
|
141
120
|
"enable_deep_search": _deep(),
|
|
142
|
-
"enable_corner_markers": False,
|
|
121
|
+
"enable_corner_markers": False,
|
|
143
122
|
}).encode("utf-8")
|
|
144
123
|
req = urllib.request.Request(
|
|
145
124
|
f"{_base()}/chat/completions",
|
|
@@ -193,7 +172,6 @@ def format_for_prompt(query: str, payload) -> str:
|
|
|
193
172
|
refs = payload.get("references", [])
|
|
194
173
|
if not answer and not refs:
|
|
195
174
|
return ""
|
|
196
|
-
# 我方的作答指令(可信,放在 <untrusted> 之外);检索到的外部内容(不可信,放在 <untrusted> 之内)。
|
|
197
175
|
framing = (
|
|
198
176
|
f"以下是针对「{query}」的实时联网搜索结果(来自百度 AI 搜索)。\n"
|
|
199
177
|
f"【严格要求】只能依据下面 <untrusted> 数据块里的搜索结果作答:结果里没有出现的型号 / "
|
|
@@ -215,13 +193,11 @@ def format_for_prompt(query: str, payload) -> str:
|
|
|
215
193
|
if u:
|
|
216
194
|
inner += f"\n链接:{u}"
|
|
217
195
|
inner += f"\n[webpage {i} end]\n"
|
|
218
|
-
# 隔离外部内容:搜索结果是外部不可控数据,包裹进 <untrusted> 防间接注入(见 injection_guard)。
|
|
219
196
|
from . import injection_guard as _ig
|
|
220
197
|
block = _ig.wrap_untrusted(inner.rstrip(), source="web_search")
|
|
221
198
|
closing = ("\n请**严格基于以上 <untrusted> 内的搜索结果**回答用户的问题;结果里没提到的内容,"
|
|
222
199
|
"就明确说「搜索结果中未提及」,绝不编造型号/版本号。")
|
|
223
200
|
return framing + block + closing
|
|
224
|
-
# 列表形式(兼容)
|
|
225
201
|
results = payload or []
|
|
226
202
|
if not results:
|
|
227
203
|
return ""
|
|
@@ -17,9 +17,9 @@ from datetime import datetime
|
|
|
17
17
|
@dataclass
|
|
18
18
|
class RiskFactor:
|
|
19
19
|
"""单个风险因子:是什么、证据、权重。"""
|
|
20
|
-
factor: str
|
|
21
|
-
evidence: str
|
|
22
|
-
weight: float
|
|
20
|
+
factor: str
|
|
21
|
+
evidence: str
|
|
22
|
+
weight: float
|
|
23
23
|
|
|
24
24
|
def to_dict(self) -> dict:
|
|
25
25
|
return {"factor": self.factor, "evidence": self.evidence,
|
|
@@ -29,14 +29,14 @@ class RiskFactor:
|
|
|
29
29
|
@dataclass
|
|
30
30
|
class EvidenceChain:
|
|
31
31
|
"""结构化决策证据链 —— 财务决策的可审计输出。"""
|
|
32
|
-
decision_type: str
|
|
33
|
-
risk_score: float = 0.0
|
|
34
|
-
risk_factors: list = field(default_factory=list)
|
|
35
|
-
mitigating_factors: list = field(default_factory=list)
|
|
36
|
-
rationale: str = ""
|
|
37
|
-
recommended_action: str = ""
|
|
38
|
-
confidence: float = 0.0
|
|
39
|
-
additional_checks: list = field(default_factory=list)
|
|
32
|
+
decision_type: str
|
|
33
|
+
risk_score: float = 0.0
|
|
34
|
+
risk_factors: list = field(default_factory=list)
|
|
35
|
+
mitigating_factors: list = field(default_factory=list)
|
|
36
|
+
rationale: str = ""
|
|
37
|
+
recommended_action: str = ""
|
|
38
|
+
confidence: float = 0.0
|
|
39
|
+
additional_checks: list = field(default_factory=list)
|
|
40
40
|
created_at: str = field(default_factory=lambda: datetime.now().isoformat(timespec="seconds"))
|
|
41
41
|
|
|
42
42
|
def add_factor(self, factor: str, evidence: str, weight: float):
|
|
@@ -53,10 +53,10 @@ class EvidenceChain:
|
|
|
53
53
|
self.risk_score = 0.0
|
|
54
54
|
return 0.0
|
|
55
55
|
weights = sorted((f.weight for f in self.risk_factors), reverse=True)
|
|
56
|
-
score = weights[0]
|
|
56
|
+
score = weights[0]
|
|
57
57
|
for w in weights[1:]:
|
|
58
|
-
score += w * (1 - score) * 0.5
|
|
59
|
-
mitigation = min(0.2, 0.1 * len(self.mitigating_factors))
|
|
58
|
+
score += w * (1 - score) * 0.5
|
|
59
|
+
mitigation = min(0.2, 0.1 * len(self.mitigating_factors))
|
|
60
60
|
self.risk_score = max(0.0, min(1.0, round(score - mitigation, 3)))
|
|
61
61
|
return self.risk_score
|
|
62
62
|
|
|
@@ -100,7 +100,6 @@ class FinanceExplainer:
|
|
|
100
100
|
cat = b.get("category", "?")
|
|
101
101
|
if ratio >= 1.0:
|
|
102
102
|
over = b.get("spent", 0) - b.get("monthly_limit", 0)
|
|
103
|
-
# 权重随超支程度递增:超10%→0.5,超50%→0.65,超100%+→0.8(上限)
|
|
104
103
|
w = min(0.8, 0.45 + (ratio - 1.0) * 0.35)
|
|
105
104
|
ec.add_factor("超预算", f"{cat} 已用 {ratio*100:.0f}%,超支 ¥{over:.0f}",
|
|
106
105
|
weight=w)
|
|
@@ -109,13 +108,11 @@ class FinanceExplainer:
|
|
|
109
108
|
ec.add_factor("接近预算上限",
|
|
110
109
|
f"{cat} 已用 {ratio*100:.0f}%,接近上限", weight=0.25)
|
|
111
110
|
near_budgets.append(cat)
|
|
112
|
-
# 缓解因素:有预算分类未超支
|
|
113
111
|
healthy = [b.get("category") for b in budget_status
|
|
114
112
|
if b.get("usage_ratio", 0) < 0.8]
|
|
115
113
|
if healthy:
|
|
116
114
|
ec.mitigating_factors.append(f"{len(healthy)} 个分类预算健康")
|
|
117
115
|
ec.compute_risk()
|
|
118
|
-
# 理由 + 建议
|
|
119
116
|
if over_budgets:
|
|
120
117
|
ec.rationale = f"{','.join(over_budgets)} 已超预算,需立即关注"
|
|
121
118
|
ec.recommended_action = f"削减 {over_budgets[0]} 支出或调整预算"
|
|
@@ -134,7 +131,6 @@ class FinanceExplainer:
|
|
|
134
131
|
ec = EvidenceChain(decision_type="anomaly", confidence=0.85)
|
|
135
132
|
for a in anomalies[:5]:
|
|
136
133
|
z = a.get("z_score", 0)
|
|
137
|
-
# z 分数越高,权重越大(裁剪到 0.6)
|
|
138
134
|
w = min(0.6, 0.15 + z * 0.05)
|
|
139
135
|
ec.add_factor("异常大额支出",
|
|
140
136
|
f"{a.get('entity','?')} ¥{abs(a.get('amount',0)):.0f}"
|
|
@@ -15,7 +15,6 @@ from pathlib import Path
|
|
|
15
15
|
|
|
16
16
|
BASE_CURRENCY = "CNY"
|
|
17
17
|
|
|
18
|
-
# 参考价(1 单位外币 = 多少 CNY)。宁可粗,但要能改。
|
|
19
18
|
DEFAULT_RATES = {
|
|
20
19
|
"CNY": 1.0,
|
|
21
20
|
"USD": 7.2,
|
|
@@ -33,7 +32,7 @@ DEFAULT_RATES = {
|
|
|
33
32
|
class FxTable:
|
|
34
33
|
"""币种 → 本位币折算率,带落盘(原子写)。"""
|
|
35
34
|
|
|
36
|
-
def __init__(self, path: str = None):
|
|
35
|
+
def __init__(self, path: str | None = None):
|
|
37
36
|
self.path = Path(path) if path else None
|
|
38
37
|
self.rates = dict(DEFAULT_RATES)
|
|
39
38
|
self.note = "static"
|
|
@@ -54,7 +53,6 @@ class FxTable:
|
|
|
54
53
|
if data.get("note"):
|
|
55
54
|
self.note = data["note"]
|
|
56
55
|
except Exception:
|
|
57
|
-
# 汇率表坏了不能拖垮记账:退回出厂参考价,下次保存时自动覆盖
|
|
58
56
|
pass
|
|
59
57
|
|
|
60
58
|
def save(self) -> bool:
|
|
@@ -18,7 +18,7 @@ from dataclasses import dataclass, field
|
|
|
18
18
|
@dataclass
|
|
19
19
|
class GraphNode:
|
|
20
20
|
node_id: str
|
|
21
|
-
node_type: str
|
|
21
|
+
node_type: str
|
|
22
22
|
label: str
|
|
23
23
|
attrs: dict = field(default_factory=dict)
|
|
24
24
|
|
|
@@ -31,7 +31,7 @@ class GraphNode:
|
|
|
31
31
|
class GraphEdge:
|
|
32
32
|
src: str
|
|
33
33
|
dst: str
|
|
34
|
-
rel: str
|
|
34
|
+
rel: str
|
|
35
35
|
attrs: dict = field(default_factory=dict)
|
|
36
36
|
|
|
37
37
|
def to_dict(self) -> dict:
|
|
@@ -45,15 +45,13 @@ class FinancialDataGraph:
|
|
|
45
45
|
def __init__(self):
|
|
46
46
|
self.nodes: dict[str, GraphNode] = {}
|
|
47
47
|
self.edges: list[GraphEdge] = []
|
|
48
|
-
self._adj: dict[str, list] = defaultdict(list)
|
|
49
|
-
self._radj: dict[str, list] = defaultdict(list)
|
|
48
|
+
self._adj: dict[str, list] = defaultdict(list)
|
|
49
|
+
self._radj: dict[str, list] = defaultdict(list)
|
|
50
50
|
|
|
51
|
-
# ── 构建 ──
|
|
52
51
|
def _ensure_node(self, node_id: str, node_type: str, label: str, **attrs):
|
|
53
52
|
if node_id not in self.nodes:
|
|
54
53
|
self.nodes[node_id] = GraphNode(node_id, node_type, label, dict(attrs))
|
|
55
54
|
else:
|
|
56
|
-
# 已存在 → 累加统计类属性
|
|
57
55
|
self.nodes[node_id].attrs.update(attrs)
|
|
58
56
|
return self.nodes[node_id]
|
|
59
57
|
|
|
@@ -63,7 +61,7 @@ class FinancialDataGraph:
|
|
|
63
61
|
self._adj[src].append(e)
|
|
64
62
|
self._radj[dst].append(e)
|
|
65
63
|
|
|
66
|
-
def build(self, transactions: list, budgets: list = None):
|
|
64
|
+
def build(self, transactions: list, budgets: list | None = None):
|
|
67
65
|
"""从交易 + 预算构建图。"""
|
|
68
66
|
self.nodes.clear(); self.edges.clear()
|
|
69
67
|
self._adj.clear(); self._radj.clear()
|
|
@@ -73,7 +71,6 @@ class FinancialDataGraph:
|
|
|
73
71
|
self._ensure_node(tid, "transaction", f"{t.entity} ¥{abs(t.amount):.0f}",
|
|
74
72
|
amount=t.amount, timestamp=t.timestamp,
|
|
75
73
|
is_expense=t.is_expense)
|
|
76
|
-
# 分类节点 + 边(消费统计只累加支出,收入/退款不计入'花费')
|
|
77
74
|
cat_id = f"cat:{t.category}"
|
|
78
75
|
cnode = self._ensure_node(cat_id, "category", t.category)
|
|
79
76
|
if t.is_expense:
|
|
@@ -82,19 +79,16 @@ class FinancialDataGraph:
|
|
|
82
79
|
else:
|
|
83
80
|
cnode.attrs["income"] = cnode.attrs.get("income", 0) + abs(t.amount)
|
|
84
81
|
self._add_edge(tid, cat_id, "belongs_to")
|
|
85
|
-
# 商户节点 + 边(同样只把支出计入消费)
|
|
86
82
|
ent_id = f"ent:{t.entity}"
|
|
87
83
|
enode = self._ensure_node(ent_id, "entity", t.entity)
|
|
88
84
|
if t.is_expense:
|
|
89
85
|
enode.attrs["total"] = enode.attrs.get("total", 0) + abs(t.amount)
|
|
90
86
|
enode.attrs["count"] = enode.attrs.get("count", 0) + 1
|
|
91
87
|
self._add_edge(tid, ent_id, "paid_to")
|
|
92
|
-
# 账户节点 + 边
|
|
93
88
|
acc_id = f"acc:{t.account}"
|
|
94
89
|
self._ensure_node(acc_id, "account", t.account)
|
|
95
90
|
self._add_edge(tid, acc_id, "charged_on")
|
|
96
91
|
|
|
97
|
-
# 预算节点 + 监管边(预算 monitors 分类)
|
|
98
92
|
for b in (budgets or []):
|
|
99
93
|
bid = f"bud:{b.category}"
|
|
100
94
|
self._ensure_node(bid, "budget", f"{b.category}预算",
|
|
@@ -104,8 +98,7 @@ class FinancialDataGraph:
|
|
|
104
98
|
self._add_edge(bid, cat_id, "monitors")
|
|
105
99
|
return self
|
|
106
100
|
|
|
107
|
-
|
|
108
|
-
def neighbors(self, node_id: str, rel: str = None) -> list:
|
|
101
|
+
def neighbors(self, node_id: str, rel: str | None = None) -> list:
|
|
109
102
|
"""出边邻居(可按关系过滤)。"""
|
|
110
103
|
return [self.nodes[e.dst] for e in self._adj.get(node_id, [])
|
|
111
104
|
if rel is None or e.rel == rel]
|
|
@@ -119,11 +112,10 @@ class FinancialDataGraph:
|
|
|
119
112
|
"""某分类下,按消费额排名的商户 —— 回答'这个分类的钱花在谁身上'。"""
|
|
120
113
|
cat_id = f"cat:{category}"
|
|
121
114
|
txns = self.transactions_of(cat_id)
|
|
122
|
-
by_entity = defaultdict(float)
|
|
115
|
+
by_entity: defaultdict[str, float] = defaultdict(float)
|
|
123
116
|
for t in txns:
|
|
124
117
|
if not t.attrs.get("is_expense"):
|
|
125
|
-
continue
|
|
126
|
-
# 找这笔交易付给了谁
|
|
118
|
+
continue
|
|
127
119
|
for e in self._adj.get(t.node_id, []):
|
|
128
120
|
if e.rel == "paid_to":
|
|
129
121
|
by_entity[self.nodes[e.dst].label] += abs(t.attrs.get("amount", 0))
|
|
@@ -139,7 +131,6 @@ class FinancialDataGraph:
|
|
|
139
131
|
cat = self.nodes[cat_id]
|
|
140
132
|
spent = cat.attrs.get("total", 0)
|
|
141
133
|
limit = self.nodes[bud_id].attrs.get("monthly_limit") if bud_id in self.nodes else None
|
|
142
|
-
# 主要贡献者(商户)
|
|
143
134
|
contributors = self.top_entities_in_category(category, top=3)
|
|
144
135
|
result = {
|
|
145
136
|
"category": category, "found": True,
|
|
@@ -170,7 +161,7 @@ class FinancialDataGraph:
|
|
|
170
161
|
return sorted(out, key=lambda x: -x["outflow"])
|
|
171
162
|
|
|
172
163
|
def stats(self) -> dict:
|
|
173
|
-
by_type = defaultdict(int)
|
|
164
|
+
by_type: defaultdict[str, int] = defaultdict(int)
|
|
174
165
|
for n in self.nodes.values():
|
|
175
166
|
by_type[n.node_type] += 1
|
|
176
167
|
return {"nodes": len(self.nodes), "edges": len(self.edges),
|
|
@@ -22,21 +22,20 @@ class Transaction:
|
|
|
22
22
|
原币金额与币种另存 `original_amount` / `currency` / `fx_rate` —— 这样「100 美元」
|
|
23
23
|
不会和「100 元」在求和时混在一起,同时账本里又看得到用户当时说的原话数额。
|
|
24
24
|
"""
|
|
25
|
-
amount: float
|
|
26
|
-
category: str
|
|
27
|
-
entity: str
|
|
28
|
-
account: str
|
|
25
|
+
amount: float
|
|
26
|
+
category: str
|
|
27
|
+
entity: str
|
|
28
|
+
account: str
|
|
29
29
|
timestamp: str = field(default_factory=lambda: datetime.now().isoformat())
|
|
30
|
-
tags: list = field(default_factory=list)
|
|
30
|
+
tags: list = field(default_factory=list)
|
|
31
31
|
note: str = ""
|
|
32
32
|
id: str = field(default_factory=lambda: f"txn_{uuid.uuid4().hex[:10]}")
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
source: str = "manual" # manual / text / csv / bank
|
|
33
|
+
currency: str = "CNY"
|
|
34
|
+
original_amount: Optional[float] = None
|
|
35
|
+
fx_rate: float = 1.0
|
|
36
|
+
description: str = ""
|
|
37
|
+
status: str = "settled"
|
|
38
|
+
source: str = "manual"
|
|
40
39
|
|
|
41
40
|
@property
|
|
42
41
|
def is_expense(self) -> bool:
|
|
@@ -72,8 +71,8 @@ class Transaction:
|
|
|
72
71
|
class Budget:
|
|
73
72
|
"""某分类的预算状态"""
|
|
74
73
|
category: str
|
|
75
|
-
monthly_limit: float
|
|
76
|
-
spent: float = 0.0
|
|
74
|
+
monthly_limit: float
|
|
75
|
+
spent: float = 0.0
|
|
77
76
|
period: str = field(default_factory=lambda: datetime.now().strftime("%Y-%m"))
|
|
78
77
|
|
|
79
78
|
@property
|
|
@@ -102,12 +101,12 @@ class FinancialEntity:
|
|
|
102
101
|
"""账户 / 商户 / 订阅"""
|
|
103
102
|
name: str
|
|
104
103
|
entity_type: EntityType
|
|
105
|
-
balance: Optional[float] = None
|
|
106
|
-
recurring_amount: Optional[float] = None
|
|
107
|
-
cycle: Optional[str] = None
|
|
104
|
+
balance: Optional[float] = None
|
|
105
|
+
recurring_amount: Optional[float] = None
|
|
106
|
+
cycle: Optional[str] = None
|
|
108
107
|
last_seen: str = ""
|
|
109
108
|
txn_count: int = 0
|
|
110
|
-
total_amount: float = 0.0
|
|
109
|
+
total_amount: float = 0.0
|
|
111
110
|
|
|
112
111
|
def to_dict(self) -> dict:
|
|
113
112
|
return asdict(self)
|
|
@@ -119,16 +118,13 @@ class FinancePolicyRule:
|
|
|
119
118
|
与 Harness 的 Policy YAML 同构,但作用于资金行为。
|
|
120
119
|
"""
|
|
121
120
|
id: str
|
|
122
|
-
condition: str
|
|
121
|
+
condition: str
|
|
123
122
|
action: Literal["alert", "require_confirmation", "freeze", "veto", "suggest"]
|
|
124
123
|
threshold: Optional[float] = None
|
|
125
|
-
scope: str = "global"
|
|
124
|
+
scope: str = "global"
|
|
126
125
|
severity: Literal["info", "warn", "critical"] = "warn"
|
|
127
126
|
source: Literal["builtin", "user", "learned"] = "builtin"
|
|
128
127
|
enabled: bool = True
|
|
129
|
-
# 自校准留痕:calibrated_value = 上次校准写下的阈值(用于判断"之后有没有人手动改过";
|
|
130
|
-
# 若 threshold 已偏离它,说明是人的决定,校准就此收手)。calibrated_from = 依据说明,
|
|
131
|
-
# 让阈值可解释 —— 用户看得到"这个 800 是怎么来的",才敢信它。
|
|
132
128
|
calibrated_value: Optional[float] = None
|
|
133
129
|
calibrated_from: Optional[str] = None
|
|
134
130
|
|
|
@@ -144,7 +140,7 @@ class PolicyVerdict:
|
|
|
144
140
|
severity: str
|
|
145
141
|
message: str
|
|
146
142
|
transaction_id: Optional[str] = None
|
|
147
|
-
requires_user_action: bool = False
|
|
143
|
+
requires_user_action: bool = False
|
|
148
144
|
|
|
149
145
|
def to_dict(self) -> dict:
|
|
150
146
|
return asdict(self)
|