[项目边界]:完成全双工Agent入口和配置冻结,包含run-agent-live入口、配置校验和迁移文档
This commit is contained in:
@@ -74,6 +74,7 @@ class ModelsConfigTests(unittest.TestCase):
|
||||
)
|
||||
)
|
||||
config = AppConfig.from_dotenv(path)
|
||||
self.assertEqual(config.assistant_mode, "turn_based_voice_pet")
|
||||
self.assertEqual(config.llm_base_url, "https://token-plan-cn.xiaomimimo.com/v1")
|
||||
self.assertEqual(config.llm_api_key, "secret-value")
|
||||
self.assertEqual(config.llm_model, "test-model")
|
||||
@@ -122,14 +123,133 @@ class ModelsConfigTests(unittest.TestCase):
|
||||
self.assertEqual(str(config.end_chime_file), "assets/sounds/codex-notification.wav")
|
||||
self.assertEqual(config.end_chime_frequency_hz, 880)
|
||||
self.assertEqual(config.end_chime_duration_ms, 140)
|
||||
self.assertEqual(config.audio_apm_provider, "webrtc")
|
||||
self.assertTrue(config.audio_aec_enabled)
|
||||
self.assertTrue(config.audio_ns_enabled)
|
||||
self.assertTrue(config.audio_agc_enabled)
|
||||
self.assertTrue(config.audio_apm_required)
|
||||
self.assertEqual(config.audio_frame_ms, 20)
|
||||
self.assertEqual(config.audio_ring_buffer_ms, 3000)
|
||||
self.assertTrue(config.interrupt_enabled)
|
||||
self.assertEqual(config.interrupt_target_latency_ms, 200)
|
||||
self.assertEqual(config.streaming_stt_provider, "faster_whisper")
|
||||
self.assertEqual(config.streaming_stt_product_candidate, "sensevoice")
|
||||
self.assertEqual(config.streaming_tts_provider, "cosyvoice")
|
||||
self.assertFalse(config.memory_enabled)
|
||||
self.assertEqual(config.memory_provider, "faiss_sqlite")
|
||||
self.assertEqual(config.memory_top_k, 5)
|
||||
self.assertFalse(config.memory_auto_save_sensitive)
|
||||
self.assertFalse(config.tool_router_enabled)
|
||||
self.assertEqual(config.tool_max_calls_per_turn, 5)
|
||||
self.assertEqual(config.tool_timeout_ms, 30000)
|
||||
self.assertFalse(config.openinterpreter_enabled)
|
||||
self.assertEqual(config.openinterpreter_command, "openinterpreter")
|
||||
self.assertFalse(config.browser_playwright_enabled)
|
||||
self.assertFalse(config.computer_control_enabled)
|
||||
self.assertTrue(config.llm_stream)
|
||||
self.assertEqual(config.validate_basic(), [])
|
||||
|
||||
def test_full_duplex_agent_config_values_are_read_from_dotenv(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
path = f"{tmp}/.env"
|
||||
with open(path, "w", encoding="utf-8") as handle:
|
||||
handle.write(
|
||||
"\n".join(
|
||||
[
|
||||
"OWNER_ASSISTANT_MODE=full_duplex_agent",
|
||||
"OWNER_AUDIO_APM_PROVIDER=fake",
|
||||
"OWNER_AUDIO_AEC_ENABLED=0",
|
||||
"OWNER_AUDIO_NS_ENABLED=0",
|
||||
"OWNER_AUDIO_AGC_ENABLED=0",
|
||||
"OWNER_AUDIO_APM_REQUIRED=0",
|
||||
"OWNER_AUDIO_FRAME_MS=10",
|
||||
"OWNER_AUDIO_RING_BUFFER_MS=1200",
|
||||
"OWNER_INTERRUPT_ENABLED=0",
|
||||
"OWNER_INTERRUPT_TARGET_LATENCY_MS=180",
|
||||
"OWNER_STREAMING_STT_PROVIDER=sherpa_onnx",
|
||||
"OWNER_STREAMING_STT_PRODUCT_CANDIDATE=faster_whisper",
|
||||
"OWNER_STREAMING_TTS_PROVIDER=macos_say",
|
||||
"OWNER_MEMORY_ENABLED=1",
|
||||
"OWNER_MEMORY_PROVIDER=faiss_sqlite",
|
||||
"OWNER_MEMORY_TOP_K=3",
|
||||
"OWNER_MEMORY_AUTO_SAVE_SENSITIVE=1",
|
||||
"OWNER_TOOL_ROUTER_ENABLED=1",
|
||||
"OWNER_TOOL_MAX_CALLS_PER_TURN=2",
|
||||
"OWNER_TOOL_TIMEOUT_MS=1000",
|
||||
"OWNER_OPENINTERPRETER_ENABLED=1",
|
||||
"OWNER_OPENINTERPRETER_COMMAND=/tmp/openinterpreter",
|
||||
"OWNER_BROWSER_PLAYWRIGHT_ENABLED=1",
|
||||
"OWNER_COMPUTER_CONTROL_ENABLED=1",
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
config = AppConfig.from_dotenv(path)
|
||||
|
||||
self.assertEqual(config.assistant_mode, "full_duplex_agent")
|
||||
self.assertEqual(config.audio_apm_provider, "fake")
|
||||
self.assertFalse(config.audio_aec_enabled)
|
||||
self.assertFalse(config.audio_ns_enabled)
|
||||
self.assertFalse(config.audio_agc_enabled)
|
||||
self.assertFalse(config.audio_apm_required)
|
||||
self.assertEqual(config.audio_frame_ms, 10)
|
||||
self.assertEqual(config.audio_ring_buffer_ms, 1200)
|
||||
self.assertFalse(config.interrupt_enabled)
|
||||
self.assertEqual(config.interrupt_target_latency_ms, 180)
|
||||
self.assertEqual(config.streaming_stt_provider, "sherpa_onnx")
|
||||
self.assertEqual(config.streaming_stt_product_candidate, "faster_whisper")
|
||||
self.assertEqual(config.streaming_tts_provider, "macos_say")
|
||||
self.assertTrue(config.memory_enabled)
|
||||
self.assertEqual(config.memory_provider, "faiss_sqlite")
|
||||
self.assertEqual(config.memory_top_k, 3)
|
||||
self.assertTrue(config.memory_auto_save_sensitive)
|
||||
self.assertTrue(config.tool_router_enabled)
|
||||
self.assertEqual(config.tool_max_calls_per_turn, 2)
|
||||
self.assertEqual(config.tool_timeout_ms, 1000)
|
||||
self.assertTrue(config.openinterpreter_enabled)
|
||||
self.assertEqual(config.openinterpreter_command, "/tmp/openinterpreter")
|
||||
self.assertTrue(config.browser_playwright_enabled)
|
||||
self.assertTrue(config.computer_control_enabled)
|
||||
self.assertEqual(config.validate_basic(), [])
|
||||
|
||||
def test_speech_provider_must_be_cloud_or_local(self) -> None:
|
||||
config = AppConfig(speech_provider="invalid")
|
||||
errors = config.validate_basic()
|
||||
self.assertTrue(any("OWNER_SPEECH_PROVIDER" in error.message for error in errors))
|
||||
|
||||
def test_full_duplex_agent_config_is_validated(self) -> None:
|
||||
config = AppConfig(
|
||||
assistant_mode="invalid",
|
||||
audio_apm_provider="invalid",
|
||||
audio_frame_ms=0,
|
||||
audio_ring_buffer_ms=0,
|
||||
interrupt_target_latency_ms=0,
|
||||
streaming_stt_provider="invalid",
|
||||
streaming_stt_product_candidate="invalid",
|
||||
streaming_tts_provider="invalid",
|
||||
memory_provider="invalid",
|
||||
memory_top_k=0,
|
||||
tool_max_calls_per_turn=0,
|
||||
tool_timeout_ms=0,
|
||||
openinterpreter_enabled=True,
|
||||
openinterpreter_command="",
|
||||
)
|
||||
errors = config.validate_basic()
|
||||
|
||||
self.assertTrue(any("OWNER_ASSISTANT_MODE" in error.message for error in errors))
|
||||
self.assertTrue(any("OWNER_AUDIO_APM_PROVIDER" in error.message for error in errors))
|
||||
self.assertTrue(any("OWNER_AUDIO_FRAME_MS" in error.message for error in errors))
|
||||
self.assertTrue(any("OWNER_AUDIO_RING_BUFFER_MS" in error.message for error in errors))
|
||||
self.assertTrue(any("OWNER_INTERRUPT_TARGET_LATENCY_MS" in error.message for error in errors))
|
||||
self.assertTrue(any("OWNER_STREAMING_STT_PROVIDER" in error.message for error in errors))
|
||||
self.assertTrue(any("OWNER_STREAMING_STT_PRODUCT_CANDIDATE" in error.message for error in errors))
|
||||
self.assertTrue(any("OWNER_STREAMING_TTS_PROVIDER" in error.message for error in errors))
|
||||
self.assertTrue(any("OWNER_MEMORY_PROVIDER" in error.message for error in errors))
|
||||
self.assertTrue(any("OWNER_MEMORY_TOP_K" in error.message for error in errors))
|
||||
self.assertTrue(any("OWNER_TOOL_MAX_CALLS_PER_TURN" in error.message for error in errors))
|
||||
self.assertTrue(any("OWNER_TOOL_TIMEOUT_MS" in error.message for error in errors))
|
||||
self.assertTrue(any("OWNER_OPENINTERPRETER_COMMAND" in error.message for error in errors))
|
||||
|
||||
def test_realtime_transcript_idle_timeout_must_be_non_negative(self) -> None:
|
||||
config = AppConfig(realtime_transcript_idle_timeout_ms=-1)
|
||||
errors = config.validate_basic()
|
||||
|
||||
Reference in New Issue
Block a user