# pyquickbase API reference: Tools for models This describes version 1.0.0rc13. Other pages: [Quickbase](https://matthew-williams-solutions.github.io/pyquickbase/reference/quickbase.txt), [App](https://matthew-williams-solutions.github.io/pyquickbase/reference/app.txt), [Table](https://matthew-williams-solutions.github.io/pyquickbase/reference/table.txt), [Report](https://matthew-williams-solutions.github.io/pyquickbase/reference/report.txt), [Operation](https://matthew-williams-solutions.github.io/pyquickbase/reference/operation.txt), [SchemaOperation](https://matthew-williams-solutions.github.io/pyquickbase/reference/schema-operation.txt), [Queries](https://matthew-williams-solutions.github.io/pyquickbase/reference/queries.txt), [Records and results](https://matthew-williams-solutions.github.io/pyquickbase/reference/records.txt), [Metadata](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata.txt), [Schema changes](https://matthew-williams-solutions.github.io/pyquickbase/reference/schema-changes.txt), [Policies](https://matthew-williams-solutions.github.io/pyquickbase/reference/policies.txt), [Rules](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules.txt), [Retries](https://matthew-williams-solutions.github.io/pyquickbase/reference/retries.txt), [Errors](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors.txt). These turn results and errors into plain data for the result of a model's tool call, in the same shapes and value formats whatever the call. They work with any tool-calling framework, as `json.dumps()` always accepts what they return. [`Table.operation()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#operation) makes the tools themselves. Each example makes its own client from the environment, with `bqtasks123` standing for the ID of the `Job Tasks` table. `Job Tasks` has `Title`, `Status`, `Hours`, `Due Date`, `Code`, which is unique, and `Attachment`. It starts with three records, and each example starts from those again. A live test runs every example with both clients and checks its output. ## Results For the full picture, see [Using pyquickbase from an AI agent](https://matthew-williams-solutions.github.io/pyquickbase/agents/). ### `tool_result` Describe a result for a model, as the result of its tool call. ```python page = await tasks.query_page(limit=50) return tool_result(page, compact=True) ``` The result is `{"ok": true, ...}`, which `json.dumps()` always accepts, with a key for what it holds: | Value | Keys | |---|---| | [`Page`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#Page) | `records`, or `columns` and `rows` with `compact`, and `page` | | A list of records or dictionaries | The same. `page.total` is null, as it isn't known | | [`Record`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#Record) or None | `record`, which is null for None | | [`WriteResult`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#WriteResult) | `created_ids`, `updated_ids`, and `unchanged_ids` | | A list of [`SummaryRow`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#SummaryRow) | `groups`, each with `group`, `count`, and its totals, and `page` | | A `summarize` operation's result | As for a list of [`SummaryRow`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#SummaryRow), sorted and paged as asked | | [`UpdatePreview`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#UpdatePreview) | `changes`, `unchanged_ids`, and `missing_ids` | | A `preview_update` operation's result | As for an [`UpdatePreview`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#UpdatePreview), with the approval keys | | A `preview_delete` operation's result | As for a [`Page`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#Page), with the approval keys | | [`SchemaPlan`](https://matthew-williams-solutions.github.io/pyquickbase/reference/schema-changes/#SchemaPlan) | `changes` and `has_changes`, and the approval keys | | [`SchemaChangeResult`](https://matthew-williams-solutions.github.io/pyquickbase/reference/schema-changes/#SchemaChangeResult) | `applied` and `already_done`, and `failed` and `not_attempted` too | | A list of [`TableSchema`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#TableSchema) | [`tables`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#tables), each with [`fields`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#fields) and `relationships` | | [`FormulaResult`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#FormulaResult) | `value` and `type` | | A `count` operation's result | `count` | | A `delete` operation's result | `deleted_count` | | Text, a number, a date, or a bool | `value` | | [`TableInfo`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#TableInfo), [`Field`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#Field), or [`ReportInfo`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#ReportInfo) | `table`, `field`, or `report`, without `raw` | | A list of one of those | [`tables`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#tables), [`fields`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#fields), or `reports` | | [`FileDownload`](https://matthew-williams-solutions.github.io/pyquickbase/reference/records/#FileDownload) | `file`, with `file_name`, `content_type`, `size`, and `text` or `binary` | A file's `text` is its contents if they're UTF-8 text, and otherwise it's null and `binary` is true, as a model can't read bytes. `page` holds `returned`, `total`, `skip`, `next_skip`, and `has_more`, so a model can tell whether it has every record, and ask for the next page. An empty list counts as no records. For a summary, `page` counts groups. A `query` operation that left out some of the table's default fields, because it has more than its `max_default_fields`, adds `fields_left_out` with how many and a `hint`. A preview's approval keys are `blockers`, what would stop the change, `warnings`, what else it would do, and, if nothing stops it, the `confirmation` the change needs and the UTC time it `expires_at`. A `preview_delete` operation's result has a blocker when more records match than the operation's `max_records`, as a delete with the same filter would be refused. Give both operations the same `max_records` for this to be right. Values are written the same way whatever their field: a date as `YYYY-MM-DD`, a timestamp in UTC as `2026-03-04T05:06:07Z`, a `Decimal` as a number when that's exact and as text otherwise, and every kind of empty value, such as `""` or an empty file, as null. Records are keyed by label. ```python tool_result( value: Any, *, compact: bool = False, max_records: int | None = None, max_text_chars: int | None = None, max_chars: int | None = None, ) -> dict[str, Any] ``` **Parameters** - **`value`** `Any`, required The result to describe. - **`compact`** `bool`, default `False` Send each label once, as `columns`, with a list of values for each record in `rows`, rather than repeating every label on every record. A file value becomes the latest file's name. Use it when a tool returns many records, to save tokens. - **`max_records`** `int | None`, optional The most records to include. Any more are left out, and `page.has_more` is true, so a model knows it doesn't have them all. - **`max_text_chars`** `int | None`, optional The most characters to include of each text value. A longer one is cut and ends by saying how much was left out, and `text_truncated` is true. - **`max_chars`** `int | None`, optional About the most characters the result may take as compact JSON, so that one call can't fill a model's context. Records and groups are included from the first until the next wouldn't fit, then `page.has_more` is true and `page.next_skip` says where to carry on, so paging still reaches every one. At least one is always included: if it's too large on its own, its text is cut shorter until it fits, down to 50 characters a value. Other keys, such as a file's text, aren't limited by it. **Returns** `dict[str, Any]` The result, as plain data. **Raises** - `TypeError`: The value isn't one of these, such as an [`AppInfo`](https://matthew-williams-solutions.github.io/pyquickbase/reference/metadata/#AppInfo), or holds a value of another type. - `ValueError`: `max_records`, `max_text_chars`, or `max_chars` isn't a positive whole number. Sync example: ```python import json from pyquickbase import Quickbase, Policy, tool_result with Quickbase.from_env( policy=Policy.read_only() ) as qb: tasks = qb.app("bq1234567").table("bqtasks123") page = tasks.query_page( select=["Title", "Due Date"], limit=2 ) result = tool_result(page, compact=True) print(json.dumps(result, indent=2)) ``` Async example: ```python import json from pyquickbase import AsyncQuickbase, Policy, tool_result async with AsyncQuickbase.from_env( policy=Policy.read_only() ) as qb: tasks = qb.app("bq1234567").table("bqtasks123") page = await tasks.query_page( select=["Title", "Due Date"], limit=2 ) result = tool_result(page, compact=True) print(json.dumps(result, indent=2)) ``` Output: ```text { "ok": true, "columns": [ "Record ID#", "Title", "Due Date" ], "rows": [ [ 1, "Pour slab", "2027-01-15" ], [ 2, "Frame walls", "2027-02-01" ] ], "page": { "returned": 2, "total": 3, "skip": 0, "next_skip": 2, "has_more": true } } ``` ## Errors For the full picture, see [Using pyquickbase from an AI agent](https://matthew-williams-solutions.github.io/pyquickbase/agents/). ### `tool_error` Describe an exception for a model, as the result of its tool call. ```python try: page = await tasks.query_page( where=where, limit=50 ) return tool_result(page) except QuickbaseError as error: return tool_error(error) ``` The result is `{"ok": false, "error": {...}}`, which `json.dumps()` always accepts. `error` holds `code`, `message`, `hint`, `retryable`, and `data_changed`, and more for some codes: - `code` is one of [`ToolErrorCode`](https://matthew-williams-solutions.github.io/pyquickbase/reference/tools/#ToolErrorCode), which doesn't change between 1.x versions, unlike the wording of `message`. - `hint` says what the model should do next, in fixed words for each code. A failed schema change has its own, as it has no failed rows, and so does a `partial_write` whose `data_changed` is `unknown`, as its failed rows may have been written. - `retryable` is true when the same call can succeed later, such as after a rate limit or a timeout. `retry_after` is added when Quickbase said how long to wait. - `data_changed` is `no` if nothing was changed, `partly` if part of a write was, and `unknown` if a write may have been applied, such as after a timeout. A model should check the data before repeating a call whose `data_changed` isn't `no`. - A [`WriteError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#WriteError) adds `written`, with the IDs that were created, updated, and left unchanged, and `failed_rows`, with each row's `index`, `reason`, `field_id`, and Quickbase's `messages`. - An [`APIError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#APIError) adds `status`, the HTTP status. - A [`TooManyRecordsError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#TooManyRecordsError) adds `matched`, how many records match, and [`limit`](https://matthew-williams-solutions.github.io/pyquickbase/reference/rules/#limit), the most the summary may read. An exception from outside the SDK gets the code `internal` and a message that says nothing about it, so a bug's details and any values in it don't reach the model. Log it yourself. ```python tool_error(error: BaseException) -> dict[str, Any] ``` **Parameters** - **`error`** `BaseException`, required The exception, usually a [`QuickbaseError`](https://matthew-williams-solutions.github.io/pyquickbase/reference/errors/#QuickbaseError). **Returns** `dict[str, Any]` The error, as plain data. Sync example: ```python from pyquickbase import ( Quickbase, Policy, QuickbaseError, tool_error, ) with Quickbase.from_env( policy=Policy.read_only() ) as qb: tasks = qb.app("bq1234567").table("bqtasks123") try: tasks.insert([{"Title": "Paint walls"}]) except QuickbaseError as error: found = tool_error(error)["error"] print(found["code"], found["data_changed"]) print(found["hint"]) ``` Async example: ```python from pyquickbase import ( AsyncQuickbase, Policy, QuickbaseError, tool_error, ) async with AsyncQuickbase.from_env( policy=Policy.read_only() ) as qb: tasks = qb.app("bq1234567").table("bqtasks123") try: await tasks.insert([{"Title": "Paint walls"}]) except QuickbaseError as error: found = tool_error(error)["error"] print(found["code"], found["data_changed"]) print(found["hint"]) ``` Output: ```text write_refused no This tool may not make that change. Don't try it another way. Tell the user. ``` ### `ToolErrorCode` The `code` in what [`tool_error`](https://matthew-williams-solutions.github.io/pyquickbase/reference/tools/#tool_error) returns. Codes don't change within 1.x, so a tool can act on them. ```python ToolErrorCode = Literal[ "misconfigured", "needs_confirmation", "invalid_arguments", "too_many_records", "invalid_value", "read_refused", "write_refused", "unknown_table", "unknown_field", "unknown_report", "unsupported_report", "schema_mismatch", "partial_write", "write_failed", "write_permission_denied", "authentication", "permission_denied", "not_found", "bad_request", "rate_limited", "server_error", "api_error", "timeout", "network_error", "invalid_response", "quickbase_error", "internal", ] ``` ## Operations For the full picture, see [Using pyquickbase from an AI agent](https://matthew-williams-solutions.github.io/pyquickbase/agents/). ### `OperationName` What [`Table.operation()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/table/#operation) can offer a model, each running the table or report method of the same name. ```python OperationName = Literal[ "query", "count", "summarize", "insert", "update", "delete", "preview_update", "preview_delete", "run_report", "run_summary_report", "run_formula", "download_file", "delete_file_version", ] ``` ### `SchemaOperationName` What [`App.operation()`](https://matthew-williams-solutions.github.io/pyquickbase/reference/app/#operation) can offer a model. ```python SchemaOperationName = Literal[ "describe_schema", "preview_schema_changes", "apply_schema_changes", ] ```