Coverage for src\genesis\inference\factory.py: 98%
35 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-26 14:20 +0800
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-26 14:20 +0800
1"""真实 InferenceEngine 工厂:读取环境变量/.env 构造 HttpLLMClient + InferenceEngine。
3设计要点:
4- 不硬编码任何密钥;优先级 GENESIS_INFERENCE__* 环境变量 > 裸 DEEPSEEK_API_KEY/LLM_BASE_URL > 默认值。
5- 自动加载仓库根目录 .env(被 .gitignore 忽略,密钥不入库)。
6- engine=None 时由 orchestrator/qa_loop 调用,接通 P5-T10 人工质量门禁的真实 LLM 路径。
7"""
8from __future__ import annotations
10import os
11from pathlib import Path
13from genesis.config import InferenceModels, ModelSpec
14from genesis.inference.client import HttpLLMClient
15from genesis.inference.engine import InferenceEngine
16from genesis.inference.exceptions import LLMNotConfiguredError
18_DEFAULT_BASE_URL = "https://api.deepseek.com"
19_DEFAULT_MODEL = "deepseek-chat"
20_DEFAULT_FALLBACK = "qwen-max"
23def _load_dotenv(path: Path = Path(".env")) -> None:
24 """将 .env 中的 KEY=VALUE 注入 os.environ(仅当变量尚未设置时)。"""
25 if not path.is_file():
26 return
27 for raw in path.read_text(encoding="utf-8").splitlines():
28 line = raw.strip()
29 if not line or line.startswith("#") or "=" not in line:
30 continue
31 key, _, val = line.partition("=")
32 key = key.strip()
33 val = val.strip()
34 if len(val) >= 2 and val[0] in "\"'":
35 if val[-1] == val[0]: 35 ↛ 37line 35 didn't jump to line 37 because the condition on line 35 was always true
36 val = val[1:-1]
37 os.environ.setdefault(key, val)
40def build_inference_engine() -> InferenceEngine:
41 _load_dotenv()
42 api_key = os.environ.get("GENESIS_INFERENCE__API_KEY") or os.environ.get("DEEPSEEK_API_KEY")
43 base_url = (
44 os.environ.get("GENESIS_INFERENCE__BASE_URL")
45 or os.environ.get("LLM_BASE_URL")
46 or _DEFAULT_BASE_URL
47 )
48 model = (
49 os.environ.get("GENESIS_INFERENCE__MODEL")
50 or os.environ.get("LLM_MODEL")
51 or _DEFAULT_MODEL
52 )
53 fallback = (
54 os.environ.get("GENESIS_INFERENCE__FALLBACK_MODEL")
55 or os.environ.get("LLM_FALLBACK_MODEL")
56 or _DEFAULT_FALLBACK
57 )
58 if not api_key:
59 raise LLMNotConfiguredError(
60 "缺少 LLM API Key:请设置 GENESIS_INFERENCE__API_KEY"
61 "(或 .env / 环境变量 DEEPSEEK_API_KEY)"
62 )
63 models = InferenceModels(
64 primary=ModelSpec(name=model),
65 fallback=ModelSpec(name=fallback),
66 )
67 client = HttpLLMClient(base_url=base_url, api_key=api_key)
68 return InferenceEngine(client=client, models=models)