Add web search knowledge base, message tagging, and word-memory tip topics
Introduces websearch.py (Tavily) + knowledge.py to ground chat responses in search results, tagging.py to tag user and assistant messages via LLM and pick related topics for the Discord useless-tips job, and word_memory.py to track recent conversation nouns (surfaced in the new WordPool.vue sidebar) used to pick tip topics close to a related tag.
This commit is contained in:
@@ -7,6 +7,7 @@ from contextlib import asynccontextmanager
|
||||
|
||||
import litellm
|
||||
from app.core.config import DEFAULT_MODEL, DEBUG
|
||||
from app.core.websearch import WEB_SEARCH_TOOL, search_web
|
||||
|
||||
litellm.drop_params = True
|
||||
if DEBUG:
|
||||
@@ -58,6 +59,7 @@ async def stream_chat(
|
||||
messages: list[dict],
|
||||
model: str | None = None,
|
||||
session_id: str | None = None,
|
||||
tools: list[dict] | None = None,
|
||||
) -> AsyncIterator[str]:
|
||||
used_model = model or DEFAULT_MODEL
|
||||
async with _log_context(used_model, messages, session_id) as log:
|
||||
@@ -65,6 +67,7 @@ async def stream_chat(
|
||||
model=used_model,
|
||||
messages=messages,
|
||||
stream=True,
|
||||
**({"tools": tools} if tools else {}),
|
||||
)
|
||||
chunks: list[str] = []
|
||||
async for chunk in response:
|
||||
@@ -92,3 +95,70 @@ async def complete(
|
||||
log["response_text"] = result
|
||||
_save_log(log)
|
||||
return result
|
||||
|
||||
|
||||
async def resolve_web_search(
|
||||
messages: list[dict],
|
||||
model: str | None = None,
|
||||
session_id: str | None = None,
|
||||
max_rounds: int = 2,
|
||||
) -> tuple[list[dict], list[dict]]:
|
||||
"""必要に応じてLLMにweb_searchツールを呼ばせ、検索結果を会話に組み込んだ上でメッセージ列を返す。
|
||||
|
||||
モデルがtool callingに対応していない場合は何もせず元のmessagesをそのまま返す。
|
||||
戻り値: (拡張後のmessages, 実際に検索したクエリと結果のリスト)
|
||||
"""
|
||||
used_model = model or DEFAULT_MODEL
|
||||
working_messages = list(messages)
|
||||
searches: list[dict] = []
|
||||
|
||||
for _ in range(max_rounds):
|
||||
try:
|
||||
response = await litellm.acompletion(
|
||||
model=used_model,
|
||||
messages=working_messages,
|
||||
tools=[WEB_SEARCH_TOOL],
|
||||
tool_choice="auto",
|
||||
)
|
||||
except Exception:
|
||||
logger.info("Model %s does not support tool calling; skipping web search", used_model)
|
||||
return messages, searches
|
||||
|
||||
choice_message = response.choices[0].message
|
||||
tool_calls = getattr(choice_message, "tool_calls", None)
|
||||
if not tool_calls:
|
||||
break
|
||||
|
||||
working_messages.append({
|
||||
"role": "assistant",
|
||||
"content": choice_message.content or "",
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": tc.id,
|
||||
"type": "function",
|
||||
"function": {"name": tc.function.name, "arguments": tc.function.arguments},
|
||||
}
|
||||
for tc in tool_calls
|
||||
],
|
||||
})
|
||||
|
||||
for tc in tool_calls:
|
||||
try:
|
||||
args = json.loads(tc.function.arguments or "{}")
|
||||
except json.JSONDecodeError:
|
||||
args = {}
|
||||
query = args.get("query", "")
|
||||
results = await search_web(query)
|
||||
if results:
|
||||
tool_text = "\n\n".join(f"[{r['title']}]({r['url']})\n{r['content']}" for r in results)
|
||||
searches.append({"query": query, "results": results})
|
||||
else:
|
||||
tool_text = "検索結果が見つかりませんでした。"
|
||||
|
||||
working_messages.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tc.id,
|
||||
"content": tool_text,
|
||||
})
|
||||
|
||||
return working_messages, searches
|
||||
|
||||
Reference in New Issue
Block a user