253 lines
7.2 KiB
Python
253 lines
7.2 KiB
Python
import os
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
os.environ.setdefault("LLM_API_KEY", "test-openai-key")
|
|
os.environ.setdefault("TTS_API_KEY", "test-tts-key")
|
|
os.environ.setdefault("ASR_API_KEY", "test-asr-key")
|
|
|
|
from app.config import load_settings
|
|
|
|
|
|
def _write_yaml(path: Path, content: str) -> None:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(content, encoding="utf-8")
|
|
|
|
|
|
def _full_agent_yaml(llm_model: str = "gpt-4o-mini", llm_key: str = "test-openai-key") -> str:
|
|
return f"""
|
|
agent:
|
|
vad:
|
|
type: silero
|
|
model_path: data/vad/silero_vad.onnx
|
|
threshold: 0.63
|
|
min_speech_duration_ms: 100
|
|
eou_threshold_ms: 800
|
|
|
|
llm:
|
|
provider: openai_compatible
|
|
model: {llm_model}
|
|
temperature: 0.2
|
|
api_key: {llm_key}
|
|
api_url: https://example-llm.invalid/v1
|
|
|
|
tts:
|
|
provider: openai_compatible
|
|
api_key: test-tts-key
|
|
api_url: https://example-tts.invalid/v1/audio/speech
|
|
model: FunAudioLLM/CosyVoice2-0.5B
|
|
voice: anna
|
|
speed: 1.0
|
|
|
|
asr:
|
|
provider: openai_compatible
|
|
api_key: test-asr-key
|
|
api_url: https://example-asr.invalid/v1/audio/transcriptions
|
|
model: FunAudioLLM/SenseVoiceSmall
|
|
interim_interval_ms: 500
|
|
min_audio_ms: 300
|
|
start_min_speech_ms: 160
|
|
pre_speech_ms: 240
|
|
final_tail_ms: 120
|
|
|
|
duplex:
|
|
enabled: true
|
|
system_prompt: You are a strict test assistant.
|
|
|
|
barge_in:
|
|
min_duration_ms: 200
|
|
silence_tolerance_ms: 60
|
|
""".strip()
|
|
|
|
|
|
def test_cli_profile_loads_agent_yaml(monkeypatch, tmp_path):
|
|
monkeypatch.chdir(tmp_path)
|
|
config_dir = tmp_path / "config" / "agents"
|
|
_write_yaml(
|
|
config_dir / "support.yaml",
|
|
_full_agent_yaml(llm_model="gpt-4.1-mini"),
|
|
)
|
|
|
|
settings = load_settings(
|
|
argv=["--agent-profile", "support"],
|
|
)
|
|
|
|
assert settings.llm_model == "gpt-4.1-mini"
|
|
assert settings.llm_temperature == 0.2
|
|
assert settings.vad_threshold == 0.63
|
|
assert settings.agent_config_source == "cli_profile"
|
|
assert settings.agent_config_path == str((config_dir / "support.yaml").resolve())
|
|
|
|
|
|
def test_cli_path_has_higher_priority_than_env(monkeypatch, tmp_path):
|
|
monkeypatch.chdir(tmp_path)
|
|
env_file = tmp_path / "config" / "agents" / "env.yaml"
|
|
cli_file = tmp_path / "config" / "agents" / "cli.yaml"
|
|
|
|
_write_yaml(env_file, _full_agent_yaml(llm_model="env-model"))
|
|
_write_yaml(cli_file, _full_agent_yaml(llm_model="cli-model"))
|
|
|
|
monkeypatch.setenv("AGENT_CONFIG_PATH", str(env_file))
|
|
|
|
settings = load_settings(argv=["--agent-config", str(cli_file)])
|
|
|
|
assert settings.llm_model == "cli-model"
|
|
assert settings.agent_config_source == "cli_path"
|
|
assert settings.agent_config_path == str(cli_file.resolve())
|
|
|
|
|
|
def test_default_yaml_is_loaded_without_args_or_env(monkeypatch, tmp_path):
|
|
monkeypatch.chdir(tmp_path)
|
|
default_file = tmp_path / "config" / "agents" / "default.yaml"
|
|
_write_yaml(default_file, _full_agent_yaml(llm_model="from-default"))
|
|
|
|
monkeypatch.delenv("AGENT_CONFIG_PATH", raising=False)
|
|
monkeypatch.delenv("AGENT_PROFILE", raising=False)
|
|
|
|
settings = load_settings(argv=[])
|
|
|
|
assert settings.llm_model == "from-default"
|
|
assert settings.agent_config_source == "default"
|
|
assert settings.agent_config_path == str(default_file.resolve())
|
|
|
|
|
|
def test_missing_required_agent_settings_fail(monkeypatch, tmp_path):
|
|
monkeypatch.chdir(tmp_path)
|
|
file_path = tmp_path / "missing-required.yaml"
|
|
_write_yaml(
|
|
file_path,
|
|
"""
|
|
agent:
|
|
llm:
|
|
model: gpt-4o-mini
|
|
""".strip(),
|
|
)
|
|
|
|
with pytest.raises(ValueError, match="Missing required agent settings in YAML"):
|
|
load_settings(argv=["--agent-config", str(file_path)])
|
|
|
|
|
|
def test_blank_required_provider_key_fails(monkeypatch, tmp_path):
|
|
monkeypatch.chdir(tmp_path)
|
|
file_path = tmp_path / "blank-key.yaml"
|
|
_write_yaml(file_path, _full_agent_yaml(llm_key=""))
|
|
|
|
with pytest.raises(ValueError, match="Missing required agent settings in YAML"):
|
|
load_settings(argv=["--agent-config", str(file_path)])
|
|
|
|
|
|
def test_missing_tts_api_url_fails(monkeypatch, tmp_path):
|
|
monkeypatch.chdir(tmp_path)
|
|
file_path = tmp_path / "missing-tts-url.yaml"
|
|
_write_yaml(
|
|
file_path,
|
|
_full_agent_yaml().replace(
|
|
" api_url: https://example-tts.invalid/v1/audio/speech\n",
|
|
"",
|
|
),
|
|
)
|
|
|
|
with pytest.raises(ValueError, match="Missing required agent settings in YAML"):
|
|
load_settings(argv=["--agent-config", str(file_path)])
|
|
|
|
|
|
def test_missing_asr_api_url_fails(monkeypatch, tmp_path):
|
|
monkeypatch.chdir(tmp_path)
|
|
file_path = tmp_path / "missing-asr-url.yaml"
|
|
_write_yaml(
|
|
file_path,
|
|
_full_agent_yaml().replace(
|
|
" api_url: https://example-asr.invalid/v1/audio/transcriptions\n",
|
|
"",
|
|
),
|
|
)
|
|
|
|
with pytest.raises(ValueError, match="Missing required agent settings in YAML"):
|
|
load_settings(argv=["--agent-config", str(file_path)])
|
|
|
|
|
|
def test_agent_yaml_unknown_key_fails(monkeypatch, tmp_path):
|
|
monkeypatch.chdir(tmp_path)
|
|
file_path = tmp_path / "bad-agent.yaml"
|
|
_write_yaml(file_path, _full_agent_yaml() + "\n unknown_option: true")
|
|
|
|
with pytest.raises(ValueError, match="Unknown agent config keys"):
|
|
load_settings(argv=["--agent-config", str(file_path)])
|
|
|
|
|
|
def test_legacy_siliconflow_section_fails(monkeypatch, tmp_path):
|
|
monkeypatch.chdir(tmp_path)
|
|
file_path = tmp_path / "legacy-siliconflow.yaml"
|
|
_write_yaml(
|
|
file_path,
|
|
"""
|
|
agent:
|
|
siliconflow:
|
|
api_key: x
|
|
""".strip(),
|
|
)
|
|
|
|
with pytest.raises(ValueError, match="Section 'siliconflow' is no longer supported"):
|
|
load_settings(argv=["--agent-config", str(file_path)])
|
|
|
|
|
|
def test_agent_yaml_missing_env_reference_fails(monkeypatch, tmp_path):
|
|
monkeypatch.chdir(tmp_path)
|
|
file_path = tmp_path / "bad-ref.yaml"
|
|
_write_yaml(
|
|
file_path,
|
|
_full_agent_yaml(llm_key="${UNSET_LLM_API_KEY}"),
|
|
)
|
|
|
|
with pytest.raises(ValueError, match="Missing environment variable"):
|
|
load_settings(argv=["--agent-config", str(file_path)])
|
|
|
|
|
|
def test_agent_yaml_tools_list_is_loaded(monkeypatch, tmp_path):
|
|
monkeypatch.chdir(tmp_path)
|
|
file_path = tmp_path / "tools-agent.yaml"
|
|
_write_yaml(
|
|
file_path,
|
|
_full_agent_yaml()
|
|
+ """
|
|
|
|
tools:
|
|
- current_time
|
|
- name: weather
|
|
description: Get weather by city.
|
|
parameters:
|
|
type: object
|
|
properties:
|
|
city:
|
|
type: string
|
|
required: [city]
|
|
executor: server
|
|
""",
|
|
)
|
|
|
|
settings = load_settings(argv=["--agent-config", str(file_path)])
|
|
|
|
assert isinstance(settings.tools, list)
|
|
assert settings.tools[0] == "current_time"
|
|
assert settings.tools[1]["name"] == "weather"
|
|
assert settings.tools[1]["executor"] == "server"
|
|
|
|
|
|
def test_agent_yaml_tools_must_be_list(monkeypatch, tmp_path):
|
|
monkeypatch.chdir(tmp_path)
|
|
file_path = tmp_path / "bad-tools-agent.yaml"
|
|
_write_yaml(
|
|
file_path,
|
|
_full_agent_yaml()
|
|
+ """
|
|
|
|
tools:
|
|
weather:
|
|
executor: server
|
|
""",
|
|
)
|
|
|
|
with pytest.raises(ValueError, match="Agent config key 'tools' must be a list"):
|
|
load_settings(argv=["--agent-config", str(file_path)])
|