diff --git a/src/micro_bot/api/chat.py b/src/micro_bot/api/chat.py index bab8ef2..dc64960 100644 --- a/src/micro_bot/api/chat.py +++ b/src/micro_bot/api/chat.py @@ -83,7 +83,9 @@ async def chat( state.corpus_version = await state.documents.corpus_version() if payload.stream: - events = _events(request, payload.message, state, conversation, history, started_at) + events = _events( + request, payload.message, state, conversation, history, language, started_at + ) return StreamingResponse(events, media_type="text/event-stream", headers=_SSE_HEADERS) if not payload.message.strip(): # I18: nothing to search; the API shape stays the same retrieval = Retrieval(chunks=[], top_score=0.0, decision="refuse") @@ -132,11 +134,14 @@ async def _events( state: object, conversation: Conversation, history: list[Turn], + language: QueryLang, started_at: float, ) -> AsyncIterator[bytes]: - """The SSE body of a ``stream=true`` chat; every failure ends in an ``error`` event.""" + """The SSE body of a ``stream=true`` chat; every failure ends in an ``error`` event. + + ``language`` is this message's, as on the JSON path: not the conversation's first one. + """ cid = conversation.conversation_id - language = conversation.language yield _sse("meta", {"conversation_id": cid}) try: chunks: list[Chunk] = [] diff --git a/tests/test_chat_stream.py b/tests/test_chat_stream.py index 46a4b26..d9dda45 100644 --- a/tests/test_chat_stream.py +++ b/tests/test_chat_stream.py @@ -289,3 +289,51 @@ async def test_client_without_key_makes_no_call() -> None: items = await _collect(OpenRouterClient(Settings(openrouter_api_key=None))) assert items == [GenerationResult(None, GenerationUsage(llm_calls=0), "not_configured")] + + +def test_stream_answers_in_each_message_language_not_the_first(tmp_path: Path) -> None: + @dataclass + class ByWord: + def detect(self, text: str) -> str: + return "fr" if "quand" in text.lower() else "en" + + prompts: list[str] = [] + + @dataclass + class Recording(StreamingGenerator): + async def stream(self, system: str, user: str): + prompts.append(system) + async for item in super().stream(system, user): + yield item + + settings = Settings(data_dir=tmp_path, api_key="api-secret") + state = ApplicationState( + settings=settings, + documents=FakeDocumentBackend(), + document_probe=FakeHealthProbe(), + provider_probe=FakeHealthProbe(), + retrieval=FakeRetrieval(), + generator=Recording(["Friday. [S1]"]), + language_detector=ByWord(), + ) + client = TestClient(create_app(settings=settings, state=state)) + first = events( + client.post( + "/api/v1/chat", + json={"message": "Quand ferment les inscriptions au club ?", "stream": True}, + headers=KEY, + ) + ) + cid = first[0][1]["conversation_id"] + client.post( + "/api/v1/chat", + json={ + "message": "And when does the robotics workshop start this year?", + "conversation_id": cid, + "stream": True, + }, + headers=KEY, + ) + + assert "Answer in French" in prompts[0] + assert "Answer in English" in prompts[1]