cellium-agent 1.4.0__py3-none-any.whl
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.
- app/__init__.py +4 -0
- app/agent/control/__init__.py +84 -0
- app/agent/control/action_bandit.py +336 -0
- app/agent/control/constraint_gene/__init__.py +15 -0
- app/agent/control/constraint_gene/composer.py +158 -0
- app/agent/control/constraint_gene/evolution.py +954 -0
- app/agent/control/constraint_gene/matcher.py +241 -0
- app/agent/control/control_loop.py +590 -0
- app/agent/control/decision_renderer.py +184 -0
- app/agent/control/feedback_evaluator.py +276 -0
- app/agent/control/gene_post_session.py +200 -0
- app/agent/control/hard_constraints.py +459 -0
- app/agent/control/hybrid_controller.py +440 -0
- app/agent/control/loop_state.py +179 -0
- app/agent/control/thought_parser.py +174 -0
- app/agent/di_config.py +568 -0
- app/agent/events/__init__.py +40 -0
- app/agent/events/event_models.py +115 -0
- app/agent/events/event_types.py +37 -0
- app/agent/heuristics/__init__.py +42 -0
- app/agent/heuristics/config.py +108 -0
- app/agent/heuristics/engine.py +373 -0
- app/agent/heuristics/features.py +588 -0
- app/agent/heuristics/integration.py +237 -0
- app/agent/heuristics/rules/__init__.py +30 -0
- app/agent/heuristics/rules/base_rule.py +60 -0
- app/agent/heuristics/rules/loop_detection.py +242 -0
- app/agent/heuristics/rules/termination.py +404 -0
- app/agent/heuristics/scoring.py +175 -0
- app/agent/heuristics/trace.py +135 -0
- app/agent/heuristics/types.py +134 -0
- app/agent/learning/__init__.py +24 -0
- app/agent/learning/bandit.py +97 -0
- app/agent/learning/integration.py +282 -0
- app/agent/learning/memory_policy.py +184 -0
- app/agent/learning/policy.py +143 -0
- app/agent/llm/__init__.py +10 -0
- app/agent/llm/engine.py +958 -0
- app/agent/llm/models.py +31 -0
- app/agent/llm/transport.py +259 -0
- app/agent/loop/__init__.py +29 -0
- app/agent/loop/agent_loop.py +2303 -0
- app/agent/loop/agent_loop_manager.py +289 -0
- app/agent/loop/auto_hints.py +438 -0
- app/agent/loop/command_handler.py +74 -0
- app/agent/loop/loop_controller.py +122 -0
- app/agent/loop/loop_event_publisher.py +268 -0
- app/agent/loop/memory.py +347 -0
- app/agent/loop/round_trimmer.py +75 -0
- app/agent/loop/session_manager.py +444 -0
- app/agent/loop/session_store.py +329 -0
- app/agent/loop/tool_executor.py +671 -0
- app/agent/memory/__init__.py +30 -0
- app/agent/memory/archive_store.py +106 -0
- app/agent/memory/chinese_tokenizer.py +112 -0
- app/agent/memory/fts5_searcher.py +699 -0
- app/agent/memory/knowledge_extractor.py +189 -0
- app/agent/memory/repository.py +1521 -0
- app/agent/memory/session_compact.py +631 -0
- app/agent/memory/session_notes.py +323 -0
- app/agent/memory/three_layer.py +319 -0
- app/agent/memory/vector_native.py +169 -0
- app/agent/prompt/__init__.py +21 -0
- app/agent/prompt/builder.py +170 -0
- app/agent/prompt/diff.py +247 -0
- app/agent/prompt/piece.py +96 -0
- app/agent/prompt/pieces.py +304 -0
- app/agent/runtime/__init__.py +14 -0
- app/agent/runtime/context.py +841 -0
- app/agent/runtime/core.py +314 -0
- app/agent/runtime/diagnostics.py +375 -0
- app/agent/runtime/patch.py +126 -0
- app/agent/runtime/patch_applier.py +301 -0
- app/agent/runtime/transaction.py +78 -0
- app/agent/security/__init__.py +13 -0
- app/agent/security/policy.py +11 -0
- app/agent/shell/__init__.py +3 -0
- app/agent/shell/cellium_shell.py +1193 -0
- app/agent/tools/__init__.py +13 -0
- app/agent/tools/base_tool.py +338 -0
- app/agent/tools/config_tool.py +454 -0
- app/agent/tools/edit_tool.py +299 -0
- app/agent/tools/file_cache.py +100 -0
- app/agent/tools/file_tool.py +315 -0
- app/agent/tools/glob_tool.py +86 -0
- app/agent/tools/grep_tool.py +606 -0
- app/agent/tools/ls_tool.py +97 -0
- app/agent/tools/memory_tool.py +823 -0
- app/agent/tools/read_tool.py +453 -0
- app/agent/tools/shell_tool.py +345 -0
- app/channels/__init__.py +99 -0
- app/channels/base.py +289 -0
- app/channels/channel_manager.py +673 -0
- app/channels/channel_registry.py +44 -0
- app/channels/feishu/__init__.py +5 -0
- app/channels/feishu/feishu_adapter.py +852 -0
- app/channels/feishu/feishu_config.py +89 -0
- app/channels/qq/__init__.py +6 -0
- app/channels/qq/qq_adapter.py +1151 -0
- app/channels/qq/qq_config.py +78 -0
- app/channels/qq/qq_connect_client.py +284 -0
- app/channels/telegram/__init__.py +5 -0
- app/channels/telegram/telegram_adapter.py +1022 -0
- app/channels/telegram/telegram_config.py +103 -0
- app/channels/weixin/__init__.py +5 -0
- app/channels/weixin/weixin_adapter.py +1383 -0
- app/channels/weixin/weixin_config.py +68 -0
- app/core/__init__.py +10 -0
- app/core/bootstrap.py +211 -0
- app/core/bus/__init__.py +51 -0
- app/core/bus/event_bus.py +531 -0
- app/core/bus/event_models.py +194 -0
- app/core/bus/events.py +28 -0
- app/core/di/__init__.py +17 -0
- app/core/di/container.py +180 -0
- app/core/exception.py +16 -0
- app/core/interface/base_cell.py +94 -0
- app/core/interface/icell.py +68 -0
- app/core/interface/memory.py +45 -0
- app/core/scheduler/__init__.py +30 -0
- app/core/scheduler/executor.py +297 -0
- app/core/scheduler/manager.py +417 -0
- app/core/security/__init__.py +2 -0
- app/core/security/policy.py +390 -0
- app/core/util/__init__.py +16 -0
- app/core/util/agent_config.py +642 -0
- app/core/util/browser_runtime.py +67 -0
- app/core/util/browser_utils.py +163 -0
- app/core/util/cell_tool_adapter.py +872 -0
- app/core/util/component_auditor.py +540 -0
- app/core/util/component_sandbox.py +592 -0
- app/core/util/component_tool_registry.py +330 -0
- app/core/util/component_watcher.py +334 -0
- app/core/util/components_loader.py +1037 -0
- app/core/util/logger.py +581 -0
- app/core/util/mp_manager.py +183 -0
- app/core/util/protected_modules.py +263 -0
- app/core/util/runtime_paths.py +236 -0
- app/core/util/sandbox_entry.py +228 -0
- app/core/window/__init__.py +3 -0
- app/core/window/main_window.py +665 -0
- app/server/__init__.py +24 -0
- app/server/routes/__init__.py +2 -0
- app/server/routes/channels.py +306 -0
- app/server/routes/chat.py +997 -0
- app/server/routes/components.py +275 -0
- app/server/routes/config.py +497 -0
- app/server/routes/gene.py +299 -0
- app/server/routes/logs.py +171 -0
- app/server/routes/memory.py +225 -0
- app/server/routes/scheduler.py +209 -0
- app/server/routes/session_events.py +120 -0
- app/server/routes/skills.py +151 -0
- app/server/routes/upload.py +158 -0
- app/server/routes/ws_event_manager.py +322 -0
- app/server/task_manager.py +440 -0
- app/server/web_server.py +147 -0
- app/tui/__init__.py +2 -0
- app/tui/app.py +1799 -0
- app/tui/commands.py +45 -0
- app/tui/history_render.py +285 -0
- app/tui/i18n.py +423 -0
- app/tui/model_picker.py +315 -0
- app/tui/runner.py +151 -0
- app/tui/settings_screen.py +213 -0
- app/tui/spinner.py +20 -0
- app/tui/theme.py +112 -0
- app/tui/widgets.py +1148 -0
- cellium_agent-1.4.0.data/data/cellium/components/__init__.py +8 -0
- cellium_agent-1.4.0.data/data/cellium/components/_example_component.py +299 -0
- cellium_agent-1.4.0.data/data/cellium/components/component_builder.py +1247 -0
- cellium_agent-1.4.0.data/data/cellium/components/feishu_files.py +466 -0
- cellium_agent-1.4.0.data/data/cellium/components/qq_files.py +340 -0
- cellium_agent-1.4.0.data/data/cellium/components/scheduler.py +256 -0
- cellium_agent-1.4.0.data/data/cellium/components/skill_installer.py +979 -0
- cellium_agent-1.4.0.data/data/cellium/components/skill_manager.py +222 -0
- cellium_agent-1.4.0.data/data/cellium/components/skills/__init__.py +39 -0
- cellium_agent-1.4.0.data/data/cellium/components/sub_agent.py +625 -0
- cellium_agent-1.4.0.data/data/cellium/components/telegram_files.py +353 -0
- cellium_agent-1.4.0.data/data/cellium/components/web_fetch.py +2563 -0
- cellium_agent-1.4.0.data/data/cellium/components/web_search.py +1076 -0
- cellium_agent-1.4.0.data/data/cellium/components/weixin_files.py +342 -0
- cellium_agent-1.4.0.data/data/cellium/config/agent/agent.yaml +9 -0
- cellium_agent-1.4.0.data/data/cellium/config/agent/channels.yaml +24 -0
- cellium_agent-1.4.0.data/data/cellium/config/agent/heuristics.yaml +43 -0
- cellium_agent-1.4.0.data/data/cellium/config/agent/learning.yaml +30 -0
- cellium_agent-1.4.0.data/data/cellium/config/agent/llm.yaml +18 -0
- cellium_agent-1.4.0.data/data/cellium/config/agent/logging.yaml +11 -0
- cellium_agent-1.4.0.data/data/cellium/config/agent/memory.yaml +22 -0
- cellium_agent-1.4.0.data/data/cellium/config/agent/model_registry.yaml +105 -0
- cellium_agent-1.4.0.data/data/cellium/config/agent/routes.yaml +26 -0
- cellium_agent-1.4.0.data/data/cellium/config/agent/security.yaml +20 -0
- cellium_agent-1.4.0.data/data/cellium/config/agent/server.yaml +18 -0
- cellium_agent-1.4.0.data/data/cellium/config/settings.yaml +24 -0
- cellium_agent-1.4.0.data/data/cellium/dll/.gitkeep +3 -0
- cellium_agent-1.4.0.data/data/cellium/dll/darwin-arm64/libvector_engine.dylib +0 -0
- cellium_agent-1.4.0.data/data/cellium/dll/linux-aarch64/libvector_engine.so +0 -0
- cellium_agent-1.4.0.data/data/cellium/dll/linux-x86_64/libvector_engine.so +0 -0
- cellium_agent-1.4.0.data/data/cellium/dll/windows-x86_64/vector_engine.dll +0 -0
- cellium_agent-1.4.0.data/data/cellium/html/assets/SettingsPage-D_NlgEkQ.js +1 -0
- cellium_agent-1.4.0.data/data/cellium/html/assets/i18n-vendor-CLSNeGwf.js +1 -0
- cellium_agent-1.4.0.data/data/cellium/html/assets/index-BTZUwOV4.css +1 -0
- cellium_agent-1.4.0.data/data/cellium/html/assets/index-CfsVsH8O.js +4 -0
- cellium_agent-1.4.0.data/data/cellium/html/assets/markdown-vendor-Ckj0zzMC.js +66 -0
- cellium_agent-1.4.0.data/data/cellium/html/assets/react-vendor-CEaRprYI.js +23 -0
- cellium_agent-1.4.0.data/data/cellium/html/assets/rolldown-runtime-xSXa1GVp.js +1 -0
- cellium_agent-1.4.0.data/data/cellium/html/font/Roboto-Regular.ttf +0 -0
- cellium_agent-1.4.0.data/data/cellium/html/index.html +27 -0
- cellium_agent-1.4.0.data/data/cellium/html/logo.png +0 -0
- cellium_agent-1.4.0.data/data/cellium/licenses/ripgrep-LICENSE +26 -0
- cellium_agent-1.4.0.data/data/cellium/memory/personality.md +385 -0
- cellium_agent-1.4.0.data/data/cellium/vendor/.gitkeep +0 -0
- cellium_agent-1.4.0.data/data/cellium/vendor/ripgrep/.gitkeep +0 -0
- cellium_agent-1.4.0.data/data/cellium/vendor/ripgrep/aarch64-apple-darwin/rg +0 -0
- cellium_agent-1.4.0.data/data/cellium/vendor/ripgrep/aarch64-unknown-linux-gnu/rg +0 -0
- cellium_agent-1.4.0.data/data/cellium/vendor/ripgrep/x86_64-pc-windows-msvc/rg.exe +0 -0
- cellium_agent-1.4.0.data/data/cellium/vendor/ripgrep/x86_64-unknown-linux-musl/rg +0 -0
- cellium_agent-1.4.0.dist-info/METADATA +725 -0
- cellium_agent-1.4.0.dist-info/RECORD +224 -0
- cellium_agent-1.4.0.dist-info/WHEEL +5 -0
- cellium_agent-1.4.0.dist-info/entry_points.txt +3 -0
- cellium_agent-1.4.0.dist-info/licenses/LICENSE +13 -0
- cellium_agent-1.4.0.dist-info/top_level.txt +1 -0
- main.py +63 -0
app/__init__.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Control Loop Harness - LLM 控制环
|
|
4
|
+
|
|
5
|
+
核心组件:
|
|
6
|
+
- LoopState: 控制环状态快照
|
|
7
|
+
- ControlDecision: Action-based 决策
|
|
8
|
+
- FeedbackEvaluator: 分段式反馈评估
|
|
9
|
+
- ActionBandit: Action 选择器
|
|
10
|
+
- ControlLoop: 统一控制入口
|
|
11
|
+
- DecisionRenderer: 软约束渲染器(默认)
|
|
12
|
+
- HardConstraintRenderer: 强约束渲染器(PromptBuilder v3)
|
|
13
|
+
- ThoughtParser: 思考解析器
|
|
14
|
+
- HybridController: Plan-Execute-Observe-RePlan 混合控制器
|
|
15
|
+
|
|
16
|
+
Gene 相关(constraint_gene 子模块):
|
|
17
|
+
- TaskSignalMatcher: 任务信号匹配器
|
|
18
|
+
- GeneEvolution: Gene 进化系统
|
|
19
|
+
- GeneComposer: Gene 组合器
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from .loop_state import LoopState, ControlDecision
|
|
23
|
+
from .feedback_evaluator import FeedbackEvaluator
|
|
24
|
+
from .action_bandit import ActionBandit
|
|
25
|
+
from .control_loop import ControlLoop, create_control_loop
|
|
26
|
+
from .decision_renderer import DecisionRenderer, RenderedPrompt
|
|
27
|
+
from .hard_constraints import (
|
|
28
|
+
HardConstraint,
|
|
29
|
+
HardConstraintRenderer,
|
|
30
|
+
HardConstraintTemplates,
|
|
31
|
+
ActionFusion,
|
|
32
|
+
)
|
|
33
|
+
from .thought_parser import (
|
|
34
|
+
ThoughtParser,
|
|
35
|
+
ParsedThought,
|
|
36
|
+
ThoughtStep,
|
|
37
|
+
ActionType,
|
|
38
|
+
THOUGHT_SCHEMA,
|
|
39
|
+
)
|
|
40
|
+
from .hybrid_controller import (
|
|
41
|
+
HybridController,
|
|
42
|
+
HybridPhase,
|
|
43
|
+
HybridState,
|
|
44
|
+
Observation,
|
|
45
|
+
create_hybrid_controller,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
from .constraint_gene import TaskSignalMatcher, GeneEvolution, GeneComposer
|
|
49
|
+
from .gene_post_session import GenePostSessionAnalyzer, analyze_session_for_gene
|
|
50
|
+
|
|
51
|
+
__all__ = [
|
|
52
|
+
"LoopState",
|
|
53
|
+
"ControlDecision",
|
|
54
|
+
"FeedbackEvaluator",
|
|
55
|
+
"ActionBandit",
|
|
56
|
+
"ControlLoop",
|
|
57
|
+
"create_control_loop",
|
|
58
|
+
"DecisionRenderer",
|
|
59
|
+
"RenderedPrompt",
|
|
60
|
+
# 强约束版
|
|
61
|
+
"HardConstraint",
|
|
62
|
+
"HardConstraintRenderer",
|
|
63
|
+
"HardConstraintTemplates",
|
|
64
|
+
"ActionFusion",
|
|
65
|
+
# 思考系统
|
|
66
|
+
"ThoughtParser",
|
|
67
|
+
"ParsedThought",
|
|
68
|
+
"ThoughtStep",
|
|
69
|
+
"ActionType",
|
|
70
|
+
"THOUGHT_SCHEMA",
|
|
71
|
+
# Hybrid 控制器
|
|
72
|
+
"HybridController",
|
|
73
|
+
"HybridPhase",
|
|
74
|
+
"HybridState",
|
|
75
|
+
"Observation",
|
|
76
|
+
"create_hybrid_controller",
|
|
77
|
+
# Gene 相关
|
|
78
|
+
"TaskSignalMatcher",
|
|
79
|
+
"GeneEvolution",
|
|
80
|
+
"GeneComposer",
|
|
81
|
+
# 会话后 Gene 分析
|
|
82
|
+
"GenePostSessionAnalyzer",
|
|
83
|
+
"analyze_session_for_gene",
|
|
84
|
+
]
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
ActionBandit - Action-based Bandit
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
from collections import deque
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Dict, List, Optional
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
ACTION_TYPES = ["continue", "retry", "redirect", "compress", "terminate"]
|
|
17
|
+
ACTION_PRIORITY = ["continue", "retry", "compress", "redirect", "terminate"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class ActionStats:
|
|
22
|
+
"""单个 Action 的统计数据"""
|
|
23
|
+
alpha: float = 2.0
|
|
24
|
+
beta: float = 2.0
|
|
25
|
+
count: int = 0
|
|
26
|
+
|
|
27
|
+
def to_dict(self) -> Dict:
|
|
28
|
+
return {"alpha": self.alpha, "beta": self.beta, "count": self.count}
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def from_dict(cls, data: Dict) -> "ActionStats":
|
|
32
|
+
return cls(
|
|
33
|
+
alpha=data.get("alpha", 2.0),
|
|
34
|
+
beta=data.get("beta", 2.0),
|
|
35
|
+
count=data.get("count", 0),
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ActionBandit:
|
|
40
|
+
"""
|
|
41
|
+
Action-based Bandit
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, memory_path: Optional[str] = None, n_step: int = 3):
|
|
45
|
+
"""
|
|
46
|
+
初始化
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
memory_path: 统计数据持久化路径
|
|
50
|
+
n_step: n-step return 窗口大小
|
|
51
|
+
"""
|
|
52
|
+
self.memory_path = memory_path
|
|
53
|
+
self.n_step = n_step
|
|
54
|
+
|
|
55
|
+
self._stats: Dict[str, ActionStats] = {
|
|
56
|
+
action: ActionStats() for action in ACTION_TYPES
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
self._decay_factor = 0.99
|
|
60
|
+
self._decay_interval = 50
|
|
61
|
+
self._session_count = 0
|
|
62
|
+
|
|
63
|
+
self._policy_thresholds: Dict[str, Any] = {}
|
|
64
|
+
self._reward_buffer: deque = deque(maxlen=n_step)
|
|
65
|
+
self._last_actions: deque = deque(maxlen=n_step)
|
|
66
|
+
|
|
67
|
+
if memory_path:
|
|
68
|
+
self._load()
|
|
69
|
+
|
|
70
|
+
def select_action(
|
|
71
|
+
self,
|
|
72
|
+
features: Any,
|
|
73
|
+
candidate_actions: Optional[List[str]] = None,
|
|
74
|
+
) -> str:
|
|
75
|
+
"""
|
|
76
|
+
选择 Action。
|
|
77
|
+
|
|
78
|
+
当前策略:
|
|
79
|
+
1. 规则/控制环先给出候选 action
|
|
80
|
+
2. Bandit 只在候选集合内部做 tie-break
|
|
81
|
+
3. terminate 仍保留硬规则保护
|
|
82
|
+
"""
|
|
83
|
+
is_terminate_hard = (
|
|
84
|
+
hasattr(features, 'is_output_loop')
|
|
85
|
+
and features.is_output_loop
|
|
86
|
+
and hasattr(features, 'exact_repetition_count')
|
|
87
|
+
and features.exact_repetition_count >= 5
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
if is_terminate_hard:
|
|
91
|
+
logger.info(
|
|
92
|
+
"[ActionBandit] 硬规则: terminate (output_loop=%d)",
|
|
93
|
+
features.exact_repetition_count
|
|
94
|
+
)
|
|
95
|
+
return "terminate"
|
|
96
|
+
|
|
97
|
+
candidates = self._normalize_candidates(candidate_actions)
|
|
98
|
+
if not candidates:
|
|
99
|
+
return "continue"
|
|
100
|
+
|
|
101
|
+
if len(candidates) == 1:
|
|
102
|
+
return candidates[0]
|
|
103
|
+
|
|
104
|
+
action = self._thompson_with_bias(features, candidate_actions=candidates)
|
|
105
|
+
logger.debug("[ActionBandit] tie-break: %s | candidates=%s", action, candidates)
|
|
106
|
+
return action
|
|
107
|
+
|
|
108
|
+
def _normalize_candidates(self, candidate_actions: Optional[List[str]]) -> List[str]:
|
|
109
|
+
if not candidate_actions:
|
|
110
|
+
return [action for action in ACTION_TYPES if action != "terminate"]
|
|
111
|
+
|
|
112
|
+
candidates = []
|
|
113
|
+
for action in candidate_actions:
|
|
114
|
+
if action in ACTION_TYPES and action not in candidates:
|
|
115
|
+
candidates.append(action)
|
|
116
|
+
|
|
117
|
+
return candidates
|
|
118
|
+
|
|
119
|
+
def _thompson_with_bias(self, features: Any, candidate_actions: Optional[List[str]] = None) -> str:
|
|
120
|
+
"""
|
|
121
|
+
Thompson Sampling + Heuristic Bias
|
|
122
|
+
|
|
123
|
+
原理:
|
|
124
|
+
- 每个 action 从 Beta 分布采样
|
|
125
|
+
- 加上 Heuristic 提供的 bias
|
|
126
|
+
- 选择分数最高的 action
|
|
127
|
+
"""
|
|
128
|
+
import random
|
|
129
|
+
|
|
130
|
+
candidates = self._normalize_candidates(candidate_actions) or list(self._stats.keys())
|
|
131
|
+
|
|
132
|
+
scores = {}
|
|
133
|
+
for action in candidates:
|
|
134
|
+
stats = self._stats[action]
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
import numpy as np
|
|
138
|
+
sample = np.random.beta(stats.alpha, stats.beta)
|
|
139
|
+
except ImportError:
|
|
140
|
+
mean = stats.alpha / (stats.alpha + stats.beta) if (stats.alpha + stats.beta) > 0 else 0.5
|
|
141
|
+
noise = random.gauss(0, 0.1)
|
|
142
|
+
sample = max(0, min(1, mean + noise))
|
|
143
|
+
|
|
144
|
+
bias = self._heuristic_bias(action, features)
|
|
145
|
+
|
|
146
|
+
scores[action] = sample + bias
|
|
147
|
+
|
|
148
|
+
if not scores:
|
|
149
|
+
return "continue"
|
|
150
|
+
|
|
151
|
+
max_score = max(scores.values())
|
|
152
|
+
best_actions = [a for a, s in scores.items() if s == max_score]
|
|
153
|
+
|
|
154
|
+
# 优先级:continue > retry > compress > redirect > terminate
|
|
155
|
+
for priority_action in ACTION_PRIORITY:
|
|
156
|
+
if priority_action in best_actions:
|
|
157
|
+
return priority_action
|
|
158
|
+
|
|
159
|
+
return best_actions[0]
|
|
160
|
+
|
|
161
|
+
def _heuristic_bias(self, action: str, features: Any) -> float:
|
|
162
|
+
"""
|
|
163
|
+
Heuristic Bias 计算
|
|
164
|
+
|
|
165
|
+
原理:
|
|
166
|
+
- Heuristic 不决定 action,只调整分数
|
|
167
|
+
- bias > 0 表示"推荐",但 Bandit 仍可选择其他
|
|
168
|
+
- 不同 action 有不同的触发条件
|
|
169
|
+
- 使用 Policy 阈值动态调整判断条件
|
|
170
|
+
- 冷启动保护:统计不足时自动放大 bias,让启发式兜底
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
bias 值 [0, 1]
|
|
174
|
+
"""
|
|
175
|
+
bias = 0.0
|
|
176
|
+
|
|
177
|
+
stuck_threshold = self._policy_thresholds.get("stuck_iterations", 5)
|
|
178
|
+
repetition_threshold = self._policy_thresholds.get("repetition_threshold", 4)
|
|
179
|
+
repetition_score_threshold = self._policy_thresholds.get("repetition_score", 0.7)
|
|
180
|
+
|
|
181
|
+
if action == "redirect":
|
|
182
|
+
if hasattr(features, 'repetition_score') and features.repetition_score > repetition_score_threshold:
|
|
183
|
+
bias = max(bias, 0.03 * features.repetition_score)
|
|
184
|
+
if hasattr(features, 'stuck_iterations') and features.stuck_iterations >= stuck_threshold:
|
|
185
|
+
bias = max(bias, 0.04)
|
|
186
|
+
|
|
187
|
+
if action == "retry":
|
|
188
|
+
if hasattr(features, 'stuck_iterations') and 1 <= features.stuck_iterations < stuck_threshold:
|
|
189
|
+
bias = max(bias, 0.02)
|
|
190
|
+
if hasattr(features, 'progress_trend') and 0 < features.progress_trend < 0.3:
|
|
191
|
+
bias = max(bias, 0.01)
|
|
192
|
+
|
|
193
|
+
if action == "compress":
|
|
194
|
+
if hasattr(features, 'context_saturation') and features.context_saturation > 0.6:
|
|
195
|
+
bias = max(bias, 0.03)
|
|
196
|
+
if hasattr(features, 'stuck_iterations') and features.stuck_iterations >= stuck_threshold // 2:
|
|
197
|
+
bias = max(bias, 0.02)
|
|
198
|
+
|
|
199
|
+
if action == "continue":
|
|
200
|
+
if hasattr(features, 'progress_score') and features.progress_score > 0.5:
|
|
201
|
+
bias = max(bias, 0.02)
|
|
202
|
+
if hasattr(features, 'stuck_iterations') and features.stuck_iterations == 0:
|
|
203
|
+
bias = max(bias, 0.03)
|
|
204
|
+
|
|
205
|
+
# 冷启动保护:该 action 统计不足时放大 bias,退回启发式主导
|
|
206
|
+
if action in self._stats and self._stats[action].count < 5:
|
|
207
|
+
bias *= 5
|
|
208
|
+
|
|
209
|
+
return bias
|
|
210
|
+
|
|
211
|
+
def set_policy_thresholds(self, thresholds: Dict[str, Any]):
|
|
212
|
+
"""设置 Policy 阈值约束"""
|
|
213
|
+
self._policy_thresholds = thresholds
|
|
214
|
+
logger.info("[ActionBandit] Policy 阈值已设置: %s", list(thresholds.keys()))
|
|
215
|
+
|
|
216
|
+
def update(self, action: str, reward: float):
|
|
217
|
+
if action not in self._stats:
|
|
218
|
+
logger.warning("[ActionBandit] 未知 action: %s", action)
|
|
219
|
+
return
|
|
220
|
+
|
|
221
|
+
self._reward_buffer.append(reward)
|
|
222
|
+
self._last_actions.append(action)
|
|
223
|
+
|
|
224
|
+
n_step_reward = self._compute_n_step_return()
|
|
225
|
+
|
|
226
|
+
stats = self._stats[action]
|
|
227
|
+
stats.count += 1
|
|
228
|
+
|
|
229
|
+
if n_step_reward > 0.5:
|
|
230
|
+
stats.alpha += n_step_reward
|
|
231
|
+
else:
|
|
232
|
+
stats.beta += (1 - n_step_reward)
|
|
233
|
+
|
|
234
|
+
logger.debug(
|
|
235
|
+
"[ActionBandit] 更新 %s | reward=%.2f | n_step=%.2f | alpha=%.1f beta=%.1f",
|
|
236
|
+
action, reward, n_step_reward, stats.alpha, stats.beta
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
if self.memory_path:
|
|
240
|
+
self._save()
|
|
241
|
+
|
|
242
|
+
def _compute_n_step_return(self) -> float:
|
|
243
|
+
"""
|
|
244
|
+
计算 n-step return
|
|
245
|
+
G_t = (r_t + r_{t+1} + ... + r_{t+n-1}) / n
|
|
246
|
+
|
|
247
|
+
"""
|
|
248
|
+
if not self._reward_buffer:
|
|
249
|
+
return 0.5 # 默认中性
|
|
250
|
+
|
|
251
|
+
return sum(self._reward_buffer) / len(self._reward_buffer)
|
|
252
|
+
|
|
253
|
+
def end_session(self):
|
|
254
|
+
self._session_count += 1
|
|
255
|
+
|
|
256
|
+
if self._session_count >= self._decay_interval:
|
|
257
|
+
self._decay()
|
|
258
|
+
self._session_count = 0
|
|
259
|
+
|
|
260
|
+
self._reward_buffer.clear()
|
|
261
|
+
self._last_actions.clear()
|
|
262
|
+
|
|
263
|
+
def _decay(self):
|
|
264
|
+
for action, stats in self._stats.items():
|
|
265
|
+
stats.alpha = 1 + (stats.alpha - 1) * self._decay_factor
|
|
266
|
+
stats.beta = 1 + (stats.beta - 1) * self._decay_factor
|
|
267
|
+
|
|
268
|
+
logger.info("[ActionBandit] 统计已衰减 (factor=%.2f)", self._decay_factor)
|
|
269
|
+
|
|
270
|
+
def get_stats(self) -> Dict[str, Dict]:
|
|
271
|
+
return {
|
|
272
|
+
action: stats.to_dict()
|
|
273
|
+
for action, stats in self._stats.items()
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
def get_summary(self) -> Dict[str, Any]:
|
|
277
|
+
summary = {
|
|
278
|
+
"total_actions": sum(s.count for s in self._stats.values()),
|
|
279
|
+
"actions": {},
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
for action, stats in self._stats.items():
|
|
283
|
+
mean = stats.alpha / (stats.alpha + stats.beta) if (stats.alpha + stats.beta) > 0 else 0.5
|
|
284
|
+
summary["actions"][action] = {
|
|
285
|
+
"count": stats.count,
|
|
286
|
+
"success_rate": mean,
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return summary
|
|
290
|
+
|
|
291
|
+
def _load(self):
|
|
292
|
+
if not self.memory_path or not os.path.exists(self.memory_path):
|
|
293
|
+
return
|
|
294
|
+
|
|
295
|
+
try:
|
|
296
|
+
with open(self.memory_path, "r", encoding="utf-8") as f:
|
|
297
|
+
data = json.load(f)
|
|
298
|
+
|
|
299
|
+
for action, stats_data in data.get("actions", {}).items():
|
|
300
|
+
if action in self._stats:
|
|
301
|
+
self._stats[action] = ActionStats.from_dict(stats_data)
|
|
302
|
+
|
|
303
|
+
logger.info("[ActionBandit] 加载统计数据: %s", self.memory_path)
|
|
304
|
+
|
|
305
|
+
except Exception as e:
|
|
306
|
+
logger.warning("[ActionBandit] 加载失败: %s", e)
|
|
307
|
+
|
|
308
|
+
def _save(self):
|
|
309
|
+
if not self.memory_path:
|
|
310
|
+
return
|
|
311
|
+
|
|
312
|
+
try:
|
|
313
|
+
Path(self.memory_path).parent.mkdir(parents=True, exist_ok=True)
|
|
314
|
+
|
|
315
|
+
data = {
|
|
316
|
+
"actions": self.get_stats(),
|
|
317
|
+
"session_count": self._session_count,
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
with open(self.memory_path, "w", encoding="utf-8") as f:
|
|
321
|
+
json.dump(data, f, indent=2, ensure_ascii=False)
|
|
322
|
+
|
|
323
|
+
except Exception as e:
|
|
324
|
+
logger.warning("[ActionBandit] 保存失败: %s", e)
|
|
325
|
+
|
|
326
|
+
def reset(self):
|
|
327
|
+
for action in self._stats:
|
|
328
|
+
self._stats[action] = ActionStats()
|
|
329
|
+
self._session_count = 0
|
|
330
|
+
self._reward_buffer.clear()
|
|
331
|
+
self._last_actions.clear()
|
|
332
|
+
|
|
333
|
+
if self.memory_path:
|
|
334
|
+
self._save()
|
|
335
|
+
|
|
336
|
+
logger.info("[ActionBandit] 统计已重置")
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Constraint Gene - 控制约束 Gene 子模块
|
|
4
|
+
|
|
5
|
+
包含:
|
|
6
|
+
- TaskSignalMatcher: 任务信号匹配器
|
|
7
|
+
- GeneEvolution: Gene 进化系统
|
|
8
|
+
- GeneComposer: Gene 组合器
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from .matcher import TaskSignalMatcher
|
|
12
|
+
from .evolution import GeneEvolution
|
|
13
|
+
from .composer import GeneComposer
|
|
14
|
+
|
|
15
|
+
__all__ = ["TaskSignalMatcher", "GeneEvolution", "GeneComposer"]
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
GeneComposer - Gene 组合器
|
|
4
|
+
|
|
5
|
+
职责:
|
|
6
|
+
1. 多任务匹配
|
|
7
|
+
2. Gene 组合与合并
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from typing import Any, Dict, List, Optional
|
|
12
|
+
|
|
13
|
+
from .matcher import TaskSignalMatcher
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class GeneComposer:
|
|
19
|
+
"""Gene 组合器 - 处理多任务匹配和组合"""
|
|
20
|
+
|
|
21
|
+
TASK_PRIORITY = {
|
|
22
|
+
"code_debug": 100,
|
|
23
|
+
"file_operation": 90,
|
|
24
|
+
"web_search": 80,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def match_multiple(cls, user_input: str) -> List[Dict[str, Any]]:
|
|
29
|
+
if not user_input:
|
|
30
|
+
return []
|
|
31
|
+
|
|
32
|
+
user_lower = user_input.lower()
|
|
33
|
+
matches = []
|
|
34
|
+
|
|
35
|
+
for task_type, config in TaskSignalMatcher._cache.items():
|
|
36
|
+
signals = config.get("signals", [])
|
|
37
|
+
if any(signal in user_lower for signal in signals):
|
|
38
|
+
matches.append({
|
|
39
|
+
"task_type": task_type,
|
|
40
|
+
"gene_template": config["gene_template"],
|
|
41
|
+
"forbidden_tools": config["forbidden_tools"],
|
|
42
|
+
"preferred_tools": config["preferred_tools"],
|
|
43
|
+
"priority": cls.TASK_PRIORITY.get(task_type, 50),
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
for task_type, config in TaskSignalMatcher.TASK_PATTERNS.items():
|
|
47
|
+
if any(signal in user_lower for signal in config["signals"]):
|
|
48
|
+
if not any(m["task_type"] == task_type for m in matches):
|
|
49
|
+
matches.append({
|
|
50
|
+
"task_type": task_type,
|
|
51
|
+
"gene_template": config["gene_template"],
|
|
52
|
+
"forbidden_tools": config["forbidden_tools"],
|
|
53
|
+
"preferred_tools": config["preferred_tools"],
|
|
54
|
+
"priority": cls.TASK_PRIORITY.get(task_type, 50),
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
matches.sort(key=lambda x: x["priority"], reverse=True)
|
|
58
|
+
return matches
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def compose(cls, matches: List[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
|
|
62
|
+
if not matches:
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
if len(matches) == 1:
|
|
66
|
+
return matches[0]
|
|
67
|
+
|
|
68
|
+
task_types = [m["task_type"] for m in matches]
|
|
69
|
+
combined_forbidden = list(set(
|
|
70
|
+
tool for m in matches for tool in m["forbidden_tools"]
|
|
71
|
+
))
|
|
72
|
+
combined_preferred = list(set(
|
|
73
|
+
tool for m in matches for tool in m["preferred_tools"]
|
|
74
|
+
))
|
|
75
|
+
|
|
76
|
+
hard_constraints = cls._build_combined_constraints(matches)
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
"task_type": f"combined:{','.join(task_types)}",
|
|
80
|
+
"gene_template": hard_constraints,
|
|
81
|
+
"forbidden_tools": combined_forbidden,
|
|
82
|
+
"preferred_tools": combined_preferred,
|
|
83
|
+
"component_tasks": task_types,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@classmethod
|
|
87
|
+
def _build_combined_constraints(cls, matches: List[Dict[str, Any]]) -> str:
|
|
88
|
+
task_types = [m["task_type"] for m in matches]
|
|
89
|
+
|
|
90
|
+
lines = [
|
|
91
|
+
"[HARD CONSTRAINTS]",
|
|
92
|
+
f"Multi-task: {' + '.join(task_types)}",
|
|
93
|
+
"",
|
|
94
|
+
"[CONTROL ACTION]",
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
for i, match in enumerate(matches, 1):
|
|
98
|
+
task_type = match["task_type"]
|
|
99
|
+
template = match["gene_template"]
|
|
100
|
+
|
|
101
|
+
must_section = cls._extract_section(template, "MUST:")
|
|
102
|
+
must_not_section = cls._extract_section(template, "MUST NOT:")
|
|
103
|
+
|
|
104
|
+
lines.append(f"STEP {i} [{task_type}]:")
|
|
105
|
+
if must_section:
|
|
106
|
+
lines.append(f" MUST: {must_section}")
|
|
107
|
+
if must_not_section:
|
|
108
|
+
lines.append(f" MUST NOT: {must_not_section}")
|
|
109
|
+
lines.append("")
|
|
110
|
+
|
|
111
|
+
avoid_items = set()
|
|
112
|
+
for match in matches:
|
|
113
|
+
template = match["gene_template"]
|
|
114
|
+
items = cls._extract_avoid_items(template)
|
|
115
|
+
avoid_items.update(items)
|
|
116
|
+
|
|
117
|
+
if avoid_items:
|
|
118
|
+
lines.append("[AVOID]")
|
|
119
|
+
for item in sorted(avoid_items):
|
|
120
|
+
lines.append(f"- {item}")
|
|
121
|
+
|
|
122
|
+
return "\n".join(lines)
|
|
123
|
+
|
|
124
|
+
@classmethod
|
|
125
|
+
def _extract_section(cls, template: str, marker: str) -> str:
|
|
126
|
+
lines = template.split("\n")
|
|
127
|
+
result = []
|
|
128
|
+
capturing = False
|
|
129
|
+
|
|
130
|
+
for line in lines:
|
|
131
|
+
if marker in line:
|
|
132
|
+
capturing = True
|
|
133
|
+
result.append(line.split(marker, 1)[-1].strip())
|
|
134
|
+
elif capturing:
|
|
135
|
+
if line.strip().startswith("MUST") or line.strip().startswith("["):
|
|
136
|
+
break
|
|
137
|
+
if line.strip():
|
|
138
|
+
result.append(line.strip())
|
|
139
|
+
|
|
140
|
+
return " ".join(result) if result else ""
|
|
141
|
+
|
|
142
|
+
@classmethod
|
|
143
|
+
def _extract_avoid_items(cls, template: str) -> List[str]:
|
|
144
|
+
lines = template.split("\n")
|
|
145
|
+
items = []
|
|
146
|
+
in_avoid = False
|
|
147
|
+
|
|
148
|
+
for line in lines:
|
|
149
|
+
if "[AVOID]" in line:
|
|
150
|
+
in_avoid = True
|
|
151
|
+
continue
|
|
152
|
+
if in_avoid:
|
|
153
|
+
if line.strip().startswith("["):
|
|
154
|
+
break
|
|
155
|
+
if line.strip().startswith("-"):
|
|
156
|
+
items.append(line.strip()[1:].strip())
|
|
157
|
+
|
|
158
|
+
return items
|