powermem 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.
- powermem/__init__.py +103 -0
- powermem/agent/__init__.py +35 -0
- powermem/agent/abstract/__init__.py +22 -0
- powermem/agent/abstract/collaboration.py +259 -0
- powermem/agent/abstract/context.py +187 -0
- powermem/agent/abstract/manager.py +232 -0
- powermem/agent/abstract/permission.py +217 -0
- powermem/agent/abstract/privacy.py +267 -0
- powermem/agent/abstract/scope.py +199 -0
- powermem/agent/agent.py +791 -0
- powermem/agent/components/__init__.py +18 -0
- powermem/agent/components/collaboration_coordinator.py +645 -0
- powermem/agent/components/permission_controller.py +586 -0
- powermem/agent/components/privacy_protector.py +767 -0
- powermem/agent/components/scope_controller.py +685 -0
- powermem/agent/factories/__init__.py +16 -0
- powermem/agent/factories/agent_factory.py +266 -0
- powermem/agent/factories/config_factory.py +308 -0
- powermem/agent/factories/memory_factory.py +229 -0
- powermem/agent/implementations/__init__.py +16 -0
- powermem/agent/implementations/hybrid.py +728 -0
- powermem/agent/implementations/multi_agent.py +1040 -0
- powermem/agent/implementations/multi_user.py +1020 -0
- powermem/agent/types.py +53 -0
- powermem/agent/wrappers/__init__.py +14 -0
- powermem/agent/wrappers/agent_memory_wrapper.py +427 -0
- powermem/agent/wrappers/compatibility_wrapper.py +520 -0
- powermem/config_loader.py +318 -0
- powermem/configs.py +249 -0
- powermem/core/__init__.py +19 -0
- powermem/core/async_memory.py +1493 -0
- powermem/core/audit.py +258 -0
- powermem/core/base.py +165 -0
- powermem/core/memory.py +1567 -0
- powermem/core/setup.py +162 -0
- powermem/core/telemetry.py +215 -0
- powermem/integrations/__init__.py +17 -0
- powermem/integrations/embeddings/__init__.py +13 -0
- powermem/integrations/embeddings/aws_bedrock.py +100 -0
- powermem/integrations/embeddings/azure_openai.py +55 -0
- powermem/integrations/embeddings/base.py +31 -0
- powermem/integrations/embeddings/config/base.py +132 -0
- powermem/integrations/embeddings/configs.py +31 -0
- powermem/integrations/embeddings/factory.py +48 -0
- powermem/integrations/embeddings/gemini.py +39 -0
- powermem/integrations/embeddings/huggingface.py +41 -0
- powermem/integrations/embeddings/langchain.py +35 -0
- powermem/integrations/embeddings/lmstudio.py +29 -0
- powermem/integrations/embeddings/mock.py +11 -0
- powermem/integrations/embeddings/ollama.py +53 -0
- powermem/integrations/embeddings/openai.py +49 -0
- powermem/integrations/embeddings/qwen.py +102 -0
- powermem/integrations/embeddings/together.py +31 -0
- powermem/integrations/embeddings/vertexai.py +54 -0
- powermem/integrations/llm/__init__.py +18 -0
- powermem/integrations/llm/anthropic.py +87 -0
- powermem/integrations/llm/base.py +132 -0
- powermem/integrations/llm/config/anthropic.py +56 -0
- powermem/integrations/llm/config/azure.py +56 -0
- powermem/integrations/llm/config/base.py +62 -0
- powermem/integrations/llm/config/deepseek.py +56 -0
- powermem/integrations/llm/config/ollama.py +56 -0
- powermem/integrations/llm/config/openai.py +79 -0
- powermem/integrations/llm/config/qwen.py +68 -0
- powermem/integrations/llm/config/qwen_asr.py +46 -0
- powermem/integrations/llm/config/vllm.py +56 -0
- powermem/integrations/llm/configs.py +26 -0
- powermem/integrations/llm/deepseek.py +106 -0
- powermem/integrations/llm/factory.py +118 -0
- powermem/integrations/llm/gemini.py +201 -0
- powermem/integrations/llm/langchain.py +65 -0
- powermem/integrations/llm/ollama.py +106 -0
- powermem/integrations/llm/openai.py +166 -0
- powermem/integrations/llm/openai_structured.py +80 -0
- powermem/integrations/llm/qwen.py +207 -0
- powermem/integrations/llm/qwen_asr.py +171 -0
- powermem/integrations/llm/vllm.py +106 -0
- powermem/integrations/rerank/__init__.py +20 -0
- powermem/integrations/rerank/base.py +43 -0
- powermem/integrations/rerank/config/__init__.py +7 -0
- powermem/integrations/rerank/config/base.py +27 -0
- powermem/integrations/rerank/configs.py +23 -0
- powermem/integrations/rerank/factory.py +68 -0
- powermem/integrations/rerank/qwen.py +159 -0
- powermem/intelligence/__init__.py +17 -0
- powermem/intelligence/ebbinghaus_algorithm.py +354 -0
- powermem/intelligence/importance_evaluator.py +361 -0
- powermem/intelligence/intelligent_memory_manager.py +284 -0
- powermem/intelligence/manager.py +148 -0
- powermem/intelligence/plugin.py +229 -0
- powermem/prompts/__init__.py +29 -0
- powermem/prompts/graph/graph_prompts.py +217 -0
- powermem/prompts/graph/graph_tools_prompts.py +469 -0
- powermem/prompts/importance_evaluation.py +246 -0
- powermem/prompts/intelligent_memory_prompts.py +163 -0
- powermem/prompts/templates.py +193 -0
- powermem/storage/__init__.py +14 -0
- powermem/storage/adapter.py +896 -0
- powermem/storage/base.py +109 -0
- powermem/storage/config/base.py +13 -0
- powermem/storage/config/oceanbase.py +58 -0
- powermem/storage/config/pgvector.py +52 -0
- powermem/storage/config/sqlite.py +27 -0
- powermem/storage/configs.py +159 -0
- powermem/storage/factory.py +59 -0
- powermem/storage/migration_manager.py +438 -0
- powermem/storage/oceanbase/__init__.py +8 -0
- powermem/storage/oceanbase/constants.py +162 -0
- powermem/storage/oceanbase/oceanbase.py +1384 -0
- powermem/storage/oceanbase/oceanbase_graph.py +1441 -0
- powermem/storage/pgvector/__init__.py +7 -0
- powermem/storage/pgvector/pgvector.py +420 -0
- powermem/storage/sqlite/__init__.py +0 -0
- powermem/storage/sqlite/sqlite.py +218 -0
- powermem/storage/sqlite/sqlite_vector_store.py +311 -0
- powermem/utils/__init__.py +35 -0
- powermem/utils/utils.py +605 -0
- powermem/version.py +23 -0
- powermem-0.1.0.dist-info/METADATA +187 -0
- powermem-0.1.0.dist-info/RECORD +123 -0
- powermem-0.1.0.dist-info/WHEEL +5 -0
- powermem-0.1.0.dist-info/licenses/LICENSE +206 -0
- powermem-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
from typing import Dict, List, Optional, Union
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
import dashscope
|
|
7
|
+
from dashscope.api_entities.dashscope_response import DashScopeAPIResponse
|
|
8
|
+
except ImportError:
|
|
9
|
+
dashscope = None
|
|
10
|
+
DashScopeAPIResponse = None
|
|
11
|
+
|
|
12
|
+
from powermem.integrations.llm import LLMBase
|
|
13
|
+
from powermem.integrations.llm.config.base import BaseLLMConfig
|
|
14
|
+
from powermem.integrations.llm.config.qwen_asr import QwenASRConfig
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class QwenASR(LLMBase):
|
|
18
|
+
"""
|
|
19
|
+
Qwen ASR (Automatic Speech Recognition) integration.
|
|
20
|
+
Converts audio to text using Qwen ASR models.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, config: Optional[Union[BaseLLMConfig, QwenASRConfig, Dict]] = None):
|
|
24
|
+
# Check if dashscope is available first
|
|
25
|
+
if dashscope is None:
|
|
26
|
+
raise ImportError(
|
|
27
|
+
"DashScope SDK is not installed. Please install it with: pip install dashscope"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# Convert to QwenASRConfig if needed
|
|
31
|
+
if config is None:
|
|
32
|
+
config = QwenASRConfig()
|
|
33
|
+
elif isinstance(config, dict):
|
|
34
|
+
config = QwenASRConfig(**config)
|
|
35
|
+
elif isinstance(config, BaseLLMConfig) and not isinstance(config, QwenASRConfig):
|
|
36
|
+
# Convert BaseLLMConfig to QwenASRConfig (only use model and api_key for ASR)
|
|
37
|
+
config = QwenASRConfig(
|
|
38
|
+
model=config.model,
|
|
39
|
+
api_key=config.api_key,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
super().__init__(config)
|
|
43
|
+
|
|
44
|
+
if not self.config.model:
|
|
45
|
+
self.config.model = "qwen3-asr-flash"
|
|
46
|
+
|
|
47
|
+
# Set API key
|
|
48
|
+
api_key = self.config.api_key or os.getenv("DASHSCOPE_API_KEY")
|
|
49
|
+
if not api_key:
|
|
50
|
+
raise ValueError(
|
|
51
|
+
"API key is required. Set DASHSCOPE_API_KEY environment variable or pass api_key in config."
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Set API key for DashScope SDK
|
|
55
|
+
dashscope.api_key = api_key
|
|
56
|
+
|
|
57
|
+
# Set base URL
|
|
58
|
+
base_url = self.config.dashscope_base_url or os.getenv(
|
|
59
|
+
"DASHSCOPE_BASE_URL") or "https://dashscope.aliyuncs.com/api/v1"
|
|
60
|
+
|
|
61
|
+
if base_url:
|
|
62
|
+
dashscope.base_http_api_url = base_url
|
|
63
|
+
|
|
64
|
+
def _parse_response(self, response: DashScopeAPIResponse) -> str:
|
|
65
|
+
"""
|
|
66
|
+
Parse the ASR response and extract text content.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
response: The raw response from DashScope API.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
str: The extracted text content.
|
|
73
|
+
"""
|
|
74
|
+
if response.status_code != 200:
|
|
75
|
+
raise Exception(f"API request failed with status {response.status_code}: {response.message}")
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
choices = response.output.choices
|
|
79
|
+
if choices:
|
|
80
|
+
content = choices[0].message.content
|
|
81
|
+
if content:
|
|
82
|
+
# Extract all text fields from content list
|
|
83
|
+
texts = [item["text"] for item in content if "text" in item]
|
|
84
|
+
# Join all text segments
|
|
85
|
+
return " ".join(texts)
|
|
86
|
+
return ""
|
|
87
|
+
except Exception as e:
|
|
88
|
+
logging.error(f"Error parsing ASR response: {e}")
|
|
89
|
+
raise
|
|
90
|
+
|
|
91
|
+
def generate_response(
|
|
92
|
+
self,
|
|
93
|
+
messages: List[Dict[str, Union[str, List]]],
|
|
94
|
+
**kwargs,
|
|
95
|
+
):
|
|
96
|
+
"""
|
|
97
|
+
Generate text response from audio input using Qwen ASR.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
messages: List of message dicts. For ASR, messages should contain audio content.
|
|
101
|
+
Example:
|
|
102
|
+
[
|
|
103
|
+
{
|
|
104
|
+
"role": "system",
|
|
105
|
+
"content": [{"text": ""}]
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"role": "user",
|
|
109
|
+
"content": [{"audio": "https://example.com/audio.wav"}]
|
|
110
|
+
}
|
|
111
|
+
]
|
|
112
|
+
**kwargs: Additional ASR-specific parameters.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
str: The transcribed text from the audio.
|
|
116
|
+
"""
|
|
117
|
+
# Prepare ASR parameters
|
|
118
|
+
asr_params = {
|
|
119
|
+
"api_key": self.config.api_key or os.getenv("DASHSCOPE_API_KEY"),
|
|
120
|
+
"model": self.config.model,
|
|
121
|
+
"messages": messages,
|
|
122
|
+
"result_format": self.config.result_format,
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
# Add ASR options
|
|
126
|
+
asr_options = kwargs.get("asr_options", self.config.asr_options)
|
|
127
|
+
if asr_options:
|
|
128
|
+
asr_params["asr_options"] = asr_options
|
|
129
|
+
|
|
130
|
+
# Add any other kwargs
|
|
131
|
+
for key, value in kwargs.items():
|
|
132
|
+
if key not in ["asr_options"]:
|
|
133
|
+
asr_params[key] = value
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
response = dashscope.MultiModalConversation.call(**asr_params)
|
|
137
|
+
return self._parse_response(response)
|
|
138
|
+
except Exception as e:
|
|
139
|
+
logging.error(f"Qwen ASR API call failed: {e}")
|
|
140
|
+
raise
|
|
141
|
+
|
|
142
|
+
def transcribe(
|
|
143
|
+
self,
|
|
144
|
+
audio_url: str,
|
|
145
|
+
system_text: Optional[str] = "",
|
|
146
|
+
asr_options: Optional[dict] = None,
|
|
147
|
+
) -> str:
|
|
148
|
+
"""
|
|
149
|
+
Convenience method to transcribe audio from URL file path.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
audio_url: URL file path to the audio file.
|
|
153
|
+
system_text: Optional system message text for customization context.
|
|
154
|
+
asr_options: Optional ASR-specific options (e.g., {"language": "zh", "enable_itn": True}).
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
str: The transcribed text from the audio.
|
|
158
|
+
"""
|
|
159
|
+
messages = [
|
|
160
|
+
{
|
|
161
|
+
"role": "system",
|
|
162
|
+
"content": [{"text": system_text}]
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"role": "user",
|
|
166
|
+
"content": [{"audio": audio_url}]
|
|
167
|
+
}
|
|
168
|
+
]
|
|
169
|
+
|
|
170
|
+
return self.generate_response(messages, asr_options=asr_options or self.config.asr_options)
|
|
171
|
+
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from typing import Dict, List, Optional, Union
|
|
4
|
+
|
|
5
|
+
from openai import OpenAI
|
|
6
|
+
from powermem.integrations.llm import LLMBase
|
|
7
|
+
from powermem.integrations.llm.config.base import BaseLLMConfig
|
|
8
|
+
from powermem.integrations.llm.config.vllm import VllmConfig
|
|
9
|
+
from powermem.utils.utils import extract_json
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class VllmLLM(LLMBase):
|
|
13
|
+
def __init__(self, config: Optional[Union[BaseLLMConfig, VllmConfig, Dict]] = None):
|
|
14
|
+
# Convert to VllmConfig if needed
|
|
15
|
+
if config is None:
|
|
16
|
+
config = VllmConfig()
|
|
17
|
+
elif isinstance(config, dict):
|
|
18
|
+
config = VllmConfig(**config)
|
|
19
|
+
elif isinstance(config, BaseLLMConfig) and not isinstance(config, VllmConfig):
|
|
20
|
+
# Convert BaseLLMConfig to VllmConfig
|
|
21
|
+
config = VllmConfig(
|
|
22
|
+
model=config.model,
|
|
23
|
+
temperature=config.temperature,
|
|
24
|
+
api_key=config.api_key,
|
|
25
|
+
max_tokens=config.max_tokens,
|
|
26
|
+
top_p=config.top_p,
|
|
27
|
+
top_k=config.top_k,
|
|
28
|
+
enable_vision=config.enable_vision,
|
|
29
|
+
vision_details=config.vision_details,
|
|
30
|
+
http_client_proxies=config.http_client,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
super().__init__(config)
|
|
34
|
+
|
|
35
|
+
if not self.config.model:
|
|
36
|
+
self.config.model = "Qwen/Qwen2.5-32B-Instruct"
|
|
37
|
+
|
|
38
|
+
self.config.api_key = self.config.api_key or os.getenv("VLLM_API_KEY") or "vllm-api-key"
|
|
39
|
+
base_url = self.config.vllm_base_url or os.getenv("VLLM_BASE_URL")
|
|
40
|
+
self.client = OpenAI(api_key=self.config.api_key, base_url=base_url)
|
|
41
|
+
|
|
42
|
+
def _parse_response(self, response, tools):
|
|
43
|
+
"""
|
|
44
|
+
Process the response based on whether tools are used or not.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
response: The raw response from API.
|
|
48
|
+
tools: The list of tools provided in the request.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
str or dict: The processed response.
|
|
52
|
+
"""
|
|
53
|
+
if tools:
|
|
54
|
+
processed_response = {
|
|
55
|
+
"content": response.choices[0].message.content,
|
|
56
|
+
"tool_calls": [],
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if response.choices[0].message.tool_calls:
|
|
60
|
+
for tool_call in response.choices[0].message.tool_calls:
|
|
61
|
+
processed_response["tool_calls"].append(
|
|
62
|
+
{
|
|
63
|
+
"name": tool_call.function.name,
|
|
64
|
+
"arguments": json.loads(extract_json(tool_call.function.arguments)),
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
return processed_response
|
|
69
|
+
else:
|
|
70
|
+
return response.choices[0].message.content
|
|
71
|
+
|
|
72
|
+
def generate_response(
|
|
73
|
+
self,
|
|
74
|
+
messages: List[Dict[str, str]],
|
|
75
|
+
response_format=None,
|
|
76
|
+
tools: Optional[List[Dict]] = None,
|
|
77
|
+
tool_choice: str = "auto",
|
|
78
|
+
**kwargs,
|
|
79
|
+
):
|
|
80
|
+
"""
|
|
81
|
+
Generate a response based on the given messages using vLLM.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
messages (list): List of message dicts containing 'role' and 'content'.
|
|
85
|
+
response_format (str or object, optional): Format of the response. Defaults to "text".
|
|
86
|
+
tools (list, optional): List of tools that the model can call. Defaults to None.
|
|
87
|
+
tool_choice (str, optional): Tool choice method. Defaults to "auto".
|
|
88
|
+
**kwargs: Additional vLLM-specific parameters.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
str: The generated response.
|
|
92
|
+
"""
|
|
93
|
+
params = self._get_supported_params(messages=messages, **kwargs)
|
|
94
|
+
params.update(
|
|
95
|
+
{
|
|
96
|
+
"model": self.config.model,
|
|
97
|
+
"messages": messages,
|
|
98
|
+
}
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
if tools:
|
|
102
|
+
params["tools"] = tools
|
|
103
|
+
params["tool_choice"] = tool_choice
|
|
104
|
+
|
|
105
|
+
response = self.client.chat.completions.create(**params)
|
|
106
|
+
return self._parse_response(response, tools)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Rerank integration module
|
|
3
|
+
|
|
4
|
+
This module provides integration with various rerank services.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .base import RerankBase
|
|
8
|
+
from .factory import RerankFactory
|
|
9
|
+
from .qwen import QwenRerank
|
|
10
|
+
from .config.base import BaseRerankConfig
|
|
11
|
+
from .configs import RerankConfig
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"RerankBase",
|
|
15
|
+
"RerankFactory",
|
|
16
|
+
"QwenRerank",
|
|
17
|
+
"BaseRerankConfig",
|
|
18
|
+
"RerankConfig",
|
|
19
|
+
]
|
|
20
|
+
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base class for rerank models
|
|
3
|
+
"""
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from typing import List, Optional, Tuple
|
|
6
|
+
|
|
7
|
+
from powermem.integrations.rerank.config.base import BaseRerankConfig
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class RerankBase(ABC):
|
|
11
|
+
"""Base class for rerank models
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
config (Optional[BaseRerankConfig]): Configuration for the rerank model
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, config: Optional[BaseRerankConfig] = None):
|
|
18
|
+
if config is None:
|
|
19
|
+
self.config = BaseRerankConfig()
|
|
20
|
+
else:
|
|
21
|
+
self.config = config
|
|
22
|
+
|
|
23
|
+
@abstractmethod
|
|
24
|
+
def rerank(
|
|
25
|
+
self,
|
|
26
|
+
query: str,
|
|
27
|
+
documents: List[str],
|
|
28
|
+
top_n: Optional[int] = None
|
|
29
|
+
) -> List[Tuple[int, float]]:
|
|
30
|
+
"""
|
|
31
|
+
Rerank documents based on relevance to the query.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
query (str): The search query
|
|
35
|
+
documents (List[str]): List of document texts to rerank
|
|
36
|
+
top_n (Optional[int]): Number of top results to return. If None, uses config.top_n
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
List[Tuple[int, float]]: List of (document_index, relevance_score) tuples,
|
|
40
|
+
sorted by relevance score in descending order
|
|
41
|
+
"""
|
|
42
|
+
pass
|
|
43
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base configuration for rerank models
|
|
3
|
+
"""
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BaseRerankConfig:
|
|
8
|
+
"""Base configuration for rerank models
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
model (str): The rerank model to use
|
|
12
|
+
api_key (Optional[str]): API key for the rerank service
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
model: Optional[str] = None,
|
|
18
|
+
api_key: Optional[str] = None,
|
|
19
|
+
**kwargs,
|
|
20
|
+
):
|
|
21
|
+
self.model = model
|
|
22
|
+
self.api_key = api_key
|
|
23
|
+
|
|
24
|
+
# Store any additional kwargs
|
|
25
|
+
for key, value in kwargs.items():
|
|
26
|
+
setattr(self, key, value)
|
|
27
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration for rerank models
|
|
3
|
+
"""
|
|
4
|
+
from typing import Optional, Dict, Any
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RerankConfig(BaseModel):
|
|
9
|
+
"""Configuration for rerank functionality."""
|
|
10
|
+
|
|
11
|
+
enabled: bool = Field(
|
|
12
|
+
description="Whether to enable reranker",
|
|
13
|
+
default=False,
|
|
14
|
+
)
|
|
15
|
+
provider: str = Field(
|
|
16
|
+
description="Reranker provider (e.g., 'qwen', 'cohere')",
|
|
17
|
+
default="qwen",
|
|
18
|
+
)
|
|
19
|
+
config: Optional[Dict[str, Any]] = Field(
|
|
20
|
+
description="Configuration for the specific reranker provider",
|
|
21
|
+
default=None
|
|
22
|
+
)
|
|
23
|
+
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Rerank factory for creating rerank instances
|
|
3
|
+
|
|
4
|
+
This module provides a factory for creating different rerank instances.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import importlib
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from powermem.integrations.rerank.config.base import BaseRerankConfig
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def load_class(class_type):
|
|
14
|
+
"""Dynamically load a class from a string path"""
|
|
15
|
+
module_path, class_name = class_type.rsplit(".", 1)
|
|
16
|
+
module = importlib.import_module(module_path)
|
|
17
|
+
return getattr(module, class_name)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class RerankFactory:
|
|
21
|
+
"""Factory class for creating rerank model instances
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
provider_to_class = {
|
|
25
|
+
"qwen": "powermem.integrations.rerank.qwen.QwenRerank",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def create(cls, provider_name: str = "qwen", config: Optional[dict] = None):
|
|
30
|
+
"""
|
|
31
|
+
Create a rerank instance based on provider name.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
provider_name (str): Name of the rerank provider. Defaults to "qwen"
|
|
35
|
+
config (Optional[dict]): Configuration dictionary for the rerank model
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
RerankBase: An instance of the requested rerank provider
|
|
39
|
+
|
|
40
|
+
Raises:
|
|
41
|
+
ValueError: If the provider is not supported
|
|
42
|
+
"""
|
|
43
|
+
class_type = cls.provider_to_class.get(provider_name)
|
|
44
|
+
if class_type:
|
|
45
|
+
reranker_class = load_class(class_type)
|
|
46
|
+
# Create config if provided
|
|
47
|
+
if config:
|
|
48
|
+
base_config = BaseRerankConfig(**config)
|
|
49
|
+
return reranker_class(base_config)
|
|
50
|
+
else:
|
|
51
|
+
return reranker_class()
|
|
52
|
+
else:
|
|
53
|
+
supported = ", ".join(cls.provider_to_class.keys())
|
|
54
|
+
raise ValueError(
|
|
55
|
+
f"Unsupported rerank provider: {provider_name}. "
|
|
56
|
+
f"Supported providers: {supported}"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def list_providers(cls) -> list:
|
|
61
|
+
"""
|
|
62
|
+
List all supported rerank providers.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
list: List of supported provider names
|
|
66
|
+
"""
|
|
67
|
+
return list(cls.provider_to_class.keys())
|
|
68
|
+
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Qwen Rerank implementation using Alibaba Cloud DashScope API
|
|
3
|
+
"""
|
|
4
|
+
import os
|
|
5
|
+
from typing import List, Optional, Tuple
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
import dashscope
|
|
9
|
+
from dashscope import TextReRank
|
|
10
|
+
except ImportError:
|
|
11
|
+
TextReRank = None
|
|
12
|
+
dashscope = None
|
|
13
|
+
|
|
14
|
+
from powermem.integrations.rerank.base import RerankBase
|
|
15
|
+
from powermem.integrations.rerank.config.base import BaseRerankConfig
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class QwenRerank(RerankBase):
|
|
19
|
+
"""Qwen3 Rerank implementation using Alibaba Cloud Bailian API
|
|
20
|
+
|
|
21
|
+
This implementation uses the qwen3-rerank model through DashScope SDK.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
config (Optional[BaseRerankConfig]): Configuration for the rerank model
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, config: Optional[BaseRerankConfig] = None):
|
|
28
|
+
super().__init__(config)
|
|
29
|
+
|
|
30
|
+
# Set default model
|
|
31
|
+
self.config.model = self.config.model or "qwen3-rerank"
|
|
32
|
+
|
|
33
|
+
# Check if dashscope is available
|
|
34
|
+
if TextReRank is None or dashscope is None:
|
|
35
|
+
raise ImportError(
|
|
36
|
+
"DashScope SDK is not installed. Please install it with: pip install dashscope"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Set API key
|
|
40
|
+
api_key = self.config.api_key or os.getenv("DASHSCOPE_API_KEY")
|
|
41
|
+
if not api_key:
|
|
42
|
+
raise ValueError(
|
|
43
|
+
"API key is required. Set DASHSCOPE_API_KEY environment variable or pass api_key in config."
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Set API key for DashScope SDK
|
|
47
|
+
dashscope.api_key = api_key
|
|
48
|
+
|
|
49
|
+
def rerank(
|
|
50
|
+
self,
|
|
51
|
+
query: str,
|
|
52
|
+
documents: List[str],
|
|
53
|
+
top_n: Optional[int] = None,
|
|
54
|
+
instruct: Optional[str] = None
|
|
55
|
+
) -> List[Tuple[int, float]]:
|
|
56
|
+
"""
|
|
57
|
+
Rerank documents based on relevance to the query using Qwen3 Rerank model.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
query (str): The search query
|
|
61
|
+
documents (List[str]): List of document texts to rerank
|
|
62
|
+
top_n (Optional[int]): Number of top results to return. If None, uses config.top_n
|
|
63
|
+
instruct (Optional[str]): Instruct for rerank
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
List[Tuple[int, float]]: List of (document_index, relevance_score) tuples,
|
|
67
|
+
sorted by relevance score in descending order
|
|
68
|
+
|
|
69
|
+
Raises:
|
|
70
|
+
ValueError: If query is empty or documents list is empty
|
|
71
|
+
Exception: If API call fails
|
|
72
|
+
"""
|
|
73
|
+
# Validate inputs
|
|
74
|
+
if not query or not query.strip():
|
|
75
|
+
raise ValueError("Query cannot be empty")
|
|
76
|
+
|
|
77
|
+
if not documents or len(documents) == 0:
|
|
78
|
+
raise ValueError("Documents list cannot be empty")
|
|
79
|
+
|
|
80
|
+
# Clean query
|
|
81
|
+
query = query.strip()
|
|
82
|
+
|
|
83
|
+
# Use provided top_n or return all results
|
|
84
|
+
effective_top_n = top_n if top_n is not None else len(documents)
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
# Call the Rerank API
|
|
88
|
+
if instruct is not None:
|
|
89
|
+
response = TextReRank.call(
|
|
90
|
+
model=self.config.model,
|
|
91
|
+
query=query,
|
|
92
|
+
documents=documents,
|
|
93
|
+
top_n=effective_top_n,
|
|
94
|
+
return_documents=False,
|
|
95
|
+
instruct=instruct
|
|
96
|
+
)
|
|
97
|
+
else:
|
|
98
|
+
response = TextReRank.call(
|
|
99
|
+
model=self.config.model,
|
|
100
|
+
query=query,
|
|
101
|
+
documents=documents,
|
|
102
|
+
top_n=effective_top_n,
|
|
103
|
+
return_documents=False,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# Check response status
|
|
107
|
+
if response.status_code != 200:
|
|
108
|
+
raise Exception(
|
|
109
|
+
f"Rerank API request failed with status {response.status_code}: {response.message}"
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# Parse results
|
|
113
|
+
results = []
|
|
114
|
+
if hasattr(response, 'output') and isinstance(response.output, dict):
|
|
115
|
+
rerank_results = response.output.get('results', [])
|
|
116
|
+
for result in rerank_results:
|
|
117
|
+
index = result.get('index')
|
|
118
|
+
score = result.get('relevance_score', 0.0)
|
|
119
|
+
if index is not None:
|
|
120
|
+
results.append((index, float(score)))
|
|
121
|
+
else:
|
|
122
|
+
raise Exception("Unexpected response format from Rerank API")
|
|
123
|
+
|
|
124
|
+
return results
|
|
125
|
+
|
|
126
|
+
except Exception as e:
|
|
127
|
+
raise Exception(f"Failed to rerank documents: {e}")
|
|
128
|
+
|
|
129
|
+
def rerank_with_texts(
|
|
130
|
+
self,
|
|
131
|
+
query: str,
|
|
132
|
+
documents: List[str],
|
|
133
|
+
top_n: Optional[int] = None,
|
|
134
|
+
instruct: Optional[str] = None
|
|
135
|
+
) -> List[Tuple[str, float]]:
|
|
136
|
+
"""
|
|
137
|
+
Rerank documents and return texts with scores instead of indices.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
query (str): The search query
|
|
141
|
+
documents (List[str]): List of document texts to rerank
|
|
142
|
+
top_n (Optional[int]): Number of top results to return
|
|
143
|
+
instruct (Optional[str]): Instruct for rerank
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
List[Tuple[str, float]]: List of (document_text, relevance_score) tuples,
|
|
147
|
+
sorted by relevance score in descending order
|
|
148
|
+
"""
|
|
149
|
+
# Get reranked indices and scores
|
|
150
|
+
reranked_results = self.rerank(query, documents, top_n,instruct)
|
|
151
|
+
|
|
152
|
+
# Map indices back to document texts
|
|
153
|
+
results_with_texts = [
|
|
154
|
+
(documents[idx], score)
|
|
155
|
+
for idx, score in reranked_results
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
return results_with_texts
|
|
159
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Intelligence layer for memory processing
|
|
3
|
+
|
|
4
|
+
This module provides intelligent memory processing capabilities.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .manager import IntelligenceManager
|
|
8
|
+
from .intelligent_memory_manager import IntelligentMemoryManager
|
|
9
|
+
from .importance_evaluator import ImportanceEvaluator
|
|
10
|
+
from .ebbinghaus_algorithm import EbbinghausAlgorithm
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"IntelligenceManager",
|
|
14
|
+
"IntelligentMemoryManager",
|
|
15
|
+
"ImportanceEvaluator",
|
|
16
|
+
"EbbinghausAlgorithm",
|
|
17
|
+
]
|