diff --git a/.env.example b/.env.example index 3ead6cd..6449e65 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,5 @@ DEEPSEEK_API_KEY=your-api-key +SERVICE_PORT=8000 DEEPSEEK_BASE_URL=https://api.deepseek.com DEEPSEEK_MODEL=deepseek-v4-flash DEFAULT_SYSTEM_PROMPT=You are a helpful assistant. diff --git a/README.md b/README.md index 8619459..875130e 100644 --- a/README.md +++ b/README.md @@ -6,13 +6,14 @@ ```bash uv sync -export DEEPSEEK_API_KEY="your-api-key" +cp .env.example .env +# 编辑 .env 并填写 DEEPSEEK_API_KEY uv run uvicorn main:app --reload ``` 服务默认运行在 `http://127.0.0.1:8000`,交互式 API 文档位于 `/docs`。 -可通过环境变量配置 `DEEPSEEK_BASE_URL`、`DEEPSEEK_MODEL`、`DEFAULT_SYSTEM_PROMPT` 和 `CHAT_DATA_DIR`,完整示例见 `.env.example`。第一版应只使用一个 Uvicorn worker。 +服务会自动加载项目根目录的 `.env`,已有系统环境变量优先级更高。可配置 `DEEPSEEK_BASE_URL`、`DEEPSEEK_MODEL`、`DEFAULT_SYSTEM_PROMPT` 和 `CHAT_DATA_DIR`,完整示例见 `.env.example`。第一版应只使用一个 Uvicorn worker。 ## 使用 diff --git a/config.py b/config.py index a6cd6de..83018d9 100644 --- a/config.py +++ b/config.py @@ -2,9 +2,12 @@ from dataclasses import dataclass from pathlib import Path import os +from dotenv import load_dotenv + @dataclass(frozen=True, slots=True) class Settings: + port: int api_key: str | None base_url: str model: str @@ -13,7 +16,9 @@ class Settings: @classmethod def from_env(cls) -> "Settings": + load_dotenv(dotenv_path=Path.cwd() / ".env", override=False) return cls( + port=int(os.getenv("SERVICE_PORT", "8000")), api_key=os.getenv("DEEPSEEK_API_KEY"), base_url=os.getenv("DEEPSEEK_BASE_URL", "https://api.deepseek.com"), model=os.getenv("DEEPSEEK_MODEL", "deepseek-v4-flash"), diff --git a/main.py b/main.py index 8bf6f50..b76f231 100644 --- a/main.py +++ b/main.py @@ -1,7 +1,8 @@ from app import app - +from config import Settings if __name__ == "__main__": import uvicorn - uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True) + settings = Settings.from_env() + uvicorn.run("main:app", host="0.0.0.0", port=settings.port, reload=True) diff --git a/pyproject.toml b/pyproject.toml index 6156b03..ebb81e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,6 +8,7 @@ dependencies = [ "fastapi>=0.115.0", "httpx[socks]>=0.28.0", "openai>=1.55.0", + "python-dotenv>=1.0.0", "uvicorn[standard]>=0.32.0", ] diff --git a/tests/test_api.py b/tests/test_api.py index ac057b9..81c3f17 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -49,6 +49,7 @@ class FakeClient: def client_and_provider(tmp_path: Path) -> tuple[TestClient, FakeClient]: provider = FakeClient() settings = Settings( + port=8000, api_key=None, base_url="https://api.deepseek.com", model="deepseek-v4-flash", @@ -66,6 +67,18 @@ def create_session(client: TestClient, **body: str) -> str: return response.json()["session_id"] +def test_settings_load_api_key_from_dotenv( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + monkeypatch.chdir(tmp_path) + monkeypatch.delenv("DEEPSEEK_API_KEY", raising=False) + (tmp_path / ".env").write_text("DEEPSEEK_API_KEY=test-key\n") + + settings = Settings.from_env() + + assert settings.api_key == "test-key" + + def test_create_session_uses_default_prompt( client_and_provider: tuple[TestClient, FakeClient], tmp_path: Path ) -> None: diff --git a/uv.lock b/uv.lock index 6ea1721..a31efdb 100644 --- a/uv.lock +++ b/uv.lock @@ -366,6 +366,7 @@ dependencies = [ { name = "fastapi" }, { name = "httpx", extra = ["socks"] }, { name = "openai" }, + { name = "python-dotenv" }, { name = "uvicorn", extra = ["standard"] }, ] @@ -379,6 +380,7 @@ requires-dist = [ { name = "fastapi", specifier = ">=0.115.0" }, { name = "httpx", extras = ["socks"], specifier = ">=0.28.0" }, { name = "openai", specifier = ">=1.55.0" }, + { name = "python-dotenv", specifier = ">=1.0.0" }, { name = "uvicorn", extras = ["standard"], specifier = ">=0.32.0" }, ]