FIX: Make sure custom tool enums follow json-schema. (#718)

Enums didn't work as expected because we the dialect couldn't translate
them correctly. It doesn't understand what "enum_values" is.
This commit is contained in:
Roman Rizzi 2024-07-16 14:23:17 -03:00 committed by GitHub
parent 0a8195242b
commit f328b81c78
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 76 additions and 24 deletions

View File

@ -81,7 +81,7 @@ module DiscourseAi
:description,
:script,
:summary,
parameters: [:name, :type, :description, :required, :enum, enum_values: []],
parameters: [:name, :type, :description, :required, enum: []],
)
end
end

View File

@ -27,9 +27,13 @@ export default class AiTool extends RestModel {
attrs.parameters?.map((p) => {
const parameter = new TrackedObject(p);
//Backwards-compatibility code.
// TODO(roman): Remove aug 2024. Leave only else clause.
if (parameter.enum_values) {
parameter.enumValues = new TrackedArray(parameter.enum_values);
parameter.enum = new TrackedArray(parameter.enum_values);
delete parameter.enum_values;
} else {
parameter.enum = new TrackedArray(parameter.enum);
}
return parameter;

View File

@ -71,13 +71,6 @@ export default class AiToolEditor extends Component {
"summary"
);
for (const p of data.parameters) {
if (p.enumValues) {
p.enum_values = p.enumValues;
delete p.enumValues;
}
}
await this.args.model.save(data);
this.toasts.success({
@ -124,7 +117,7 @@ export default class AiToolEditor extends Component {
<template>
<BackButton
@route="adminPlugins.show.discourse-ai-tools"
@label="discourse_ai.ai_tool.back"
@label="discourse_ai.tools.back"
/>
<form

View File

@ -7,6 +7,7 @@ import DButton from "discourse/components/d-button";
import withEventValue from "discourse/helpers/with-event-value";
import I18n from "discourse-i18n";
import ComboBox from "select-kit/components/combo-box";
import and from "truth-helpers/helpers/and";
const PARAMETER_TYPES = [
{ name: "string", id: "string" },
@ -24,8 +25,7 @@ export default class AiToolParameterEditor extends Component {
description: "",
type: "string",
required: false,
enum: false,
enumValues: null,
enum: null,
})
);
}
@ -43,26 +43,27 @@ export default class AiToolParameterEditor extends Component {
@action
toggleEnum(parameter) {
parameter.enum = !parameter.enum;
if (!parameter.enum) {
parameter.enumValues = null;
if (parameter.enum) {
parameter.enum = null;
} else {
this.addEnumValue(parameter);
}
}
@action
addEnumValue(parameter) {
parameter.enumValues ||= new TrackedArray();
parameter.enumValues.push("");
parameter.enum ||= new TrackedArray();
parameter.enum.push("");
}
@action
removeEnumValue(parameter, index) {
parameter.enumValues.splice(index, 1);
parameter.enum.splice(index, 1);
}
@action
updateEnumValue(parameter, index, event) {
parameter.enumValues[index] = event.target.value;
parameter.enum[index] = event.target.value;
}
<template>
@ -100,7 +101,7 @@ export default class AiToolParameterEditor extends Component {
<label>
<input
{{on "input" (fn this.toggleEnum parameter)}}
checked={{parameter.enum}}
checked={{and parameter.enum parameter.enum.length}}
type="checkbox"
/>
{{I18n.t "discourse_ai.tools.parameter_enum"}}
@ -113,9 +114,9 @@ export default class AiToolParameterEditor extends Component {
/>
</div>
{{#if parameter.enum}}
{{#if (and parameter.enum parameter.enum.length)}}
<div class="parameter-enum-values">
{{#each parameter.enumValues as |enumValue enumIndex|}}
{{#each parameter.enum as |enumValue enumIndex|}}
<div class="enum-value-row">
<input
{{on "change" (fn this.updateEnumValue parameter enumIndex)}}

View File

@ -184,6 +184,7 @@ en:
remove: "Remove upload"
tools:
back: "Back"
short_title: "Tools"
new: "New Tool"
name: "Name"

View File

@ -18,7 +18,10 @@ module DiscourseAi
name = p[:name]
memo[:required] << name if p[:required]
memo[:properties][name] = p.except(:name, :required, :item_type)
except = %i[name required item_type]
except << :enum if p[:enum].blank?
memo[:properties][name] = p.except(*except)
memo[:properties][name][:items] = { type: p[:item_type] } if p[:item_type]
memo

View File

@ -7,7 +7,15 @@ RSpec.describe DiscourseAi::Admin::AiToolsController do
name: "Test Tool",
description: "A test tool",
script: "function invoke(params) { return params; }",
parameters: [{ name: "query", type: "string", description: "perform a search" }],
parameters: [
{
name: "unit",
type: "string",
description: "the unit of measurement celcius c or fahrenheit f",
enum: %w[c f],
required: true,
},
],
summary: "Test tool summary",
created_by_id: -1,
)
@ -58,6 +66,27 @@ RSpec.describe DiscourseAi::Admin::AiToolsController do
expect(response).to have_http_status(:created)
expect(response.parsed_body["ai_tool"]["name"]).to eq("Test Tool")
end
context "when the parameter is a enum" do
it "creates the tool with the correct parameters" do
attrs = valid_attributes
attrs[:parameters] = [attrs[:parameters].first.merge(enum: %w[c f])]
expect {
post "/admin/plugins/discourse-ai/ai-tools.json",
params: { ai_tool: valid_attributes }.to_json,
headers: {
"CONTENT_TYPE" => "application/json",
}
}.to change(AiTool, :count).by(1)
expect(response).to have_http_status(:created)
expect(response.parsed_body.dig("ai_tool", "parameters", 0, "enum")).to contain_exactly(
"c",
"f",
)
end
end
end
describe "PUT #update" do
@ -72,6 +101,27 @@ RSpec.describe DiscourseAi::Admin::AiToolsController do
expect(response).to be_successful
expect(ai_tool.reload.name).to eq("Updated Tool")
end
context "when updating an enum parameters" do
it "updates the enum fixed values" do
put "/admin/plugins/discourse-ai/ai-tools/#{ai_tool.id}.json",
params: {
ai_tool: {
parameters: [
{
name: "unit",
type: "string",
description: "the unit of measurement celcius c or fahrenheit f",
enum: %w[g d],
},
],
},
}
expect(response).to be_successful
expect(ai_tool.reload.parameters.dig(0, "enum")).to contain_exactly("g", "d")
end
end
end
describe "DELETE #destroy" do