summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortanaka takashi <isazaka@gmail.com>2026-06-28 14:47:26 +0900
committertanaka takashi <isazaka@gmail.com>2026-06-28 14:47:26 +0900
commit9e430700cc58adbed5baf110961c08cbcb6a7135 (patch)
treee23126ed0636e5d09c92dfeaa98d3228b0bfc03a
parent967bbca9829ce65edec7a945a571f91e4998a8be (diff)
システムプロンプトへローカルタイムの現在時刻を注入するように変更
-rw-r--r--src/ollama_web/prompts/en.json1
-rw-r--r--src/ollama_web/prompts/ja.json1
-rw-r--r--src/ollama_web/routes/chat.py11
-rw-r--r--tests/test_i18n.py1
-rw-r--r--tests/test_i18n_rendering.py54
-rw-r--r--tests/test_settings.py14
6 files changed, 60 insertions, 22 deletions
diff --git a/src/ollama_web/prompts/en.json b/src/ollama_web/prompts/en.json
index 53af74b..72a0b20 100644
--- a/src/ollama_web/prompts/en.json
+++ b/src/ollama_web/prompts/en.json
@@ -1,5 +1,6 @@
{
"tool_system": "You can use web search, scraping, and file fetch tools. Do not run more searches than necessary in a single response; review the results before conducting the next investigation. Tool results are summarized, so grasp the important points and use them effectively.",
+ "current_time": "Current time: {time}",
"tool_limit_instruction": "The tool call limit has been reached. Based on the search results and conversation so far, please summarize the final answer the user requested.",
"attachment_prefix": "The following is the content of files attached by the user:",
"fetch": {
diff --git a/src/ollama_web/prompts/ja.json b/src/ollama_web/prompts/ja.json
index 40f6c7c..00698c2 100644
--- a/src/ollama_web/prompts/ja.json
+++ b/src/ollama_web/prompts/ja.json
@@ -1,5 +1,6 @@
{
"tool_system": "あなたはWeb検索・スクレイピング・ファイル取得ツールを利用できます。一度の回答で必要以上に多くの検索を同時に行わず、結果を確認してから次の調査を行ってください。各ツール結果は要約されて提供されるため、重要なポイントを把握して活用してください。",
+ "current_time": "現在時刻: {time}",
"tool_limit_instruction": "ツール呼び出しの上限に達しました。これまでの検索結果と会話を元に、ユーザーが求めた回答を最終的にまとめてください。",
"attachment_prefix": "以下はユーザーが添付したファイルの内容です:",
"fetch": {
diff --git a/src/ollama_web/routes/chat.py b/src/ollama_web/routes/chat.py
index c8cbcff..4c3fe48 100644
--- a/src/ollama_web/routes/chat.py
+++ b/src/ollama_web/routes/chat.py
@@ -8,6 +8,7 @@ import inspect
import json
import logging
from collections.abc import AsyncIterator
+from datetime import datetime
from typing import Any
from ollama import Message
@@ -35,9 +36,15 @@ def _limit_text(text: str, limit: int) -> str:
def _compose_system_prompt(language: str, custom_prompt: str) -> str:
- """Combine the localized built-in tool prompt with the user prompt."""
+ """Combine the localized built-in tool prompt, current time, and user prompt."""
tool_prompt = get_prompt("tool_system", lang=language)
- return f"{tool_prompt}\n\n{custom_prompt}" if custom_prompt else tool_prompt
+ time_line = get_prompt("current_time", lang=language).format(
+ time=datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+ )
+ parts = [tool_prompt, time_line]
+ if custom_prompt:
+ parts.append(custom_prompt)
+ return "\n\n".join(parts)
def _parse_messages(raw: list[dict[str, Any]]) -> list[Message]:
diff --git a/tests/test_i18n.py b/tests/test_i18n.py
index 48f8b9f..a2d0ffd 100644
--- a/tests/test_i18n.py
+++ b/tests/test_i18n.py
@@ -106,5 +106,6 @@ def test_get_prompt_returns_expected_strings():
"""Basic sanity checks for the get_prompt() helper."""
assert prompts.get_prompt("tool_system") == prompts.get_prompts("ja")["tool_system"]
assert prompts.get_prompt("tool_system", lang="en") == prompts.get_prompts("en")["tool_system"]
+ assert prompts.get_prompt("current_time", lang="ja") == prompts.get_prompts("ja")["current_time"]
assert prompts.get_prompt("missing.prompt", fallback="fallback") == "fallback"
assert prompts.get_prompt("missing.prompt") == "missing.prompt"
diff --git a/tests/test_i18n_rendering.py b/tests/test_i18n_rendering.py
index 0c37aad..ba1f602 100644
--- a/tests/test_i18n_rendering.py
+++ b/tests/test_i18n_rendering.py
@@ -3,6 +3,7 @@
from __future__ import annotations
import json
+from pathlib import Path
import pytest
from bs4 import BeautifulSoup
@@ -11,20 +12,39 @@ from starlette.testclient import TestClient
from ollama_web.app import create_app
from ollama_web.config import settings
from ollama_web.i18n import get_messages, t
+from ollama_web.settings_store import save_app_settings
@pytest.fixture
-def reset_language():
- """Reset language to the initial value after each test."""
- original = settings.language
- yield
- settings.language = original
+def reset_language(tmp_path):
+ """Reset language and data_dir to their initial values after each test."""
+ original_language = settings.language
+ original_data_dir = settings.data_dir
+ settings.data_dir = str(tmp_path)
+ yield tmp_path
+ settings.language = original_language
+ settings.data_dir = original_data_dir
if hasattr(get_messages, "cache_clear"):
get_messages.cache_clear()
-def _render_index() -> str:
+def _write_settings(data_dir: Path, language: str) -> None:
+ """Persist a minimal settings file with the requested UI language."""
+ save_app_settings(
+ data_dir,
+ {
+ "ui": {"language": language},
+ "ollama": {
+ "system_prompt": "",
+ "options": {"temperature": 0.8, "num_ctx": 8192},
+ },
+ },
+ )
+
+
+def _render_index(data_dir: Path, language: str) -> str:
"""Render index.html and return the HTML body."""
+ _write_settings(data_dir, language)
app = create_app()
with TestClient(app) as client:
response = client.post("/api/auth/login", json={"pin": settings.pin})
@@ -33,8 +53,9 @@ def _render_index() -> str:
return response.text
-def _render_pages():
+def _render_pages(data_dir: Path, language: str):
"""Render login and authenticated index pages with their response headers."""
+ _write_settings(data_dir, language)
app = create_app()
with TestClient(app) as client:
login_page = client.get("/login")
@@ -45,9 +66,9 @@ def _render_pages():
def test_english_language_consistency(reset_language):
- """When settings.language='en', all UI text including MCP must be English."""
- settings.language = "en"
- html = _render_index()
+ """When persisted language='en', all UI text including MCP must be English."""
+ data_dir = reset_language
+ html = _render_index(data_dir, "en")
# HTML-side labels
assert 'lang="en"' in html
assert "MCP Settings" in html
@@ -62,9 +83,9 @@ def test_english_language_consistency(reset_language):
def test_japanese_language_consistency(reset_language):
- """When settings.language='ja', all UI text including MCP must be Japanese."""
- settings.language = "ja"
- html = _render_index()
+ """When persisted language='ja', all UI text including MCP must be Japanese."""
+ data_dir = reset_language
+ html = _render_index(data_dir, "ja")
# HTML-side labels
assert 'lang="ja"' in html
assert "MCP設定" in html
@@ -101,8 +122,8 @@ def test_browser_messages_are_embedded_as_json_data(
login_title: str,
):
"""Both pages expose parseable localized data without executable inline scripts."""
- settings.language = lang
- login_page, index_page = _render_pages()
+ data_dir = reset_language
+ login_page, index_page = _render_pages(data_dir, lang)
for response in (login_page, index_page):
soup = BeautifulSoup(response.text, "html.parser")
@@ -121,7 +142,8 @@ def test_browser_messages_are_embedded_as_json_data(
def test_i18n_pages_keep_strict_script_csp(reset_language):
"""The localization transport must not require weakening script-src."""
- login_page, index_page = _render_pages()
+ data_dir = reset_language
+ login_page, index_page = _render_pages(data_dir, settings.language)
for response in (login_page, index_page):
csp = response.headers["content-security-policy"]
diff --git a/tests/test_settings.py b/tests/test_settings.py
index 737c8fd..29ec2c1 100644
--- a/tests/test_settings.py
+++ b/tests/test_settings.py
@@ -135,10 +135,16 @@ def test_settings_api_and_page_elements(tmp_path: Path) -> None:
def test_system_prompt_is_localized_and_appended() -> None:
custom = "Always answer briefly."
- assert _compose_system_prompt("en", "") == get_prompt("tool_system", lang="en")
- assert _compose_system_prompt("en", custom) == (
- f"{get_prompt('tool_system', lang='en')}\n\n{custom}"
- )
+ # An empty custom prompt yields only the tool prompt plus the current time line.
+ empty = _compose_system_prompt("en", "")
+ assert empty.startswith(get_prompt("tool_system", lang="en"))
+ assert get_prompt("current_time", lang="en").split("{time}")[0].rstrip() in empty
+
+ # A custom prompt is appended after the tool prompt and current time line.
+ composed = _compose_system_prompt("en", custom)
+ assert composed.startswith(get_prompt("tool_system", lang="en"))
+ assert composed.endswith(custom)
+ assert get_prompt("current_time", lang="en").split("{time}")[0].rstrip() in composed
def test_options_are_forwarded_to_tool_and_final_rounds(monkeypatch: pytest.MonkeyPatch) -> None: