lct-07 и lct-19: звонящий на LLM, офлайн-таблица, предгенерация
Что можно сказать, решает слот-автомат, а не модель: в промпт попадают только раскрытые факты. Иначе LLM услужливо назовёт адрес, которого не спрашивали. Промпты лежат файлами — их правит тот, кто ведёт занятия. Отказ провайдера не роняет занятие: звонящий откатывается на заготовки. Молчащий звонящий хуже шаблонной фразы. Офлайн — таблица, а не локальная модель. Ветвление уже делает слот-автомат, поэтому таблица индексируется парой «событие × настроение» и выходит небольшой: для пожарного сценария 62 реплики, около 36 секунд генерации. Формулировки в ней от облачной модели, а задержка на занятии нулевая — локальная 3–4B дала бы формулировки хуже и за 2.5 секунды. Найдено на бесплатной модели OpenRouter: рассуждающие модели тратят сотни токенов на размышление и при малом бюджете возвращают пустой content с finish_reason=length. Пустой ответ теперь честный отказ, бюджет токенов поднят, рассуждение из ответа исключается. Живые запросы к LLM вынесены из общего прогона: они требуют сети, а рассуждающая модель отвечает десятками секунд.
This commit is contained in:
parent
34eb88270e
commit
2ceb26f2cd
19 changed files with 888 additions and 26 deletions
|
|
@ -9,6 +9,7 @@
|
|||
Команды: /подсказка, /факты, /итог, /выход
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
|
@ -16,7 +17,7 @@ ROOT = Path(__file__).resolve().parents[1]
|
|||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from app.config import get_settings # noqa: E402
|
||||
from app.dialog.caller import TemplateCaller # noqa: E402
|
||||
from app.dialog.factory import build_caller # noqa: E402
|
||||
from app.dialog.embeddings import E5Embedder # noqa: E402
|
||||
from app.dialog.persona import PersonaState # noqa: E402
|
||||
from app.dialog.slots import SlotMachine # noqa: E402
|
||||
|
|
@ -35,7 +36,7 @@ def summary(slots: SlotMachine) -> None:
|
|||
print(f" E1 не добыт {fact_id}{hint}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
async def main() -> None:
|
||||
scenario_id = sys.argv[1] if len(sys.argv) > 1 and sys.argv[1] else None
|
||||
try:
|
||||
library = {scenario.id: scenario for scenario in load_library(LIBRARY)}
|
||||
|
|
@ -53,7 +54,7 @@ def main() -> None:
|
|||
|
||||
slots = SlotMachine(scenario, embedder)
|
||||
persona = PersonaState(scenario.persona)
|
||||
caller = TemplateCaller()
|
||||
caller = build_caller(scenario.id)
|
||||
|
||||
print(f"── {scenario.title} ({scenario.level.value}) ──")
|
||||
print("Вы — оператор 112. Команды: /подсказка /факты /итог /выход\n")
|
||||
|
|
@ -82,7 +83,7 @@ def main() -> None:
|
|||
continue
|
||||
|
||||
turn = slots.hear(line)
|
||||
reply = caller.reply(turn, persona, slots)
|
||||
reply = await caller.reply(turn, persona, slots)
|
||||
if turn.matched:
|
||||
print(f" [понято: {', '.join(turn.matched)}]")
|
||||
print(f"ЗВОНЯЩИЙ [{reply.mood.value}]: {reply.text}")
|
||||
|
|
@ -91,4 +92,4 @@ def main() -> None:
|
|||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
asyncio.run(main())
|
||||
|
|
|
|||
Loading…
Reference in a new issue