mcs-core 0.1.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.
- mcs/__init__.py +52 -0
- mcs/core/__init__.py +18 -0
- mcs/core/builder.py +433 -0
- mcs/core/calibrated_estimator.py +61 -0
- mcs/core/content_merge.py +81 -0
- mcs/core/context_renderer.py +245 -0
- mcs/core/errors.py +63 -0
- mcs/core/mcs.py +251 -0
- mcs/core/plugin.py +109 -0
- mcs/core/plugin_manager.py +113 -0
- mcs/core/query_engine.py +864 -0
- mcs/core/store.py +364 -0
- mcs/core/token_budget.py +180 -0
- mcs/core/write_pipeline.py +1044 -0
- mcs/diagnostics/__init__.py +5 -0
- mcs/diagnostics/graph_quality.py +278 -0
- mcs/entities/__init__.py +54 -0
- mcs/entities/config.py +325 -0
- mcs/entities/decisions.py +217 -0
- mcs/entities/graph.py +170 -0
- mcs/interfaces/__init__.py +23 -0
- mcs/interfaces/arbitration_plugin.py +50 -0
- mcs/interfaces/compaction_plugin.py +64 -0
- mcs/interfaces/edge_extension.py +63 -0
- mcs/interfaces/entry_plugin.py +54 -0
- mcs/interfaces/index.py +52 -0
- mcs/interfaces/llm.py +304 -0
- mcs/interfaces/maintenance.py +35 -0
- mcs/interfaces/node_extension.py +57 -0
- mcs/interfaces/postprocess_plugin.py +43 -0
- mcs/interfaces/preprocess_plugin.py +25 -0
- mcs/interfaces/priority_scorer.py +44 -0
- mcs/interfaces/query_preprocess_plugin.py +43 -0
- mcs/interfaces/storage_schema_ext.py +35 -0
- mcs/interfaces/trim_plugin.py +61 -0
- mcs/interfaces/write_preprocess_plugin.py +43 -0
- mcs/plugins/__init__.py +7 -0
- mcs/plugins/entry/__init__.py +5 -0
- mcs/plugins/entry/hub_fallback.py +142 -0
- mcs/plugins/index/__init__.py +8 -0
- mcs/plugins/index/alias_index.py +179 -0
- mcs/plugins/llm/__init__.py +7 -0
- mcs/plugins/llm/claude_llm.py +191 -0
- mcs/plugins/llm/deepseek_llm.py +163 -0
- mcs/plugins/llm/ollama_llm.py +259 -0
- mcs/plugins/maintenance/__init__.py +6 -0
- mcs/plugins/maintenance/dedup_maintenance.py +186 -0
- mcs/plugins/maintenance/fanout_reducer.py +905 -0
- mcs/plugins/maintenance/graph_summary.py +96 -0
- mcs/plugins/maintenance/summary_regen.py +92 -0
- mcs/plugins/postprocess/__init__.py +6 -0
- mcs/plugins/postprocess/rerank.py +197 -0
- mcs/plugins/postprocess/summary.py +70 -0
- mcs/plugins/preprocess/__init__.py +9 -0
- mcs/plugins/preprocess/cross_doc_linker.py +387 -0
- mcs/plugins/preprocess/source_tracking.py +372 -0
- mcs/plugins/trim/__init__.py +5 -0
- mcs/plugins/trim/llm_seed_selector.py +108 -0
- mcs/plugins/trim/priority_trim.py +71 -0
- mcs/presets/__init__.py +21 -0
- mcs/presets/phase1.py +226 -0
- mcs/prompts/__init__.py +141 -0
- mcs/prompts/adjudicate.py +61 -0
- mcs/prompts/arbitrate.py +35 -0
- mcs/prompts/decide_directions.py +41 -0
- mcs/prompts/decide_hub.py +141 -0
- mcs/prompts/extract_concepts.py +131 -0
- mcs/prompts/extract_work_events.py +111 -0
- mcs/prompts/gen_aliases.py +36 -0
- mcs/prompts/gen_graph_summary.py +30 -0
- mcs/prompts/gen_summary.py +24 -0
- mcs/prompts/generalize.py +40 -0
- mcs/prompts/judge_relations.py +207 -0
- mcs/prompts/merge.py +101 -0
- mcs/prompts/merge_content.py +66 -0
- mcs/prompts/navigate_hub.py +89 -0
- mcs/prompts/select_facts.py +144 -0
- mcs/prompts/select_nodes.py +60 -0
- mcs/prompts/split.py +128 -0
- mcs/prompts/synthesize.py +27 -0
- mcs/rendering.py +64 -0
- mcs/stores/__init__.py +11 -0
- mcs/stores/in_memory.py +450 -0
- mcs/stores/sqlite_store.py +920 -0
- mcs/utils/__init__.py +6 -0
- mcs/utils/env_expand.py +57 -0
- mcs/utils/imports.py +62 -0
- mcs/utils/text_utils.py +196 -0
- mcs/utils/timestamps.py +62 -0
- mcs/utils/tokenizer.py +51 -0
- mcs_agent/__init__.py +50 -0
- mcs_agent/__main__.py +10 -0
- mcs_agent/app.py +165 -0
- mcs_agent/builder.py +290 -0
- mcs_agent/context.py +374 -0
- mcs_agent/llm.py +41 -0
- mcs_agent/llms/__init__.py +17 -0
- mcs_agent/llms/anthropic.py +219 -0
- mcs_agent/llms/base.py +42 -0
- mcs_agent/llms/callable.py +36 -0
- mcs_agent/llms/openai.py +101 -0
- mcs_agent/llms/registry.py +30 -0
- mcs_agent/loop.py +359 -0
- mcs_agent/memory.py +1102 -0
- mcs_agent/tools.py +537 -0
- mcs_agent/trace.py +125 -0
- mcs_core-0.1.0.dist-info/METADATA +231 -0
- mcs_core-0.1.0.dist-info/RECORD +115 -0
- mcs_core-0.1.0.dist-info/WHEEL +5 -0
- mcs_core-0.1.0.dist-info/entry_points.txt +3 -0
- mcs_core-0.1.0.dist-info/licenses/LICENSE +21 -0
- mcs_core-0.1.0.dist-info/top_level.txt +3 -0
- mcs_mcp/__init__.py +17 -0
- mcs_mcp/__main__.py +8 -0
- mcs_mcp/server.py +292 -0
mcs/__init__.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""MCS - Maximum-Context Subgraph:可扩展的知识图谱与检索引擎。
|
|
2
|
+
|
|
3
|
+
顶层 ``MCS`` 类将图存储、LLM 后端、插件链、读写管线组装在一起。
|
|
4
|
+
推荐使用 Builder 构建 MCS 实例:
|
|
5
|
+
|
|
6
|
+
# 快捷方式(推荐)
|
|
7
|
+
from mcs.presets import create_mcs
|
|
8
|
+
mcs = create_mcs(llm="deepseek", db_path="mcs.db")
|
|
9
|
+
|
|
10
|
+
# 或完整自定义
|
|
11
|
+
from mcs.presets import Phase1Builder
|
|
12
|
+
from mcs.entities.config import MCSConfig
|
|
13
|
+
|
|
14
|
+
config = MCSConfig.knowledge_graph(write_llm="deepseek", read_llm="deepseek")
|
|
15
|
+
config.plugin_configs["deepseek_llm"]["api_key"] = "..."
|
|
16
|
+
builder = Phase1Builder(config)
|
|
17
|
+
mcs = builder.build() # 返回即用的 MCS 实例
|
|
18
|
+
|
|
19
|
+
# 使用
|
|
20
|
+
mcs.ingest("深度学习是机器学习的一个子领域...")
|
|
21
|
+
nodes = mcs.query("什么是深度学习?")
|
|
22
|
+
mcs.shutdown()
|
|
23
|
+
|
|
24
|
+
MCS 实例由 Builder 一次性构建完成,无需调用 initialize()。
|
|
25
|
+
|
|
26
|
+
参见 ``openspec/specs/`` 获取各能力的契约定义。
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
__version__ = "0.1.0"
|
|
32
|
+
|
|
33
|
+
from mcs.core.builder import MCSBuilder
|
|
34
|
+
from mcs.core.mcs import MCS
|
|
35
|
+
from mcs.core.plugin import Plugin, PluginType
|
|
36
|
+
from mcs.core.store import StoreInterface
|
|
37
|
+
from mcs.entities.config import MCSConfig
|
|
38
|
+
from mcs.presets import Phase1Builder, create_mcs, get_phase1_plugin_registry
|
|
39
|
+
from mcs.stores.in_memory import InMemoryStore
|
|
40
|
+
from mcs.stores.sqlite_store import SQLiteStore
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
"MCS",
|
|
44
|
+
"MCSConfig",
|
|
45
|
+
"MCSBuilder",
|
|
46
|
+
"Phase1Builder",
|
|
47
|
+
"StoreInterface",
|
|
48
|
+
"InMemoryStore",
|
|
49
|
+
"SQLiteStore",
|
|
50
|
+
"create_mcs",
|
|
51
|
+
"get_phase1_plugin_registry",
|
|
52
|
+
]
|
mcs/core/__init__.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Core engine - stable graph operations and pipeline state machines.
|
|
2
|
+
|
|
3
|
+
Contains:
|
|
4
|
+
- ``mcs``: MCS top-level orchestrator with dual PluginManager architecture
|
|
5
|
+
- ``builder``: MCSBuilder abstract base class for building MCS instances
|
|
6
|
+
- ``errors``: Exception hierarchy
|
|
7
|
+
- ``plugin``: Plugin base class and PluginType enum
|
|
8
|
+
- ``plugin_manager``: PluginManager and PluginContext
|
|
9
|
+
- ``query_engine``: QueryEngine for read pipeline
|
|
10
|
+
- ``store``: StoreInterface ABC
|
|
11
|
+
- ``token_budget``: TokenBudget
|
|
12
|
+
- ``write_pipeline``: WritePipeline for ingest pipeline
|
|
13
|
+
- ``context_renderer``: ContextRenderer for LLM input rendering
|
|
14
|
+
|
|
15
|
+
纯数据模型(Node/Edge/Subgraph、Decision 系列、MCSConfig)已迁至 ``mcs.entities``。
|
|
16
|
+
|
|
17
|
+
See architecture.md §2.
|
|
18
|
+
"""
|
mcs/core/builder.py
ADDED
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
"""MCSBuilder 抽象基类 — 全量组装契约。
|
|
2
|
+
|
|
3
|
+
Builder 接管从配置到完成态 MCS 的完整构建流程,包括:
|
|
4
|
+
- Store 初始化
|
|
5
|
+
- 插件实例化与注册
|
|
6
|
+
- PluginContext 构建与插件初始化
|
|
7
|
+
- QueryEngine 和 WritePipeline 构建
|
|
8
|
+
- Load-on-startup
|
|
9
|
+
|
|
10
|
+
MCS 构造后即处于 ready 状态,无需调用 initialize()。
|
|
11
|
+
|
|
12
|
+
参见 openspec/specs/mcs-builder/spec.md "MCSBuilder 抽象基类定义构建契约"。
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
from abc import ABC, abstractmethod
|
|
19
|
+
from typing import TYPE_CHECKING
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from mcs.core.plugin import Plugin
|
|
23
|
+
from mcs.entities.config import MCSConfig
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class MCSBuilder(ABC):
|
|
29
|
+
"""抽象构建器 — 全量组装 MCS。
|
|
30
|
+
|
|
31
|
+
子类需实现 ``get_plugin_class()`` 方法,根据插件名称返回插件类。
|
|
32
|
+
``build()`` 方法封装从配置到初始化完成的整个构建流程。
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, config: "MCSConfig"):
|
|
36
|
+
"""初始化构建器。
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
config: MCS 配置对象
|
|
40
|
+
"""
|
|
41
|
+
self.config = config
|
|
42
|
+
|
|
43
|
+
@abstractmethod
|
|
44
|
+
def get_plugin_class(self, name: str) -> type["Plugin"] | None:
|
|
45
|
+
"""根据插件名称返回插件类。
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
name: 插件名称(如 "sqlite_storage", "deepseek_llm")
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
插件类,若未找到则返回 None
|
|
52
|
+
"""
|
|
53
|
+
...
|
|
54
|
+
|
|
55
|
+
def build(self) -> "MCS":
|
|
56
|
+
"""构建并返回即用的 MCS 实例。
|
|
57
|
+
|
|
58
|
+
执行完整的 14 步初始化流程。
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
已完成初始化、可直接使用的 MCS 实例
|
|
62
|
+
"""
|
|
63
|
+
from mcs.core.context_renderer import ContextRenderer
|
|
64
|
+
from mcs.core.mcs import MCS
|
|
65
|
+
from mcs.core.plugin_manager import PluginContext, PluginManager
|
|
66
|
+
from mcs.core.query_engine import QueryEngine
|
|
67
|
+
from mcs.core.token_budget import TokenBudget
|
|
68
|
+
from mcs.core.write_pipeline import WritePipeline
|
|
69
|
+
from mcs.interfaces.priority_scorer import DefaultPriorityScorer
|
|
70
|
+
from mcs.stores.in_memory import InMemoryStore
|
|
71
|
+
from mcs.stores.sqlite_store import SQLiteStore
|
|
72
|
+
|
|
73
|
+
# 1. 实例化 Store
|
|
74
|
+
store = self._init_store()
|
|
75
|
+
|
|
76
|
+
# 2. 实例化双 PluginManager
|
|
77
|
+
write_manager = PluginManager()
|
|
78
|
+
read_manager = PluginManager()
|
|
79
|
+
|
|
80
|
+
# 3. 按配置实例化并注册插件(获取 write_llm / read_llm)
|
|
81
|
+
write_llm, read_llm = self._register_plugins(write_manager, read_manager)
|
|
82
|
+
|
|
83
|
+
# 4. 用 write_llm.count_tokens 构造 TokenBudget
|
|
84
|
+
# write_llm 必非 None:_register_llm_plugins 在 write_llm 缺失时直接 raise
|
|
85
|
+
token_budget = TokenBudget(
|
|
86
|
+
self.config.token_budget,
|
|
87
|
+
counter=write_llm.count_tokens,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# 5. 初始化 SQLiteStore(若使用)
|
|
91
|
+
if isinstance(store, SQLiteStore) and store.conn is None:
|
|
92
|
+
from mcs.core.plugin import PluginType
|
|
93
|
+
schema_exts = write_manager.get_all(PluginType.STORAGE_SCHEMA_EXT)
|
|
94
|
+
node_exts = {
|
|
95
|
+
p.get_name(): p
|
|
96
|
+
for p in write_manager.get_all(PluginType.NODE_EXTENSION)
|
|
97
|
+
}
|
|
98
|
+
edge_exts = {
|
|
99
|
+
p.get_name(): p
|
|
100
|
+
for p in write_manager.get_all(PluginType.EDGE_EXTENSION)
|
|
101
|
+
}
|
|
102
|
+
store.initialize(
|
|
103
|
+
schema_extensions=schema_exts,
|
|
104
|
+
node_extensions=node_exts,
|
|
105
|
+
edge_extensions=edge_exts,
|
|
106
|
+
priority_scorer=DefaultPriorityScorer(),
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# 6. 创建 ContextRenderer(传入 read_manager)
|
|
110
|
+
context_renderer = ContextRenderer(read_manager)
|
|
111
|
+
|
|
112
|
+
# 7. 构建 PluginContext 并初始化所有插件
|
|
113
|
+
self._init_plugin_context(
|
|
114
|
+
store, token_budget, context_renderer, write_manager, read_manager
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# 8. 应用 prompt_overrides 到 LLM
|
|
118
|
+
self._apply_prompt_overrides(write_llm, read_llm)
|
|
119
|
+
|
|
120
|
+
# 9. 构建 QueryEngine(read_manager + read_llm)
|
|
121
|
+
query_engine = QueryEngine(
|
|
122
|
+
store=store,
|
|
123
|
+
llm=read_llm,
|
|
124
|
+
plugin_manager=read_manager,
|
|
125
|
+
token_budget=token_budget,
|
|
126
|
+
max_rounds=self.config.max_rounds,
|
|
127
|
+
max_accumulated_nodes=self.config.max_accumulated_nodes,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# 10. 构建 WritePipeline(write_manager + write_llm + query_engine)
|
|
131
|
+
write_pipeline = WritePipeline(
|
|
132
|
+
store=store,
|
|
133
|
+
llm=write_llm,
|
|
134
|
+
query_engine=query_engine,
|
|
135
|
+
plugin_manager=write_manager,
|
|
136
|
+
token_budget=token_budget,
|
|
137
|
+
config=self.config,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# 11. 构建 MCS
|
|
141
|
+
mcs = MCS(
|
|
142
|
+
write_pipeline=write_pipeline,
|
|
143
|
+
query_engine=query_engine,
|
|
144
|
+
store=store,
|
|
145
|
+
write_manager=write_manager,
|
|
146
|
+
read_manager=read_manager,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# 12. 执行 load-on-startup
|
|
150
|
+
self._load_on_startup(store, read_manager)
|
|
151
|
+
|
|
152
|
+
return mcs
|
|
153
|
+
|
|
154
|
+
def _init_store(self) -> "StoreInterface":
|
|
155
|
+
"""根据配置实例化 Store。
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
Store 实例
|
|
159
|
+
"""
|
|
160
|
+
from mcs.stores.in_memory import InMemoryStore
|
|
161
|
+
from mcs.stores.sqlite_store import SQLiteStore
|
|
162
|
+
|
|
163
|
+
sqlite_config = self.config.plugin_configs.get("sqlite_storage", {})
|
|
164
|
+
db_path = sqlite_config.get("path", "")
|
|
165
|
+
|
|
166
|
+
if db_path and db_path != ":memory:":
|
|
167
|
+
return SQLiteStore(sqlite_config)
|
|
168
|
+
return InMemoryStore()
|
|
169
|
+
|
|
170
|
+
def _register_plugins(
|
|
171
|
+
self,
|
|
172
|
+
write_manager: "PluginManager",
|
|
173
|
+
read_manager: "PluginManager",
|
|
174
|
+
) -> tuple["Plugin", "Plugin"]:
|
|
175
|
+
"""按 shared/write/read 分类实例化并注册插件到双 manager。
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
write_manager: 写入侧插件管理器
|
|
179
|
+
read_manager: 读取侧插件管理器
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
(write_llm, read_llm) 插件实例元组
|
|
183
|
+
"""
|
|
184
|
+
# 1. 共享插件:同一实例注册到两个 manager
|
|
185
|
+
for plugin_name in self.config.shared_plugins:
|
|
186
|
+
plugin = self._instantiate_plugin(plugin_name)
|
|
187
|
+
if plugin is None:
|
|
188
|
+
continue
|
|
189
|
+
write_manager.register(plugin)
|
|
190
|
+
read_manager.register(plugin)
|
|
191
|
+
|
|
192
|
+
# 2. 写入专用:只注册到 write_manager
|
|
193
|
+
for plugin_name in self.config.write_plugins:
|
|
194
|
+
plugin = self._instantiate_plugin(plugin_name)
|
|
195
|
+
if plugin is None:
|
|
196
|
+
continue
|
|
197
|
+
write_manager.register(plugin)
|
|
198
|
+
|
|
199
|
+
# 3. 读取专用:只注册到 read_manager
|
|
200
|
+
for plugin_name in self.config.read_plugins:
|
|
201
|
+
plugin = self._instantiate_plugin(plugin_name)
|
|
202
|
+
if plugin is None:
|
|
203
|
+
continue
|
|
204
|
+
read_manager.register(plugin)
|
|
205
|
+
|
|
206
|
+
# 4. 处理 LLM 分离
|
|
207
|
+
return self._register_llm_plugins(write_manager, read_manager)
|
|
208
|
+
|
|
209
|
+
def _instantiate_plugin(self, plugin_name: str) -> "Plugin | None":
|
|
210
|
+
"""从注册表实例化插件。
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
plugin_name: 插件名称
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
插件实例,若未找到则返回 None
|
|
217
|
+
"""
|
|
218
|
+
cls = self.get_plugin_class(plugin_name)
|
|
219
|
+
if cls is None:
|
|
220
|
+
return None
|
|
221
|
+
plugin_config = self.config.plugin_configs.get(plugin_name, {})
|
|
222
|
+
try:
|
|
223
|
+
return cls(plugin_config)
|
|
224
|
+
except TypeError:
|
|
225
|
+
return cls()
|
|
226
|
+
|
|
227
|
+
def _register_llm_plugins(
|
|
228
|
+
self,
|
|
229
|
+
write_manager: "PluginManager",
|
|
230
|
+
read_manager: "PluginManager",
|
|
231
|
+
) -> tuple["Plugin", "Plugin"]:
|
|
232
|
+
"""处理 LLM 分离:write_llm / read_llm 从对应 manager 查找;相同时共享实例。
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
write_manager: 写入侧插件管理器
|
|
236
|
+
read_manager: 读取侧插件管理器
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
(write_llm, read_llm) 插件实例元组
|
|
240
|
+
"""
|
|
241
|
+
write_llm_name = self.config.write_llm
|
|
242
|
+
read_llm_name = self.config.read_llm
|
|
243
|
+
|
|
244
|
+
if not write_llm_name:
|
|
245
|
+
raise RuntimeError(
|
|
246
|
+
"未指定 write_llm。请在 MCSConfig 中设置 write_llm。"
|
|
247
|
+
)
|
|
248
|
+
if not read_llm_name:
|
|
249
|
+
raise RuntimeError(
|
|
250
|
+
"未指定 read_llm。请在 MCSConfig 中设置 read_llm。"
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
if write_llm_name == read_llm_name:
|
|
254
|
+
# 同一 LLM:共享实例
|
|
255
|
+
# 先检查是否已通过 register_plugin 注册
|
|
256
|
+
existing = write_manager.get_by_name(write_llm_name) or \
|
|
257
|
+
read_manager.get_by_name(read_llm_name)
|
|
258
|
+
if existing is not None:
|
|
259
|
+
# 已注册,只需确保两个 manager 都有
|
|
260
|
+
if write_manager.get_by_name(write_llm_name) is None:
|
|
261
|
+
write_manager.register(existing)
|
|
262
|
+
if read_manager.get_by_name(read_llm_name) is None:
|
|
263
|
+
read_manager.register(existing)
|
|
264
|
+
else:
|
|
265
|
+
# 从 registry 实例化
|
|
266
|
+
plugin = self._instantiate_plugin(write_llm_name)
|
|
267
|
+
if plugin is None:
|
|
268
|
+
raise RuntimeError(
|
|
269
|
+
f"未找到 LLM 插件: {write_llm_name!r}。"
|
|
270
|
+
"请确保插件注册表中包含该插件。"
|
|
271
|
+
)
|
|
272
|
+
write_manager.register(plugin)
|
|
273
|
+
read_manager.register(plugin)
|
|
274
|
+
else:
|
|
275
|
+
# 不同 LLM:各自独立
|
|
276
|
+
write_plugin = write_manager.get_by_name(write_llm_name) or \
|
|
277
|
+
read_manager.get_by_name(write_llm_name)
|
|
278
|
+
if write_plugin is None:
|
|
279
|
+
write_plugin = self._instantiate_plugin(write_llm_name)
|
|
280
|
+
if write_plugin is None:
|
|
281
|
+
raise RuntimeError(
|
|
282
|
+
f"未找到写入 LLM 插件: {write_llm_name!r}。"
|
|
283
|
+
"请确保插件注册表中包含该插件。"
|
|
284
|
+
)
|
|
285
|
+
if write_manager.get_by_name(write_llm_name) is None:
|
|
286
|
+
write_manager.register(write_plugin)
|
|
287
|
+
|
|
288
|
+
read_plugin = read_manager.get_by_name(read_llm_name) or \
|
|
289
|
+
write_manager.get_by_name(read_llm_name)
|
|
290
|
+
if read_plugin is None:
|
|
291
|
+
read_plugin = self._instantiate_plugin(read_llm_name)
|
|
292
|
+
if read_plugin is None:
|
|
293
|
+
raise RuntimeError(
|
|
294
|
+
f"未找到读取 LLM 插件: {read_llm_name!r}。"
|
|
295
|
+
"请确保插件注册表中包含该插件。"
|
|
296
|
+
)
|
|
297
|
+
if read_manager.get_by_name(read_llm_name) is None:
|
|
298
|
+
read_manager.register(read_plugin)
|
|
299
|
+
|
|
300
|
+
# 解析 LLM 引用
|
|
301
|
+
write_llm = write_manager.get_by_name(write_llm_name)
|
|
302
|
+
read_llm = read_manager.get_by_name(read_llm_name)
|
|
303
|
+
|
|
304
|
+
return write_llm, read_llm
|
|
305
|
+
|
|
306
|
+
def _init_plugin_context(
|
|
307
|
+
self,
|
|
308
|
+
store: "StoreInterface",
|
|
309
|
+
token_budget: "TokenBudget",
|
|
310
|
+
context_renderer: "ContextRenderer",
|
|
311
|
+
write_manager: "PluginManager",
|
|
312
|
+
read_manager: "PluginManager",
|
|
313
|
+
) -> None:
|
|
314
|
+
"""创建 PluginContext 并初始化所有插件。
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
store: 存储实例
|
|
318
|
+
token_budget: Token 预算
|
|
319
|
+
context_renderer: 上下文渲染器
|
|
320
|
+
write_manager: 写入侧插件管理器
|
|
321
|
+
read_manager: 读取侧插件管理器
|
|
322
|
+
"""
|
|
323
|
+
from mcs.core.plugin_manager import PluginContext
|
|
324
|
+
|
|
325
|
+
# 写入侧插件初始化
|
|
326
|
+
write_ctx = PluginContext(
|
|
327
|
+
store=store,
|
|
328
|
+
config=self.config,
|
|
329
|
+
token_budget=token_budget,
|
|
330
|
+
context_renderer=context_renderer,
|
|
331
|
+
plugin_manager=write_manager,
|
|
332
|
+
)
|
|
333
|
+
write_manager.initialize_all(write_ctx)
|
|
334
|
+
|
|
335
|
+
# 读取侧插件初始化
|
|
336
|
+
read_ctx = PluginContext(
|
|
337
|
+
store=store,
|
|
338
|
+
config=self.config,
|
|
339
|
+
token_budget=token_budget,
|
|
340
|
+
context_renderer=context_renderer,
|
|
341
|
+
plugin_manager=read_manager,
|
|
342
|
+
)
|
|
343
|
+
read_manager.initialize_all(read_ctx)
|
|
344
|
+
|
|
345
|
+
def _apply_prompt_overrides(
|
|
346
|
+
self,
|
|
347
|
+
write_llm: "Plugin",
|
|
348
|
+
read_llm: "Plugin",
|
|
349
|
+
) -> None:
|
|
350
|
+
"""将用户 prompt 覆盖应用到 LLM。
|
|
351
|
+
|
|
352
|
+
Args:
|
|
353
|
+
write_llm: 写入 LLM 插件
|
|
354
|
+
read_llm: 读取 LLM 插件
|
|
355
|
+
"""
|
|
356
|
+
for purpose, overrides in (self.config.prompt_overrides or {}).items():
|
|
357
|
+
write_llm.register_prompt(
|
|
358
|
+
purpose,
|
|
359
|
+
system=overrides.get("system"),
|
|
360
|
+
template=overrides.get("template"),
|
|
361
|
+
parser=overrides.get("parser"),
|
|
362
|
+
)
|
|
363
|
+
# 如果 read_llm 与 write_llm 不同,也应用覆盖
|
|
364
|
+
if read_llm is not write_llm:
|
|
365
|
+
read_llm.register_prompt(
|
|
366
|
+
purpose,
|
|
367
|
+
system=overrides.get("system"),
|
|
368
|
+
template=overrides.get("template"),
|
|
369
|
+
parser=overrides.get("parser"),
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
def _load_on_startup(
|
|
373
|
+
self,
|
|
374
|
+
store: "StoreInterface",
|
|
375
|
+
read_manager: "PluginManager",
|
|
376
|
+
) -> None:
|
|
377
|
+
"""若 Store 为空且 SQLite 可用,加载已有数据并重建 Index。
|
|
378
|
+
|
|
379
|
+
Args:
|
|
380
|
+
store: 存储实例
|
|
381
|
+
read_manager: 读取侧插件管理器(用于获取 Index 插件)
|
|
382
|
+
"""
|
|
383
|
+
from mcs.core.plugin import PluginType
|
|
384
|
+
from mcs.stores.sqlite_store import SQLiteStore
|
|
385
|
+
|
|
386
|
+
if store.get_all_nodes():
|
|
387
|
+
return
|
|
388
|
+
if not isinstance(store, SQLiteStore):
|
|
389
|
+
return
|
|
390
|
+
try:
|
|
391
|
+
store.load()
|
|
392
|
+
# reload 后重建所有 IndexInterface 索引
|
|
393
|
+
for index in read_manager.get_all(PluginType.INDEX):
|
|
394
|
+
try:
|
|
395
|
+
index.build(store)
|
|
396
|
+
except NotImplementedError:
|
|
397
|
+
continue
|
|
398
|
+
except Exception:
|
|
399
|
+
logger.warning("Load-on-startup failed", exc_info=True)
|
|
400
|
+
|
|
401
|
+
def _collect_registry(self) -> dict[str, type["Plugin"]]:
|
|
402
|
+
"""从 shared + write + read + LLM 收集插件注册表。
|
|
403
|
+
|
|
404
|
+
Returns:
|
|
405
|
+
插件名称 → 插件类的映射
|
|
406
|
+
"""
|
|
407
|
+
all_names: list[str] = []
|
|
408
|
+
|
|
409
|
+
# 收集所有插件名称(去重)
|
|
410
|
+
seen: set[str] = set()
|
|
411
|
+
for name in (
|
|
412
|
+
self.config.shared_plugins +
|
|
413
|
+
self.config.write_plugins +
|
|
414
|
+
self.config.read_plugins
|
|
415
|
+
):
|
|
416
|
+
if name not in seen:
|
|
417
|
+
all_names.append(name)
|
|
418
|
+
seen.add(name)
|
|
419
|
+
|
|
420
|
+
# LLM 也加入(即使不在 plugins 列表中)
|
|
421
|
+
for llm in [self.config.write_llm, self.config.read_llm]:
|
|
422
|
+
if llm and llm not in seen:
|
|
423
|
+
all_names.append(llm)
|
|
424
|
+
seen.add(llm)
|
|
425
|
+
|
|
426
|
+
# 查找插件类
|
|
427
|
+
registry: dict[str, type["Plugin"]] = {}
|
|
428
|
+
for name in all_names:
|
|
429
|
+
cls = self.get_plugin_class(name)
|
|
430
|
+
if cls is not None:
|
|
431
|
+
registry[name] = cls
|
|
432
|
+
|
|
433
|
+
return registry
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""校准经验式 token 估算器——精确方案的兜底。
|
|
2
|
+
|
|
3
|
+
当 LLM 插件的精确计数(API / tiktoken)不可用时,使用按模型族调整系数的
|
|
4
|
+
经验式替代当前硬编码的 CJK 1:1 + 非CJK 4:1。
|
|
5
|
+
|
|
6
|
+
设计原则:宁可高估(过早触发裂变)也不低估(破坏核心不变量)。
|
|
7
|
+
unknown 模型采用 claude/gpt 系数(最保守)。
|
|
8
|
+
|
|
9
|
+
系数来源:下表系数为文献/经验值(CJK 系数取各模型族中文 token/字 的保守上界)。
|
|
10
|
+
``bench/calibration/calibrate_token_estimator.py`` 提供校准工具(对照精确计数拟合
|
|
11
|
+
系数),尚未跑实测定标;定标后更新 ``COEFFICIENTS``。
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CalibratedEstimator:
|
|
18
|
+
"""按模型族调整系数的经验式 token 估算器。
|
|
19
|
+
|
|
20
|
+
每个模型族有独立的 CJK 系数和非 CJK 除数:
|
|
21
|
+
|
|
22
|
+
estimate(text) = cjk_count * cjk_coeff + non_cjk_count // non_cjk_divisor
|
|
23
|
+
|
|
24
|
+
系数表为文献/经验值(详见 design.md §校准方法论),待 bench/calibration 实测定标。
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
# 模型族 → (CJK 系数, 非CJK 字符/token 除数)。文献/经验值,待实测定标
|
|
28
|
+
COEFFICIENTS: dict[str, tuple[float, int]] = {
|
|
29
|
+
"claude": (1.7, 3),
|
|
30
|
+
"gpt": (1.7, 3),
|
|
31
|
+
"deepseek": (1.3, 4),
|
|
32
|
+
"ollama": (1.3, 4),
|
|
33
|
+
"unknown": (1.7, 3), # 保守——与 claude/gpt 相同
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
def __init__(self, model_family: str = "unknown") -> None:
|
|
37
|
+
self._model_family = model_family
|
|
38
|
+
cjk_coeff, non_cjk_divisor = self.COEFFICIENTS.get(
|
|
39
|
+
model_family, self.COEFFICIENTS["unknown"]
|
|
40
|
+
)
|
|
41
|
+
self._cjk_coeff = cjk_coeff
|
|
42
|
+
self._non_cjk_divisor = non_cjk_divisor
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def model_family(self) -> str:
|
|
46
|
+
"""返回构造时指定的模型族名称。"""
|
|
47
|
+
return self._model_family
|
|
48
|
+
|
|
49
|
+
def estimate(self, text: str | None) -> int:
|
|
50
|
+
"""估算 ``text`` 的 token 数量。
|
|
51
|
+
|
|
52
|
+
- CJK 字符按 ``cjk_coeff`` 计(如 claude ×1.7)
|
|
53
|
+
- 非 CJK 字符按 ``non_cjk_divisor`` 除(如 claude ÷3)
|
|
54
|
+
- 空值/None 返回 0
|
|
55
|
+
- 非空文本至少返回 1
|
|
56
|
+
"""
|
|
57
|
+
if not text:
|
|
58
|
+
return 0
|
|
59
|
+
cjk = sum(1 for ch in text if "一" <= ch <= "鿿")
|
|
60
|
+
non_cjk = len(text) - cjk
|
|
61
|
+
return max(1, int(cjk * self._cjk_coeff + non_cjk // self._non_cjk_divisor))
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""公共 content 合并 helper。
|
|
2
|
+
|
|
3
|
+
统一 3 条字面合并路径的 content 处理(write path / query read-repair / 后台 dedup),
|
|
4
|
+
落实 unified-graph-schema「图质量最终收敛」content 合并守则:不机械换行追加。
|
|
5
|
+
|
|
6
|
+
行为:
|
|
7
|
+
- 子串关系(target ⊇ incoming → 跳过;incoming ⊇ target → 替换)→ 零成本,所有路径共用。
|
|
8
|
+
- 非子串 + merge_llm 传入 → LLM 语义合并成一个稳定定义(守时间归属)。
|
|
9
|
+
- 非子串 + merge_llm=None → 返回 target(不碰;调用方决定挂起 / 保留 dup)。
|
|
10
|
+
|
|
11
|
+
``substring_relation`` 抽出子串包含判定,供 ``merge_content`` 与后台 dedup 共用——
|
|
12
|
+
单点定义,防两处分支口径漂移。
|
|
13
|
+
|
|
14
|
+
调用方分流(按 LLM 可用性):
|
|
15
|
+
- write path `_dispatch_merge` 传 merge_llm(每次 ingest 同名对齐,本就 LLM)。
|
|
16
|
+
- query read-repair `_try_read_repair` 不传(读路径零 LLM;被并方节点保留)。
|
|
17
|
+
- 后台 dedup 不传(子串才合删 dup,非子串保留 dup;彻底合并靠 write path)。
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import logging
|
|
23
|
+
from typing import Callable, Optional
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def substring_relation(target: str, incoming: str) -> Optional[str]:
|
|
29
|
+
"""两段 content 的子串包含关系(``merge_content`` 与 dedup 共用,防口径漂移)。
|
|
30
|
+
|
|
31
|
+
返回:
|
|
32
|
+
- ``"target"``:incoming 为空,或 incoming ⊆ target(target 更全 / 相等)。
|
|
33
|
+
- ``"incoming"``:target 为空,或 target ⊆ incoming(incoming 更全)。
|
|
34
|
+
- ``None``:两方都非空且互不包含(非子串)。
|
|
35
|
+
|
|
36
|
+
调用方据此区分「可零成本合并」(返回非 None)与「需 LLM / 保留 dup」(None)。
|
|
37
|
+
"""
|
|
38
|
+
t = target or ""
|
|
39
|
+
i = incoming or ""
|
|
40
|
+
if not i.strip():
|
|
41
|
+
return "target"
|
|
42
|
+
if not t.strip():
|
|
43
|
+
return "incoming"
|
|
44
|
+
if i in t:
|
|
45
|
+
return "target"
|
|
46
|
+
if t in i:
|
|
47
|
+
return "incoming"
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def merge_content(
|
|
52
|
+
target: str,
|
|
53
|
+
incoming: str,
|
|
54
|
+
merge_llm: Optional[Callable[[str, str], str]] = None,
|
|
55
|
+
) -> str:
|
|
56
|
+
"""合并两段 content 成一个稳定定义(不机械换行追加)。
|
|
57
|
+
|
|
58
|
+
- 子串关系零成本(经 ``substring_relation``:incoming ⊆ target → target;
|
|
59
|
+
target ⊆ incoming → incoming;含一方空)。
|
|
60
|
+
- 非子串 + merge_llm:LLM 语义合并;异常 / 空返回 → 降级保留 target + warning。
|
|
61
|
+
- 非子串 + merge_llm=None:返回 target(不碰)。
|
|
62
|
+
"""
|
|
63
|
+
rel = substring_relation(target, incoming)
|
|
64
|
+
if rel == "target":
|
|
65
|
+
return target
|
|
66
|
+
if rel == "incoming":
|
|
67
|
+
return incoming
|
|
68
|
+
|
|
69
|
+
# 非子串(两方都非空、互不包含)
|
|
70
|
+
if merge_llm is None:
|
|
71
|
+
return target
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
merged = merge_llm(target, incoming)
|
|
75
|
+
except Exception:
|
|
76
|
+
logger.warning("merge_content: LLM 合并失败,保留 target content", exc_info=True)
|
|
77
|
+
return target
|
|
78
|
+
if not merged or not merged.strip():
|
|
79
|
+
logger.warning("merge_content: LLM 返回空,保留 target content")
|
|
80
|
+
return target
|
|
81
|
+
return merged.strip()
|