posit-dev / posit-dev/querychat

TblSqlSource.execute_query() returns lazy tbl objects that fail ellmer serialization with Spark/Databricks connections

Open
#198 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
212
Forks
29
Avg merge
22h 24m
Merged PRs (30d)
27

Description

When using querychat with a Databricks (Spark SQL) connection via dbplyr::tbl(), the execute_query() method in TblSqlSource returns a lazy tbl_sql object. When ellmer tries to serialize this tool result to JSON, it fails because jsonlite::toJSON() calls nrow() on the lazy table, which returns NA for remote connections.

Error Message
Error in tool_string() at ellmer/R/provider-aws.R:
! Could not convert tool result from a  object to JSON.
ℹ If you are the tool author, update the tool to convert the result to a string or JSON.
Caused by error in if (!nrow(x)) ...:
! missing value where TRUE/FALSE needed
Reproduction
library(querychat)
library(DBI)
library(odbc)
library(dbplyr)

con <- dbConnect(odbc(), dsn = "Databricks_DSN")
tbl_obj <- tbl(con, in_catalog("catalog", "schema", "table_name"))

qc <- QueryChat$new(
  tbl_obj,
  client = chat_openai()  # or any ellmer chat client
)

When the LLM invokes the update_dashboard tool, the error occurs

Root Cause

In TblSqlSource.R, execute_query() returns a lazy tbl:

execute_query = function(query) {
  sql_query <- self$prep_query(query)
  dplyr::tbl(private$conn, dplyr::sql(sql_query))  # Returns lazy tbl
}

This works for the Shiny dashboard (which calls collect() before rendering), but fails when ellmer tries to serialize the tool result for the LLM.

Note: test_query() works correctly because it delegates to DBISource which returns collected data.

Attempted Workarounds

  1. Using DBISource directly - Passing the DBI connection + table name doesn't work with Databricks three-part naming (catalog.schema.table) due to table name validation.
  2. Custom DataSource subclass - Creating a subclass that overrides execute_query() to collect results hits the same table name validation issue.
  3. Registering S3 methods for jsonlite::toJSON - The method dispatch doesn't seem to pick up custom methods for tbl_sql classes.

Suggested Fix

One of:

  1. Collect in execute_query() for TblSqlSource:
    execute_query = function(query) {
    sql_query <- self$prep_query(query)
    dplyr::collect(dplyr::tbl(private$conn, dplyr::sql(sql_query)))
    }
  2. Add a parameter to control whether results are collected (for users who need lazy evaluation).
  3. Handle serialization in querychat_tool_result() by collecting lazy tbls before returning.

Environment

  • querychat: (version)
  • ellmer: (version)
  • R: (version)
  • Database: Databricks (Spark SQL) via odbc
  • OS: Linux

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Read execute_query() in TblSqlSource.R and querychat_tool_result(), comparing them with test_query(), which already returns collected data. Use the Databricks reproduction to trace the tool-result serialization path. Done means update_dashboard results from lazy tbl_sql sources can be serialized without the nrow() failure while existing dashboard behavior remains intact.

Written by the indexing model from the issue text.

Assessment

Tech stack
r, sql
Domain
backend, databases
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.