feat(llm): horoskop powstaje zawsze — kontynuacja, okna kontekstu, budzet max
PRZYCZYNA PUSTYCH ODPOWIEDZI NA ANTHROPICU (potwierdzona w dokumentacji API): domyslnym modelem byl `claude-sonnet-5`, ktory przy POMINIETYM parametrze `thinking` wlacza myslenie adaptacyjne, a `thinking.display` domyslnie jest "omitted". Tokeny myslenia licza sie do max_tokens, wiec przy LLM_MAX_TOKENS=2000 cala tura wychodzila jako bloki `thinking` z pustym tekstem — parser filtrowal type=="text" i zwracal pusty string. Opus 4.8 bez `thinking` nie mysli, wiec tam objaw by nie wystapil. Gwarancja niepustej odpowiedzi (wszyscy trzej dostawcy): - generate() to teraz PETLA, nie pojedynczy strzal: tura -> jesli urwana na limicie, dopisz ture „kontynuuj" w tej samej rozmowie i sklej tekst, - tura zlozona z samego myslenia traktowana jak urwana (nie jak pustka), - pusta i NIE urwana -> jedna proba z podpowiedzia, dopiero potem blad, - kontynuacja konczy sie tura UZYTKOWNIKA — Claude odrzuca prefill asystenta (400), - `thinking` konfigurowany JAWNIE (adaptive + effort=high; ANTHROPIC_THINKING=off). Okna kontekstu i rezerwa na odpowiedz (app/llm/limits.py): - tabela okien/limitow wyjscia per model + nadpisanie z ENV, - plan() liczy okno odpowiedzi jako okno - prompt - margines i NIGDY nie oddaje calego kontekstu promptowi, - Anthropic liczy tokeny DOKLADNIE (/v1/messages/count_tokens), reszta szacuje, - >90 tys. tokenow promptu -> ostrzezenie, ale wyslanie NADAL mozliwe i z pelnym oknem odpowiedzi. UI: suwak budzetu rozszerzony o „bardzo obszerny" i „maksymalny kontekst modelu" (liczony z okna wybranego modelu po odjeciu rezerwy); przy wyniku widac plan tokenow, liczbe tur i ostrzezenia. Domyslny model Anthropic: claude-opus-4-8. Testy: 170 passed / 1 skipped (logika) + 15 (prezentacja). Nowe testy pokrywaja sklejanie kontynuacji, brak prefillu asystenta, ture z samego myslenia, rezerwe na odpowiedz i prog ostrzezenia. Zweryfikowane e2e na atrapie Anthropica odtwarzajacej zgloszony objaw: 3 tury, obie czesci tekstu obecne. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -28,8 +28,14 @@ BUDGETS: dict[str, int] = {
|
||||
"concise": 4_000,
|
||||
"medium": 12_000,
|
||||
"extensive": 30_000,
|
||||
"huge": 120_000,
|
||||
# „maksymalny kontekst modelu" — wyliczany dynamicznie z okna kontekstu
|
||||
# wybranego modelu, ZAWSZE po odjęciu miejsca zarezerwowanego na odpowiedź.
|
||||
# Wartość poniżej jest tylko zapasem, gdy limity modelu są nieznane.
|
||||
"max": 400_000,
|
||||
}
|
||||
DEFAULT_BUDGET = "medium"
|
||||
MAX_BUDGET = "max"
|
||||
|
||||
MAX_EFFECT_CHARS = 320 # dłuższe opisy skracamy (krok 5 redukcji)
|
||||
CHARS_PER_TOKEN = 4.0 # zgrubny szacunek tokenów do podglądu w UI
|
||||
@@ -284,11 +290,16 @@ def _assemble(task: str, chart_sec: str, ind_sec: str, output: str) -> str:
|
||||
return "\n\n".join([task, chart_sec, ind_sec, output, f"# ZASTRZEŻENIE\n{DISCLAIMER}"])
|
||||
|
||||
|
||||
def _budget_chars(budget: str) -> int:
|
||||
def _budget_chars(budget: str, budget_chars: int | None = None) -> int:
|
||||
"""Limit znaków promptu. `budget_chars` nadpisuje tabelę — używane dla opcji
|
||||
„maksymalny kontekst modelu", gdzie limit zależy od wybranego modelu i musi
|
||||
być policzony po odjęciu miejsca zarezerwowanego na odpowiedź."""
|
||||
if budget not in BUDGETS:
|
||||
raise ValueError(
|
||||
f"Nieznany budżet: {budget!r} (dostępne: {', '.join(BUDGETS)})"
|
||||
)
|
||||
if budget_chars and budget_chars > 0:
|
||||
return budget_chars
|
||||
return BUDGETS[budget]
|
||||
|
||||
|
||||
@@ -305,9 +316,10 @@ def _finish(prompt: str, budget: str, limit: int, stats: dict, profile: str) ->
|
||||
|
||||
|
||||
def build_natal_prompt(chart: dict, report: dict, budget: str = DEFAULT_BUDGET,
|
||||
moment_label: str | None = None) -> dict:
|
||||
moment_label: str | None = None,
|
||||
budget_chars: int | None = None) -> dict:
|
||||
"""Prompt na horoskop urodzeniowy (ekran „Interpretacje")."""
|
||||
limit = _budget_chars(budget)
|
||||
limit = _budget_chars(budget, budget_chars)
|
||||
chart_sec = _chart_section(chart, moment_label)
|
||||
fixed = len(_NATAL_TASK) + len(chart_sec) + len(_NATAL_OUTPUT) + len(DISCLAIMER) + 200
|
||||
chosen, stats = reduce_indications(natal_indications(report), max(limit - fixed, 500))
|
||||
@@ -316,9 +328,10 @@ def build_natal_prompt(chart: dict, report: dict, budget: str = DEFAULT_BUDGET,
|
||||
|
||||
|
||||
def build_period_prompt(chart: dict, events: list[dict], from_date: str, to_date: str,
|
||||
budget: str = DEFAULT_BUDGET, moment_label: str | None = None) -> dict:
|
||||
budget: str = DEFAULT_BUDGET, moment_label: str | None = None,
|
||||
budget_chars: int | None = None) -> dict:
|
||||
"""Prompt na horoskop okresowy (ekran „Kalendarz")."""
|
||||
limit = _budget_chars(budget)
|
||||
limit = _budget_chars(budget, budget_chars)
|
||||
chart_sec = _chart_section(chart, moment_label)
|
||||
task = f"{_PERIOD_TASK}\nZakres prognozy: **{from_date} — {to_date}**."
|
||||
|
||||
|
||||
Reference in New Issue
Block a user