import logging import os import httpx logger = logging.getLogger(__name__) TAVILY_SEARCH_URL = "https://api.tavily.com/search" def _api_key() -> str | None: """GUIの設定画面から実行時に変更できるよう、起動時定数ではなく都度 os.environ を見に行く。 app.core.config の定数は import 時に評価されるため、DBに保存した値を settings_store.load_into_env() が os.environ に入れても反映されない。 """ return os.environ.get("TAVILY_API_KEY") or None WEB_SEARCH_TOOL = { "type": "function", "function": { "name": "web_search", "description": "最新情報・事実確認・知らない固有名詞など、自分の知識だけでは答えられない可能性があるときにWebを検索する。" "ただし、システムプロンプトに提示されたローカルナレッジベースの資料で回答できる場合は使わないこと。", "parameters": { "type": "object", "properties": { "query": {"type": "string", "description": "検索したい内容を表す検索クエリ"}, }, "required": ["query"], }, }, } async def search_web(query: str, max_results: int = 5) -> list[dict]: """Tavily APIでWeb検索する。結果は [{title, url, content}, ...]。失敗時は空リスト。""" api_key = _api_key() if not api_key: logger.warning("TAVILY_API_KEY is not set; skipping web search") return [] try: async with httpx.AsyncClient(timeout=15.0) as client: response = await client.post( TAVILY_SEARCH_URL, json={ "api_key": api_key, "query": query, "max_results": max_results, }, ) response.raise_for_status() data = response.json() except Exception: logger.exception("Tavily search failed for query=%r", query) return [] results = [] for item in data.get("results", []): results.append({ "title": item.get("title") or "", "url": item.get("url") or "", "content": item.get("content") or "", }) return results