release: dev → main (сдача хакатона) #106
@@ -448,7 +448,9 @@ async def _idle_text(ctx: Ctx, event: Event) -> Step:
|
||||
await save_checkin(ctx, **fields)
|
||||
if text is None and facts.med_question and not facts.quit_intent:
|
||||
text = await answer_question(ctx, await current_prescription(ctx))
|
||||
if text is None and not fields and looks_like_question(ctx.text):
|
||||
# A question is answered even if it also mentioned alcohol/side effects (those are recorded above).
|
||||
# Quit intent gets the rule reaction (delayed-onset explanation + doctor notified) — no second answer.
|
||||
if text is None and not facts.quit_intent and looks_like_question(ctx.text):
|
||||
text = await knowledge_answer(ctx, explicit=False) # general question → clinical guidelines (#64)
|
||||
if text is None:
|
||||
text = await ctx.gateway.phrase("support")
|
||||
|
||||
@@ -64,6 +64,16 @@ def build_provider(settings: Settings | None = None) -> ToolChatProvider | None:
|
||||
return None
|
||||
|
||||
|
||||
async def _chat_with_retry(provider: ToolChatProvider, messages: list[dict[str, Any]]):
|
||||
"""One retry: OpenRouter occasionally fails a single call (seen live as intermittent ProviderError)."""
|
||||
wire = _wire(messages) if provider.name != "mock" else messages
|
||||
try:
|
||||
return await provider.chat(wire, TOOL_SPECS)
|
||||
except ProviderError as exc:
|
||||
log.warning("chat agent provider=%s retry after %s", provider.name, exc)
|
||||
return await provider.chat(wire, TOOL_SPECS)
|
||||
|
||||
|
||||
async def _patient_brief(ctx: ToolContext) -> str:
|
||||
"""One line of context so the model knows what «мои таблетки» means (drug names only, no identifiers)."""
|
||||
rec = await ctx.record()
|
||||
@@ -99,7 +109,7 @@ async def answer_chat(
|
||||
try:
|
||||
async with asyncio.timeout(s.chat_agent_timeout_s * 2):
|
||||
for _ in range(s.chat_agent_max_steps):
|
||||
turn = await provider.chat(_wire(messages) if provider.name != "mock" else messages, TOOL_SPECS)
|
||||
turn = await _chat_with_retry(provider, messages)
|
||||
if not turn.tool_calls:
|
||||
if not (turn.content or "").strip() and not nudged:
|
||||
# Some models (Gemini Flash) occasionally end with an empty message after tool results.
|
||||
@@ -127,7 +137,7 @@ async def answer_chat(
|
||||
log.warning("chat agent: step limit reached tools=%s", ",".join(ctx.used))
|
||||
return None
|
||||
except (ProviderError, TimeoutError) as exc:
|
||||
log.error("chat agent provider=%s failed: %s", provider.name, type(exc).__name__ or "timeout")
|
||||
log.error("chat agent provider=%s failed: %s %s", provider.name, type(exc).__name__, exc)
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@@ -9,11 +9,15 @@ SYSTEM_PROMPT = """Ты — помощник пациента в сервисе
|
||||
о «моих таблетках», для текущего назначения). Вопросы об алкоголе, еде, вождении, сочетании с другими
|
||||
средствами, составе и непереносимостях — тоже вопросы о лекарстве: проверь текущее назначение через
|
||||
check_drug_for_patient (там есть состав, сравнение с непереносимостями и все взаимодействия).
|
||||
Вопросы «когда подействует», «что будет, если бросить», «как принимать», побочные эффекты — смотри поле
|
||||
instruction в check_drug_for_patient (инструкция ГРЛС) и дополняй search_guidelines.
|
||||
Для общих вопросов о лечении вызывай search_guidelines.
|
||||
2. Отвечай ТОЛЬКО на основе результатов инструментов. Если в них нет ответа — честно скажи, что в
|
||||
рекомендациях и инструкции этого нет, и предложи уточнить у лечащего врача. Ничего не додумывай.
|
||||
3. Если check_drug_for_patient вернул findings — назови КАЖДУЮ находку (сначала severity=high) простыми
|
||||
словами и посоветуй обсудить с врачом до приёма. Пустой список не значит «безопасно».
|
||||
Находки (findings) упоминай, когда вопрос о приёме, сочетании или безопасности препарата; на вопросы о
|
||||
сроках действия, отмене, побочных эффектах отвечай по существу, без посторонних находок.
|
||||
Про состав и непереносимости (intolerance_check) говори, только если о них спросили.
|
||||
Про алкоголь бери текст из general_interactions.
|
||||
4. Ссылайся на источник в тексте коротко: «по клиническим рекомендациям», «по инструкции к препарату»,
|
||||
|
||||
@@ -66,6 +66,12 @@ class OpenRouterProvider:
|
||||
except httpx.HTTPError as exc:
|
||||
raise ProviderError(type(exc).__name__) from exc
|
||||
if resp.status_code != 200:
|
||||
# Provider error bodies are generic (no patient text) — log the message to make failures diagnosable.
|
||||
try:
|
||||
detail = str(resp.json().get("error", {}).get("message", ""))[:200]
|
||||
except ValueError:
|
||||
detail = ""
|
||||
log.error("openrouter http %s: %s", resp.status_code, detail)
|
||||
raise ProviderError(f"http {resp.status_code}")
|
||||
try:
|
||||
msg = resp.json()["choices"][0]["message"]
|
||||
|
||||
@@ -10,6 +10,7 @@ import re
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any
|
||||
|
||||
from app.integrations.base import integrations
|
||||
from app.integrations.emias_record import PatientRecord, emias_records
|
||||
from app.llm.agent.drug_safety import check_patient, find_drug, load_db
|
||||
from app.llm.rag import retrieve
|
||||
@@ -183,10 +184,12 @@ async def _check(ctx: ToolContext, name: str) -> dict[str, Any]:
|
||||
if hit
|
||||
else "в примере состава этого вещества нет; состав зависит от производителя."
|
||||
)
|
||||
instruction = await _instruction(ctx, entry.inn)
|
||||
return {
|
||||
"found": True,
|
||||
"inn": entry.inn,
|
||||
"record": True,
|
||||
"instruction": instruction, # ГРЛС excerpts: onset, side effects, discontinuation, administration
|
||||
"findings": [f.model_dump() for f in findings],
|
||||
"intolerance_check": intolerance_check,
|
||||
"composition": comp.model_dump(exclude={"excipient_tags"}) if comp else None,
|
||||
@@ -196,5 +199,21 @@ async def _check(ctx: ToolContext, name: str) -> dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
INSTRUCTION_SECTIONS = ("onset", "side_effects", "discontinuation", "administration", "alcohol")
|
||||
|
||||
|
||||
async def _instruction(ctx: ToolContext, inn: str) -> dict[str, str]:
|
||||
"""ГРЛС excerpts for the drug (no doses in mg — see data/meds/grls)."""
|
||||
try:
|
||||
sections = await integrations.drug_info.sections(inn)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
log.error("drug_info unavailable: %s", type(exc).__name__)
|
||||
return {}
|
||||
out = {s.section: s.text for s in sections if s.section in INSTRUCTION_SECTIONS}
|
||||
if out:
|
||||
ctx.cite(sections[0].source.split(",")[0] + ": инструкция к препарату")
|
||||
return out
|
||||
|
||||
|
||||
def _json(data: Any) -> str:
|
||||
return json.dumps(data, ensure_ascii=False)
|
||||
|
||||
Reference in New Issue
Block a user