diff options
| author | tanaka takashi <isazaka@gmail.com> | 2026-06-28 14:47:26 +0900 |
|---|---|---|
| committer | tanaka takashi <isazaka@gmail.com> | 2026-06-28 14:47:26 +0900 |
| commit | 9e430700cc58adbed5baf110961c08cbcb6a7135 (patch) | |
| tree | e23126ed0636e5d09c92dfeaa98d3228b0bfc03a | |
| parent | 967bbca9829ce65edec7a945a571f91e4998a8be (diff) | |
システムプロンプトへローカルタイムの現在時刻を注入するように変更
| -rw-r--r-- | src/ollama_web/prompts/en.json | 1 | ||||
| -rw-r--r-- | src/ollama_web/prompts/ja.json | 1 | ||||
| -rw-r--r-- | src/ollama_web/routes/chat.py | 11 | ||||
| -rw-r--r-- | tests/test_i18n.py | 1 | ||||
| -rw-r--r-- | tests/test_i18n_rendering.py | 54 | ||||
| -rw-r--r-- | tests/test_settings.py | 14 |
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: |
