diff --git a/CHANGELOG.md b/CHANGELOG.md index c6614ce21..17309e503 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ - chore: update json-as and remove hack [#857](https://github.com/hypermodeinc/modus/pull/857) - chore: rename agent lifecycle methods and APIs [#858](https://github.com/hypermodeinc/modus/pull/858) - feat: enforce WASI reactor mode [#859](https://github.com/hypermodeinc/modus/pull/859) -- feat: return user and chat errors in API response [#863](https://github.com/hypermodeinc/modus/pull/863) +- feat: return user and chat errors in API response [#863](https://github.com/hypermodeinc/modus/pull/863) [#864](https://github.com/hypermodeinc/modus/pull/864) - feat: list agents on health endpoint [#865](https://github.com/hypermodeinc/modus/pull/865) - feat: add agent management APIs [#866](https://github.com/hypermodeinc/modus/pull/866) diff --git a/sdk/assemblyscript/src/assembly/models.ts b/sdk/assemblyscript/src/assembly/models.ts index f5f9b3422..104d00895 100644 --- a/sdk/assemblyscript/src/assembly/models.ts +++ b/sdk/assemblyscript/src/assembly/models.ts @@ -58,11 +58,18 @@ export interface ModelFactory { getModel(modelName: string): T; } + +@json +export abstract class ModelError { + abstract toString(): string; +} + export abstract class Model { static invoker: ModelInvoker | null = null; protected constructor(public info: ModelInfo) {} debug: boolean = false; + validator: ((data: string) => ModelError | null) | null = null; /** * Invokes the model with the given input. @@ -81,7 +88,7 @@ export abstract class Model { } const outputJson = Model.invoker(modelName, inputJson); - if (!outputJson) { + if (outputJson === null) { throw new Error(`Failed to invoke ${modelName} model.`); } @@ -89,6 +96,13 @@ export abstract class Model { console.debug(`Received output: ${outputJson}`); } + if (this.validator) { + const err = this.validator(outputJson); + if (err !== null) { + utils.throwUserError(`The chat model returned an error: ${err}`); + } + } + return JSON.parse(outputJson); } } diff --git a/sdk/assemblyscript/src/assembly/utils.ts b/sdk/assemblyscript/src/assembly/utils.ts index 6e21dd5a9..9e81a1ff4 100644 --- a/sdk/assemblyscript/src/assembly/utils.ts +++ b/sdk/assemblyscript/src/assembly/utils.ts @@ -7,6 +7,19 @@ * SPDX-License-Identifier: Apache-2.0 */ +/** + * Checks if the result is invalid, by determining if it is a null pointer. + * This will work for any managed type, regardless of whether it is nullable or not. + */ export function resultIsInvalid(result: T): bool { return changetype(result) == 0; } + +/** + * Logs an error intended for the user, and exits. + * The message will be displayed in the API response, and in the console logs. + */ +export function throwUserError(message: string): void { + console.error(message); + process.exit(1); +} diff --git a/sdk/assemblyscript/src/models/openai/chat.ts b/sdk/assemblyscript/src/models/openai/chat.ts index 5c5df6697..689d85746 100644 --- a/sdk/assemblyscript/src/models/openai/chat.ts +++ b/sdk/assemblyscript/src/models/openai/chat.ts @@ -7,7 +7,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { Model } from "../../assembly/models"; +import { Model, ModelError } from "../../assembly/models"; import { JSON } from "json-as"; import * as base64 from "as-base64/assembly"; @@ -24,6 +24,7 @@ export class OpenAIChatModel extends Model { * @returns An input object that can be passed to the `invoke` method. */ createInput(messages: RequestMessage[]): OpenAIChatInput { + this.validator = validateChatModelResponse; const model = this.info.fullName.toLowerCase(); return { model, messages }; } @@ -1587,3 +1588,49 @@ export function parseMessages(data: string): RequestMessage[] { (msg) => new RawMessage(msg.toString()), ); } + +// Validates the response from the chat model output. +function validateChatModelResponse(data: string): ModelError | null { + if (data.length == 0) { + throw new Error("No response received from model invocation"); + } + + const obj = JSON.parse(data); + const error = obj.get("error"); + if (error) { + return JSON.parse(error.toString()); + // return error.get(); // todo: this should work instead of the above line, but fails as of json-as v1.1.14 + } + + return null; +} + +/** + * Represents an error returned from the OpenAI Chat API. + */ +@json +class ChatModelError extends ModelError { + /** + * The error type. + */ + type: string = ""; + + /** + * A human-readable description of the error. + */ + message: string = ""; + + /** + * The parameter related to the error, if any. + */ + param: string | null = null; + + /** + * The error code, if any. + */ + code: string | null = null; + + toString(): string { + return this.message; + } +}