Aarya2004
Deploy: sync hosted Space to local app (chat, document capture, Modal backends, pages, mobile/QR)
47b2a99 | """The LLM-driven brain loop: the model decides which items to add and when done. | |
| Pure and Ollama-free for testing — takes any model with a .chat(messages, tools) | |
| method (real OllamaModel or a scripted StubModel). Pricing/math stay in the | |
| deterministic tools (Facts-from-Tools). A missing price is RETURNED as a pause | |
| signal; the graph node turns that into a LangGraph interrupt. | |
| """ | |
| from quillwright.brain_tools import BRAIN_TOOLS, dispatch | |
| from quillwright.catalog import Catalog | |
| from quillwright.models import LineItem, TraceStep | |
| SYSTEM = ( | |
| "You are a field-service estimator. You are given a tech's note about a job. Add EACH " | |
| "distinct part and the labor to the estimate by calling add_priced_item(item, quantity). " | |
| "Rules:\n" | |
| "- Call add_priced_item ONCE per distinct item. Do not repeat an item.\n" | |
| "- quantity = how many units or hours. Read it from the note: 'two hours'/'2 hrs' -> 2, " | |
| "'both'/'a pair' -> 2, '4 pounds' -> 4. If no count is stated, use 1.\n" | |
| "- Labor is an item too: add it with the number of hours as the quantity.\n" | |
| "- Never invent prices — the tool applies the catalog price.\n" | |
| "- When every part and the labor have been added, call finish().\n" | |
| "Examples:\n" | |
| "Note: 'replaced the capacitor and did 2 hours labor' -> add_priced_item('capacitor', 1), " | |
| "add_priced_item('labor', 2), finish()\n" | |
| "Note: 'replaced both contactors' -> add_priced_item('contactor', 2), finish()\n" | |
| "Note: 'added 4 lbs refrigerant' -> add_priced_item('refrigerant', 4), finish()" | |
| ) | |
| def run_brain( | |
| model, | |
| catalog: Catalog, | |
| observations_text: str, | |
| transcript: str, | |
| max_steps: int = 12, | |
| ): | |
| """Drive the model to build line items. Returns (line_items, trace, pause-or-None).""" | |
| line_items: list[LineItem] = [] | |
| trace: list[TraceStep] = [] | |
| # The actual model name (e.g. "nemotron-3-nano:4b" or "StubModel") so the trace | |
| # truthfully shows which model answered — no guessing whether a model was hit. | |
| brain_name = getattr(model, "name", "brain") | |
| messages = [ | |
| {"role": "system", "content": SYSTEM}, | |
| { | |
| "role": "user", | |
| "content": f"Observed items: {observations_text}\nTech's note: {transcript}", | |
| }, | |
| ] | |
| for _ in range(max_steps): | |
| msg = model.chat(messages, BRAIN_TOOLS) | |
| tool_calls = msg.get("tool_calls") or [] | |
| if not tool_calls: | |
| break # model produced plain text -> treat as done | |
| messages.append(msg) | |
| done = False | |
| for call in tool_calls: | |
| fn = call.get("function", {}) | |
| name = fn.get("name", "") | |
| args = fn.get("arguments", {}) or {} | |
| result = dispatch(name, args, catalog) | |
| if result["status"] == "need_price": | |
| # Surface a pause for the graph to turn into an interrupt. | |
| return line_items, trace, {"item": result["item"]} | |
| if result["status"] == "done": | |
| done = True | |
| trace.append( | |
| TraceStep(action="finish", model=brain_name, detail="estimate complete") | |
| ) | |
| break | |
| if result["status"] == "added": | |
| line_items.append(result["line_item"]) | |
| li = result["line_item"] | |
| trace.append( | |
| TraceStep( | |
| action="add_priced_item", | |
| model=brain_name, | |
| detail=f"{li.quantity:g} x {li.description} -> {li.subtotal}", | |
| ) | |
| ) | |
| _tool_reply(messages, call, f"added {result['line_item'].description}") | |
| else: # unknown tool -> corrective message (validate/repair) | |
| _tool_reply(messages, call, f"error: unknown tool '{name}'") | |
| if done: | |
| break | |
| return line_items, trace, None | |
| def _tool_reply(messages: list[dict], call: dict, content: str) -> None: | |
| messages.append({"role": "tool", "content": content}) | |