# Quote from a price list

## Requirement

"Give a quote from our price list using our discount formula."

## Pieces

- **Custom data**: a collection `prices`, one record per item.
- **Custom tool**: `quote`, the discount formula, handed the price record and
  the order and answering the total.
- **Flow**: query the price, then quote.
- **The flow as a tool**, attached to a node, on a published agent.

## The shape to avoid

The prices and the formula in the prompt. The model does arithmetic badly and
inconsistently, the prices go stale, and nobody can see which price a quote
was made from. Here the price is a record, the formula is code that runs the
same way every time, and the flow ties the two together.

## Steps

Ids from an earlier step are written as `{{step_N.field}}`; the test that runs
this page substitutes them.

1. The price list.

   `create_collection`

   ```json
   {
     "name": "Prices",
     "slug": "prices",
     "fields": [
       {"key": "sku", "label": "SKU", "type": "text", "required": true},
       {"key": "item", "label": "Item", "type": "text", "required": true},
       {"key": "unit_price", "label": "Unit price", "type": "number", "required": true}
     ]
   }
   ```

2. A price.

   `create_record` (`collection_id` = `{{step_1.id}}`)

   ```json
   {"values": {"sku": "CHAIR-01", "item": "Office chair", "unit_price": 120}}
   ```

3. Another.

   `create_record` (`collection_id` = `{{step_1.id}}`)

   ```json
   {"values": {"sku": "DESK-01", "item": "Standing desk", "unit_price": 450}}
   ```

4. The formula. It is handed the matching price records, the quantity and the
   customer type, and answers a value. `tool_id` in the response is what the
   flow step names.

   `create_custom_tool`

   ```json
   {
     "name": "quote",
     "description": "The total for a quantity of one item, with the business discount applied.",
     "purpose": "Apply the discount formula: businesses ordering ten or more get 10% off; everyone else pays the list price.",
     "source_code": "def transform(arguments):\n    prices = arguments.get(\"prices\") or []\n    if not prices:\n        return {\"found\": False}\n    unit = float(prices[0][\"unit_price\"])\n    quantity = int(arguments.get(\"quantity\") or 1)\n    business = arguments.get(\"customer_type\") == \"business\"\n    discount = 0.10 if business and quantity >= 10 else 0.0\n    total = unit * quantity * (1.0 - discount)\n    return {\"found\": True, \"item\": prices[0][\"item\"], \"unit_price\": unit, \"quantity\": quantity, \"discount\": discount, \"total\": total}\n",
     "input_schema": {
       "type": "object",
       "properties": {
         "prices": {"type": "array", "description": "The matching records of prices."},
         "quantity": {"type": "integer"},
         "customer_type": {"type": "string", "enum": ["private", "business"]}
       },
       "required": ["prices", "quantity", "customer_type"]
     },
     "output_schema": {
       "type": "object",
       "properties": {
         "found": {"type": "boolean"},
         "item": {"type": "string"},
         "unit_price": {"type": "number"},
         "quantity": {"type": "integer"},
         "discount": {"type": "number"},
         "total": {"type": "number"}
       }
     },
     "declared_reach": []
   }
   ```

5. Publish it.

   `publish_custom_tool` (`custom_tool_id` = `{{step_4.id}}`)

   ```json
   {}
   ```

6. The flow.

   `create_flow`

   ```json
   {"name": "quote_from_price_list"}
   ```

7. Entry step: the price for the requested SKU, with the collection's query
   tool. The flow's inputs are in scope as `$ref:trigger.<key>`.

   `add_step` (`flow_id` = `{{step_6.id}}`)

   ```json
   {"tool_id": "{{step_1.query_tool_id}}", "param_overrides": {"sku": "$ref:trigger.sku", "limit": 1}}
   ```

8. Then the formula, over the records the query answered.

   `add_step` (`flow_id` = `{{step_6.id}}`)

   ```json
   {
     "tool_id": "{{step_4.tool_id}}",
     "param_overrides": {
       "prices": "$ref:{{step_7.id}}.records",
       "quantity": "$ref:trigger.quantity",
       "customer_type": "$ref:trigger.customer_type"
     }
   }
   ```

9. Query, then quote. The flow's result is the last step's output: the quote.

   `add_flow_edge` (`flow_id` = `{{step_6.id}}`)

   ```json
   {"source_step_id": "{{step_7.id}}", "target_step_id": "{{step_8.id}}"}
   ```

10. The flow as one tool.

    `create_tool`

    ```json
    {
      "name": "quote",
      "description": "A quote for a quantity of one item, discount applied.",
      "source": "builtin",
      "config": {"flow_id": "{{step_6.id}}"},
      "input_schema": {
        "type": "object",
        "properties": {
          "sku": {"type": "string"},
          "quantity": {"type": "integer"},
          "customer_type": {"type": "string", "enum": ["private", "business"]}
        },
        "required": ["sku", "quantity", "customer_type"]
      }
    }
    ```

11. An agent publishes only with an AI profile.

    `create_llm_settings`

    ```json
    {"name": "fast", "provider": "openai", "model": "gpt-4o-mini"}
    ```

12. The profile.

    `create_profile`

    ```json
    {"name": "default", "text": {"llm_settings_id": "{{step_11.id}}"}}
    ```

13. The agent.

    `create_agent`

    ```json
    {"name": "Sales"}
    ```

14. Its draft.

    `get_draft` (`agent_id` = `{{step_13.id}}`)

    ```json
    {}
    ```

15. The node, with the quote tool.

    `add_node` (`graph_id` = `{{step_14.graph_id}}`)

    ```json
    {
      "instructions": "You quote prices. Ask which item, how many, and whether the caller is a business or a private customer, then call quote and read back the total. Never compute a price yourself.",
      "tool_ids": ["{{step_10.id}}"]
    }
    ```

16. Named as the root.

    `set_root` (`graph_id` = `{{step_14.graph_id}}`, `node_id` = `{{step_15.id}}`)

    ```json
    {}
    ```

17. The profile on the draft.

    `edit_draft` (`version_id` = `{{step_14.id}}`)

    ```json
    {"ai_profile_id": "{{step_12.id}}"}
    ```

18. Published.

    `publish_version` (`version_id` = `{{step_14.id}}`)

    ```json
    {}
    ```

## What the agent sees

One tool, `quote`, with `sku`, `quantity` and `customer_type`, answering the
custom tool's result under `result`: the item, the unit price, the discount
and the total. The price list and the formula are invisible from the
conversation.

## Variations

- **The formula changes**: edit the custom tool's draft
  (`edit_custom_tool_draft`) and publish again; the flow, the tool and the
  agent are untouched.
- **A price changes**: `update_record`; nothing is republished.
- **The item is named, not coded**: query on `item` instead of `sku`, and let
  the node instructions ask the model to pick the SKU from `query_prices`
  first.
