SPB Git forge

spb/uqo-chat

Public
14commits 1branches 0releases
1.4 MBsize
maindefault branch
17 days agolast push
Python 64.6% TypeScript 33.7% CSS 0.8%
3.3 KB · 63 lines python
Raw Blame History
1"""SSE parser: keep-alive comments, fragmented tool_calls, usage chunk, finish_reason."""23import json45import httpx6import pytest7import respx89from app.core.config import Settings10from app.llm.openrouter import LLMClient1112CHUNKS = [13    ": OPENROUTER PROCESSING",14    "",15    'data: {"id":"gen-1","model":"anthropic/claude-sonnet-4.6","choices":[{"index":0,"delta":{"content":"Bonjour"},"finish_reason":null}]}',16    'data: {"id":"gen-1","model":"anthropic/claude-sonnet-4.6","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_1","type":"function","function":{"name":"financial_calc","arguments":"{\\"function\\": \\"fv_l"}}]},"finish_reason":null}]}',17    ": OPENROUTER PROCESSING",18    'data: {"id":"gen-1","model":"anthropic/claude-sonnet-4.6","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"ump\\", \\"params\\": {\\"rate\\": 0.05}}"}}]},"finish_reason":null}]}',19    'data: {"id":"gen-1","model":"anthropic/claude-sonnet-4.6","choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}]}',20    'data: {"id":"gen-1","model":"anthropic/claude-sonnet-4.6","choices":[],"usage":{"prompt_tokens":120,"completion_tokens":30,"cost":0.00123}}',21    "data: [DONE]",22]232425@pytest.mark.asyncio26async def test_parse_fragmented_tool_calls() -> None:27    settings = Settings(OPENROUTER_API_KEY="k", OPENROUTER_BASE_URL="https://or.test/api/v1")28    client = LLMClient(settings)29    body = "\n".join(CHUNKS) + "\n"30    with respx.mock(base_url="https://or.test/api/v1") as mock:31        mock.post("/chat/completions").mock(32            return_value=httpx.Response(200, content=body.encode(),33                                        headers={"content-type": "text/event-stream"}))34        events = [ev async for ev in client.stream_chat(35            [{"role": "user", "content": "hi"}], None, ["anthropic/claude-sonnet-4.6", "openai/gpt-5.5"])]36        sent = json.loads(mock.calls[0].request.content)37        assert sent["models"] == ["anthropic/claude-sonnet-4.6", "openai/gpt-5.5"]38        assert sent["usage"] == {"include": True}39        assert mock.calls[0].request.headers["X-Title"] == "UQO-Chat"40    types = [e.type for e in events]41    assert types[0] == "text_delta" and events[0].data["delta"] == "Bonjour"42    end = next(e for e in events if e.type == "tool_call_end")43    assert end.data["name"] == "financial_calc"44    assert json.loads(end.data["arguments"]) == {"function": "fv_lump", "params": {"rate": 0.05}}45    usage = next(e for e in events if e.type == "usage")46    assert usage.data["input_tokens"] == 120 and usage.data["cost_usd"] == 0.0012347    done = events[-1]48    assert done.type == "done" and done.data["finish_reason"] == "tool_calls"49    await client.aclose()505152@pytest.mark.asyncio53async def test_retry_then_error() -> None:54    settings = Settings(OPENROUTER_API_KEY="k", OPENROUTER_BASE_URL="https://or.test/api/v1")55    client = LLMClient(settings)56    with respx.mock(base_url="https://or.test/api/v1") as mock:57        mock.post("/chat/completions").mock(return_value=httpx.Response(500, text="boom"))58        with pytest.raises(Exception):  # noqa: B01759            _ = [ev async for ev in client.stream_chat([{"role": "user", "content": "x"}], None,60                                                        ["m"])]61        assert len(mock.calls) == 362    await client.aclose()63