Download m3d/modules/intent_planner.py from jkorstad/m3d-core: direct link, hf CLI and curl.
- Browser
- Download file 5.16 kB
-
https://huggingface.co/jkorstad/m3d-core/resolve/main/m3d/modules/intent_planner.py
- Command line
-
hf download hf://jkorstad/m3d-core/m3d/modules/intent_planner.py
-
curl -L -o intent_planner.py https://huggingface.co/jkorstad/m3d-core/resolve/main/m3d/modules/intent_planner.py
5.16 kB
| """std/intent-planner — the agent-class module (L5 intent-driven editing). | |
| Takes a natural-language intent + an asset manifest (.asset.json) and emits an | |
| executable edit DAG scoped to the asset's modalities. The planner is pluggable: | |
| rule_based_planner is the deterministic default (and what tests use); a real LLM | |
| (Hermes harness, any open model) plugs in as llm(ctx) -> plan-dict without any | |
| other change. Emitted plans run directly through the standard DAG runner — | |
| including human-gate nodes for the steps the user hand-edits in Blender etc. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import time | |
| from m3d.module import M3DModule | |
| from m3d.store import Store | |
| MANIFEST = { | |
| "id": "std/intent-planner", | |
| "version": "0.1.0", | |
| "class": "agent", | |
| "modalities": ["intent"], | |
| "runtime": {"type": "local-process", "ref": "python:m3d.modules.intent_planner"}, | |
| "interface": {"tools": ["plan"]}, | |
| "io": { | |
| "inputs": [{"name": "intent", "kind": "text"}, {"name": "manifest_uri", "kind": "uri:asset-manifest"}], | |
| "outputs": [{"name": "plan", "kind": "workflow"}], | |
| }, | |
| "license": {"code": "MIT", "model_licenses": [], "usage_profiles": ["personal", "commercial", "redistribution"]}, | |
| "health": {"probe": "in-process", "fallbacks": []}, | |
| } | |
| # ---------------------------------------------------------------- default LLM | |
| def rule_based_planner(ctx: dict) -> dict: | |
| """Deterministic intent->edit-pathway planner. Maps intent keywords against | |
| the manifest's parts; missing-modality intents raise instead of inventing nodes. | |
| Swap for a real open LLM call without changing the contract: llm(ctx) -> plan.""" | |
| intent: str = ctx["intent"].lower() | |
| parts = {p["role"]: p["uri"] for p in ctx.get("manifest", {}).get("parts", [])} | |
| nodes, edges = [], [] | |
| producers = [] | |
| wants_texture = any(w in intent for w in ("texture", "color", "colour", "material", "paint", "albedo", "tatter")) | |
| wants_mesh = any(w in intent for w in ("sculpt", "reshape", "tatter", "mesh", "geometry", "holes")) | |
| wants_audio = any(w in intent for w in ("sound", "audio", "footstep", "sfx", "music", "voice", "heavier")) | |
| if wants_texture: | |
| if "texture/albedo" not in parts: | |
| raise ValueError("intent needs a texture edit but asset manifest has no texture/albedo part") | |
| nodes.append({ | |
| "id": "tex", | |
| "module": "std/mock-texture", | |
| "tool": "repaint", | |
| "inputs": {"uri": parts["texture/albedo"], "style": "tattered" if "tatter" in intent else "repaint"}, | |
| }) | |
| producers.append("tex") | |
| if wants_mesh: | |
| if "mesh" not in parts: | |
| raise ValueError("intent needs a mesh edit but asset manifest has no mesh part") | |
| nodes.append({ | |
| "id": "mesh-edit", | |
| "type": "human-gate", | |
| "offer": ["approve", "edit-externally", "regenerate"], | |
| "recipe": {"editor": "blender", "hint": f"edit {parts['mesh']} per intent"}, | |
| }) | |
| producers.append("mesh-edit") | |
| if wants_audio: | |
| if "audio" not in parts: | |
| raise ValueError("intent needs an audio edit but asset manifest has no audio part") | |
| nodes.append({ | |
| "id": "sfx", | |
| "module": "std/mock-sfx", | |
| "tool": "process", | |
| "inputs": {"uri": parts["audio"], "intensity": 1.5}, | |
| }) | |
| producers.append("sfx") | |
| if not producers: | |
| raise ValueError(f"no modality matched intent: {intent!r}") | |
| nodes.append({"id": "review", "type": "human-gate", "offer": ["approve", "edit-externally", "regenerate"]}) | |
| nodes.append({ | |
| "id": "post", | |
| "module": "std/gltf-mcp", | |
| "tool": "inspect", | |
| "inputs": {"uri": parts.get("mesh", "asset://missing")}, | |
| }) | |
| for p in producers: | |
| edges.append(f"{p}->review") | |
| edges.append("review[approve]->post") | |
| return { | |
| "id": f"intent-{int(time.time())}", | |
| "version": "0.1.0", | |
| "license": "CC-BY-4.0", | |
| "nodes": nodes, | |
| "edges": edges, | |
| "report": { | |
| "planner": "rule_based_planner", | |
| "modalities_touched": [p.rsplit("-", 1)[0] for p in producers], | |
| "untouched": [r for r in parts if r not in ("texture/albedo", "mesh", "audio")], | |
| }, | |
| } | |
| # ---------------------------------------------------------------- module | |
| def build_module(store: Store, llm=rule_based_planner) -> M3DModule: | |
| m = M3DModule(MANIFEST) | |
| def plan(intent: str, manifest_uri: str) -> dict: | |
| """Turn a natural-language intent + asset manifest into an executable edit DAG.""" | |
| path = store.resolve(manifest_uri) | |
| manifest = json.loads(path.read_text()) | |
| ctx = {"intent": intent, "manifest": manifest} | |
| plan_wf = llm(ctx) | |
| if not isinstance(plan_wf, dict) or "nodes" not in plan_wf or "edges" not in plan_wf: | |
| return {"ok": False, "error": f"planner returned non-workflow: {plan_wf!r}"} | |
| return { | |
| "ok": True, | |
| "plan": plan_wf, | |
| "report": plan_wf.get("report", {}), | |
| } | |
| return m |