Spaces:
Running on Zero
Running on Zero
File size: 4,619 Bytes
777ea0e 0298f08 777ea0e 0298f08 777ea0e 0298f08 777ea0e 0298f08 777ea0e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | """Prompt assembly: system instruction + retrieved context -> chat messages."""
from __future__ import annotations
from rag import Hit
SYSTEM_PROMPT = (
"You are an expert Godot 4 GDScript assistant. Answer using the reference "
"snippets provided below when they are relevant. Always write GDScript that "
"targets Godot 4 (GDScript 2.0). Put runnable code in ```gdscript fenced "
"blocks. Prefer static typing and @export/@onready annotations where natural. "
"If the snippets don't cover the question, answer from general Godot knowledge "
"and say so briefly. Be concise."
)
# Keep the context budget modest so generation stays fast on ZeroGPU.
MAX_CONTEXT_CHARS = 5000 # trimmed a bit since history now shares the prompt
# Multi-turn memory bounds (keep prompts — and ZeroGPU time — bounded).
MAX_HISTORY_TURNS = 4 # default # of prior user+assistant exchanges kept
MAX_HISTORY_CHARS = 6000 # hard cap on total history text fed to the model
# Marker that app.py appends after the model's answer; everything from here on is
# our validation/sources/notes decoration and must be stripped before the answer
# is fed back as conversation history.
VALIDATION_DELIM = "\n\n---\n**Validation:**"
def _format_context(hits: list[Hit]) -> str:
blocks, used = [], 0
for i, h in enumerate(hits, 1):
src = h.repo or h.origin_url or "corpus"
snippet = h.text.strip()
block = f"# Snippet {i} (source: {src})\n{snippet}"
if used + len(block) > MAX_CONTEXT_CHARS:
break
blocks.append(block)
used += len(block)
return "\n\n".join(blocks)
def _clean_assistant(content: str) -> str:
"""Strip our validation/sources/notes so only the model's answer remains."""
return content.split(VALIDATION_DELIM, 1)[0].rstrip()
def _normalize(history) -> list[dict]:
"""Flatten gradio history (messages OR tuples format) to role/content dicts."""
out: list[dict] = []
for item in history or []:
if isinstance(item, dict) and "role" in item:
role, content = item.get("role"), item.get("content", "")
if not isinstance(content, str): # skip file/component messages
continue
if role == "assistant":
content = _clean_assistant(content)
if content.strip():
out.append({"role": role, "content": content})
elif isinstance(item, (list, tuple)) and len(item) == 2:
u, a = item
if isinstance(u, str) and u.strip():
out.append({"role": "user", "content": u})
if isinstance(a, str) and a.strip():
out.append({"role": "assistant", "content": _clean_assistant(a)})
return out
def _prepare_history(history, max_turns: int) -> list[dict]:
"""Last `max_turns` exchanges, sanitized and char-bounded, starting on a user turn."""
if max_turns <= 0: # 0 = single-turn ([-0:] would keep ALL)
return []
msgs = _normalize(history)[-2 * max_turns:]
# Apply the char budget from the most recent message backwards.
total, bounded = 0, []
for m in reversed(msgs):
total += len(m["content"])
if bounded and total > MAX_HISTORY_CHARS:
break
bounded.append(m)
bounded.reverse()
while bounded and bounded[0]["role"] != "user": # don't start on an assistant turn
bounded.pop(0)
return bounded
def build_messages(question: str, hits: list[Hit],
history=None, max_turns: int = MAX_HISTORY_TURNS) -> list[dict]:
"""Build chat-template messages: system + bounded history + context+question."""
context = _format_context(hits)
messages: list[dict] = [{"role": "system", "content": SYSTEM_PROMPT}]
messages.extend(_prepare_history(history, max_turns))
user = question if not context else (
f"Reference GDScript snippets from a curated Godot corpus:\n\n"
f"{context}\n\n---\n\nQuestion: {question}"
)
messages.append({"role": "user", "content": user})
return messages
def build_fix_messages(broken_code: str, error: str) -> list[dict]:
"""Messages asking the model to fix a GDScript snippet that failed to parse."""
return [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": (
"The following GDScript failed to parse with this error:\n"
f"{error}\n\nFix it and return ONLY the corrected GDScript in a "
f"```gdscript block:\n\n```gdscript\n{broken_code}\n```"
)},
]
|