76 lines
3.5 KiB
Python
76 lines
3.5 KiB
Python
"""Plan der KI je Chat (→ große Aufgaben, Plan-Modus).
|
|
|
|
update_plan ersetzt die Todo-Liste des Chats; sie wirkt nur in der Plattform
|
|
(write_internal). Im Planungsmodus bekommt die KI nur lesende Tools und dieses
|
|
Tool, bis ein Mensch „Plan umsetzen“ klickt. Der Plan geht in jeden Prompt, in
|
|
die Arbeitsnotiz und in die Beschreibung des Pull Requests.
|
|
"""
|
|
|
|
from typing import Literal
|
|
|
|
from pydantic import Field
|
|
|
|
from app.chat.models import ChatSession
|
|
from app.core.errors import Invalid
|
|
from app.security import untrusted
|
|
from app.tools.context import ToolContext
|
|
from app.tools.registry import Args, Tool, register
|
|
|
|
NAME = "update_plan"
|
|
MARKS = {"done": "[x]", "in_progress": "[~]", "open": "[ ]"}
|
|
EXECUTE_MESSAGE = ("Bitte setze den Plan jetzt um. Arbeite die offenen Punkte der Reihe nach ab und halte den "
|
|
"Plan mit update_plan aktuell.")
|
|
|
|
|
|
class Step(Args):
|
|
text: str = Field(min_length=3, max_length=300, description="Konkreter Schritt, gern mit betroffenen Dateien.")
|
|
status: Literal["open", "in_progress", "done"] = Field("open", description="open, in_progress oder done.")
|
|
|
|
|
|
class UpdatePlanArgs(Args):
|
|
steps: list[Step] = Field(min_length=1, max_length=30,
|
|
description="Der vollständige Plan in Reihenfolge; ersetzt den bisherigen.")
|
|
|
|
|
|
def update_plan(context: ToolContext, args: UpdatePlanArgs) -> str:
|
|
if context.db is None or context.chat_session_id is None:
|
|
raise Invalid("Ein Plan ist nur in einem Chat möglich.")
|
|
steps = [{"text": " ".join(s.text.split()), "status": s.status} for s in args.steps]
|
|
with context.db.session() as session:
|
|
session.get(ChatSession, context.chat_session_id).plan = steps
|
|
counts = {k: sum(s["status"] == k for s in steps) for k in MARKS}
|
|
return (f"Plan gespeichert: {counts['done']} erledigt, {counts['in_progress']} in Arbeit, {counts['open']} offen. "
|
|
"Er ist im Chat sichtbar.")
|
|
|
|
|
|
register(Tool(
|
|
NAME, "git",
|
|
"Plan für eine größere Aufgabe anlegen oder aktualisieren: eine Liste konkreter Schritte mit Status. "
|
|
"Immer den vollständigen Plan senden; erledigte Schritte als done markieren, den aktuellen als in_progress.",
|
|
UpdatePlanArgs, "write_internal", run=update_plan, untrusted=False))
|
|
|
|
|
|
def as_text(plan: list[dict]) -> str:
|
|
return "\n".join(f"- {MARKS.get(s.get('status'), '[ ]')} {s.get('text', '')}" for s in plan)
|
|
|
|
|
|
def prompt_text(plan: list[dict]) -> str:
|
|
"""Plan im Prompt: vom Modell verfasst, also eingefasst und entschärft."""
|
|
return f"<plan_schritte>\n{untrusted.neutralize(as_text(plan))}\n</plan_schritte>"
|
|
|
|
|
|
def prompt_note(plan: list[dict], plan_mode: bool, offered: bool) -> str:
|
|
if not offered:
|
|
return ""
|
|
if plan_mode:
|
|
note = ("\n\nPLANUNGSMODUS: Ändere nichts. Untersuche die Aufgabe mit den lesenden Tools und lege dann mit "
|
|
"update_plan einen Plan aus konkreten Schritten an (betroffene Dateien, Reihenfolge, offene Fragen). "
|
|
"Fasse den Plan in der Antwort kurz zusammen. Umgesetzt wird erst, wenn der Nutzer „Plan umsetzen“ "
|
|
"klickt; bis dahin kannst du den Plan auf Rückfragen hin anpassen.")
|
|
else:
|
|
note = ("\n\nBei größeren Aufgaben (mehrere Dateien oder Schritte) lege zuerst mit update_plan einen Plan an "
|
|
"und halte ihn aktuell.")
|
|
if plan:
|
|
note += ("\nAktueller Plan dieses Chats ([x] erledigt, [~] in Arbeit, [ ] offen); setze beim nächsten "
|
|
f"offenen Punkt an:\n{prompt_text(plan)}")
|
|
return note
|