aetherforge-platform 1.0.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.
- aetherforge_platform-1.0.0.dist-info/METADATA +86 -0
- aetherforge_platform-1.0.0.dist-info/RECORD +55 -0
- aetherforge_platform-1.0.0.dist-info/WHEEL +5 -0
- aetherforge_platform-1.0.0.dist-info/top_level.txt +4 -0
- ai-life-assistant-copy/ai_agent.py +145 -0
- ai-life-assistant-copy/avatar_manager.py +231 -0
- ai-life-assistant-copy/avatar_packer.py +261 -0
- ai-life-assistant-copy/backup_all.py +262 -0
- ai-life-assistant-copy/backups/backup_20260404_193836/ai_agent.py +145 -0
- ai-life-assistant-copy/backups/backup_20260404_193836/avatar_manager.py +231 -0
- ai-life-assistant-copy/backups/backup_20260404_193836/avatar_packer.py +261 -0
- ai-life-assistant-copy/backups/backup_20260404_193836/backup_all.py +262 -0
- ai-life-assistant-copy/backups/backup_20260404_193836/commands.py +210 -0
- ai-life-assistant-copy/backups/backup_20260404_193836/config.py +30 -0
- ai-life-assistant-copy/backups/backup_20260404_193836/daemon/__init__.py +3 -0
- ai-life-assistant-copy/backups/backup_20260404_193836/daemon/daemon.py +174 -0
- ai-life-assistant-copy/backups/backup_20260404_193836/database.py +292 -0
- ai-life-assistant-copy/backups/backup_20260404_193836/graph.py +531 -0
- ai-life-assistant-copy/backups/backup_20260404_193836/main.py +830 -0
- ai-life-assistant-copy/backups/backup_20260404_193836/mcp_tools.py +449 -0
- ai-life-assistant-copy/backups/backup_20260404_193836/memory.py +92 -0
- ai-life-assistant-copy/backups/backup_20260404_193836/memory_v2.py +333 -0
- ai-life-assistant-copy/backups/backup_20260404_193836/mock_shopping_data.py +172 -0
- ai-life-assistant-copy/backups/backup_20260404_193836/personality.py +159 -0
- ai-life-assistant-copy/backups/backup_20260404_193836/speech.py +41 -0
- ai-life-assistant-copy/backups/backup_20260404_193836/test_simple.py +127 -0
- ai-life-assistant-copy/backups/backup_20260404_193836/tools/__init__.py +15 -0
- ai-life-assistant-copy/backups/backup_20260404_193836/tools/amazon_tool.py +103 -0
- ai-life-assistant-copy/backups/backup_20260404_193836/tools/calendar_tool.py +92 -0
- ai-life-assistant-copy/backups/backup_20260404_193836/tools/reminder_tool.py +92 -0
- ai-life-assistant-copy/backups/backup_20260404_193836/tools/weather_tool.py +45 -0
- ai-life-assistant-copy/backups/backup_20260404_193836/tree_memory.py +340 -0
- ai-life-assistant-copy/commands.py +210 -0
- ai-life-assistant-copy/config.py +30 -0
- ai-life-assistant-copy/daemon/__init__.py +3 -0
- ai-life-assistant-copy/daemon/daemon.py +174 -0
- ai-life-assistant-copy/database.py +292 -0
- ai-life-assistant-copy/graph.py +531 -0
- ai-life-assistant-copy/main.py +830 -0
- ai-life-assistant-copy/mcp_tools.py +449 -0
- ai-life-assistant-copy/memory.py +92 -0
- ai-life-assistant-copy/memory_v2.py +333 -0
- ai-life-assistant-copy/mock_shopping_data.py +172 -0
- ai-life-assistant-copy/personality.py +159 -0
- ai-life-assistant-copy/speech.py +41 -0
- ai-life-assistant-copy/test_simple.py +127 -0
- ai-life-assistant-copy/tools/__init__.py +15 -0
- ai-life-assistant-copy/tools/amazon_tool.py +103 -0
- ai-life-assistant-copy/tools/calendar_tool.py +92 -0
- ai-life-assistant-copy/tools/reminder_tool.py +92 -0
- ai-life-assistant-copy/tools/weather_tool.py +45 -0
- ai-life-assistant-copy/tree_memory.py +340 -0
- ai_agent_runtime.py +447 -0
- main.py +6752 -0
- mcp_server.py +427 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from typing import List, Dict, Any, Optional
|
|
4
|
+
from datetime import datetime, timedelta
|
|
5
|
+
from enum import Enum
|
|
6
|
+
|
|
7
|
+
class MemoryType(Enum):
|
|
8
|
+
USER = "user"
|
|
9
|
+
FEEDBACK = "feedback"
|
|
10
|
+
PROJECT = "project"
|
|
11
|
+
REFERENCE = "reference"
|
|
12
|
+
|
|
13
|
+
class Memory:
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
name: str,
|
|
17
|
+
description: str,
|
|
18
|
+
memory_type: MemoryType,
|
|
19
|
+
content: str,
|
|
20
|
+
created_at: Optional[str] = None,
|
|
21
|
+
updated_at: Optional[str] = None,
|
|
22
|
+
importance: float = 0.5,
|
|
23
|
+
access_count: int = 0
|
|
24
|
+
):
|
|
25
|
+
self.name = name
|
|
26
|
+
self.description = description
|
|
27
|
+
self.memory_type = memory_type
|
|
28
|
+
self.content = content
|
|
29
|
+
self.created_at = created_at or datetime.now().isoformat()
|
|
30
|
+
self.updated_at = updated_at or datetime.now().isoformat()
|
|
31
|
+
self.importance = importance # 0.0 - 1.0,记忆重要性
|
|
32
|
+
self.access_count = access_count # 被访问次数
|
|
33
|
+
|
|
34
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
35
|
+
return {
|
|
36
|
+
"name": self.name,
|
|
37
|
+
"description": self.description,
|
|
38
|
+
"type": self.memory_type.value,
|
|
39
|
+
"content": self.content,
|
|
40
|
+
"created_at": self.created_at,
|
|
41
|
+
"updated_at": self.updated_at,
|
|
42
|
+
"importance": self.importance,
|
|
43
|
+
"access_count": self.access_count
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def from_dict(cls, data: Dict[str, Any]) -> "Memory":
|
|
48
|
+
return cls(
|
|
49
|
+
name=data["name"],
|
|
50
|
+
description=data["description"],
|
|
51
|
+
memory_type=MemoryType(data["type"]),
|
|
52
|
+
content=data["content"],
|
|
53
|
+
created_at=data.get("created_at"),
|
|
54
|
+
updated_at=data.get("updated_at"),
|
|
55
|
+
importance=data.get("importance", 0.5),
|
|
56
|
+
access_count=data.get("access_count", 0)
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
def get_age_in_days(self) -> float:
|
|
60
|
+
created = datetime.fromisoformat(self.created_at)
|
|
61
|
+
now = datetime.now()
|
|
62
|
+
return (now - created).total_seconds() / 86400
|
|
63
|
+
|
|
64
|
+
def get_recency_score(self) -> float:
|
|
65
|
+
age_days = self.get_age_in_days()
|
|
66
|
+
if age_days < 1:
|
|
67
|
+
return 1.0
|
|
68
|
+
elif age_days < 7:
|
|
69
|
+
return 0.8
|
|
70
|
+
elif age_days < 30:
|
|
71
|
+
return 0.5
|
|
72
|
+
elif age_days < 90:
|
|
73
|
+
return 0.2
|
|
74
|
+
else:
|
|
75
|
+
return 0.05
|
|
76
|
+
|
|
77
|
+
def get_relevance_score(self, query: str) -> float:
|
|
78
|
+
query_lower = query.lower()
|
|
79
|
+
content_lower = self.content.lower()
|
|
80
|
+
name_lower = self.name.lower()
|
|
81
|
+
|
|
82
|
+
score = 0.0
|
|
83
|
+
if query_lower in content_lower:
|
|
84
|
+
score += 0.6
|
|
85
|
+
if query_lower in name_lower:
|
|
86
|
+
score += 0.3
|
|
87
|
+
|
|
88
|
+
score += self.importance * 0.3
|
|
89
|
+
score += self.get_recency_score() * 0.4
|
|
90
|
+
|
|
91
|
+
return min(1.0, score)
|
|
92
|
+
|
|
93
|
+
def to_markdown(self) -> str:
|
|
94
|
+
frontmatter = f"""---
|
|
95
|
+
name: {self.name}
|
|
96
|
+
description: {self.description}
|
|
97
|
+
type: {self.memory_type.value}
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
{self.content}
|
|
101
|
+
"""
|
|
102
|
+
return frontmatter
|
|
103
|
+
|
|
104
|
+
class MemorySystemV2:
|
|
105
|
+
def __init__(self, memory_dir: str = "user_memory_v2"):
|
|
106
|
+
self.memory_dir = memory_dir
|
|
107
|
+
if not os.path.exists(memory_dir):
|
|
108
|
+
os.makedirs(memory_dir)
|
|
109
|
+
|
|
110
|
+
for mem_type in MemoryType:
|
|
111
|
+
type_dir = os.path.join(memory_dir, mem_type.value)
|
|
112
|
+
if not os.path.exists(type_dir):
|
|
113
|
+
os.makedirs(type_dir)
|
|
114
|
+
|
|
115
|
+
def _get_memory_path(self, user_id: str, memory_type: MemoryType, name: str) -> str:
|
|
116
|
+
safe_name = "".join(c for c in name if c.isalnum() or c in (" ", "-", "_")).rstrip()
|
|
117
|
+
safe_name = safe_name.replace(" ", "_")
|
|
118
|
+
return os.path.join(self.memory_dir, memory_type.value, f"{user_id}_{safe_name}.json")
|
|
119
|
+
|
|
120
|
+
def _get_all_memory_files(self, user_id: str, memory_type: Optional[MemoryType] = None) -> List[str]:
|
|
121
|
+
files = []
|
|
122
|
+
types_to_check = [memory_type] if memory_type else list(MemoryType)
|
|
123
|
+
|
|
124
|
+
for mem_type in types_to_check:
|
|
125
|
+
type_dir = os.path.join(self.memory_dir, mem_type.value)
|
|
126
|
+
if os.path.exists(type_dir):
|
|
127
|
+
for filename in os.listdir(type_dir):
|
|
128
|
+
if filename.startswith(f"{user_id}_") and filename.endswith(".json"):
|
|
129
|
+
files.append(os.path.join(type_dir, filename))
|
|
130
|
+
|
|
131
|
+
return files
|
|
132
|
+
|
|
133
|
+
def add_memory(self, user_id: str, memory: Memory) -> bool:
|
|
134
|
+
try:
|
|
135
|
+
file_path = self._get_memory_path(user_id, memory.memory_type, memory.name)
|
|
136
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
|
137
|
+
json.dump(memory.to_dict(), f, ensure_ascii=False, indent=2)
|
|
138
|
+
return True
|
|
139
|
+
except Exception as e:
|
|
140
|
+
print(f"Error adding memory: {e}")
|
|
141
|
+
return False
|
|
142
|
+
|
|
143
|
+
def get_memory(self, user_id: str, memory_type: MemoryType, name: str) -> Optional[Memory]:
|
|
144
|
+
file_path = self._get_memory_path(user_id, memory_type, name)
|
|
145
|
+
if os.path.exists(file_path):
|
|
146
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
147
|
+
data = json.load(f)
|
|
148
|
+
return Memory.from_dict(data)
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
def get_all_memories(self, user_id: str, memory_type: Optional[MemoryType] = None) -> List[Memory]:
|
|
152
|
+
memories = []
|
|
153
|
+
files = self._get_all_memory_files(user_id, memory_type)
|
|
154
|
+
|
|
155
|
+
for file_path in files:
|
|
156
|
+
try:
|
|
157
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
158
|
+
data = json.load(f)
|
|
159
|
+
memories.append(Memory.from_dict(data))
|
|
160
|
+
except Exception as e:
|
|
161
|
+
print(f"Error reading memory {file_path}: {e}")
|
|
162
|
+
|
|
163
|
+
return sorted(memories, key=lambda m: m.updated_at, reverse=True)
|
|
164
|
+
|
|
165
|
+
def delete_memory(self, user_id: str, memory_type: MemoryType, name: str) -> bool:
|
|
166
|
+
file_path = self._get_memory_path(user_id, memory_type, name)
|
|
167
|
+
if os.path.exists(file_path):
|
|
168
|
+
os.remove(file_path)
|
|
169
|
+
return True
|
|
170
|
+
return False
|
|
171
|
+
|
|
172
|
+
def search_memories(self, user_id: str, query: str, memory_type: Optional[MemoryType] = None) -> List[Memory]:
|
|
173
|
+
memories = self.get_all_memories(user_id, memory_type)
|
|
174
|
+
query_lower = query.lower()
|
|
175
|
+
|
|
176
|
+
results = []
|
|
177
|
+
for memory in memories:
|
|
178
|
+
if (query_lower in memory.name.lower() or
|
|
179
|
+
query_lower in memory.description.lower() or
|
|
180
|
+
query_lower in memory.content.lower()):
|
|
181
|
+
results.append(memory)
|
|
182
|
+
|
|
183
|
+
return results
|
|
184
|
+
|
|
185
|
+
def get_relevant_context(self, user_id: str, current_message: str, min_relevance: float = 0.3) -> str:
|
|
186
|
+
context_parts = []
|
|
187
|
+
all_memories = self.get_all_memories(user_id)
|
|
188
|
+
|
|
189
|
+
if not all_memories:
|
|
190
|
+
return ""
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
from langchain_groq import ChatGroq
|
|
194
|
+
from langchain.prompts import ChatPromptTemplate
|
|
195
|
+
from config import GROK_API_KEY
|
|
196
|
+
|
|
197
|
+
llm = ChatGroq(
|
|
198
|
+
api_key=GROK_API_KEY,
|
|
199
|
+
model="mixtral-8x7b-32768"
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
memories_text = "\n".join([
|
|
203
|
+
f"[{i}] {mem.memory_type.value}: {mem.content}"
|
|
204
|
+
for i, mem in enumerate(all_memories[:10])
|
|
205
|
+
])
|
|
206
|
+
|
|
207
|
+
prompt = ChatPromptTemplate.from_messages([
|
|
208
|
+
("system", """你是一个非常谨慎的记忆筛选专家。
|
|
209
|
+
|
|
210
|
+
当前用户消息: {current_message}
|
|
211
|
+
|
|
212
|
+
可用记忆:
|
|
213
|
+
{memories_text}
|
|
214
|
+
|
|
215
|
+
重要原则:
|
|
216
|
+
1. 只有当记忆对回答当前问题**绝对必要**时才选
|
|
217
|
+
2. 如果不用这个记忆也能回答,就不要选
|
|
218
|
+
3. 不要让 AI 听起来像是在刻意展示它知道用户的事情
|
|
219
|
+
4. 优先考虑:不用记忆,让 AI 自然地回答
|
|
220
|
+
|
|
221
|
+
请返回一个 JSON 数组,包含真正必要的记忆索引(从0开始)。
|
|
222
|
+
格式示例: [0]
|
|
223
|
+
|
|
224
|
+
如果没有真正必要的记忆,返回空数组: []
|
|
225
|
+
|
|
226
|
+
只返回 JSON,不要其他文字。"""),
|
|
227
|
+
])
|
|
228
|
+
|
|
229
|
+
chain = prompt | llm
|
|
230
|
+
response = chain.invoke({
|
|
231
|
+
"current_message": current_message,
|
|
232
|
+
"memories_text": memories_text
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
try:
|
|
236
|
+
relevant_indices = json.loads(response.content.strip())
|
|
237
|
+
if not isinstance(relevant_indices, list):
|
|
238
|
+
relevant_indices = []
|
|
239
|
+
except json.JSONDecodeError:
|
|
240
|
+
relevant_indices = []
|
|
241
|
+
|
|
242
|
+
selected_memories = []
|
|
243
|
+
for idx in relevant_indices:
|
|
244
|
+
if 0 <= idx < len(all_memories):
|
|
245
|
+
selected_memories.append(all_memories[idx])
|
|
246
|
+
|
|
247
|
+
if selected_memories:
|
|
248
|
+
context_parts.append("【用户背景信息 - 请自然地使用这些信息,不要刻意提及或展示】")
|
|
249
|
+
|
|
250
|
+
for mem_type in MemoryType:
|
|
251
|
+
type_memories = [m for m in selected_memories if m.memory_type == mem_type]
|
|
252
|
+
if type_memories:
|
|
253
|
+
for mem in type_memories[:2]:
|
|
254
|
+
mem.access_count += 1
|
|
255
|
+
self.add_memory(user_id, mem)
|
|
256
|
+
context_parts.append(f"- {mem.content}")
|
|
257
|
+
|
|
258
|
+
if context_parts:
|
|
259
|
+
return "\n".join(context_parts)
|
|
260
|
+
return ""
|
|
261
|
+
|
|
262
|
+
except Exception as e:
|
|
263
|
+
print(f"LLM relevance check error: {e}")
|
|
264
|
+
return ""
|
|
265
|
+
|
|
266
|
+
def _get_relevant_context_fallback(self, user_id: str, current_message: str, min_relevance: float = 0.3) -> str:
|
|
267
|
+
context_parts = []
|
|
268
|
+
all_memories = self.get_all_memories(user_id)
|
|
269
|
+
|
|
270
|
+
for memory in all_memories:
|
|
271
|
+
relevance = memory.get_relevance_score(current_message)
|
|
272
|
+
memory.relevance_score = relevance
|
|
273
|
+
|
|
274
|
+
filtered_memories = [m for m in all_memories if hasattr(m, 'relevance_score') and m.relevance_score >= min_relevance]
|
|
275
|
+
sorted_memories = sorted(filtered_memories, key=lambda m: m.relevance_score, reverse=True)
|
|
276
|
+
|
|
277
|
+
for mem_type in MemoryType:
|
|
278
|
+
type_memories = [m for m in sorted_memories if m.memory_type == mem_type]
|
|
279
|
+
if type_memories:
|
|
280
|
+
section_title = {
|
|
281
|
+
MemoryType.USER: "## 用户信息",
|
|
282
|
+
MemoryType.FEEDBACK: "## 用户反馈",
|
|
283
|
+
MemoryType.PROJECT: "## 项目信息",
|
|
284
|
+
MemoryType.REFERENCE: "## 参考链接"
|
|
285
|
+
}[mem_type]
|
|
286
|
+
context_parts.append(section_title)
|
|
287
|
+
|
|
288
|
+
for mem in type_memories[:4]:
|
|
289
|
+
mem.access_count += 1
|
|
290
|
+
self.add_memory(user_id, mem)
|
|
291
|
+
context_parts.append(f"- {mem.name}: {mem.content}")
|
|
292
|
+
|
|
293
|
+
if context_parts:
|
|
294
|
+
return "\n".join(context_parts)
|
|
295
|
+
return "暂无相关记忆"
|
|
296
|
+
|
|
297
|
+
def extract_and_save_memory(self, user_id: str, message: str, response: str):
|
|
298
|
+
message_lower = message.lower()
|
|
299
|
+
|
|
300
|
+
importance = 0.5
|
|
301
|
+
if "永远" in message_lower or "绝对" in message_lower or "绝不" in message_lower:
|
|
302
|
+
importance = 0.9
|
|
303
|
+
elif "非常" in message_lower or "特别" in message_lower:
|
|
304
|
+
importance = 0.75
|
|
305
|
+
|
|
306
|
+
user_keywords = ["我是", "我喜欢", "我偏好", "我不喜欢", "我讨厌", "我想要", "我爱吃", "我爱玩"]
|
|
307
|
+
for keyword in user_keywords:
|
|
308
|
+
if keyword in message_lower:
|
|
309
|
+
memory = Memory(
|
|
310
|
+
name=f"用户偏好_{datetime.now().strftime('%Y%m%d_%H%M%S')}",
|
|
311
|
+
description="用户表达的偏好",
|
|
312
|
+
memory_type=MemoryType.USER,
|
|
313
|
+
content=message,
|
|
314
|
+
importance=importance
|
|
315
|
+
)
|
|
316
|
+
self.add_memory(user_id, memory)
|
|
317
|
+
break
|
|
318
|
+
|
|
319
|
+
feedback_keywords = ["不要", "别", "停止", "就这样", "对", "是的", "完美", "很好", "不行"]
|
|
320
|
+
for keyword in feedback_keywords:
|
|
321
|
+
if keyword in message_lower:
|
|
322
|
+
memory = Memory(
|
|
323
|
+
name=f"用户反馈_{datetime.now().strftime('%Y%m%d_%H%M%S')}",
|
|
324
|
+
description="用户的反馈",
|
|
325
|
+
memory_type=MemoryType.FEEDBACK,
|
|
326
|
+
content=f"用户说: {message}\nAI回复: {response[:200]}...",
|
|
327
|
+
importance=importance
|
|
328
|
+
)
|
|
329
|
+
self.add_memory(user_id, memory)
|
|
330
|
+
break
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
memory_system_v2 = MemorySystemV2()
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""
|
|
2
|
+
模拟购物数据 - 开发阶段使用
|
|
3
|
+
不需要真实 API Key,先把功能做出来
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import random
|
|
7
|
+
from datetime import datetime, timedelta
|
|
8
|
+
|
|
9
|
+
MOCK_PRODUCTS = [
|
|
10
|
+
{
|
|
11
|
+
"id": "B081F5Z8P2",
|
|
12
|
+
"title": "NVIDIA GeForce RTX 4060 Ti 8GB GDDR6",
|
|
13
|
+
"description": "Next-gen gaming GPU with DLSS 3 and ray tracing",
|
|
14
|
+
"price": 399.99,
|
|
15
|
+
"originalPrice": 449.99,
|
|
16
|
+
"currency": "USD",
|
|
17
|
+
"image": "https://images.unsplash.com/photo-1591488320449-011701bb6704?w=300&h=300&fit=crop",
|
|
18
|
+
"url": "https://amazon.com/dp/B081F5Z8P2",
|
|
19
|
+
"platform": "Amazon",
|
|
20
|
+
"rating": 4.5,
|
|
21
|
+
"reviewCount": 2847,
|
|
22
|
+
"inStock": True,
|
|
23
|
+
"priceChange": -11.1,
|
|
24
|
+
"priceChangeDirection": "down"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"id": "B09XYZ1234",
|
|
28
|
+
"title": "NVIDIA GeForce RTX 4060 Ti 8GB - Brand New",
|
|
29
|
+
"description": "Factory sealed, free shipping",
|
|
30
|
+
"price": 379.99,
|
|
31
|
+
"originalPrice": 399.99,
|
|
32
|
+
"currency": "USD",
|
|
33
|
+
"image": "https://images.unsplash.com/photo-1587202372775-e229f1d21684?w=300&h=300&fit=crop",
|
|
34
|
+
"url": "https://ebay.com/itm/1234567890",
|
|
35
|
+
"platform": "Ebay",
|
|
36
|
+
"rating": 4.8,
|
|
37
|
+
"reviewCount": 15234,
|
|
38
|
+
"inStock": True,
|
|
39
|
+
"priceChange": -5.0,
|
|
40
|
+
"priceChangeDirection": "down"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"id": "BBY6543210",
|
|
44
|
+
"title": "NVIDIA - GeForce RTX 4060 Ti 8GB GDDR6",
|
|
45
|
+
"description": "In-store pickup available today",
|
|
46
|
+
"price": 389.99,
|
|
47
|
+
"originalPrice": 399.99,
|
|
48
|
+
"currency": "USD",
|
|
49
|
+
"image": "https://images.unsplash.com/photo-1550745165-9bc0b252726f?w=300&h=300&fit=crop",
|
|
50
|
+
"url": "https://bestbuy.com/site/6543210.p",
|
|
51
|
+
"platform": "Best Buy",
|
|
52
|
+
"rating": 4.6,
|
|
53
|
+
"reviewCount": 847,
|
|
54
|
+
"inStock": True,
|
|
55
|
+
"priceChange": -2.5,
|
|
56
|
+
"priceChangeDirection": "down"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"id": "AMZ9876543",
|
|
60
|
+
"title": "Apple AirPods Pro (2nd Generation)",
|
|
61
|
+
"description": "Active noise cancellation, spatial audio",
|
|
62
|
+
"price": 199.99,
|
|
63
|
+
"originalPrice": 249.99,
|
|
64
|
+
"currency": "USD",
|
|
65
|
+
"image": "https://images.unsplash.com/photo-1572569511254-d8f925fe2cbb?w=300&h=300&fit=crop",
|
|
66
|
+
"url": "https://amazon.com/dp/AMZ9876543",
|
|
67
|
+
"platform": "Amazon",
|
|
68
|
+
"rating": 4.9,
|
|
69
|
+
"reviewCount": 45231,
|
|
70
|
+
"inStock": True,
|
|
71
|
+
"priceChange": -20.0,
|
|
72
|
+
"priceChangeDirection": "down"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"id": "BBY1234567",
|
|
76
|
+
"title": "Sony WH-1000XM5 Wireless Headphones",
|
|
77
|
+
"description": "Industry-leading noise cancellation",
|
|
78
|
+
"price": 328.00,
|
|
79
|
+
"originalPrice": 399.99,
|
|
80
|
+
"currency": "USD",
|
|
81
|
+
"image": "https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=300&h=300&fit=crop",
|
|
82
|
+
"url": "https://bestbuy.com/site/1234567.p",
|
|
83
|
+
"platform": "Best Buy",
|
|
84
|
+
"rating": 4.7,
|
|
85
|
+
"reviewCount": 12453,
|
|
86
|
+
"inStock": True,
|
|
87
|
+
"priceChange": -18.0,
|
|
88
|
+
"priceChangeDirection": "down"
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def search_mock_products(keyword):
|
|
94
|
+
"""
|
|
95
|
+
模拟产品搜索
|
|
96
|
+
"""
|
|
97
|
+
results = []
|
|
98
|
+
keyword_lower = keyword.lower()
|
|
99
|
+
|
|
100
|
+
for product in MOCK_PRODUCTS:
|
|
101
|
+
if (keyword_lower in product["title"].lower() or
|
|
102
|
+
keyword_lower in product["description"].lower()):
|
|
103
|
+
results.append(product)
|
|
104
|
+
|
|
105
|
+
if not results:
|
|
106
|
+
results = MOCK_PRODUCTS[:3]
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
"success": True,
|
|
110
|
+
"keyword": keyword,
|
|
111
|
+
"results": results,
|
|
112
|
+
"total_count": len(results)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def get_price_history(product_id, days=30):
|
|
117
|
+
"""
|
|
118
|
+
模拟价格历史数据
|
|
119
|
+
"""
|
|
120
|
+
base_price = next((p["price"] for p in MOCK_PRODUCTS if p["id"] == product_id), 100)
|
|
121
|
+
|
|
122
|
+
history = []
|
|
123
|
+
today = datetime.now()
|
|
124
|
+
|
|
125
|
+
for i in range(days):
|
|
126
|
+
date = today - timedelta(days=i)
|
|
127
|
+
price_variation = random.uniform(-0.1, 0.1)
|
|
128
|
+
price = base_price * (1 + price_variation)
|
|
129
|
+
|
|
130
|
+
history.append({
|
|
131
|
+
"date": date.strftime("%Y-%m-%d"),
|
|
132
|
+
"price": round(price, 2)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
"success": True,
|
|
137
|
+
"product_id": product_id,
|
|
138
|
+
"history": list(reversed(history))
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def compare_prices(product_name):
|
|
143
|
+
"""
|
|
144
|
+
模拟多平台价格比较
|
|
145
|
+
"""
|
|
146
|
+
amazon_price = random.uniform(180, 220)
|
|
147
|
+
ebay_price = random.uniform(170, 210)
|
|
148
|
+
bestbuy_price = random.uniform(175, 225)
|
|
149
|
+
|
|
150
|
+
prices = [
|
|
151
|
+
{"platform": "Amazon", "price": round(amazon_price, 2), "url": "https://amazon.com"},
|
|
152
|
+
{"platform": "Ebay", "price": round(ebay_price, 2), "url": "https://ebay.com"},
|
|
153
|
+
{"platform": "Best Buy", "price": round(bestbuy_price, 2), "url": "https://bestbuy.com"}
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
prices_sorted = sorted(prices, key=lambda x: x["price"])
|
|
157
|
+
lowest_price = prices_sorted[0]
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
"success": True,
|
|
161
|
+
"product_name": product_name,
|
|
162
|
+
"prices": prices,
|
|
163
|
+
"lowest_price": lowest_price,
|
|
164
|
+
"savings": round(prices_sorted[-1]["price"] - lowest_price["price"], 2)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
if __name__ == "__main__":
|
|
169
|
+
results = search_mock_products("4060")
|
|
170
|
+
print(f"Found {results['total_count']} products:")
|
|
171
|
+
for product in results["results"]:
|
|
172
|
+
print(f" - {product['title']} ({product['platform']}): ${product['price']}")
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from typing import Dict, List, Any, Optional
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from config import GROK_API_KEY
|
|
7
|
+
|
|
8
|
+
class PersonalityTrait(Enum):
|
|
9
|
+
EXTROVERSION = "extroversion"
|
|
10
|
+
AGREEABLENESS = "agreeableness"
|
|
11
|
+
CONSCIENTIOUSNESS = "conscientiousness"
|
|
12
|
+
NEUROTICISM = "neuroticism"
|
|
13
|
+
OPENNESS = "openness"
|
|
14
|
+
|
|
15
|
+
class InterestCategory(Enum):
|
|
16
|
+
TECH = "tech"
|
|
17
|
+
SPORTS = "sports"
|
|
18
|
+
MUSIC = "music"
|
|
19
|
+
MOVIES = "movies"
|
|
20
|
+
BOOKS = "books"
|
|
21
|
+
TRAVEL = "travel"
|
|
22
|
+
FOOD = "food"
|
|
23
|
+
GAMES = "games"
|
|
24
|
+
ART = "art"
|
|
25
|
+
FITNESS = "fitness"
|
|
26
|
+
|
|
27
|
+
class PersonalityAnalyzer:
|
|
28
|
+
def __init__(self, data_dir: str = "personality_data"):
|
|
29
|
+
self.data_dir = data_dir
|
|
30
|
+
if not os.path.exists(data_dir):
|
|
31
|
+
os.makedirs(data_dir)
|
|
32
|
+
|
|
33
|
+
def _get_user_file(self, user_id: str) -> str:
|
|
34
|
+
return os.path.join(self.data_dir, f"{user_id}_personality.json")
|
|
35
|
+
|
|
36
|
+
def analyze_conversation(self, user_id: str, messages: List[Dict[str, str]]) -> Dict[str, Any]:
|
|
37
|
+
try:
|
|
38
|
+
from langchain_groq import ChatGroq
|
|
39
|
+
from langchain.prompts import ChatPromptTemplate
|
|
40
|
+
|
|
41
|
+
llm = ChatGroq(
|
|
42
|
+
api_key=GROK_API_KEY,
|
|
43
|
+
model="mixtral-8x7b-32768"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
conversation_text = "\n".join([
|
|
47
|
+
f"{msg['role']}: {msg['content']}"
|
|
48
|
+
for msg in messages[-30:]
|
|
49
|
+
])
|
|
50
|
+
|
|
51
|
+
prompt = ChatPromptTemplate.from_messages([
|
|
52
|
+
("system", """你是一个专业的性格分析师。请分析用户的对话记录,评估用户的性格特征。
|
|
53
|
+
|
|
54
|
+
请以 JSON 格式返回分析结果,包含以下字段:
|
|
55
|
+
{{
|
|
56
|
+
"big_five": {{
|
|
57
|
+
"extroversion": 0-100,
|
|
58
|
+
"agreeableness": 0-100,
|
|
59
|
+
"conscientiousness": 0-100,
|
|
60
|
+
"neuroticism": 0-100,
|
|
61
|
+
"openness": 0-100
|
|
62
|
+
}},
|
|
63
|
+
"interests": ["兴趣1", "兴趣2", ...],
|
|
64
|
+
"communication_style": "沟通风格描述",
|
|
65
|
+
"values": ["价值观1", "价值观2", ...],
|
|
66
|
+
"summary": "简短的性格总结"
|
|
67
|
+
}}
|
|
68
|
+
|
|
69
|
+
只返回 JSON,不要其他文字。"""),
|
|
70
|
+
("human", "对话记录:\n\n{conversation}")
|
|
71
|
+
])
|
|
72
|
+
|
|
73
|
+
chain = prompt | llm
|
|
74
|
+
response = chain.invoke({"conversation": conversation_text})
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
analysis = json.loads(response.content)
|
|
78
|
+
self._save_analysis(user_id, analysis)
|
|
79
|
+
return analysis
|
|
80
|
+
except json.JSONDecodeError:
|
|
81
|
+
return self._get_default_analysis()
|
|
82
|
+
|
|
83
|
+
except Exception as e:
|
|
84
|
+
print(f"Personality analysis error: {e}")
|
|
85
|
+
return self._get_default_analysis()
|
|
86
|
+
|
|
87
|
+
def _get_default_analysis(self) -> Dict[str, Any]:
|
|
88
|
+
return {
|
|
89
|
+
"big_five": {
|
|
90
|
+
"extroversion": 50,
|
|
91
|
+
"agreeableness": 50,
|
|
92
|
+
"conscientiousness": 50,
|
|
93
|
+
"neuroticism": 50,
|
|
94
|
+
"openness": 50
|
|
95
|
+
},
|
|
96
|
+
"interests": [],
|
|
97
|
+
"communication_style": "中立",
|
|
98
|
+
"values": [],
|
|
99
|
+
"summary": "需要更多对话来分析性格"
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
def _save_analysis(self, user_id: str, analysis: Dict[str, Any]):
|
|
103
|
+
file_path = self._get_user_file(user_id)
|
|
104
|
+
analysis["updated_at"] = datetime.now().isoformat()
|
|
105
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
|
106
|
+
json.dump(analysis, f, ensure_ascii=False, indent=2)
|
|
107
|
+
|
|
108
|
+
def get_analysis(self, user_id: str) -> Optional[Dict[str, Any]]:
|
|
109
|
+
file_path = self._get_user_file(user_id)
|
|
110
|
+
if os.path.exists(file_path):
|
|
111
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
112
|
+
return json.load(f)
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
def calculate_compatibility(self, user1_analysis: Dict[str, Any], user2_analysis: Dict[str, Any]) -> Dict[str, Any]:
|
|
116
|
+
big_five1 = user1_analysis.get("big_five", {})
|
|
117
|
+
big_five2 = user2_analysis.get("big_five", {})
|
|
118
|
+
|
|
119
|
+
trait_scores = {}
|
|
120
|
+
for trait in ["extroversion", "agreeableness", "conscientiousness", "neuroticism", "openness"]:
|
|
121
|
+
score1 = big_five1.get(trait, 50)
|
|
122
|
+
score2 = big_five2.get(trait, 50)
|
|
123
|
+
difference = abs(score1 - score2)
|
|
124
|
+
trait_scores[trait] = max(0, 100 - difference)
|
|
125
|
+
|
|
126
|
+
interests1 = set(user1_analysis.get("interests", []))
|
|
127
|
+
interests2 = set(user2_analysis.get("interests", []))
|
|
128
|
+
common_interests = interests1.intersection(interests2)
|
|
129
|
+
all_interests = interests1.union(interests2)
|
|
130
|
+
|
|
131
|
+
interest_score = 0
|
|
132
|
+
if all_interests:
|
|
133
|
+
interest_score = int((len(common_interests) / len(all_interests)) * 100)
|
|
134
|
+
|
|
135
|
+
overall_score = int(
|
|
136
|
+
(sum(trait_scores.values()) / len(trait_scores) * 0.6) +
|
|
137
|
+
(interest_score * 0.4)
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
"overall_score": overall_score,
|
|
142
|
+
"trait_scores": trait_scores,
|
|
143
|
+
"interest_score": interest_score,
|
|
144
|
+
"common_interests": list(common_interests),
|
|
145
|
+
"compatibility_level": self._get_compatibility_level(overall_score)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
def _get_compatibility_level(self, score: int) -> str:
|
|
149
|
+
if score >= 80:
|
|
150
|
+
return "非常匹配"
|
|
151
|
+
elif score >= 60:
|
|
152
|
+
return "比较匹配"
|
|
153
|
+
elif score >= 40:
|
|
154
|
+
return "一般匹配"
|
|
155
|
+
else:
|
|
156
|
+
return "不太匹配"
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
personality_analyzer = PersonalityAnalyzer()
|