Skip to content

Muse Spark

Vision extraction with structured output, the decision cards and the listing drafts, the search plan, tool-calling steps for project proposals, and search grounding for prices. Runs at low reasoning effort throughout. No verdict is the model's: the rules decide fit.

Search grounding

Price guidance is one call with the web_search tool and the strict JSON schema of Findings (comparables with their page, the estimate, the basis, caveats):

  • Request. tools: [{"type": "web_search", "search_context_size": PRICE_SEARCH_CONTEXT}], with an approximate user_location (country from the request or PRICE_COUNTRY, city from the request) when one is given, max_tool_calls: PRICE_MAX_SEARCHES (4) with the same bound said in the instructions, since a guidance left to itself ran eleven or twelve searches and read 70,000 to 100,000 input tokens, and include: ["web_search_call.results"] so the pages the search returned come back with the answer.
  • Response. One web_search_call item per search, each with its results (title, URL, snippet), and url_citation annotations on the text. The rules keep a comparable only when its page is among those results or citations: the model proposes, the pages decide.
  • Billing. The API reports no search count, so the client counts the web_search_call items and records them in the ledger at the published per-query rate, on top of the tokens.
  • Operation name price, which is also the recorded fixture that answers in fixture mode: one search, four results, five proposed comparables of which one rests on no result.

The code path is app/value/service.py; the value guide shows what the four figures mean.

Search the web for a part is the same shape: one call with the web_search tool (search_context_size: PRICE_SEARCH_CONTEXT), max_tool_calls: PART_MAX_SEARCHES (4) said in the instructions too, include: ["web_search_call.results"], and the strict JSON schema of WebFinds (each page's URL, title, site, price and dimensions as stated, and a quote). The brief is the search plan's, led by the device the part came from when it is recorded. The same check keeps a page only when it is among the results or the citations (app/model_api/client.py backed_pages). Operation name part-search; its fixture is a stub written by hand (three results on example.org, four proposed pages of which one rests on no result), not a recording. The code path is app/find/service.py (search guide).

Tool calling

A project proposal is a loop of calls with function tools and the strict JSON schema of ProposalSet. The tools are list_inventory (the items a build may count on, with their fields), template_catalog (the projects that may be proposed, with what each requires) and item_details (one item in full, by id). Each step is stateless: the request carries the brief, every output item the model wrote so far and one function_call_output per function_call, matched by call_id, since nothing is stored on the server. The loop ends with the first answer that calls no tool, parsed against the schema, or fails after four steps. The rules then keep only the allocations the inventory backs. Operation names propose-1, propose-2 and so on, one per step, which are also the recorded fixtures: the first step calls two tools, the second answers with three proposals of which one rests on an item nobody has. The code path is app/projects/service.py and the loop itself is respond_with_tools in the client.

Listing text

A pasted listing is read with one call and the strict JSON schema of ListingReading: the title, the asking price and currency, the place and what is included as written, and the fields the text supports with a confidence each. The field names offered are the label fields plus the search profile's rule keys, so a plug diameter a seller wrote can be read; the instructions say to leave out anything the seller did not state. Known fields pass through the same normalisers as a label reading; a rule key the label set does not know is kept as read; any other name is dropped. Operation name listing-text, which is also the recorded fixture. The code path is app/find/service.py.

Search plan

A plan for a missing item is one call (effort low) with the strict JSON schema of PlanWriting: three to six search phrasings with a line each on what they find, one match pattern over listing text and the exclusion words. The brief carries the need's title, the profile, the needed values, the values of the item that needs it, the notes and the top price; the instructions ask for an RE2 pattern with short gaps and classes, no lookarounds, backreferences, comments or inline flags, and never a part number the need does not give. The pattern goes through the same checks as one typed by the owner and is dropped, with the reason, when it fails them. Operation name search-plan, which is also the recorded fixture. The code path is app/find/service.py; the checks are app/find/plan.py.

Decision cards

What should I do with it? is one call (effort low, an answer budget of 1,200 tokens) with the strict JSON schema of ScenariosWriting: a keep and fix card (whether it is worth fixing, the likely problems, the steps, the difficulty for this person and why, the parts, cost and time), two or three reuse cards (each naming the kinds of item it uses from the rest of the inventory) and a sell card (selling points and effort, never a price), with one recommendation and its reason. The brief carries the item's accepted values (none that identifies it), its condition, faults, tests, accessories and intent, the owner's rated skills, tools, effort, whether they open devices up and budget, and the kinds of item they own besides with a count each. Each text is cut to its bound and a reuse card without a title is dropped. Operation name scenarios, which is also the recorded fixture. The code path is app/decide/service.py; see Decide what it becomes.

Listing drafts

A Facebook Marketplace (US) draft is one call (effort low, an answer budget of 1,100 tokens) with the strict JSON schema of DraftWriting: the title, the description, the category, the condition, the tags, what it could be used for (a give-away), the photo order with a reason each, and the facts each line rests on, named by field. The input is the brief (accepted values without any that identifies the item, the public listing details, the mode) and the item's first four photos at 768 px. The rules then check the category and the condition against app/listing/marketplace_us.yaml, set the price, cut the title and the tags, append the disclosures and drop any line that rests on nothing confirmed. Operation names draft-sell and draft-give, which are also the recorded fixtures. The code path is app/listing/service.py; see the seller flow.

Uses

What an item is good for is one call with the strict JSON schema of UsesWriting: up to three uses, each with a title, what it would do, what else it needs, the effort and one thing to check first. The brief carries the item's accepted values (none that identifies it or its owner), its condition, faults, tests and accessories, the owner's preferences and the project templates the rules already found it fills. The answer is shown under Ideas from the model: each text is trimmed and cut to 200 characters, at most six needs and three uses are kept, and a use without a title is dropped. Operation name uses, which is also the recorded fixture: three uses for a laptop. The code path is app/decide/service.py; see Decide what it becomes.

Label extraction

One call per request, however many photos (up to MAX_PHOTOS, default 4):

  • Input. The instructions describe the job (report only what is printed, confidence per field, leave out unsupported fields). The user message carries the field list from app/extract/fields.py and one input_image part per photo. Photos are re-encoded first: EXIF applied and dropped, longest side IMAGE_MAX_PX (default 1280), JPEG. JPEG, PNG, WebP and GIF up to 50 megapixels are accepted; HEIC is refused with a message to export as JPEG.
  • Output. text.format is the strict JSON schema of LabelReading: item_type, label_text (a transcription) and a list of {name, value, unit, confidence}. The reply is validated back into the model; anything else is a 502 for the caller.
  • Effort and size. reasoning.effort is EXTRACT_REASONING_EFFORT (default low), the answer budget is 800 tokens. Reasoning tokens count against max_output_tokens on this API (a read at low spent 798 of 800 on reasoning and stopped before the answer), so the client sends the budget plus a headroom by effort, 4,096 at low, under MODEL_MAX_OUTPUT_TOKENS when set; tokens are billed as used, not as capped. The recorded one-photo call uses 1,480 input and 310 output tokens, about 0.003 USD at the standard rate.
  • Operation name extract, which is also the name of the recorded fixture that answers every extraction in fixture mode.

The code path is app/extract/service.py; the normalisers in app/extract/normalize.py turn the printed values into comparable ones, see the evidence model.

python -m app.extract photo.jpg          # prints the extraction as JSON
./dev shell backend                      # then the same inside the container