-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathconfig.py
More file actions
151 lines (123 loc) · 8.2 KB
/
Copy pathconfig.py
File metadata and controls
151 lines (123 loc) · 8.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
"""Configuration for AI Voice Assistant - AIY Voice Kit V1"""
import os
from pathlib import Path
from dotenv import load_dotenv
# Load .env from project root (silently ignored if absent)
load_dotenv(Path(__file__).parent / ".env")
# ─── LLM provider: "ollama", "deepseek", or "claude" ────────────────────────
LLM_PROVIDER = os.environ.get("LLM_PROVIDER", "ollama")
# ─── Ollama (Mac Mini M4) ────────────────────────────────────────────────────
OLLAMA_HOST = os.environ.get("OLLAMA_HOST", "")
OLLAMA_MODEL = os.environ.get("OLLAMA_MODEL", "gemma3:4b")
OLLAMA_USERNAME = os.environ.get("OLLAMA_USERNAME", "")
OLLAMA_PASSWORD = os.environ.get("OLLAMA_PASSWORD", "")
OLLAMA_TIMEOUT = int(os.environ.get("OLLAMA_TIMEOUT", "60"))
OLLAMA_KEEP_ALIVE = os.environ.get("OLLAMA_KEEP_ALIVE", "30m")
OLLAMA_NUM_PREDICT = int(os.environ.get("OLLAMA_NUM_PREDICT", "160"))
# Smaller chunks reduce first-token latency for requests.iter_lines().
LLM_STREAM_CHUNK_SIZE = int(os.environ.get("LLM_STREAM_CHUNK_SIZE", "1"))
# Keep voice turns responsive by bounding prompt growth from persisted history.
LLM_CONTEXT_MAX_CHARS = int(os.environ.get("LLM_CONTEXT_MAX_CHARS", "12000"))
# ─── DeepSeek API ─────────────────────────────────────────────────────────────
DEEPSEEK_API_KEY = os.environ.get("DEEPSEEK_API_KEY", "")
DEEPSEEK_HOST = os.environ.get("DEEPSEEK_HOST", "https://api.deepseek.com")
DEEPSEEK_MODEL = os.environ.get("DEEPSEEK_MODEL", "deepseek-chat")
DEEPSEEK_TIMEOUT = int(os.environ.get("DEEPSEEK_TIMEOUT", "60"))
DEEPSEEK_MAX_TOKENS = int(os.environ.get("DEEPSEEK_MAX_TOKENS", "256"))
# ─── Anthropic Claude API ─────────────────────────────────────────────────────
# Auth priority (highest to lowest):
# 1. OAuth token from credentials/auth-profiles.json (recommended)
# 2. ANTHROPIC_API_KEY environment variable (fallback)
#
# OAuth credentials file format (same as OpenClaw):
# { "anthropic:claude-cli": {"type":"oauth","provider":"anthropic","token":"sk-ant-oau04-..."} }
# Copy credentials/auth-profiles.json.example → credentials/auth-profiles.json and fill in token.
# The credentials/ directory is gitignored — never commit real tokens.
ANTHROPIC_API_KEY = os.environ.get("ANTHROPIC_API_KEY", "")
ANTHROPIC_HOST = os.environ.get("ANTHROPIC_HOST", "https://api.anthropic.com")
ANTHROPIC_MODEL = os.environ.get("ANTHROPIC_MODEL", "claude-sonnet-4-6")
ANTHROPIC_MAX_TOKENS = int(os.environ.get("ANTHROPIC_MAX_TOKENS", "256"))
ANTHROPIC_TIMEOUT = int(os.environ.get("ANTHROPIC_TIMEOUT", "60"))
# Path to OAuth credentials file (gitignored; override via env var if needed)
ANTHROPIC_OAUTH_CREDENTIALS = os.environ.get(
"ANTHROPIC_OAUTH_CREDENTIALS",
os.path.join(os.path.dirname(__file__), "credentials", "auth-profiles.json")
)
SYSTEM_PROMPT = (
"你是树莓派上的语音助手,说话简短自然,像朋友聊天一样。"
"必须遵守以下规则:\n"
"1. 每次回答只说1到3句话,不要长篇大论。\n"
"2. 绝对不用markdown格式,不用星号、井号、横线、列表或冒号引出列表。\n"
"3. 不用任何特殊符号或格式字符。\n"
"4. 用对话口语回答,不要书面语或说明文体。\n"
"5. 用户说中文就回中文,说英文就回英文。\n"
"如果问题太宽泛,简单说一句概括,再问用户想了解哪方面。"
)
# ─── Tools (real-time data) ──────────────────────────────────────────────────
# User's city for weather queries (also auto-parsed from USER.md if set there)
USER_CITY = os.environ.get("USER_CITY", "")
# Timeout (seconds) for weather API calls
WEATHER_TIMEOUT = int(os.environ.get("WEATHER_TIMEOUT", "5"))
# ─── Audio ───────────────────────────────────────────────────────────────────
# AIY Voice Kit V1 — card name stays stable across reboots
ALSA_CARD = "sndrpigooglevoi"
ALSA_DEVICE = f"plughw:{ALSA_CARD},0"
SAMPLE_RATE = 16000 # Vosk requires 16 kHz
CHANNELS = 1
SAMPLE_FORMAT = "S16_LE"
# TTS engine: "edge" (Microsoft Neural, online), "piper" (offline), or "espeak"
TTS_ENGINE = os.environ.get("TTS_ENGINE", "edge").lower()
# edge-tts (Microsoft Neural TTS — zh-CN-XiaoxiaoNeural etc.)
TTS_VOICE = os.environ.get("TTS_VOICE", "zh-CN-XiaoxiaoNeural")
TTS_RATE = os.environ.get("TTS_RATE", "+0%")
TTS_VOLUME = os.environ.get("TTS_VOLUME", "+0%")
# Piper (offline fallback)
PIPER_BIN = os.environ.get(
"PIPER_BIN",
os.path.join(os.path.dirname(__file__), ".venv", "bin", "piper")
)
PIPER_MODEL = os.environ.get(
"PIPER_MODEL",
os.path.join(os.path.dirname(__file__), "models", "piper", "zh_CN-huayan-medium.onnx")
)
PIPER_CONFIG = os.environ.get("PIPER_CONFIG", PIPER_MODEL + ".json")
PIPER_VOLUME = float(os.environ.get("PIPER_VOLUME", "0.2"))
LOCAL_TTS_VOICE = os.environ.get("LOCAL_TTS_VOICE", "cmn")
LOCAL_TTS_SPEED = int(os.environ.get("LOCAL_TTS_SPEED", "175"))
LOCAL_TTS_AMPLITUDE = int(os.environ.get("LOCAL_TTS_AMPLITUDE", "20"))
# TTS segmentation. The first segment is intentionally short so playback can
# start before the model has produced a full sentence-ending punctuation mark.
TTS_FIRST_CHUNK_CHARS = int(os.environ.get("TTS_FIRST_CHUNK_CHARS", "18"))
TTS_CHUNK_CHARS = int(os.environ.get("TTS_CHUNK_CHARS", "36"))
# ─── GPIO (AIY Voice Kit V1) ────────────────────────────────────────────────
BUTTON_PIN = int(os.environ.get("BUTTON_PIN", "23"))
LED_PIN = int(os.environ.get("LED_PIN", "25"))
# ─── sherpa-onnx STT model (streaming zipformer-bilingual-zh-en) ─────────────
SHERPA_MODEL_DIR = os.path.join(os.path.dirname(__file__), "sherpa-model")
SHERPA_ENCODER = os.environ.get("SHERPA_ENCODER", "encoder-epoch-99-avg-1.int8.onnx")
SHERPA_DECODER = os.environ.get("SHERPA_DECODER", "decoder-epoch-99-avg-1.int8.onnx")
SHERPA_JOINER = os.environ.get("SHERPA_JOINER", "joiner-epoch-99-avg-1.int8.onnx")
SHERPA_BPE = os.environ.get("SHERPA_BPE", "bpe.model") # bilingual BPE tokenizer
# ─── Memory / persistence ────────────────────────────────────────────────────
from pathlib import Path
MEMORY_DIR = Path(os.environ.get(
"MEMORY_DIR",
os.path.join(os.path.dirname(__file__), "memory")
))
MEMORY_ENABLED = os.environ.get("MEMORY_ENABLED", "true").lower() == "true"
# History limits (Pi 3B: 1GB RAM — stay conservative)
MAX_HISTORY_MESSAGES = int(os.environ.get("MAX_HISTORY_MESSAGES", "50"))
HISTORY_ROTATION_THRESHOLD = int(os.environ.get("HISTORY_ROTATION_THRESHOLD", "2000"))
# Memory update marker (LLM output trigger)
MEMORY_UPDATE_MARKER = os.environ.get("MEMORY_UPDATE_MARKER", "[UPDATE_MEMORY]")
# Bootstrap system (OpenClaw-inspired) ────────────────────────────────────────
# Enable Bootstrap injection (SOUL.md, IDENTITY.md, USER.md)
BOOTSTRAP_ENABLED = os.environ.get("BOOTSTRAP_ENABLED", "true").lower() == "true"
# Max chars per Bootstrap file (prevent context overflow)
BOOTSTRAP_MAX_CHARS = int(os.environ.get("BOOTSTRAP_MAX_CHARS", "20000"))
# Total max chars for all Bootstrap files combined
BOOTSTRAP_TOTAL_MAX_CHARS = int(os.environ.get("BOOTSTRAP_TOTAL_MAX_CHARS", "150000"))
# Bootstrap update markers (for LLM to modify Bootstrap files)
SOUL_UPDATE_MARKER = os.environ.get("SOUL_UPDATE_MARKER", "[UPDATE_SOUL]")
IDENTITY_UPDATE_MARKER = os.environ.get("IDENTITY_UPDATE_MARKER", "[UPDATE_IDENTITY]")
USER_UPDATE_MARKER = os.environ.get("USER_UPDATE_MARKER", "[UPDATE_USER]")