discourse/plugins/discourse-data-explorer/spec/lib/ai_query_params_spec.rb
Natalie Tay 81942bf4b2
DEV: Switch DE agent to use tools for structure and accuracy (#40315)
Previously, Data Explorer AI query generation used structured output for
the final query payload, which conflicted with tool usage and could
return SQL that had not been validated.

This commit gets the required `name`, `description`, and `sql` through a
final `submit_query` tool, requiring the exact SQL to pass `run_sql` before it
is returned. It also gives `run_sql` representative values for declared
params, including `current_user_id`, and returns a targeted error when
SQL uses params that were not declared in a `-- [params]` block.
2026-06-03 22:08:44 +08:00

75 lines
1.9 KiB
Ruby
Vendored

# frozen_string_literal: true
RSpec.describe DiscourseDataExplorer::AiQueryParams do
fab!(:user)
def sample_for(declaration)
sql = <<~SQL
-- [params]
#{declaration}
SELECT 1
SQL
query = Fabricate(:query, sql: sql)
described_class.sample_for(query, current_user: user)
end
it "returns representative static values for scalar types" do
result = sample_for(<<~PARAMS)
-- int :i
-- boolean :b
-- string :s
PARAMS
expect(result["i"]).to eq("1")
expect(result["b"]).to eq("true")
expect(result["s"]).to eq("sample")
end
it "uses the current user's username for user types" do
result = sample_for(<<~PARAMS)
-- user_id :u
-- user_list :ul
PARAMS
expect(result["u"]).to eq(user.username)
expect(result["ul"]).to eq(user.username)
end
it "samples existing records for id types" do
Fabricate(:post)
Fabricate(:category)
Fabricate(:group)
result = sample_for(<<~PARAMS)
-- post_id :p
-- topic_id :t
-- category_id :c
-- group_id :g
-- group_list :gl
PARAMS
expect(Post.where(deleted_at: nil).exists?(id: result["p"])).to eq(true)
expect(Topic.where(deleted_at: nil).exists?(id: result["t"])).to eq(true)
expect(Category.where(read_restricted: false).exists?(id: result["c"])).to eq(true)
expect(Group.exists?(name: result["g"])).to eq(true)
expect(Group.exists?(name: result["gl"])).to eq(true)
end
it "skips soft-deleted posts when sampling post_id" do
deleted_post = Fabricate(:post)
live_post = Fabricate(:post)
deleted_post.trash!
result = sample_for("-- post_id :p")
expect(result["p"]).to eq(live_post.id.to_s)
expect(result["p"]).not_to eq(deleted_post.id.to_s)
end
it "does not sample params that declare a default" do
result = sample_for("-- int :i = 5")
expect(result).not_to have_key("i")
end
end