feat: nerd-Brain (呆脑子) - 初始提交
- anchor_writer.py: 记忆写入与情感打标 - anchor_search.py: 多维语义检索引擎 - anchor_decay.py: 遗忘曲线 + 归档 + 随机返场 - anchor_vault_sync.py: Vault 索引生成 - README.md: 项目说明文档
This commit is contained in:
114
anchor_writer.py
Normal file
114
anchor_writer.py
Normal file
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Anchor Memory Writer
|
||||
Writes memory to Obsidian Vault with YAML frontmatter and emotion tags.
|
||||
Uses Qwen API (configured in Hermes) for summarization and tagging.
|
||||
"""
|
||||
import yaml
|
||||
import json
|
||||
import os
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
from datetime import datetime
|
||||
import re
|
||||
|
||||
VAULT_DIR = "/Users/fyah/Documents/如梦初醒/memory/test/Anchor-Memory"
|
||||
CONFIG_PATH = os.path.expanduser("~/.hermes/config.yaml")
|
||||
|
||||
def get_api_config():
|
||||
"""Read API config from Hermes config.yaml"""
|
||||
with open(CONFIG_PATH, 'r', encoding='utf-8') as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
providers = config.get('custom_providers', [])
|
||||
if not providers:
|
||||
raise ValueError("No custom providers found in config")
|
||||
|
||||
# Find bailian provider
|
||||
bailian = None
|
||||
for p in providers:
|
||||
if p.get('name') == 'bailian':
|
||||
bailian = p
|
||||
break
|
||||
|
||||
if not bailian:
|
||||
bailian = providers[0] # Fallback to first
|
||||
|
||||
return bailian['api_key'], bailian['base_url'], bailian.get('model', 'qwen3.5-plus')
|
||||
|
||||
def call_model(content):
|
||||
"""Call Qwen API to generate summary, tags, and emotion scores"""
|
||||
api_key, base_url, model = get_api_config()
|
||||
|
||||
# Simpler prompt, no response_format to avoid JSON parsing issues
|
||||
prompt = f"Analyze this text. Output ONLY a valid JSON object. No markdown, no explanation. Keys: summary (max 50 words), tags (list of 3 keywords), valence (-1.0 to 1.0), arousal (0.0 to 1.0). Text: {content}"
|
||||
|
||||
data = json.dumps({
|
||||
"model": model,
|
||||
"messages": [{"role": "user", "content": prompt}],
|
||||
"temperature": 0.1
|
||||
}).encode('utf-8')
|
||||
|
||||
req = urllib.request.Request(
|
||||
f"{base_url}/chat/completions",
|
||||
data=data,
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {api_key}"
|
||||
}
|
||||
)
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as response:
|
||||
result = json.loads(response.read().decode('utf-8'))
|
||||
raw_content = result['choices'][0]['message']['content']
|
||||
|
||||
# Clean up potential markdown code blocks
|
||||
raw_content = re.sub(r'^```json\s*', '', raw_content)
|
||||
raw_content = re.sub(r'\s*```$', '', raw_content)
|
||||
raw_content = raw_content.strip()
|
||||
|
||||
return json.loads(raw_content)
|
||||
except Exception as e:
|
||||
print(f"[!] API Error: {e}")
|
||||
# Fallback if API fails
|
||||
return {
|
||||
"summary": content[:50],
|
||||
"tags": ["error", "fallback"],
|
||||
"valence": 0.0,
|
||||
"arousal": 0.0
|
||||
}
|
||||
|
||||
def save_memory(content):
|
||||
"""Save memory to Obsidian Vault"""
|
||||
meta = call_model(content)
|
||||
now = datetime.now()
|
||||
filename = f"{now.strftime('%Y-%m-%d_%H%M')}.md"
|
||||
filepath = os.path.join(VAULT_DIR, filename)
|
||||
|
||||
# Construct Markdown with YAML frontmatter
|
||||
md_content = f"""---
|
||||
title: {meta.get('summary', 'Untitled')[:30]}
|
||||
date: {now.strftime('%Y-%m-%d %H:%M')}
|
||||
tags: {meta.get('tags', [])}
|
||||
emotion:
|
||||
valence: {meta.get('valence', 0.0)}
|
||||
arousal: {meta.get('arousal', 0.0)}
|
||||
status: active
|
||||
---
|
||||
|
||||
# {meta.get('summary', 'Untitled')}
|
||||
|
||||
{content}
|
||||
"""
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
f.write(md_content)
|
||||
print(f"[+] Memory saved: {filepath}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
if len(sys.argv) > 1:
|
||||
content = " ".join(sys.argv[1:])
|
||||
save_memory(content)
|
||||
else:
|
||||
print("Usage: python3 anchor_writer.py <content>")
|
||||
Reference in New Issue
Block a user