Fixes age validation errors caused by the LLM not knowing the current date.
Changes:
- prompts.py: inject date.today() at the top of both system prompts so the
LLM can accurately calculate a child's age from their date of birth
- llm.py: add optional thinking_budget parameter to complete(); when set,
passes thinking={"type": "enabled", "budget_tokens": N} to litellm and
raises max_tokens to thinking_budget + 4096 (Anthropic models only)
- config.py: add thinking_budget field, read from THINKING_BUDGET env var
- .env.example: document the THINKING_BUDGET option
- core.py: pass thinking_budget through to llm.complete()
- main.py: pass thinking_budget when constructing EmailAgent
- chat_app.py: switch from stream_complete to asyncio.to_thread(complete)
so extended thinking works and so only the reply field is shown to
the parent (not the raw JSON wrapper)
To enable extended thinking set THINKING_BUDGET=8000 in .env.
https://claude.ai/code/session_01SUWzMzFvSfWiHXA2p6rPg9
- Change user message author label from "Du / You" to "Du" (German-first)
- Add CHAINLIT_HOST env var to .env.example so the server listens on all
interfaces and is reachable from outside localhost
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous implementation used asyncio.to_thread(llm.complete) to avoid
blocking the event loop, but Chainlit's contextvars context is not reliably
propagated across thread boundaries, causing the session to reset and clear
the message history on each user submission.
Changes:
- Add llm.acomplete() using litellm.acompletion() (native coroutine)
- Replace asyncio.to_thread() in on_message with await llm.acomplete()
- Store the welcome message in state.messages so it is replayed on reconnect
- Persist state to cl.user_session immediately after appending the user's
message (before the LLM call) so reconnect detection has the latest history
- Add pytest-asyncio dev dependency and asyncio_mode = "auto" config
- Add 6 async tests for acomplete() in tests/test_llm.py
https://claude.ai/code/session_01SUWzMzFvSfWiHXA2p6rPg9
Two related issues caused the screen to clear after each answer:
1. Blocking event loop: the synchronous llm.stream_complete() for-loop
was running directly in the async on_message handler, blocking the
event loop for the full LLM response duration. This caused the
WebSocket to time out and Chainlit to reconnect after each message.
Fix: replace stream_complete() with asyncio.to_thread(llm.complete)
so the network-bound LLM call runs in a thread pool and the event
loop (and WebSocket) stay alive throughout.
2. Reconnect resets history: on_chat_start always created a fresh empty
state and sent the welcome message, even on WebSocket reconnections
where cl.user_session still held the existing conversation.
Fix: if cl.user_session["state"] is already present, replay the
stored message history into the new thread instead of starting fresh.
https://claude.ai/code/session_01SUWzMzFvSfWiHXA2p6rPg9
The LLM returns a structured JSON object. Previously, raw tokens were
streamed directly to the user via msg.stream_token(), causing the full
JSON blob to appear in the chat.
Fix: collect all chunks silently, parse the JSON, then send only the
reply field with cl.Message(content=reply_text).send(). The JSON fields
(updates, next_step, registration_complete, language, intent) are still
processed in the background — parents never see them.
https://claude.ai/code/session_01SUWzMzFvSfWiHXA2p6rPg9