-
Notifications
You must be signed in to change notification settings - Fork 3.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
.Net: fix: array query parameters with single entries #9771
base: main
Are you sure you want to change the base?
Conversation
Signed-off-by: Vincent Biret <vibiret@microsoft.com>
Signed-off-by: Vincent Biret <vibiret@microsoft.com>
#else | ||
string s when s.Trim().StartsWith("[", StringComparison.OrdinalIgnoreCase) => JsonArray.Parse(s) as JsonArray, | ||
#endif | ||
string s => [JsonValue.Create(s)], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder which scenario requires the string -> JSONArray conversion. Is it when the LLM provides a string argument to a parameter of an array type? I'm not sure SK should perform this conversion, and I would argue that it's the caller's responsibility to provide a valid JSON array as an argument for the parameter of array type. If the LLM is returning the string, the error details should be sent back to it so it can make another call with the correct argument type - this is already implemented in SK.
My concern is that if we start doing the conversion because the LLM sometimes provides invalid JSON values, sooner or later we will find ourselves in a tricky situation when we are asked, for example, to create an array element for each word in the string - "v1 v2 v3," "v1, v2, v3," or "v1;v2;v3." How are we going to support that without introducing any behavioral change? We were creating an array with one element from the string, but now we would have an array with an element per word in the string. Will we add an extra option/execution parameter that allows us to tweak the conversion behavior? What if different string -> array conversion flavors are needed within the same operation or in different operations imported from the same document?
I think it's safer not to perform that conversion at the SK level and allow the LLM to self-correct based on function invocation errors. If an extra request to the LLM is not desirable, then consumers can easily decorate each operation with a delegate - How to transform a KernelFunction that would handle the conversion of arguments provided by the LLM based on the target type from the operation metadata and call original function with valid arguments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't believe this is a matter of the LLM generating "the wrong JSON" but rather how the response is being parsed, hence the fix.
For the following prompt using only the subject, recap my last 5 emails
it eventually builds createdDateTime desc
as the string value here for the $orderBy query parameter, which is correct.
- name: $orderby
in: query
description: Order items by property values
style: form
explode: false
schema:
uniqueItems: true
type: array
items:
type: string
enum:
- id
- id desc
- createdDateTime
- createdDateTime desc
However, I do agree this could be a slippery slope, especially for request and response bodies, so maybe we limit those graceful upgrades to query/paths parameters and headers?
What's probably causing the mis-alignment here is the style from and the transformation that already gets applied post parsing. We could alternatively accept the LLMs response as is since it's correct and remove the parsing all together, but this might impact reliability in other cases?
I don't think letting the LLM brute force through adding extra square brackets is a productive use of resources here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@baywet The example you show is serializing of URL parameters, as per 6570 rules. I don't think we can mix serializing of URL parameters and JSON. They follow very different rules. With 6570 whether the parameter value is a simple string, or an array of strings it would serialize correctly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which begs the question: why does the pipeline to generate query parameters value go through a JSON parsing in the first place?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the following prompt using only the subject, recap my last 5 emails it eventually builds createdDateTime desc as the string value here for the $orderBy query parameter, which is correct.
Who builds it, LLM or SK? If it's the LLM that provides "createdDate desc" string for a parameter of an array type, then it's incorrect because the "createdDate desc" string is not a valid JSON array (see https://datatracker.ietf.org/doc/html/rfc8259#section-5).
What's probably causing the mis-alignment here is the style from and the transformation that already gets applied post parsing. We could alternatively accept the LLMs response as is since it's correct and remove the parsing all together, but this might impact reliability in other cases?
To consider the LLM response as correct, it should conform to the parameter specification that requires the parameter to be of array type; every response that deviates from this is incorrect, and accepting it only pushes the issue further in the invocation chain -> parameters serialization failing to serialize the parameter value in accordance with the specified format or the REST API service itself receiving a wrongly formatted query string if the serialization part is dropped or produces the wrong result.
Parsing is required because SK supports non-LLM scenarios as well, where parameter arguments can be provided as a JSON string or .NET type. For example, the argument for the orderby
parameter can either be a JSON array - "["id desc", "createdDateTime asc"]" or an instance of a .NET List type, such as new List<string>() { "id desc" }
.
I don't think letting the LLM brute force through adding extra square brackets is a productive use of resources here.
... If an extra request to the LLM is not desirable, then consumers can easily decorate each operation with a delegate - How to transform a KernelFunction that would handle the conversion of arguments provided by the LLM based on the target type from the operation metadata and call original function with valid arguments.
Which begs the question: why does the pipeline to generate query parameters value go through a JSON parsing in the first place?
To simplify the parameters serialization functionality that would otherwise be cluttered with the parsing and validation logic required to format the parameters, as well as the if/else logic to determine whether the argument is JSON or an instance of a .NET type.
BTW: There's no need to specify "$" in the "$orderby" parameter name, SK will do it for you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the additional information.
As far as I understand kernel functions transformations, they are deeply specific to a given function, which would not be suitable in our case since we'll want a solution that works for any function, generated from any OpenAPI/Api Manifest/Copilot Plugin.
Who builds it, LLM or SK?
I'm not sure, whatever ends up calling this method. As far as I understand, SK calls the LLM to get the query parameters values generated, but maybe I understand this wrong?
In your previous answer, you seem to be eluding to the fact that we might be able to "force the hand" of the LLM and tell it "no, really, even if you come up with a single result, it needs to be returned as a valid JSON array with a single entry". Or maybe this is me extrapolating. Would you have pointers for that?
Sharing some context from debugging Here is the returned plan from the LLM (azure OpenAI GPT4)
As we can see, the LLM is returning a string, not an array of strings Here is what we currently send to the LLM to generate the plan [
{
"name": "MessagesPlugin-me_CreateMessages",
"description": "Create an open extension (openTypeExtension object) and add custom properties in a new or existing instance of a resource. You can create an open extension in a resource instance and store custom data to it all in the same operation, except for specific resources. The table in the Permissions section lists the resources that support open extensions.",
"parameters": {
"type": "object",
"required": [],
"properties": {
"id": {
"type": "string",
"description": "The unique identifier for an entity. Read-only."
},
"categories": {
"type": "array",
"items": {
"type": "string",
"nullable": true
},
"description": "The categories associated with the item"
},
"changeKey": {
"type": "string",
"description": "Identifies the version of the item. Every time the item is changed, changeKey changes as well. This allows Exchange to apply changes to the correct version of the object. Read-only.",
"nullable": true
},
"createdDateTime": {
"pattern": "^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[\u002B-][0-9][0-9]:[0-9][0-9])$",
"type": "string",
"description": "The Timestamp type represents date and time information using ISO 8601 format and is always in UTC time. For example, midnight UTC on Jan 1, 2014 is 2014-01-01T00:00:00Z",
"format": "date-time",
"nullable": true
},
"lastModifiedDateTime": {
"pattern": "^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[\u002B-][0-9][0-9]:[0-9][0-9])$",
"type": "string",
"description": "The Timestamp type represents date and time information using ISO 8601 format and is always in UTC time. For example, midnight UTC on Jan 1, 2014 is 2014-01-01T00:00:00Z",
"format": "date-time",
"nullable": true
},
"bccRecipients": {
"type": "array",
"items": {
"title": "recipient",
"type": "object",
"properties": {
"emailAddress": {
"anyOf": [
{
"title": "emailAddress",
"type": "object",
"properties": {
"address": {
"type": "string",
"description": "The email address of the person or entity.",
"nullable": true
},
"name": {
"type": "string",
"description": "The display name of the person or entity.",
"nullable": true
}
}
},
{
"type": "object",
"nullable": true
}
],
"description": "The recipient\u0027s email address."
}
}
},
"description": "The Bcc: recipients for the message."
},
"body": {
"anyOf": [
{
"title": "itemBody",
"type": "object",
"properties": {
"content": {
"type": "string",
"description": "The content of the item.",
"nullable": true
},
"contentType": {
"anyOf": [
{
"title": "bodyType",
"enum": [
"text",
"html"
],
"type": "string"
},
{
"type": "object",
"nullable": true
}
],
"description": "The type of the content. Possible values are text and html."
}
}
},
{
"type": "object",
"nullable": true
}
],
"description": "The body of the message. It can be in HTML or text format. Find out about safe HTML in a message body."
},
"bodyPreview": {
"type": "string",
"description": "The first 255 characters of the message body. It is in text format.",
"nullable": true
},
"ccRecipients": {
"type": "array",
"items": {
"title": "recipient",
"type": "object",
"properties": {
"emailAddress": {
"anyOf": [
{
"title": "emailAddress",
"type": "object",
"properties": {
"address": {
"type": "string",
"description": "The email address of the person or entity.",
"nullable": true
},
"name": {
"type": "string",
"description": "The display name of the person or entity.",
"nullable": true
}
}
},
{
"type": "object",
"nullable": true
}
],
"description": "The recipient\u0027s email address."
}
}
},
"description": "The Cc: recipients for the message."
},
"conversationId": {
"type": "string",
"description": "The ID of the conversation the email belongs to.",
"nullable": true
},
"conversationIndex": {
"type": "string",
"description": "Indicates the position of the message within the conversation.",
"format": "base64url",
"nullable": true
},
"flag": {
"anyOf": [
{
"title": "followupFlag",
"type": "object",
"properties": {
"completedDateTime": {
"anyOf": [
{
"title": "dateTimeTimeZone",
"type": "object",
"properties": {
"dateTime": {
"type": "string",
"description": "A single point of time in a combined date and time representation ({date}T{time}; for example, 2017-08-29T04:00:00.0000000)."
},
"timeZone": {
"type": "string",
"description": "Represents a time zone, for example, \u0027Pacific Standard Time\u0027. See below for more possible values.",
"nullable": true
}
}
},
{
"type": "object",
"nullable": true
}
],
"description": "The date and time that the follow-up was finished."
},
"dueDateTime": {
"anyOf": [
{
"title": "dateTimeTimeZone",
"type": "object",
"properties": {
"dateTime": {
"type": "string",
"description": "A single point of time in a combined date and time representation ({date}T{time}; for example, 2017-08-29T04:00:00.0000000)."
},
"timeZone": {
"type": "string",
"description": "Represents a time zone, for example, \u0027Pacific Standard Time\u0027. See below for more possible values.",
"nullable": true
}
}
},
{
"type": "object",
"nullable": true
}
],
"description": "The date and time that the follow-up is to be finished. Note: To set the due date, you must also specify the startDateTime; otherwise, you get a 400 Bad Request response."
},
"flagStatus": {
"anyOf": [
{
"title": "followupFlagStatus",
"enum": [
"notFlagged",
"complete",
"flagged"
],
"type": "string"
},
{
"type": "object",
"nullable": true
}
],
"description": "The status for follow-up for an item. Possible values are notFlagged, complete, and flagged."
},
"startDateTime": {
"anyOf": [
{
"title": "dateTimeTimeZone",
"type": "object",
"properties": {
"dateTime": {
"type": "string",
"description": "A single point of time in a combined date and time representation ({date}T{time}; for example, 2017-08-29T04:00:00.0000000)."
},
"timeZone": {
"type": "string",
"description": "Represents a time zone, for example, \u0027Pacific Standard Time\u0027. See below for more possible values.",
"nullable": true
}
}
},
{
"type": "object",
"nullable": true
}
],
"description": "The date and time that the follow-up is to begin."
}
}
},
{
"type": "object",
"nullable": true
}
],
"description": "The flag value that indicates the status, start date, due date, or completion date for the message."
},
"from": {
"anyOf": [
{
"title": "recipient",
"type": "object",
"properties": {
"emailAddress": {
"anyOf": [
{
"title": "emailAddress",
"type": "object",
"properties": {
"address": {
"type": "string",
"description": "The email address of the person or entity.",
"nullable": true
},
"name": {
"type": "string",
"description": "The display name of the person or entity.",
"nullable": true
}
}
},
{
"type": "object",
"nullable": true
}
],
"description": "The recipient\u0027s email address."
}
}
},
{
"type": "object",
"nullable": true
}
],
"description": "The owner of the mailbox from which the message is sent. In most cases, this value is the same as the sender property, except for sharing or delegation scenarios. The value must correspond to the actual mailbox used. Find out more about setting the from and sender properties of a message."
},
"hasAttachments": {
"type": "boolean",
"description": "Indicates whether the message has attachments. This property doesn\u0027t include inline attachments, so if a message contains only inline attachments, this property is false. To verify the existence of inline attachments, parse the body property to look for a src attribute, such as \u003CIMG src=\u0027cid:image001.jpg@01D26CD8.6C05F070\u0027\u003E.",
"nullable": true
},
"importance": {
"anyOf": [
{
"title": "importance",
"enum": [
"low",
"normal",
"high"
],
"type": "string"
},
{
"type": "object",
"nullable": true
}
],
"description": "The importance of the message. The possible values are: low, normal, and high."
},
"inferenceClassification": {
"anyOf": [
{
"title": "inferenceClassificationType",
"enum": [
"focused",
"other"
],
"type": "string"
},
{
"type": "object",
"nullable": true
}
],
"description": "The classification of the message for the user, based on inferred relevance or importance, or on an explicit override. The possible values are: focused or other."
},
"internetMessageHeaders": {
"type": "array",
"items": {
"title": "internetMessageHeader",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Represents the key in a key-value pair.",
"nullable": true
},
"value": {
"type": "string",
"description": "The value in a key-value pair.",
"nullable": true
}
}
},
"description": "A collection of message headers defined by RFC5322. The set includes message headers indicating the network path taken by a message from the sender to the recipient. It can also contain custom message headers that hold app data for the message. Returned only on applying a $select query option. Read-only."
},
"internetMessageId": {
"type": "string",
"description": "The message ID in the format specified by RFC2822.",
"nullable": true
},
"isDeliveryReceiptRequested": {
"type": "boolean",
"description": "Indicates whether a read receipt is requested for the message.",
"nullable": true
},
"isDraft": {
"type": "boolean",
"description": "Indicates whether the message is a draft. A message is a draft if it hasn\u0027t been sent yet.",
"nullable": true
},
"isRead": {
"type": "boolean",
"description": "Indicates whether the message has been read.",
"nullable": true
},
"isReadReceiptRequested": {
"type": "boolean",
"description": "Indicates whether a read receipt is requested for the message.",
"nullable": true
},
"parentFolderId": {
"type": "string",
"description": "The unique identifier for the message\u0027s parent mailFolder.",
"nullable": true
},
"receivedDateTime": {
"pattern": "^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[\u002B-][0-9][0-9]:[0-9][0-9])$",
"type": "string",
"description": "The date and time the message was received. The date and time information uses ISO 8601 format and is always in UTC time. For example, midnight UTC on Jan 1, 2014 is 2014-01-01T00:00:00Z.",
"format": "date-time",
"nullable": true
},
"replyTo": {
"type": "array",
"items": {
"title": "recipient",
"type": "object",
"properties": {
"emailAddress": {
"anyOf": [
{
"title": "emailAddress",
"type": "object",
"properties": {
"address": {
"type": "string",
"description": "The email address of the person or entity.",
"nullable": true
},
"name": {
"type": "string",
"description": "The display name of the person or entity.",
"nullable": true
}
}
},
{
"type": "object",
"nullable": true
}
],
"description": "The recipient\u0027s email address."
}
}
},
"description": "The email addresses to use when replying."
},
"sender": {
"anyOf": [
{
"title": "recipient",
"type": "object",
"properties": {
"emailAddress": {
"anyOf": [
{
"title": "emailAddress",
"type": "object",
"properties": {
"address": {
"type": "string",
"description": "The email address of the person or entity.",
"nullable": true
},
"name": {
"type": "string",
"description": "The display name of the person or entity.",
"nullable": true
}
}
},
{
"type": "object",
"nullable": true
}
],
"description": "The recipient\u0027s email address."
}
}
},
{
"type": "object",
"nullable": true
}
],
"description": "The account that is actually used to generate the message. In most cases, this value is the same as the from property. You can set this property to a different value when sending a message from a shared mailbox, for a shared calendar, or as a delegate. In any case, the value must correspond to the actual mailbox used. Find out more about setting the from and sender properties of a message."
},
"sentDateTime": {
"pattern": "^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[\u002B-][0-9][0-9]:[0-9][0-9])$",
"type": "string",
"description": "The date and time the message was sent. The date and time information uses ISO 8601 format and is always in UTC time. For example, midnight UTC on Jan 1, 2014 is 2014-01-01T00:00:00Z.",
"format": "date-time",
"nullable": true
},
"subject": {
"type": "string",
"description": "The subject of the message.",
"nullable": true
},
"toRecipients": {
"type": "array",
"items": {
"title": "recipient",
"type": "object",
"properties": {
"emailAddress": {
"anyOf": [
{
"title": "emailAddress",
"type": "object",
"properties": {
"address": {
"type": "string",
"description": "The email address of the person or entity.",
"nullable": true
},
"name": {
"type": "string",
"description": "The display name of the person or entity.",
"nullable": true
}
}
},
{
"type": "object",
"nullable": true
}
],
"description": "The recipient\u0027s email address."
}
}
},
"description": "The To: recipients for the message."
},
"uniqueBody": {
"anyOf": [
{
"title": "itemBody",
"type": "object",
"properties": {
"content": {
"type": "string",
"description": "The content of the item.",
"nullable": true
},
"contentType": {
"anyOf": [
{
"title": "bodyType",
"enum": [
"text",
"html"
],
"type": "string"
},
{
"type": "object",
"nullable": true
}
],
"description": "The type of the content. Possible values are text and html."
}
}
},
{
"type": "object",
"nullable": true
}
],
"description": "The part of the body of the message that is unique to the current message. uniqueBody is not returned by default but can be retrieved for a given message by use of the ?$select=uniqueBody query. It can be in HTML or text format."
},
"webLink": {
"type": "string",
"description": "The URL to open the message in Outlook on the web.You can append an ispopout argument to the end of the URL to change how the message is displayed. If ispopout is not present or if it is set to 1, then the message is shown in a popout window. If ispopout is set to 0, the browser shows the message in the Outlook on the web review pane.The message opens in the browser if you are signed in to your mailbox via Outlook on the web. You are prompted to sign in if you are not already signed in with the browser.This URL cannot be accessed from within an iFrame.",
"nullable": true
},
"attachments": {
"type": "array",
"items": {
"allOf": [
{
"title": "entity",
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "The unique identifier for an entity. Read-only."
}
}
},
{
"title": "attachment",
"type": "object",
"properties": {
"contentType": {
"type": "string",
"description": "The MIME type.",
"nullable": true
},
"isInline": {
"type": "boolean",
"description": "true if the attachment is an inline attachment; otherwise, false."
},
"lastModifiedDateTime": {
"pattern": "^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[\u002B-][0-9][0-9]:[0-9][0-9])$",
"type": "string",
"description": "The Timestamp type represents date and time information using ISO 8601 format and is always in UTC time. For example, midnight UTC on Jan 1, 2014 is 2014-01-01T00:00:00Z",
"format": "date-time",
"nullable": true
},
"name": {
"type": "string",
"description": "The attachment\u0027s file name.",
"nullable": true
},
"size": {
"maximum": 2147483647,
"minimum": -2147483648,
"type": "number",
"description": "The length of the attachment in bytes.",
"format": "int32"
}
}
}
]
},
"description": "The fileAttachment and itemAttachment attachments for the message."
},
"extensions": {
"type": "array",
"items": {
"allOf": [
{
"title": "entity",
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "The unique identifier for an entity. Read-only."
}
}
},
{
"title": "extension",
"type": "object"
}
]
},
"description": "The collection of open extensions defined for the message. Nullable."
},
"multiValueExtendedProperties": {
"type": "array",
"items": {
"allOf": [
{
"title": "entity",
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "The unique identifier for an entity. Read-only."
}
}
},
{
"title": "multiValueLegacyExtendedProperty",
"type": "object",
"properties": {
"value": {
"type": "array",
"items": {
"type": "string",
"nullable": true
},
"description": "A collection of property values."
}
}
}
]
},
"description": "The collection of multi-value extended properties defined for the message. Nullable."
},
"singleValueExtendedProperties": {
"type": "array",
"items": {
"allOf": [
{
"title": "entity",
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "The unique identifier for an entity. Read-only."
}
}
},
{
"title": "singleValueLegacyExtendedProperty",
"type": "object",
"properties": {
"value": {
"type": "string",
"description": "A property value.",
"nullable": true
}
}
}
]
},
"description": "The collection of single-value extended properties defined for the message. Nullable."
}
}
},
"responses": {}
},
{
"name": "MessagesPlugin-me_ListMessages",
"description": "Get an open extension (openTypeExtension object) identified by name or fully qualified name. The table in the Permissions section lists the resources that support open extensions. The following table lists the three scenarios where you can get an open extension from a supported resource instance.",
"parameters": {
"type": "object",
"required": [],
"properties": {
"includeHiddenMessages": {
"type": "string"
},
"_top": {
"minimum": 0,
"type": "integer"
},
"_skip": {
"minimum": 0,
"type": "integer"
},
"_search": {
"type": "string"
},
"_filter": {
"type": "string"
},
"_count": {
"type": "boolean"
},
"_orderby": {
"uniqueItems": true,
"type": "array",
"items": {
"enum": [
"id",
"id desc",
"categories",
"categories desc",
"changeKey",
"changeKey desc",
"createdDateTime",
"createdDateTime desc",
"lastModifiedDateTime",
"lastModifiedDateTime desc",
"bccRecipients",
"bccRecipients desc",
"body",
"body desc",
"bodyPreview",
"bodyPreview desc",
"ccRecipients",
"ccRecipients desc",
"conversationId",
"conversationId desc",
"conversationIndex",
"conversationIndex desc",
"flag",
"flag desc",
"from",
"from desc",
"hasAttachments",
"hasAttachments desc",
"importance",
"importance desc",
"inferenceClassification",
"inferenceClassification desc",
"internetMessageHeaders",
"internetMessageHeaders desc",
"internetMessageId",
"internetMessageId desc",
"isDeliveryReceiptRequested",
"isDeliveryReceiptRequested desc",
"isDraft",
"isDraft desc",
"isRead",
"isRead desc",
"isReadReceiptRequested",
"isReadReceiptRequested desc",
"parentFolderId",
"parentFolderId desc",
"receivedDateTime",
"receivedDateTime desc",
"replyTo",
"replyTo desc",
"sender",
"sender desc",
"sentDateTime",
"sentDateTime desc",
"subject",
"subject desc",
"toRecipients",
"toRecipients desc",
"uniqueBody",
"uniqueBody desc",
"webLink",
"webLink desc"
],
"type": "string"
}
},
"_select": {
"uniqueItems": true,
"type": "array",
"items": {
"enum": [
"id",
"categories",
"changeKey",
"createdDateTime",
"lastModifiedDateTime",
"bccRecipients",
"body",
"bodyPreview",
"ccRecipients",
"conversationId",
"conversationIndex",
"flag",
"from",
"hasAttachments",
"importance",
"inferenceClassification",
"internetMessageHeaders",
"internetMessageId",
"isDeliveryReceiptRequested",
"isDraft",
"isRead",
"isReadReceiptRequested",
"parentFolderId",
"receivedDateTime",
"replyTo",
"sender",
"sentDateTime",
"subject",
"toRecipients",
"uniqueBody",
"webLink",
"attachments",
"extensions",
"multiValueExtendedProperties",
"singleValueExtendedProperties"
],
"type": "string"
}
},
"_expand": {
"uniqueItems": true,
"type": "array",
"items": {
"enum": [
"*",
"attachments",
"extensions",
"multiValueExtendedProperties",
"singleValueExtendedProperties"
],
"type": "string"
}
}
}
},
"responses": {}
},
{
"name": "UserInteraction-SendFinalAnswer",
"description": "This function is used to send the final answer of a plan to the user.",
"parameters": {
"type": "object",
"required": [
"answer"
],
"properties": {
"answer": {
"type": "string",
"description": "The final answer"
}
}
},
"responses": {}
}
] |
Note: gpt4o generates the correct array structure, and even gpt4 generates the correct structure for the subject query parameter. From discussions with @SergeyMenshykh we could apply that transformation at a different place. Wrapping the function calls with a decorator. This way people could choose to implement variations based on their scenarios (no splitting, csv, scsv, space sv, ...) |
Another option still with the decorator pattern would be to reinforce the prompt, either at the schema level, or during the prompt for the planner saying something like "if the schema is an array and you only want to generate a single value, make sure you format it as an array"
|
fixes an issue where OpenAPI query parameters of type array with a single value would break the request generation