[项目边界]:完成全双工Agent入口和配置冻结,包含run-agent-live入口、配置校验和迁移文档
This commit is contained in:
@@ -8,6 +8,7 @@ from .models import ErrorCode, ProviderError
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class AppConfig:
|
||||
assistant_mode: str = "turn_based_voice_pet"
|
||||
wake_word: str = "小杰小杰"
|
||||
sample_rate: int = 16000
|
||||
channels: int = 1
|
||||
@@ -68,6 +69,29 @@ class AppConfig:
|
||||
end_chime_file: Path = Path("assets/sounds/codex-notification.wav")
|
||||
end_chime_frequency_hz: int = 880
|
||||
end_chime_duration_ms: int = 140
|
||||
audio_apm_provider: str = "webrtc"
|
||||
audio_aec_enabled: bool = True
|
||||
audio_ns_enabled: bool = True
|
||||
audio_agc_enabled: bool = True
|
||||
audio_apm_required: bool = True
|
||||
audio_frame_ms: int = 20
|
||||
audio_ring_buffer_ms: int = 3000
|
||||
interrupt_enabled: bool = True
|
||||
interrupt_target_latency_ms: int = 200
|
||||
streaming_stt_provider: str = "faster_whisper"
|
||||
streaming_stt_product_candidate: str = "sensevoice"
|
||||
streaming_tts_provider: str = "cosyvoice"
|
||||
memory_enabled: bool = False
|
||||
memory_provider: str = "faiss_sqlite"
|
||||
memory_top_k: int = 5
|
||||
memory_auto_save_sensitive: bool = False
|
||||
tool_router_enabled: bool = False
|
||||
tool_max_calls_per_turn: int = 5
|
||||
tool_timeout_ms: int = 30000
|
||||
openinterpreter_enabled: bool = False
|
||||
openinterpreter_command: str = "openinterpreter"
|
||||
browser_playwright_enabled: bool = False
|
||||
computer_control_enabled: bool = False
|
||||
|
||||
@classmethod
|
||||
def from_dotenv(cls, path: str | Path = ".env", prefix: str = "OWNER_") -> "AppConfig":
|
||||
@@ -77,7 +101,13 @@ class AppConfig:
|
||||
value = values.get(f"{prefix}{name}")
|
||||
return default if value is None or value == "" else value
|
||||
|
||||
def get_bool(name: str, default: str = "0") -> bool:
|
||||
return (get(name, default) or default).lower() not in {"0", "false", "no"}
|
||||
|
||||
return cls(
|
||||
assistant_mode=(
|
||||
get("ASSISTANT_MODE", "turn_based_voice_pet") or "turn_based_voice_pet"
|
||||
).lower(),
|
||||
wake_word=get("WAKE_WORD", "小杰小杰") or "小杰小杰",
|
||||
sample_rate=int(get("SAMPLE_RATE", "16000") or "16000"),
|
||||
channels=int(get("CHANNELS", "1") or "1"),
|
||||
@@ -156,6 +186,37 @@ class AppConfig:
|
||||
),
|
||||
end_chime_frequency_hz=int(get("END_CHIME_FREQUENCY_HZ", "880") or "880"),
|
||||
end_chime_duration_ms=int(get("END_CHIME_DURATION_MS", "140") or "140"),
|
||||
audio_apm_provider=(get("AUDIO_APM_PROVIDER", "webrtc") or "webrtc").lower(),
|
||||
audio_aec_enabled=get_bool("AUDIO_AEC_ENABLED", "1"),
|
||||
audio_ns_enabled=get_bool("AUDIO_NS_ENABLED", "1"),
|
||||
audio_agc_enabled=get_bool("AUDIO_AGC_ENABLED", "1"),
|
||||
audio_apm_required=get_bool("AUDIO_APM_REQUIRED", "1"),
|
||||
audio_frame_ms=int(get("AUDIO_FRAME_MS", "20") or "20"),
|
||||
audio_ring_buffer_ms=int(get("AUDIO_RING_BUFFER_MS", "3000") or "3000"),
|
||||
interrupt_enabled=get_bool("INTERRUPT_ENABLED", "1"),
|
||||
interrupt_target_latency_ms=int(get("INTERRUPT_TARGET_LATENCY_MS", "200") or "200"),
|
||||
streaming_stt_provider=(
|
||||
get("STREAMING_STT_PROVIDER", "faster_whisper") or "faster_whisper"
|
||||
).lower(),
|
||||
streaming_stt_product_candidate=(
|
||||
get("STREAMING_STT_PRODUCT_CANDIDATE", "sensevoice") or "sensevoice"
|
||||
).lower(),
|
||||
streaming_tts_provider=(
|
||||
get("STREAMING_TTS_PROVIDER", "cosyvoice") or "cosyvoice"
|
||||
).lower(),
|
||||
memory_enabled=get_bool("MEMORY_ENABLED", "0"),
|
||||
memory_provider=(get("MEMORY_PROVIDER", "faiss_sqlite") or "faiss_sqlite").lower(),
|
||||
memory_top_k=int(get("MEMORY_TOP_K", "5") or "5"),
|
||||
memory_auto_save_sensitive=get_bool("MEMORY_AUTO_SAVE_SENSITIVE", "0"),
|
||||
tool_router_enabled=get_bool("TOOL_ROUTER_ENABLED", "0"),
|
||||
tool_max_calls_per_turn=int(get("TOOL_MAX_CALLS_PER_TURN", "5") or "5"),
|
||||
tool_timeout_ms=int(get("TOOL_TIMEOUT_MS", "30000") or "30000"),
|
||||
openinterpreter_enabled=get_bool("OPENINTERPRETER_ENABLED", "0"),
|
||||
openinterpreter_command=(
|
||||
get("OPENINTERPRETER_COMMAND", "openinterpreter") or "openinterpreter"
|
||||
),
|
||||
browser_playwright_enabled=get_bool("BROWSER_PLAYWRIGHT_ENABLED", "0"),
|
||||
computer_control_enabled=get_bool("COMPUTER_CONTROL_ENABLED", "0"),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -174,6 +235,16 @@ class AppConfig:
|
||||
|
||||
def validate_basic(self) -> list[ProviderError]:
|
||||
errors: list[ProviderError] = []
|
||||
if self.assistant_mode not in {"turn_based_voice_pet", "full_duplex_agent"}:
|
||||
errors.append(
|
||||
ProviderError(
|
||||
ErrorCode.CONFIG_MISSING_VALUE,
|
||||
"OWNER_ASSISTANT_MODE must be turn_based_voice_pet or full_duplex_agent",
|
||||
False,
|
||||
"config",
|
||||
"startup",
|
||||
)
|
||||
)
|
||||
if self.sample_rate <= 0:
|
||||
errors.append(
|
||||
ProviderError(
|
||||
@@ -451,6 +522,12 @@ class AppConfig:
|
||||
for name, value in {
|
||||
"OWNER_END_CHIME_FREQUENCY_HZ": self.end_chime_frequency_hz,
|
||||
"OWNER_END_CHIME_DURATION_MS": self.end_chime_duration_ms,
|
||||
"OWNER_AUDIO_FRAME_MS": self.audio_frame_ms,
|
||||
"OWNER_AUDIO_RING_BUFFER_MS": self.audio_ring_buffer_ms,
|
||||
"OWNER_INTERRUPT_TARGET_LATENCY_MS": self.interrupt_target_latency_ms,
|
||||
"OWNER_MEMORY_TOP_K": self.memory_top_k,
|
||||
"OWNER_TOOL_MAX_CALLS_PER_TURN": self.tool_max_calls_per_turn,
|
||||
"OWNER_TOOL_TIMEOUT_MS": self.tool_timeout_ms,
|
||||
}.items():
|
||||
if value <= 0:
|
||||
errors.append(
|
||||
@@ -462,6 +539,66 @@ class AppConfig:
|
||||
"startup",
|
||||
)
|
||||
)
|
||||
if self.audio_apm_provider not in {"webrtc", "fake", "disabled"}:
|
||||
errors.append(
|
||||
ProviderError(
|
||||
ErrorCode.CONFIG_MISSING_VALUE,
|
||||
"OWNER_AUDIO_APM_PROVIDER must be webrtc, fake, or disabled",
|
||||
False,
|
||||
"config",
|
||||
"startup",
|
||||
)
|
||||
)
|
||||
if self.streaming_stt_provider not in {"faster_whisper", "sensevoice", "sherpa_onnx"}:
|
||||
errors.append(
|
||||
ProviderError(
|
||||
ErrorCode.CONFIG_MISSING_VALUE,
|
||||
"OWNER_STREAMING_STT_PROVIDER must be faster_whisper, sensevoice, or sherpa_onnx",
|
||||
False,
|
||||
"config",
|
||||
"startup",
|
||||
)
|
||||
)
|
||||
if self.streaming_stt_product_candidate not in {"sensevoice", "faster_whisper", "sherpa_onnx"}:
|
||||
errors.append(
|
||||
ProviderError(
|
||||
ErrorCode.CONFIG_MISSING_VALUE,
|
||||
"OWNER_STREAMING_STT_PRODUCT_CANDIDATE must be sensevoice, faster_whisper, or sherpa_onnx",
|
||||
False,
|
||||
"config",
|
||||
"startup",
|
||||
)
|
||||
)
|
||||
if self.streaming_tts_provider not in {"cosyvoice", "macos_say"}:
|
||||
errors.append(
|
||||
ProviderError(
|
||||
ErrorCode.CONFIG_MISSING_VALUE,
|
||||
"OWNER_STREAMING_TTS_PROVIDER must be cosyvoice or macos_say",
|
||||
False,
|
||||
"config",
|
||||
"startup",
|
||||
)
|
||||
)
|
||||
if self.memory_provider not in {"faiss_sqlite"}:
|
||||
errors.append(
|
||||
ProviderError(
|
||||
ErrorCode.CONFIG_MISSING_VALUE,
|
||||
"OWNER_MEMORY_PROVIDER must be faiss_sqlite",
|
||||
False,
|
||||
"config",
|
||||
"startup",
|
||||
)
|
||||
)
|
||||
if self.openinterpreter_enabled and not self.openinterpreter_command.strip():
|
||||
errors.append(
|
||||
ProviderError(
|
||||
ErrorCode.CONFIG_MISSING_VALUE,
|
||||
"OWNER_OPENINTERPRETER_COMMAND must be non-empty when Open Interpreter is enabled",
|
||||
False,
|
||||
"config",
|
||||
"startup",
|
||||
)
|
||||
)
|
||||
return errors
|
||||
|
||||
def api_url(self, path: str) -> str:
|
||||
|
||||
Reference in New Issue
Block a user