feat: cap narrative response length + tune sampling
Bot replies were running long (4 paragraphs of action+dialogue beats per turn) because we never set max_tokens on the narrative call. Three tunable knobs now in Settings (set in data/config.toml to override): - narrative_max_tokens: int = 400 Hard cap on each generated response. ~400 tokens ≈ 1–2 short paragraphs. Drop to 200 for terse banter, bump to 800+ for longer scenes. - narrative_temperature: float = 0.85 Sampling temperature. 0.7 = grounded/consistent (slightly stiff), 0.85 = creative-but-in-character (default), 1.0 = wide variety, >1.0 = often off-the-rails. - prompt closing instruction now nudges: "Keep your response to a single beat — one or two short paragraphs at most. Don't monologue; leave room for the other person to react." Both turns.py (post_turn) and regenerate.py forward the params to client.stream(). FeatherlessClient already passes **params through to the OpenAI-compat endpoint. Note: temperature doesn't control length — that was a common misconception. max_tokens is the actual length cap. Lower temperature makes word choice more predictable (slightly stiffer voice), not shorter. Both knobs are useful for different goals.
This commit is contained in:
@@ -23,6 +23,13 @@ class Settings(BaseModel):
|
||||
retrieval_k: int = 4
|
||||
narrative_budget_hard: int = 8000
|
||||
narrative_budget_soft: int = 6000
|
||||
# Cap on each generated bot response. ~400 tokens ≈ 1–2 short paragraphs.
|
||||
# Bump if you want longer scenes; drop to 200 for terse banter.
|
||||
narrative_max_tokens: int = 400
|
||||
# Sampling temperature for narrative generation. 0.7 = grounded /
|
||||
# consistent; 0.85 = creative-but-in-character (default); 1.0 = wide
|
||||
# variety, can drift; >1.0 = often off-the-rails.
|
||||
narrative_temperature: float = 0.85
|
||||
classifier_budget_hard: int = 4000
|
||||
classifier_timeout_s: float = 30.0
|
||||
# Featherless free tier and lower paid tiers cap concurrent connections.
|
||||
|
||||
@@ -211,7 +211,9 @@ def _closing_instruction(speaker_name: str, addressee_name: str) -> str:
|
||||
f"Continue the scene as {speaker_name}, in their voice, responding "
|
||||
"naturally. Use *asterisks* for actions and quotes for dialogue. "
|
||||
f"Stay in character. Do not narrate {addressee_name}'s actions or "
|
||||
"thoughts."
|
||||
"thoughts. "
|
||||
"Keep your response to a single beat — one or two short paragraphs "
|
||||
"at most. Don't monologue; leave room for the other person to react."
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -156,7 +156,10 @@ async def regenerate_assistant_turn(
|
||||
# 5. Stream the new narrative.
|
||||
accumulated: list[str] = []
|
||||
async for chunk in client.stream(
|
||||
messages, model=settings.narrative_model
|
||||
messages,
|
||||
model=settings.narrative_model,
|
||||
max_tokens=settings.narrative_max_tokens,
|
||||
temperature=settings.narrative_temperature,
|
||||
):
|
||||
accumulated.append(chunk)
|
||||
await publish(
|
||||
|
||||
+4
-1
@@ -198,7 +198,10 @@ async def post_turn(
|
||||
|
||||
async def _stream() -> None:
|
||||
async for chunk in client.stream(
|
||||
messages, model=settings.narrative_model
|
||||
messages,
|
||||
model=settings.narrative_model,
|
||||
max_tokens=settings.narrative_max_tokens,
|
||||
temperature=settings.narrative_temperature,
|
||||
):
|
||||
accumulated.append(chunk)
|
||||
await publish(
|
||||
|
||||
Reference in New Issue
Block a user