kweaver-dolphin 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.
- DolphinLanguageSDK/__init__.py +58 -0
- dolphin/__init__.py +62 -0
- dolphin/cli/__init__.py +20 -0
- dolphin/cli/args/__init__.py +9 -0
- dolphin/cli/args/parser.py +567 -0
- dolphin/cli/builtin_agents/__init__.py +22 -0
- dolphin/cli/commands/__init__.py +4 -0
- dolphin/cli/interrupt/__init__.py +8 -0
- dolphin/cli/interrupt/handler.py +205 -0
- dolphin/cli/interrupt/keyboard.py +82 -0
- dolphin/cli/main.py +49 -0
- dolphin/cli/multimodal/__init__.py +34 -0
- dolphin/cli/multimodal/clipboard.py +327 -0
- dolphin/cli/multimodal/handler.py +249 -0
- dolphin/cli/multimodal/image_processor.py +214 -0
- dolphin/cli/multimodal/input_parser.py +149 -0
- dolphin/cli/runner/__init__.py +8 -0
- dolphin/cli/runner/runner.py +989 -0
- dolphin/cli/ui/__init__.py +10 -0
- dolphin/cli/ui/console.py +2795 -0
- dolphin/cli/ui/input.py +340 -0
- dolphin/cli/ui/layout.py +425 -0
- dolphin/cli/ui/stream_renderer.py +302 -0
- dolphin/cli/utils/__init__.py +8 -0
- dolphin/cli/utils/helpers.py +135 -0
- dolphin/cli/utils/version.py +49 -0
- dolphin/core/__init__.py +107 -0
- dolphin/core/agent/__init__.py +10 -0
- dolphin/core/agent/agent_state.py +69 -0
- dolphin/core/agent/base_agent.py +970 -0
- dolphin/core/code_block/__init__.py +0 -0
- dolphin/core/code_block/agent_init_block.py +0 -0
- dolphin/core/code_block/assign_block.py +98 -0
- dolphin/core/code_block/basic_code_block.py +1865 -0
- dolphin/core/code_block/explore_block.py +1327 -0
- dolphin/core/code_block/explore_block_v2.py +712 -0
- dolphin/core/code_block/explore_strategy.py +672 -0
- dolphin/core/code_block/judge_block.py +220 -0
- dolphin/core/code_block/prompt_block.py +32 -0
- dolphin/core/code_block/skill_call_deduplicator.py +291 -0
- dolphin/core/code_block/tool_block.py +129 -0
- dolphin/core/common/__init__.py +17 -0
- dolphin/core/common/constants.py +176 -0
- dolphin/core/common/enums.py +1173 -0
- dolphin/core/common/exceptions.py +133 -0
- dolphin/core/common/multimodal.py +539 -0
- dolphin/core/common/object_type.py +165 -0
- dolphin/core/common/output_format.py +432 -0
- dolphin/core/common/types.py +36 -0
- dolphin/core/config/__init__.py +16 -0
- dolphin/core/config/global_config.py +1289 -0
- dolphin/core/config/ontology_config.py +133 -0
- dolphin/core/context/__init__.py +12 -0
- dolphin/core/context/context.py +1580 -0
- dolphin/core/context/context_manager.py +161 -0
- dolphin/core/context/var_output.py +82 -0
- dolphin/core/context/variable_pool.py +356 -0
- dolphin/core/context_engineer/__init__.py +41 -0
- dolphin/core/context_engineer/config/__init__.py +5 -0
- dolphin/core/context_engineer/config/settings.py +402 -0
- dolphin/core/context_engineer/core/__init__.py +7 -0
- dolphin/core/context_engineer/core/budget_manager.py +327 -0
- dolphin/core/context_engineer/core/context_assembler.py +583 -0
- dolphin/core/context_engineer/core/context_manager.py +637 -0
- dolphin/core/context_engineer/core/tokenizer_service.py +260 -0
- dolphin/core/context_engineer/example/incremental_example.py +267 -0
- dolphin/core/context_engineer/example/traditional_example.py +334 -0
- dolphin/core/context_engineer/services/__init__.py +5 -0
- dolphin/core/context_engineer/services/compressor.py +399 -0
- dolphin/core/context_engineer/utils/__init__.py +6 -0
- dolphin/core/context_engineer/utils/context_utils.py +441 -0
- dolphin/core/context_engineer/utils/message_formatter.py +270 -0
- dolphin/core/context_engineer/utils/token_utils.py +139 -0
- dolphin/core/coroutine/__init__.py +15 -0
- dolphin/core/coroutine/context_snapshot.py +154 -0
- dolphin/core/coroutine/context_snapshot_profile.py +922 -0
- dolphin/core/coroutine/context_snapshot_store.py +268 -0
- dolphin/core/coroutine/execution_frame.py +145 -0
- dolphin/core/coroutine/execution_state_registry.py +161 -0
- dolphin/core/coroutine/resume_handle.py +101 -0
- dolphin/core/coroutine/step_result.py +101 -0
- dolphin/core/executor/__init__.py +18 -0
- dolphin/core/executor/debug_controller.py +630 -0
- dolphin/core/executor/dolphin_executor.py +1063 -0
- dolphin/core/executor/executor.py +624 -0
- dolphin/core/flags/__init__.py +27 -0
- dolphin/core/flags/definitions.py +49 -0
- dolphin/core/flags/manager.py +113 -0
- dolphin/core/hook/__init__.py +95 -0
- dolphin/core/hook/expression_evaluator.py +499 -0
- dolphin/core/hook/hook_dispatcher.py +380 -0
- dolphin/core/hook/hook_types.py +248 -0
- dolphin/core/hook/isolated_variable_pool.py +284 -0
- dolphin/core/interfaces.py +53 -0
- dolphin/core/llm/__init__.py +0 -0
- dolphin/core/llm/llm.py +495 -0
- dolphin/core/llm/llm_call.py +100 -0
- dolphin/core/llm/llm_client.py +1285 -0
- dolphin/core/llm/message_sanitizer.py +120 -0
- dolphin/core/logging/__init__.py +20 -0
- dolphin/core/logging/logger.py +526 -0
- dolphin/core/message/__init__.py +8 -0
- dolphin/core/message/compressor.py +749 -0
- dolphin/core/parser/__init__.py +8 -0
- dolphin/core/parser/parser.py +405 -0
- dolphin/core/runtime/__init__.py +10 -0
- dolphin/core/runtime/runtime_graph.py +926 -0
- dolphin/core/runtime/runtime_instance.py +446 -0
- dolphin/core/skill/__init__.py +14 -0
- dolphin/core/skill/context_retention.py +157 -0
- dolphin/core/skill/skill_function.py +686 -0
- dolphin/core/skill/skill_matcher.py +282 -0
- dolphin/core/skill/skillkit.py +700 -0
- dolphin/core/skill/skillset.py +72 -0
- dolphin/core/trajectory/__init__.py +10 -0
- dolphin/core/trajectory/recorder.py +189 -0
- dolphin/core/trajectory/trajectory.py +522 -0
- dolphin/core/utils/__init__.py +9 -0
- dolphin/core/utils/cache_kv.py +212 -0
- dolphin/core/utils/tools.py +340 -0
- dolphin/lib/__init__.py +93 -0
- dolphin/lib/debug/__init__.py +8 -0
- dolphin/lib/debug/visualizer.py +409 -0
- dolphin/lib/memory/__init__.py +28 -0
- dolphin/lib/memory/async_processor.py +220 -0
- dolphin/lib/memory/llm_calls.py +195 -0
- dolphin/lib/memory/manager.py +78 -0
- dolphin/lib/memory/sandbox.py +46 -0
- dolphin/lib/memory/storage.py +245 -0
- dolphin/lib/memory/utils.py +51 -0
- dolphin/lib/ontology/__init__.py +12 -0
- dolphin/lib/ontology/basic/__init__.py +0 -0
- dolphin/lib/ontology/basic/base.py +102 -0
- dolphin/lib/ontology/basic/concept.py +130 -0
- dolphin/lib/ontology/basic/object.py +11 -0
- dolphin/lib/ontology/basic/relation.py +63 -0
- dolphin/lib/ontology/datasource/__init__.py +27 -0
- dolphin/lib/ontology/datasource/datasource.py +66 -0
- dolphin/lib/ontology/datasource/oracle_datasource.py +338 -0
- dolphin/lib/ontology/datasource/sql.py +845 -0
- dolphin/lib/ontology/mapping.py +177 -0
- dolphin/lib/ontology/ontology.py +733 -0
- dolphin/lib/ontology/ontology_context.py +16 -0
- dolphin/lib/ontology/ontology_manager.py +107 -0
- dolphin/lib/skill_results/__init__.py +31 -0
- dolphin/lib/skill_results/cache_backend.py +559 -0
- dolphin/lib/skill_results/result_processor.py +181 -0
- dolphin/lib/skill_results/result_reference.py +179 -0
- dolphin/lib/skill_results/skillkit_hook.py +324 -0
- dolphin/lib/skill_results/strategies.py +328 -0
- dolphin/lib/skill_results/strategy_registry.py +150 -0
- dolphin/lib/skillkits/__init__.py +44 -0
- dolphin/lib/skillkits/agent_skillkit.py +155 -0
- dolphin/lib/skillkits/cognitive_skillkit.py +82 -0
- dolphin/lib/skillkits/env_skillkit.py +250 -0
- dolphin/lib/skillkits/mcp_adapter.py +616 -0
- dolphin/lib/skillkits/mcp_skillkit.py +771 -0
- dolphin/lib/skillkits/memory_skillkit.py +650 -0
- dolphin/lib/skillkits/noop_skillkit.py +31 -0
- dolphin/lib/skillkits/ontology_skillkit.py +89 -0
- dolphin/lib/skillkits/plan_act_skillkit.py +452 -0
- dolphin/lib/skillkits/resource/__init__.py +52 -0
- dolphin/lib/skillkits/resource/models/__init__.py +6 -0
- dolphin/lib/skillkits/resource/models/skill_config.py +109 -0
- dolphin/lib/skillkits/resource/models/skill_meta.py +127 -0
- dolphin/lib/skillkits/resource/resource_skillkit.py +393 -0
- dolphin/lib/skillkits/resource/skill_cache.py +215 -0
- dolphin/lib/skillkits/resource/skill_loader.py +395 -0
- dolphin/lib/skillkits/resource/skill_validator.py +406 -0
- dolphin/lib/skillkits/resource_skillkit.py +11 -0
- dolphin/lib/skillkits/search_skillkit.py +163 -0
- dolphin/lib/skillkits/sql_skillkit.py +274 -0
- dolphin/lib/skillkits/system_skillkit.py +509 -0
- dolphin/lib/skillkits/vm_skillkit.py +65 -0
- dolphin/lib/utils/__init__.py +9 -0
- dolphin/lib/utils/data_process.py +207 -0
- dolphin/lib/utils/handle_progress.py +178 -0
- dolphin/lib/utils/security.py +139 -0
- dolphin/lib/utils/text_retrieval.py +462 -0
- dolphin/lib/vm/__init__.py +11 -0
- dolphin/lib/vm/env_executor.py +895 -0
- dolphin/lib/vm/python_session_manager.py +453 -0
- dolphin/lib/vm/vm.py +610 -0
- dolphin/sdk/__init__.py +60 -0
- dolphin/sdk/agent/__init__.py +12 -0
- dolphin/sdk/agent/agent_factory.py +236 -0
- dolphin/sdk/agent/dolphin_agent.py +1106 -0
- dolphin/sdk/api/__init__.py +4 -0
- dolphin/sdk/runtime/__init__.py +8 -0
- dolphin/sdk/runtime/env.py +363 -0
- dolphin/sdk/skill/__init__.py +10 -0
- dolphin/sdk/skill/global_skills.py +706 -0
- dolphin/sdk/skill/traditional_toolkit.py +260 -0
- kweaver_dolphin-0.1.0.dist-info/METADATA +521 -0
- kweaver_dolphin-0.1.0.dist-info/RECORD +199 -0
- kweaver_dolphin-0.1.0.dist-info/WHEEL +5 -0
- kweaver_dolphin-0.1.0.dist-info/entry_points.txt +27 -0
- kweaver_dolphin-0.1.0.dist-info/licenses/LICENSE.txt +201 -0
- kweaver_dolphin-0.1.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LLM Call abstractions for the memory system.
|
|
3
|
+
|
|
4
|
+
This module provides abstract interfaces for LLM operations in the memory system,
|
|
5
|
+
including prompt assembly, post-processing, and formatting.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from typing import List, Any
|
|
11
|
+
|
|
12
|
+
from dolphin.core.llm.llm_call import LLMCall
|
|
13
|
+
|
|
14
|
+
from dolphin.core.common.enums import KnowledgePoint, MessageRole, Messages
|
|
15
|
+
from dolphin.core.logging.logger import get_logger
|
|
16
|
+
from dolphin.lib.memory.utils import validate_knowledge_point
|
|
17
|
+
|
|
18
|
+
logger = get_logger("mem")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class KnowledgeExtractionCall(LLMCall):
|
|
22
|
+
"""LLM call for extracting knowledge from conversations."""
|
|
23
|
+
|
|
24
|
+
def execute(
|
|
25
|
+
self, conversation_history: Messages, user_id: str
|
|
26
|
+
) -> List[KnowledgePoint]:
|
|
27
|
+
"""Execute knowledge extraction for a specific conversation and user."""
|
|
28
|
+
if len(conversation_history.get_messages()) == 0:
|
|
29
|
+
return []
|
|
30
|
+
|
|
31
|
+
return super().execute(
|
|
32
|
+
llm_args={"knowledge_extraction": True},
|
|
33
|
+
conversation_history=conversation_history,
|
|
34
|
+
user_id=user_id,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
def _log(self, time_cost: float, **kwargs) -> str:
|
|
38
|
+
"""Log the execution result."""
|
|
39
|
+
logger.info(
|
|
40
|
+
f"Knowledge extraction execution user_id[{kwargs.get('user_id', 'unknown')}] time[{time_cost} seconds]"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
def _build_prompt(self, conversation_history: Messages, **kwargs) -> str:
|
|
44
|
+
"""Build prompt for knowledge extraction."""
|
|
45
|
+
conversation_text = ""
|
|
46
|
+
for msg in conversation_history.get_messages():
|
|
47
|
+
if msg.role == MessageRole.SYSTEM:
|
|
48
|
+
continue
|
|
49
|
+
|
|
50
|
+
conversation_text += f"{msg.role.value}: {msg.content}\n"
|
|
51
|
+
|
|
52
|
+
"""You are a professional knowledge management expert. Your task is to analyze the following conversation and extract valuable knowledge. The knowledge should be categorized into one of the following three types:
|
|
53
|
+
|
|
54
|
+
1. **WorldModel**: Facts about the external world, ontologies, data sources, or user-provided correction information.
|
|
55
|
+
2. **ExperientialKnowledge**: Methodologies, strategies, or lessons learned from agent task execution.
|
|
56
|
+
3. **OtherKnowledge**: Any other information worth remembering, such as user preferences or specific details.
|
|
57
|
+
|
|
58
|
+
For each piece of knowledge, provide a concise summary, its type, and a relevance score from 0 to 100 indicating its importance for future interactions.
|
|
59
|
+
|
|
60
|
+
Notes:
|
|
61
|
+
1. Do not include information related to specific database data, as this information is volatile.
|
|
62
|
+
2. Knowledge points should have complete predicates, not just single nouns.
|
|
63
|
+
|
|
64
|
+
Please output your findings strictly in JSONL format, with one JSON object per line. Each object should contain the fields: content, type, score, metadata. Do not include any other text or explanations.
|
|
65
|
+
|
|
66
|
+
Conversation history:
|
|
67
|
+
{conversation_text}
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def _post_process(
|
|
71
|
+
self, llm_output: str, user_id: str, **kwargs
|
|
72
|
+
) -> List[KnowledgePoint]:
|
|
73
|
+
"""Parse and validate LLM extraction result."""
|
|
74
|
+
knowledge_points = []
|
|
75
|
+
|
|
76
|
+
for line in llm_output.strip().split("\n"):
|
|
77
|
+
line = line.strip()
|
|
78
|
+
if not line:
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
raw_point = json.loads(line)
|
|
83
|
+
# Add user_id and timestamp to metadata
|
|
84
|
+
raw_point["user_id"] = user_id
|
|
85
|
+
if "metadata" not in raw_point:
|
|
86
|
+
raw_point["metadata"] = {}
|
|
87
|
+
raw_point["metadata"]["extraction_time"] = datetime.now().isoformat()
|
|
88
|
+
|
|
89
|
+
# Validate and convert
|
|
90
|
+
validated_point = validate_knowledge_point(
|
|
91
|
+
KnowledgePoint(**raw_point), user_id
|
|
92
|
+
)
|
|
93
|
+
knowledge_points.append(validated_point)
|
|
94
|
+
|
|
95
|
+
except Exception as e:
|
|
96
|
+
logger.warning(f"Failed to parse knowledge point: {line} - {e}")
|
|
97
|
+
continue
|
|
98
|
+
|
|
99
|
+
return knowledge_points
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class KnowledgeMergeCall(LLMCall):
|
|
103
|
+
"""LLM call for merging and deduplicating knowledge."""
|
|
104
|
+
|
|
105
|
+
def execute(
|
|
106
|
+
self, all_knowledge_points: List[KnowledgePoint]
|
|
107
|
+
) -> List[KnowledgePoint]:
|
|
108
|
+
"""Execute knowledge merging for a list of knowledge points."""
|
|
109
|
+
return super().execute(
|
|
110
|
+
llm_args={"knowledge_extraction": True},
|
|
111
|
+
all_knowledge_points=all_knowledge_points,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
def _log(self, time_cost: float, **kwargs) -> str:
|
|
115
|
+
"""Log the execution result."""
|
|
116
|
+
logger.info(
|
|
117
|
+
f"Knowledge merge execution user_id[{kwargs.get('user_id', 'unknown')}] time[{time_cost} seconds]"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def _no_merge_result(self, **kwargs) -> Any:
|
|
121
|
+
"""No merge result."""
|
|
122
|
+
return kwargs.get("all_knowledge_points", None)
|
|
123
|
+
|
|
124
|
+
def _build_prompt(self, all_knowledge_points: List[KnowledgePoint]) -> str:
|
|
125
|
+
"""Build prompt for knowledge merging."""
|
|
126
|
+
if len(all_knowledge_points) <= 1:
|
|
127
|
+
return "" # No need to merge if only one or zero points
|
|
128
|
+
|
|
129
|
+
knowledge_json = json.dumps(all_knowledge_points, indent=2, ensure_ascii=False)
|
|
130
|
+
|
|
131
|
+
"""You are a knowledge management expert. Please analyze the following knowledge points and perform intelligent merging:
|
|
132
|
+
|
|
133
|
+
1. Identify similar, redundant, or mergeable knowledge points
|
|
134
|
+
2. Merge related knowledge points into more comprehensive content
|
|
135
|
+
3. Keep unique and valuable knowledge points unchanged
|
|
136
|
+
4. Ensure no important information is lost during merging
|
|
137
|
+
5. Update scores based on综合 importance and reliability
|
|
138
|
+
|
|
139
|
+
Current knowledge points:
|
|
140
|
+
{knowledge_json}
|
|
141
|
+
|
|
142
|
+
Return the optimized knowledge base as a JSON array, where each element follows the KnowledgePoint format.
|
|
143
|
+
Prioritize quality over quantity — fewer but higher-quality knowledge points are better.
|
|
144
|
+
|
|
145
|
+
Return only the JSON array, without any additional text.
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
def _post_process(
|
|
149
|
+
self, llm_output: str, all_knowledge_points: List[KnowledgePoint]
|
|
150
|
+
) -> List[KnowledgePoint]:
|
|
151
|
+
"""Parse and validate LLM merge result."""
|
|
152
|
+
if len(all_knowledge_points) <= 1:
|
|
153
|
+
return all_knowledge_points
|
|
154
|
+
|
|
155
|
+
try:
|
|
156
|
+
merged_knowledge = json.loads(llm_output)
|
|
157
|
+
|
|
158
|
+
# Validate each point and ensure user_id consistency
|
|
159
|
+
user_id = (
|
|
160
|
+
all_knowledge_points[0]["user_id"] if all_knowledge_points else None
|
|
161
|
+
)
|
|
162
|
+
validated_knowledge = []
|
|
163
|
+
|
|
164
|
+
for point in merged_knowledge:
|
|
165
|
+
try:
|
|
166
|
+
validated_point = validate_knowledge_point(point, user_id)
|
|
167
|
+
validated_knowledge.append(validated_point)
|
|
168
|
+
except Exception as e:
|
|
169
|
+
logger.warning(f"Invalid merged knowledge point: {e}")
|
|
170
|
+
continue
|
|
171
|
+
|
|
172
|
+
return validated_knowledge
|
|
173
|
+
|
|
174
|
+
except Exception as e:
|
|
175
|
+
# Fallback: return original knowledge if LLM output is invalid
|
|
176
|
+
logger.warning(f"LLM merge failed, keeping original knowledge: {e}")
|
|
177
|
+
return self._simple_deduplication(all_knowledge_points)
|
|
178
|
+
|
|
179
|
+
def _simple_deduplication(
|
|
180
|
+
self, knowledge_points: List[KnowledgePoint]
|
|
181
|
+
) -> List[KnowledgePoint]:
|
|
182
|
+
"""Simple deduplication as fallback."""
|
|
183
|
+
seen_content = set()
|
|
184
|
+
deduplicated = []
|
|
185
|
+
|
|
186
|
+
# Sort by score to keep highest scored items
|
|
187
|
+
sorted_points = sorted(knowledge_points, key=lambda p: p["score"], reverse=True)
|
|
188
|
+
|
|
189
|
+
for point in sorted_points:
|
|
190
|
+
content_key = point["content"].strip().lower()
|
|
191
|
+
if content_key not in seen_content:
|
|
192
|
+
seen_content.add(content_key)
|
|
193
|
+
deduplicated.append(point)
|
|
194
|
+
|
|
195
|
+
return deduplicated
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Memory Manager - the main orchestrator for the memory system.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import List, Optional
|
|
6
|
+
|
|
7
|
+
from dolphin.core.common.enums import KnowledgePoint
|
|
8
|
+
from .storage import MemoryFileSys
|
|
9
|
+
from dolphin.core.config.global_config import GlobalConfig
|
|
10
|
+
from dolphin.core.logging.logger import get_logger
|
|
11
|
+
|
|
12
|
+
logger = get_logger("mem")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MemoryManager:
|
|
16
|
+
"""The main orchestrator for the memory system - focused on complex knowledge management."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, global_config: GlobalConfig):
|
|
19
|
+
"""
|
|
20
|
+
Initializes the MemoryManager.
|
|
21
|
+
|
|
22
|
+
:param global_config: Global configuration instance, will create default if None.
|
|
23
|
+
"""
|
|
24
|
+
self.global_config = global_config
|
|
25
|
+
self.memory_config = global_config.memory_config
|
|
26
|
+
|
|
27
|
+
self.memory_storage = MemoryFileSys(self.memory_config.storage_path)
|
|
28
|
+
|
|
29
|
+
def retrieve_relevant_memory(
|
|
30
|
+
self, context, user_id: str, query: str = None, top_k: Optional[int] = None
|
|
31
|
+
) -> List[KnowledgePoint]:
|
|
32
|
+
"""
|
|
33
|
+
Retrieves knowledge relevant to a given query for a specific user to be injected into context.
|
|
34
|
+
|
|
35
|
+
:param context: Context instance for accessing skillkits
|
|
36
|
+
:param user_id: The user whose knowledge should be retrieved
|
|
37
|
+
:param query: Query string (not currently used in this implementation)
|
|
38
|
+
:param top_k: Number of knowledge points to return
|
|
39
|
+
"""
|
|
40
|
+
if top_k is None:
|
|
41
|
+
top_k = self.memory_config.default_top_k
|
|
42
|
+
|
|
43
|
+
if context.get_cur_agent() is None:
|
|
44
|
+
return []
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
memory_skill_result = context.get_skillkit().exec(
|
|
48
|
+
"_read_memory",
|
|
49
|
+
agent_name=context.get_cur_agent().get_name(),
|
|
50
|
+
user_id=user_id,
|
|
51
|
+
)
|
|
52
|
+
if not memory_skill_result:
|
|
53
|
+
return []
|
|
54
|
+
|
|
55
|
+
memory_items = memory_skill_result.result
|
|
56
|
+
if not memory_items:
|
|
57
|
+
return []
|
|
58
|
+
|
|
59
|
+
converted_points = []
|
|
60
|
+
for item in memory_items:
|
|
61
|
+
try:
|
|
62
|
+
point = KnowledgePoint(
|
|
63
|
+
content=item.get("content", ""),
|
|
64
|
+
score=item.get("score", 50),
|
|
65
|
+
user_id=item.get("user_id", ""),
|
|
66
|
+
)
|
|
67
|
+
converted_points.append(point)
|
|
68
|
+
except Exception as e:
|
|
69
|
+
logger.warning(
|
|
70
|
+
f"Failed to convert dialog log to KnowledgePoint: {e}"
|
|
71
|
+
)
|
|
72
|
+
continue
|
|
73
|
+
|
|
74
|
+
return converted_points[:top_k]
|
|
75
|
+
|
|
76
|
+
except Exception as e:
|
|
77
|
+
logger.error(f"Failed to retrieve knowledge for user {user_id}: {e}")
|
|
78
|
+
return []
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MemorySandbox:
|
|
7
|
+
"""Session-scoped filesystem sandbox for memory persistence.
|
|
8
|
+
|
|
9
|
+
- Only allows relative paths under `<storage_base>/memories/<session_id>/`.
|
|
10
|
+
- Only allows `.json` files.
|
|
11
|
+
- Provides simple size checks.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
MAX_SIZE = 10 * 1024 * 1024 # 10MB
|
|
15
|
+
MAX_PATH_LENGTH = 512
|
|
16
|
+
|
|
17
|
+
def __init__(self, storage_base: str | Path = "data/memory/") -> None:
|
|
18
|
+
self.root = (Path(storage_base) / "memories").resolve()
|
|
19
|
+
self.root.mkdir(parents=True, exist_ok=True)
|
|
20
|
+
|
|
21
|
+
def resolve_session_path(self, session_id: str, rel_path: str) -> Path:
|
|
22
|
+
if not session_id:
|
|
23
|
+
raise ValueError("session_id must not be empty")
|
|
24
|
+
if not rel_path or rel_path.startswith("/"):
|
|
25
|
+
raise ValueError("Only relative paths are allowed")
|
|
26
|
+
if len(rel_path) > self.MAX_PATH_LENGTH:
|
|
27
|
+
raise ValueError("Path too long")
|
|
28
|
+
|
|
29
|
+
rel = Path(rel_path)
|
|
30
|
+
if ".." in rel.parts:
|
|
31
|
+
raise ValueError("Path escapes sandbox")
|
|
32
|
+
if rel.suffix.lower() != ".json":
|
|
33
|
+
raise ValueError("Only .json is allowed")
|
|
34
|
+
|
|
35
|
+
session_dir = (self.root / session_id).resolve()
|
|
36
|
+
session_dir.mkdir(parents=True, exist_ok=True)
|
|
37
|
+
full_path = (session_dir / rel).resolve()
|
|
38
|
+
# Ensure no escape from session_dir
|
|
39
|
+
full_path.relative_to(session_dir)
|
|
40
|
+
# Ensure parent dirs exist
|
|
41
|
+
full_path.parent.mkdir(parents=True, exist_ok=True)
|
|
42
|
+
return full_path
|
|
43
|
+
|
|
44
|
+
def check_size_bytes(self, size: int) -> None:
|
|
45
|
+
if size > self.MAX_SIZE:
|
|
46
|
+
raise ValueError("File too large")
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Storage layer for the memory management system.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import abc
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import glob
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import List, Dict, Any
|
|
11
|
+
from datetime import datetime, timedelta
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MemoryStorage(abc.ABC):
|
|
15
|
+
"""Simple memory storage abstract interface - for basic memory read and write operations"""
|
|
16
|
+
|
|
17
|
+
@abc.abstractmethod
|
|
18
|
+
def write_memory(
|
|
19
|
+
self, agent_name: str, user_id: str, memory_items: List[Dict[str, Any]]
|
|
20
|
+
) -> bool:
|
|
21
|
+
"""Write memory items
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
agent_name: Agent name
|
|
25
|
+
user_id: User ID, empty string indicates agent memory
|
|
26
|
+
memory_items: List of memory items
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
bool: Whether successful
|
|
30
|
+
"""
|
|
31
|
+
raise NotImplementedError
|
|
32
|
+
|
|
33
|
+
@abc.abstractmethod
|
|
34
|
+
def read_memory(
|
|
35
|
+
self, agent_name: str, user_id: str, days_back: int = 7
|
|
36
|
+
) -> List[Dict[str, Any]]:
|
|
37
|
+
"""Read memory items
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
agent_name: Agent name
|
|
41
|
+
user_id: User ID, empty string indicates agent memory
|
|
42
|
+
days_back: Read memories from the last N days
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
List[Dict[str, Any]]: List of memory items
|
|
46
|
+
"""
|
|
47
|
+
raise NotImplementedError
|
|
48
|
+
|
|
49
|
+
@abc.abstractmethod
|
|
50
|
+
def get_dialog_logs(
|
|
51
|
+
self, agent_name: str, user_id: str = "", count: int = 30
|
|
52
|
+
) -> List[Dict[str, Any]]:
|
|
53
|
+
"""Get conversation logs
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
agent_name: Agent name
|
|
57
|
+
user_id: User ID, empty string to get logs for all users
|
|
58
|
+
count: Number of entries to return
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
List[Dict[str, Any]]: List of conversation logs
|
|
62
|
+
"""
|
|
63
|
+
raise NotImplementedError
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class MemoryFileSys(MemoryStorage):
|
|
67
|
+
"""A simple memory storage for filesystem implementation - for MemorySkillkit"""
|
|
68
|
+
|
|
69
|
+
TimeFormat = "%Y%m%d%H%M"
|
|
70
|
+
|
|
71
|
+
def __init__(self, base_path: str):
|
|
72
|
+
"""
|
|
73
|
+
Initialize the simple memory storage.
|
|
74
|
+
|
|
75
|
+
:param base_path: Base directory path for storing memory data
|
|
76
|
+
"""
|
|
77
|
+
self.base_path = Path(base_path)
|
|
78
|
+
self.base_path.mkdir(parents=True, exist_ok=True)
|
|
79
|
+
|
|
80
|
+
def write_memory(
|
|
81
|
+
self, agent_name: str, user_id: str, memory_items: List[Dict[str, Any]]
|
|
82
|
+
) -> bool:
|
|
83
|
+
"""Write memory items to date-organized files
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
agent_name: Agent name
|
|
87
|
+
user_id: User ID, empty string indicates agent memory (using "_agent")
|
|
88
|
+
memory_items: List of memory items
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
bool: Whether successful
|
|
92
|
+
"""
|
|
93
|
+
try:
|
|
94
|
+
# If user_id is empty, use "_agent" for agent memory
|
|
95
|
+
actual_user_id = user_id if user_id else "_agent"
|
|
96
|
+
|
|
97
|
+
current_date = datetime.now().strftime(self.TimeFormat)
|
|
98
|
+
memory_dir = self.base_path / agent_name / f"user_{actual_user_id}"
|
|
99
|
+
memory_file = memory_dir / f"memory_{current_date}.jsonl"
|
|
100
|
+
|
|
101
|
+
# Create directory if not exists
|
|
102
|
+
memory_dir.mkdir(parents=True, exist_ok=True)
|
|
103
|
+
|
|
104
|
+
# Write memory items to JSONL file
|
|
105
|
+
with open(memory_file, "a", encoding="utf-8") as f:
|
|
106
|
+
for item in memory_items:
|
|
107
|
+
if isinstance(item, dict):
|
|
108
|
+
# Add timestamp if not present
|
|
109
|
+
if "timestamp" not in item:
|
|
110
|
+
item["timestamp"] = datetime.now().isoformat()
|
|
111
|
+
f.write(json.dumps(item, ensure_ascii=False) + "\n")
|
|
112
|
+
|
|
113
|
+
return True
|
|
114
|
+
|
|
115
|
+
except Exception:
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
def read_memory(
|
|
119
|
+
self, agent_name: str, user_id: str, days_back: int = 7
|
|
120
|
+
) -> List[Dict[str, Any]]:
|
|
121
|
+
"""Read memory items from the past few days
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
agent_name: Agent name
|
|
125
|
+
user_id: User ID, empty string indicates agent memory
|
|
126
|
+
days_back: Number of days to look back
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
List[Dict[str, Any]]: List of memory items
|
|
130
|
+
"""
|
|
131
|
+
try:
|
|
132
|
+
# If user_id is empty, use "_agent" for agent memory
|
|
133
|
+
actual_user_id = user_id if user_id else "_agent"
|
|
134
|
+
|
|
135
|
+
memory_dir = self.base_path / agent_name / f"user_{actual_user_id}"
|
|
136
|
+
|
|
137
|
+
if not memory_dir.exists():
|
|
138
|
+
return []
|
|
139
|
+
|
|
140
|
+
# Find memory files from recent days
|
|
141
|
+
memory_items = []
|
|
142
|
+
for file in memory_dir.iterdir():
|
|
143
|
+
if not file.is_file() or not file.name.startswith("memory_"):
|
|
144
|
+
continue
|
|
145
|
+
|
|
146
|
+
date_str = file.name.split("_")[1].split(".")[0]
|
|
147
|
+
date = datetime.strptime(date_str, self.TimeFormat)
|
|
148
|
+
if date < (datetime.now() - timedelta(days=days_back)):
|
|
149
|
+
continue
|
|
150
|
+
|
|
151
|
+
with open(file, "r", encoding="utf-8") as f:
|
|
152
|
+
for line in f:
|
|
153
|
+
if line.strip():
|
|
154
|
+
try:
|
|
155
|
+
item = json.loads(line.strip())
|
|
156
|
+
memory_items.append(item)
|
|
157
|
+
except json.JSONDecodeError:
|
|
158
|
+
continue
|
|
159
|
+
|
|
160
|
+
# Sort by timestamp if available
|
|
161
|
+
memory_items.sort(key=lambda x: x.get("timestamp", ""), reverse=True)
|
|
162
|
+
return memory_items
|
|
163
|
+
|
|
164
|
+
except Exception:
|
|
165
|
+
return []
|
|
166
|
+
|
|
167
|
+
def get_dialog_logs(
|
|
168
|
+
self, agent_name: str, user_id: str = "", count: int = 30
|
|
169
|
+
) -> List[Dict[str, Any]]:
|
|
170
|
+
"""Get conversation log information
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
agent_name: Agent name
|
|
174
|
+
user_id: User ID, empty string to get logs for all users
|
|
175
|
+
count: Number of entries to return
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
List[Dict[str, Any]]: List of conversation logs
|
|
179
|
+
"""
|
|
180
|
+
try:
|
|
181
|
+
logs = []
|
|
182
|
+
|
|
183
|
+
# Dialog path structure: dialog_base_path/{agent_name}/user_{user_id}/dialog_*.jsonl
|
|
184
|
+
dialog_base_path = self.base_path.parent / "dialog"
|
|
185
|
+
|
|
186
|
+
if user_id:
|
|
187
|
+
# Get logs for a specific user
|
|
188
|
+
dialog_dir = dialog_base_path / agent_name / f"user_{user_id}"
|
|
189
|
+
if dialog_dir.exists():
|
|
190
|
+
# Get the latest dialog file
|
|
191
|
+
dialog_files = sorted(
|
|
192
|
+
glob.glob(str(dialog_dir / "dialog_*.jsonl")), reverse=True
|
|
193
|
+
)
|
|
194
|
+
for file_path in dialog_files:
|
|
195
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
196
|
+
for line in f:
|
|
197
|
+
if line.strip():
|
|
198
|
+
try:
|
|
199
|
+
msg = json.loads(line.strip())
|
|
200
|
+
# Filter out system messages
|
|
201
|
+
if msg.get("role") != "system":
|
|
202
|
+
logs.append(msg)
|
|
203
|
+
if len(logs) >= count:
|
|
204
|
+
break
|
|
205
|
+
except json.JSONDecodeError:
|
|
206
|
+
continue
|
|
207
|
+
if len(logs) >= count:
|
|
208
|
+
break
|
|
209
|
+
else:
|
|
210
|
+
# Get logs for all users
|
|
211
|
+
dialog_base_dir = dialog_base_path / agent_name
|
|
212
|
+
if dialog_base_dir.exists():
|
|
213
|
+
all_files = []
|
|
214
|
+
for user_dir in dialog_base_dir.iterdir():
|
|
215
|
+
if user_dir.is_dir():
|
|
216
|
+
dialog_files = glob.glob(str(user_dir / "dialog_*.jsonl"))
|
|
217
|
+
for file_path in dialog_files:
|
|
218
|
+
# Get file modification time for sorting
|
|
219
|
+
mtime = os.path.getmtime(file_path)
|
|
220
|
+
all_files.append((mtime, file_path))
|
|
221
|
+
|
|
222
|
+
# Sort by modification time (newest first)
|
|
223
|
+
all_files.sort(reverse=True)
|
|
224
|
+
|
|
225
|
+
for _, file_path in all_files:
|
|
226
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
227
|
+
for line in f:
|
|
228
|
+
if line.strip():
|
|
229
|
+
try:
|
|
230
|
+
msg = json.loads(line.strip())
|
|
231
|
+
# Filter out system messages
|
|
232
|
+
if msg.get("role") != "system":
|
|
233
|
+
logs.append(msg)
|
|
234
|
+
if len(logs) >= count:
|
|
235
|
+
break
|
|
236
|
+
except json.JSONDecodeError:
|
|
237
|
+
continue
|
|
238
|
+
if len(logs) >= count:
|
|
239
|
+
break
|
|
240
|
+
|
|
241
|
+
# Return the most recent count messages
|
|
242
|
+
return logs[:count]
|
|
243
|
+
|
|
244
|
+
except Exception:
|
|
245
|
+
return []
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utility functions for the memory management system.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from dolphin.core.common.enums import KnowledgePoint
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def validate_knowledge_point(
|
|
9
|
+
data: KnowledgePoint, expected_user_id: str = None
|
|
10
|
+
) -> KnowledgePoint:
|
|
11
|
+
"""
|
|
12
|
+
Validate and convert raw data to KnowledgePoint.
|
|
13
|
+
|
|
14
|
+
:param data: Raw dictionary data
|
|
15
|
+
:param expected_user_id: Expected user_id for validation (optional)
|
|
16
|
+
:return: Validated KnowledgePoint
|
|
17
|
+
:raises KnowledgeValidationError: If validation fails
|
|
18
|
+
"""
|
|
19
|
+
if data.type not in ["WorldModel", "ExperientialKnowledge", "OtherKnowledge"]:
|
|
20
|
+
raise Exception(f"Invalid knowledge type: {data.type}")
|
|
21
|
+
|
|
22
|
+
if not isinstance(data.score, int) or not (0 <= data.score <= 100):
|
|
23
|
+
raise Exception(f"Score must be integer between 0-100: {data['score']}")
|
|
24
|
+
|
|
25
|
+
if expected_user_id and data.user_id != expected_user_id:
|
|
26
|
+
raise Exception(
|
|
27
|
+
f"User ID mismatch: expected {expected_user_id}, got {data.user_id}"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
return KnowledgePoint(
|
|
31
|
+
content=str(data.content),
|
|
32
|
+
type=data.type,
|
|
33
|
+
score=int(data.score),
|
|
34
|
+
user_id=str(data.user_id),
|
|
35
|
+
metadata=data.metadata,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def sanitize_user_id(user_id: str) -> str:
|
|
40
|
+
"""
|
|
41
|
+
Sanitize user_id for safe filesystem usage.
|
|
42
|
+
|
|
43
|
+
:param user_id: Raw user ID
|
|
44
|
+
:return: Sanitized user ID safe for filesystem
|
|
45
|
+
"""
|
|
46
|
+
# Remove or replace potentially problematic characters
|
|
47
|
+
# Allow only word characters, hyphens, and underscores
|
|
48
|
+
import re
|
|
49
|
+
|
|
50
|
+
sanitized = re.sub(r"[^\w\-_]", "_", user_id)
|
|
51
|
+
return sanitized[:50] # Limit length to avoid filesystem issues
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Ontology 模块 - 本体管理系统"""
|
|
3
|
+
|
|
4
|
+
from dolphin.lib.ontology.ontology import Ontology
|
|
5
|
+
from dolphin.lib.ontology.ontology_manager import OntologyManager
|
|
6
|
+
from dolphin.lib.ontology.ontology_context import OntologyContext
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"Ontology",
|
|
10
|
+
"OntologyManager",
|
|
11
|
+
"OntologyContext",
|
|
12
|
+
]
|
|
File without changes
|