diff options
| author | tanaka takashi <isazaka@gmail.com> | 2026-07-09 21:52:16 +0900 |
|---|---|---|
| committer | tanaka takashi <isazaka@gmail.com> | 2026-07-09 21:52:16 +0900 |
| commit | 1729e1c399a41b4128b5dd7ef0286fae128e6157 (patch) | |
| tree | ad1b8f62368419c692bd140e0005ca9c675fc349 | |
| parent | 1a56663476de9ff42d70ef68c868de09d8b2d5ff (diff) | |
ollama側のレスポンスが空の場合に1回リトライする処理に変更
トークン数の累積計算処理削除
| -rw-r--r-- | src/ollama_web/i18n/messages/en.json | 4 | ||||
| -rw-r--r-- | src/ollama_web/i18n/messages/ja.json | 4 | ||||
| -rw-r--r-- | src/ollama_web/llm.py | 39 | ||||
| -rw-r--r-- | src/ollama_web/sessions.py | 30 | ||||
| -rw-r--r-- | src/ollama_web/static/app.js | 11 | ||||
| -rw-r--r-- | tests/test_clear_messages.py | 1 |
6 files changed, 74 insertions, 15 deletions
diff --git a/src/ollama_web/i18n/messages/en.json b/src/ollama_web/i18n/messages/en.json index 9e2894a..42bce33 100644 --- a/src/ollama_web/i18n/messages/en.json +++ b/src/ollama_web/i18n/messages/en.json @@ -78,7 +78,9 @@ "tool_limit_reached": "Tool call limit reached. Summarizing findings…", "thinking": "Thinking process", "executing_tool": "Executing {label}…", - "running": "Running…" + "running": "Running…", + "empty_response": "The model returned an empty response. Please send the message again.", + "empty_response_retry": "Empty response received; retrying…" }, "errors": { "generic": "An error occurred", diff --git a/src/ollama_web/i18n/messages/ja.json b/src/ollama_web/i18n/messages/ja.json index aa9f01e..a476ca2 100644 --- a/src/ollama_web/i18n/messages/ja.json +++ b/src/ollama_web/i18n/messages/ja.json @@ -78,7 +78,9 @@ "tool_limit_reached": "ツール呼び出し上限に達しました。これまでの調査結果を整理中…", "thinking": "思考プロセス", "executing_tool": "{label} を実行中…", - "running": "実行中…" + "running": "実行中…", + "empty_response": "モデルから空の応答が返されました。もう一度送信してください。", + "empty_response_retry": "応答が空でした。リトライ中…" }, "errors": { "generic": "エラーが発生しました", diff --git a/src/ollama_web/llm.py b/src/ollama_web/llm.py index 48c177a..76a61a5 100644 --- a/src/ollama_web/llm.py +++ b/src/ollama_web/llm.py @@ -393,6 +393,9 @@ def stream_chat_with_tools( cumulative_tool_chars = 0 + MAX_EMPTY_RETRIES = 2 + empty_retries = 0 + rounds = 0 while rounds < MAX_TOOL_ROUNDS: rounds += 1 @@ -406,6 +409,7 @@ def stream_chat_with_tools( model, cumulative_tool_chars, ) + t0 = time.time() try: stream = cast(Any, raw_client).chat( @@ -449,6 +453,35 @@ def stream_chat_with_tools( time.time() - t0, ) + # Detect empty responses. Some models / proxies occasionally return a + # stream with almost no meaningful content. Log it and retry a few + # times before giving up. + is_empty = ( + not full_message.get("content") + and not full_message.get("thinking") + and not tool_calls + ) + if is_empty and empty_retries < MAX_EMPTY_RETRIES: + empty_retries += 1 + logger.warning( + "ollama returned an empty stream (chunks=%d); retry %d/%d", + chunk_count, + empty_retries, + MAX_EMPTY_RETRIES, + ) + yield { + "type": "status", + "message": t("status.empty_response_retry", lang=language), + } + # Retry the same round without consuming the tool round budget. + rounds -= 1 + # Small pause to avoid hammering the upstream. + time.sleep(0.5) + continue + # Reset retry budget on any non-empty round so isolated hiccups are fine. + if not is_empty: + empty_retries = 0 + # Reconstruct the assistant message including any tool_calls so the # next round can see them. if tool_calls: @@ -458,6 +491,12 @@ def stream_chat_with_tools( messages.append(msg) if not tool_calls: + if is_empty: + # Ran out of tool rounds while still empty; surface a warning. + yield { + "type": "warning", + "message": t("status.empty_response", lang=language), + } done_event = {"type": "done"} tokens = _token_counts(last_chunk) if tokens: diff --git a/src/ollama_web/sessions.py b/src/ollama_web/sessions.py index 593f2d7..388066b 100644 --- a/src/ollama_web/sessions.py +++ b/src/ollama_web/sessions.py @@ -243,19 +243,23 @@ class SessionStore: return session def _update_session_token_count(self, session: dict[str, Any]) -> None: - """Recalculate cumulative token_count for the session.""" - total_prompt = 0 - total_eval = 0 - for m in session.get("messages", []): - tc = m.get("token_count") or {} - total_prompt += int(tc.get("prompt_eval_count", 0) or 0) - total_eval += int(tc.get("eval_count", 0) or 0) - if total_prompt or total_eval: - session["token_count"] = { - "prompt_eval_count": total_prompt, - "eval_count": total_eval, - "total_count": total_prompt + total_eval, - } + """Update the session token_count from the most recent assistant message. + + ``prompt_eval_count`` is the input token count for the *entire* conversation + as seen by ollama in that response, not a per-message value. Summing it + across messages would over-count massively. We therefore keep the latest + assistant message's token_count as the authoritative session value. + """ + messages = session.get("messages", []) + latest_assistant_tc = None + for m in reversed(messages): + if m.get("role") == "assistant": + tc = m.get("token_count") + if tc: + latest_assistant_tc = tc + break + if latest_assistant_tc is not None: + session["token_count"] = dict(latest_assistant_tc) else: session.pop("token_count", None) diff --git a/src/ollama_web/static/app.js b/src/ollama_web/static/app.js index 378e39f..54fb2e0 100644 --- a/src/ollama_web/static/app.js +++ b/src/ollama_web/static/app.js @@ -714,6 +714,17 @@ async function send() { waiting.textContent = ev.message || t("chat.processing"); assistantBody.appendChild(waiting); } + } else if (ev.type === "warning") { + gotError = true; + if (!placeholderCleared) { + assistantBody.innerHTML = ""; + placeholderCleared = true; + } + const span = document.createElement("span"); + span.className = "warning-msg"; + span.style.color = "#f0883e"; + span.textContent = ev.message || t("status.empty_response"); + assistantBody.appendChild(span); } else if (ev.type === "thinking") { if (!thinkingBody) thinkingBody = addThinkingBubble(assistantExtras); thinkingBody.textContent += ev.content; diff --git a/tests/test_clear_messages.py b/tests/test_clear_messages.py index c3f3595..a0653ca 100644 --- a/tests/test_clear_messages.py +++ b/tests/test_clear_messages.py @@ -82,6 +82,7 @@ def test_add_message_stores_token_count_and_clears_it(): assert reloaded["token_count"] == { "prompt_eval_count": 120, "eval_count": 80, + "num_ctx": 8192, "total_count": 200, } |
