wahnsinn vibe
This commit is contained in:
113
backend/apps/ai_admin/planner.py
Normal file
113
backend/apps/ai_admin/planner.py
Normal file
@@ -0,0 +1,113 @@
|
||||
"""Build a structured action plan from a natural-language prompt (or JSON bulk)."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
from core.db import SessionLocal
|
||||
|
||||
from apps.ai_core.ollama_client import get_llm
|
||||
from apps.ai_core.tools import describe_for_prompt, get_tool, validate_args
|
||||
from apps.catalog.models import Category, Product
|
||||
|
||||
|
||||
SYSTEM_PROMPT = """You are an admin assistant for an e-commerce shop.
|
||||
You help the operator perform tasks by producing a STRUCTURED PLAN of tool calls.
|
||||
You MUST NEVER execute anything. You only propose cards that the operator confirms.
|
||||
|
||||
Output format (STRICT):
|
||||
Reply with ONLY a JSON object of the shape {"cards": [ ... ]}.
|
||||
Each card: {"tool": "<tool-name>", "args": {...}, "missing": [], "preview": "German summary", "notes": ""}.
|
||||
|
||||
Rules:
|
||||
- Only use tools from the provided TOOL CATALOG. If no tool applies, return {"cards": []}.
|
||||
- NEVER emit a single card that aggregates multiple items.
|
||||
If the user provides JSON with multiple items (bulk) → produce ONE card per item.
|
||||
If the user asks to change something about ALL or MULTIPLE existing products/categories
|
||||
→ produce ONE card per matching item from the SHOP STATE snapshot, with its exact id.
|
||||
Examples:
|
||||
"setze alle preise auf 1" on 3 products → 3 cards, each {"tool":"catalog.product.update","args":{"id":<real id>,"price":1},...}
|
||||
NOT one card with id=null or "all".
|
||||
- Numbers must be numbers in JSON (not strings). Omit optional fields instead of sending null.
|
||||
- Stay concise in "preview".
|
||||
"""
|
||||
|
||||
|
||||
def _shop_state_snapshot() -> dict:
|
||||
"""Compact snapshot of current shop state for the planner. Keep it small."""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
products = [
|
||||
{"id": p.id, "sku": p.sku, "name_de": (p.name or {}).get("de", ""), "price": float(p.price)}
|
||||
for p in db.query(Product).order_by(Product.id).all()
|
||||
]
|
||||
categories = [
|
||||
{"id": c.id, "slug": c.slug, "name_de": (c.name or {}).get("de", "")}
|
||||
for c in db.query(Category).order_by(Category.id).all()
|
||||
]
|
||||
return {"products": products, "categories": categories}
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def build_plan(user_prompt: str) -> list[dict]:
|
||||
tools = describe_for_prompt(role="admin")
|
||||
state = _shop_state_snapshot()
|
||||
user_msg = (
|
||||
f"TOOL CATALOG (JSON):\n{json.dumps(tools, ensure_ascii=False)}\n\n"
|
||||
f"SHOP STATE (current products & categories):\n{json.dumps(state, ensure_ascii=False)}\n\n"
|
||||
f"USER REQUEST:\n{user_prompt}\n\n"
|
||||
"Reply with ONLY the JSON object described in the rules."
|
||||
)
|
||||
try:
|
||||
result = get_llm().chat_json(SYSTEM_PROMPT, user_msg)
|
||||
except Exception as e: # noqa: BLE001
|
||||
return [
|
||||
{
|
||||
"tool": "_error",
|
||||
"args": {},
|
||||
"missing": [],
|
||||
"preview": f"Planer-Fehler: {e}",
|
||||
"notes": "LLM antwortete nicht verwertbar. Prompt umformulieren.",
|
||||
}
|
||||
]
|
||||
|
||||
if isinstance(result, list):
|
||||
cards = result
|
||||
elif isinstance(result, dict) and isinstance(result.get("cards"), list):
|
||||
cards = result["cards"]
|
||||
else:
|
||||
cards = []
|
||||
|
||||
# Validate and annotate
|
||||
clean: list[dict] = []
|
||||
for card in cards:
|
||||
if not isinstance(card, dict):
|
||||
continue
|
||||
name = card.get("tool", "")
|
||||
args = card.get("args") or {}
|
||||
spec = get_tool(name)
|
||||
if spec:
|
||||
# Trust only server-side required-field validation, not LLM-supplied missing
|
||||
missing = validate_args(spec, args)
|
||||
clean.append(
|
||||
{
|
||||
"tool": name,
|
||||
"args": args,
|
||||
"missing": missing,
|
||||
"preview": card.get("preview", ""),
|
||||
"notes": card.get("notes", ""),
|
||||
"schema": spec.args_schema,
|
||||
}
|
||||
)
|
||||
else:
|
||||
clean.append(
|
||||
{
|
||||
"tool": name,
|
||||
"args": args,
|
||||
"missing": list(card.get("missing") or []),
|
||||
"preview": card.get("preview", f"Unbekanntes Tool: {name}"),
|
||||
"notes": "tool not in catalog",
|
||||
"schema": {"type": "object", "properties": {}},
|
||||
}
|
||||
)
|
||||
return clean
|
||||
Reference in New Issue
Block a user