diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index ced0449..691dd37 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -15,7 +15,6 @@ Brief description of changes. - [ ] Tests pass locally - [ ] PoW solvers tested (C + JS) -- [ ] Live tests if applicable ## Checklist diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b1f4240..e5b53ce 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,36 +48,6 @@ jobs: - name: Run tests run: python tests.py -j 2 - live-test: - needs: test - runs-on: ubuntu-latest - timeout-minutes: 30 - if: github.event_name == 'push' - steps: - - uses: actions/checkout@v7 - - - uses: actions/setup-python@v7 - with: - python-version: "3.12" - cache: pip - cache-dependency-path: requirements-dev.txt - - - name: Install system tools - run: | - sudo apt-get update - sudo apt-get install -y --no-install-recommends clang nodejs - - - name: Install dependencies - run: pip install -r requirements-dev.txt - - - name: Build native PoW solver - run: clang -O3 -pthread -funroll-loops -flto -march=native -mtune=native -o danyapi/deepseek/pow_solver danyapi/deepseek/pow_solver.c - - - name: Run live tests - env: - DEEPSEEK_TOKENS: ${{ secrets.DEEPSEEK_TOKENS }} - run: python -m pytest tests/test_live.py -v - docker-build: needs: test runs-on: ubuntu-latest diff --git a/danyapi/api/openai.py b/danyapi/api/openai.py index a2ba554..dd8f2ec 100644 --- a/danyapi/api/openai.py +++ b/danyapi/api/openai.py @@ -1046,6 +1046,8 @@ async def list_models() -> dict: } ) qwen_models: list[dict] = getattr(app.state, "qwen_models", []) + if not qwen_models and _byok_mode(): + qwen_models = QWEN_DEFAULT_MODELS for model in qwen_models: models.append( { diff --git a/pyproject.toml b/pyproject.toml index af667b1..d2a9b37 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,9 +9,6 @@ select = ["B", "C4", "E", "F", "FURB", "I", "PLW", "RUF", "UP"] testpaths = ["tests"] asyncio_mode = "auto" filterwarnings = ["ignore:Using `httpx` with `starlette.testclient` is deprecated.*"] -markers = [ - "live: tests that hit the real DeepSeek API (requires DEEPSEEK_TOKENS env var)", -] [tool.mypy] python_version = "3.12" diff --git a/tests/test_byok.py b/tests/test_byok.py index 93af3ca..0ade79e 100644 --- a/tests/test_byok.py +++ b/tests/test_byok.py @@ -228,6 +228,18 @@ def test_health_reports_byok_mode(): assert payload["byok_pools"]["qwen"] == 0 +def test_list_models_byok_without_env_tokens_lists_qwen_defaults(): + app.state.byok = True + app.state.qwen_models = [] + client = TestClient(app) + data = client.get("/v1/models").json() + client.close() + ids = [m["id"] for m in data["data"]] + assert "deepseek-v4.1-flash" in ids + for model in openai_mod.QWEN_DEFAULT_MODELS: + assert model["id"] in ids + + def test_health_no_byok_key_when_disabled(): app.state.byok = False client = TestClient(app) diff --git a/tests/test_live.py b/tests/test_live.py deleted file mode 100644 index 141b8b8..0000000 --- a/tests/test_live.py +++ /dev/null @@ -1,127 +0,0 @@ -import os - -import pytest - -from danyapi.deepseek.client import DeepSeekClient -from danyapi.deepseek.stream import IncrementalSSE, MessageReconstructor -from danyapi.pow import PowManager - -_TOKEN = os.environ.get("DEEPSEEK_TOKENS", "").split(",")[0].strip() - -pytestmark = [pytest.mark.live, pytest.mark.skipif(not _TOKEN, reason="DEEPSEEK_TOKENS not set")] - - -async def _make_account(): - client = DeepSeekClient(token=_TOKEN, timeout=120) - assert await client.check_auth(), "auth failed" - pow_mgr = PowManager() - session = await client.create_session() - return client, pow_mgr, session - - -async def _complete(client, pow_mgr, session, prompt, *, model_type="default", thinking=False, search=False, ref_file_ids=None, parent_message_id=None): - pow_headers = await pow_mgr.make_header(client.create_pow_challenge) - resp = await client.completion( - chat_session_id=session.id, - prompt=prompt, - parent_message_id=parent_message_id, - model_type=model_type, - thinking_enabled=thinking, - search_enabled=search, - ref_file_ids=ref_file_ids, - pow_headers=pow_headers, - ) - rec = MessageReconstructor() - incremental = IncrementalSSE() - response_message_id = None - try: - async for chunk in resp.aiter_bytes(): - for event in incremental.feed(chunk): - if event.event == "ready" and isinstance(event.data, dict): - response_message_id = event.data.get("response_message_id") - rec.handle(event) - for event in incremental.finish(): - rec.handle(event) - finally: - try: - await resp.aclose() - except Exception: - pass - return rec, response_message_id - - -async def test_auth(): - client = DeepSeekClient(token=_TOKEN, timeout=30) - assert await client.check_auth() - await client.aclose() - - -async def test_create_session(): - client, _, session = await _make_account() - assert session.id - await client.aclose() - - -async def test_completion_basic(): - client, pow_mgr, session = await _make_account() - try: - rec, _response_message_id = await _complete(client, pow_mgr, session, "Reply with exactly: OK", model_type="default") - assert rec.content, "empty content" - assert rec.status, "no status" - finally: - await client.aclose() - - -async def test_completion_thinking(): - client, pow_mgr, session = await _make_account() - try: - rec, _ = await _complete(client, pow_mgr, session, "What is 2+2? Reply with just the number.", model_type="default", thinking=True) - assert rec.content, "empty content" - finally: - await client.aclose() - - -async def test_completion_search(): - client, pow_mgr, session = await _make_account() - try: - rec, _ = await _complete(client, pow_mgr, session, "What is the current year? Reply briefly.", model_type="default", thinking=False, search=True) - assert rec.content, "empty content" - finally: - await client.aclose() - - -async def test_completion_thinking_and_search(): - client, pow_mgr, session = await _make_account() - try: - rec, _ = await _complete(client, pow_mgr, session, "What is the capital of France? Reply briefly.", model_type="default", thinking=True, search=True) - assert rec.content, "empty content" - finally: - await client.aclose() - - -async def test_upload_file(): - client, pow_mgr, session = await _make_account() - try: - pow_upload = PowManager() - pow_headers = await pow_upload.make_header(lambda: client.create_pow_challenge("/api/v0/file/upload_file")) - file_data = b"Hello, this is a test document." - info = await client.upload_file(file_data, "test.txt", "text/plain", "default", thinking_enabled=False, pow_headers=pow_headers) - assert info.get("id"), "no file id returned" - file_id = info["id"] - - rec, _ = await _complete(client, pow_mgr, session, "What does this document say? Reply briefly.", model_type="default", ref_file_ids=[file_id]) - assert rec.content, "empty content" - finally: - await client.aclose() - - -async def test_conversation_turns(): - client, pow_mgr, session = await _make_account() - try: - rec1, _msg_id = await _complete(client, pow_mgr, session, "My name is TestBot. Reply with: OK", model_type="default") - assert rec1.content, "empty content on first turn" - - rec2, _ = await _complete(client, pow_mgr, session, "What is my name?", model_type="default", parent_message_id=rec1.id) - assert rec2.content, "empty content on second turn" - finally: - await client.aclose() diff --git a/web/index.html b/web/index.html index 70a6fe4..348d79d 100644 --- a/web/index.html +++ b/web/index.html @@ -530,29 +530,33 @@ DanyAPI -

Token Manager

-

Support the project add your free provider tokens

+
+

Token Manager

+

Support the project add your free provider tokens

+
-
- - -
+
+
+ + +
-
- - -
+
+ + +
-
- No limits. Tokens are used server-side. Your consumers just point their OpenAI client at this instance no keys needed on their side. Each token adds parallel generation capacity. -
+
+ No limits. Tokens are used server-side. Your consumers just point their OpenAI client at this instance no keys needed on their side. Each token adds parallel generation capacity. +
-
+
- + +

Usage

@@ -617,6 +621,24 @@

Usage

}); } + function initMode() { + fetch(BASE + "/health") + .then(function (r) { + if (!r.ok) throw new Error("HTTP " + r.status); + return r.json(); + }) + .then(function (data) { + if (data.byok) { + var heading = document.getElementById("token-heading"); + if (heading) heading.style.display = "none"; + var manager = document.getElementById("token-manager"); + if (manager) manager.style.display = "none"; + } + }) + .catch(function () {}); + } + + initMode(); loadUsage(); function showStatus(msg, type) {