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:
parent
0a8195242b
commit
f328b81c78
|
@ -81,7 +81,7 @@ module DiscourseAi
|
||||||
:description,
|
:description,
|
||||||
:script,
|
:script,
|
||||||
:summary,
|
:summary,
|
||||||
parameters: [:name, :type, :description, :required, :enum, enum_values: []],
|
parameters: [:name, :type, :description, :required, enum: []],
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -27,9 +27,13 @@ export default class AiTool extends RestModel {
|
||||||
attrs.parameters?.map((p) => {
|
attrs.parameters?.map((p) => {
|
||||||
const parameter = new TrackedObject(p);
|
const parameter = new TrackedObject(p);
|
||||||
|
|
||||||
|
//Backwards-compatibility code.
|
||||||
|
// TODO(roman): Remove aug 2024. Leave only else clause.
|
||||||
if (parameter.enum_values) {
|
if (parameter.enum_values) {
|
||||||
parameter.enumValues = new TrackedArray(parameter.enum_values);
|
parameter.enum = new TrackedArray(parameter.enum_values);
|
||||||
delete parameter.enum_values;
|
delete parameter.enum_values;
|
||||||
|
} else {
|
||||||
|
parameter.enum = new TrackedArray(parameter.enum);
|
||||||
}
|
}
|
||||||
|
|
||||||
return parameter;
|
return parameter;
|
||||||
|
|
|
@ -71,13 +71,6 @@ export default class AiToolEditor extends Component {
|
||||||
"summary"
|
"summary"
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const p of data.parameters) {
|
|
||||||
if (p.enumValues) {
|
|
||||||
p.enum_values = p.enumValues;
|
|
||||||
delete p.enumValues;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.args.model.save(data);
|
await this.args.model.save(data);
|
||||||
|
|
||||||
this.toasts.success({
|
this.toasts.success({
|
||||||
|
@ -124,7 +117,7 @@ export default class AiToolEditor extends Component {
|
||||||
<template>
|
<template>
|
||||||
<BackButton
|
<BackButton
|
||||||
@route="adminPlugins.show.discourse-ai-tools"
|
@route="adminPlugins.show.discourse-ai-tools"
|
||||||
@label="discourse_ai.ai_tool.back"
|
@label="discourse_ai.tools.back"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<form
|
<form
|
||||||
|
|
|
@ -7,6 +7,7 @@ import DButton from "discourse/components/d-button";
|
||||||
import withEventValue from "discourse/helpers/with-event-value";
|
import withEventValue from "discourse/helpers/with-event-value";
|
||||||
import I18n from "discourse-i18n";
|
import I18n from "discourse-i18n";
|
||||||
import ComboBox from "select-kit/components/combo-box";
|
import ComboBox from "select-kit/components/combo-box";
|
||||||
|
import and from "truth-helpers/helpers/and";
|
||||||
|
|
||||||
const PARAMETER_TYPES = [
|
const PARAMETER_TYPES = [
|
||||||
{ name: "string", id: "string" },
|
{ name: "string", id: "string" },
|
||||||
|
@ -24,8 +25,7 @@ export default class AiToolParameterEditor extends Component {
|
||||||
description: "",
|
description: "",
|
||||||
type: "string",
|
type: "string",
|
||||||
required: false,
|
required: false,
|
||||||
enum: false,
|
enum: null,
|
||||||
enumValues: null,
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -43,26 +43,27 @@ export default class AiToolParameterEditor extends Component {
|
||||||
|
|
||||||
@action
|
@action
|
||||||
toggleEnum(parameter) {
|
toggleEnum(parameter) {
|
||||||
parameter.enum = !parameter.enum;
|
if (parameter.enum) {
|
||||||
if (!parameter.enum) {
|
parameter.enum = null;
|
||||||
parameter.enumValues = null;
|
} else {
|
||||||
|
this.addEnumValue(parameter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
addEnumValue(parameter) {
|
addEnumValue(parameter) {
|
||||||
parameter.enumValues ||= new TrackedArray();
|
parameter.enum ||= new TrackedArray();
|
||||||
parameter.enumValues.push("");
|
parameter.enum.push("");
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
removeEnumValue(parameter, index) {
|
removeEnumValue(parameter, index) {
|
||||||
parameter.enumValues.splice(index, 1);
|
parameter.enum.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
updateEnumValue(parameter, index, event) {
|
updateEnumValue(parameter, index, event) {
|
||||||
parameter.enumValues[index] = event.target.value;
|
parameter.enum[index] = event.target.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -100,7 +101,7 @@ export default class AiToolParameterEditor extends Component {
|
||||||
<label>
|
<label>
|
||||||
<input
|
<input
|
||||||
{{on "input" (fn this.toggleEnum parameter)}}
|
{{on "input" (fn this.toggleEnum parameter)}}
|
||||||
checked={{parameter.enum}}
|
checked={{and parameter.enum parameter.enum.length}}
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
/>
|
/>
|
||||||
{{I18n.t "discourse_ai.tools.parameter_enum"}}
|
{{I18n.t "discourse_ai.tools.parameter_enum"}}
|
||||||
|
@ -113,9 +114,9 @@ export default class AiToolParameterEditor extends Component {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{#if parameter.enum}}
|
{{#if (and parameter.enum parameter.enum.length)}}
|
||||||
<div class="parameter-enum-values">
|
<div class="parameter-enum-values">
|
||||||
{{#each parameter.enumValues as |enumValue enumIndex|}}
|
{{#each parameter.enum as |enumValue enumIndex|}}
|
||||||
<div class="enum-value-row">
|
<div class="enum-value-row">
|
||||||
<input
|
<input
|
||||||
{{on "change" (fn this.updateEnumValue parameter enumIndex)}}
|
{{on "change" (fn this.updateEnumValue parameter enumIndex)}}
|
||||||
|
|
|
@ -184,6 +184,7 @@ en:
|
||||||
remove: "Remove upload"
|
remove: "Remove upload"
|
||||||
|
|
||||||
tools:
|
tools:
|
||||||
|
back: "Back"
|
||||||
short_title: "Tools"
|
short_title: "Tools"
|
||||||
new: "New Tool"
|
new: "New Tool"
|
||||||
name: "Name"
|
name: "Name"
|
||||||
|
|
|
@ -18,7 +18,10 @@ module DiscourseAi
|
||||||
name = p[:name]
|
name = p[:name]
|
||||||
memo[:required] << name if p[:required]
|
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[:properties][name][:items] = { type: p[:item_type] } if p[:item_type]
|
||||||
memo
|
memo
|
||||||
|
|
|
@ -7,7 +7,15 @@ RSpec.describe DiscourseAi::Admin::AiToolsController do
|
||||||
name: "Test Tool",
|
name: "Test Tool",
|
||||||
description: "A test tool",
|
description: "A test tool",
|
||||||
script: "function invoke(params) { return params; }",
|
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",
|
summary: "Test tool summary",
|
||||||
created_by_id: -1,
|
created_by_id: -1,
|
||||||
)
|
)
|
||||||
|
@ -58,6 +66,27 @@ RSpec.describe DiscourseAi::Admin::AiToolsController do
|
||||||
expect(response).to have_http_status(:created)
|
expect(response).to have_http_status(:created)
|
||||||
expect(response.parsed_body["ai_tool"]["name"]).to eq("Test Tool")
|
expect(response.parsed_body["ai_tool"]["name"]).to eq("Test Tool")
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
describe "PUT #update" do
|
describe "PUT #update" do
|
||||||
|
@ -72,6 +101,27 @@ RSpec.describe DiscourseAi::Admin::AiToolsController do
|
||||||
expect(response).to be_successful
|
expect(response).to be_successful
|
||||||
expect(ai_tool.reload.name).to eq("Updated Tool")
|
expect(ai_tool.reload.name).to eq("Updated Tool")
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
describe "DELETE #destroy" do
|
describe "DELETE #destroy" do
|
||||||
|
|
Loading…
Reference in New Issue