UNPKG

ravendb

Version:
228 lines 9.95 kB
import { RunConversationOperation } from "./Agents/RunConversationOperation.js"; import { throwError } from "../../../Exceptions/index.js"; import { StringUtil } from "../../../Utility/StringUtil.js"; export var AiHandleErrorStrategy; (function (AiHandleErrorStrategy) { AiHandleErrorStrategy["SendErrorsToModel"] = "SendErrorsToModel"; AiHandleErrorStrategy["RaiseImmediately"] = "RaiseImmediately"; })(AiHandleErrorStrategy || (AiHandleErrorStrategy = {})); export class AiConversation { _store; _databaseName; _agentId; _conversationId; _options; _actionRequests = null; _actionResponses = []; _userPrompt; _invocations = new Map(); onUnhandledAction; constructor(store, databaseName, agentId, conversationId, options, changeVector) { if (!store) throwError("InvalidArgumentException", "store is required"); if (StringUtil.isNullOrEmpty(databaseName)) throwError("InvalidArgumentException", "databaseName is required"); if (StringUtil.isNullOrEmpty(agentId)) throwError("InvalidArgumentException", "agentId is required"); if (StringUtil.isNullOrEmpty(conversationId)) throwError("InvalidArgumentException", "conversationId is required"); this._store = store; this._databaseName = databaseName; this._agentId = agentId; this._conversationId = conversationId; this._options = options; this._changeVector = changeVector; } _changeVector; get changeVector() { return this._changeVector; } get id() { if (!this._conversationId || this._conversationId.endsWith("/") || this._conversationId.endsWith("|")) { throwError("InvalidOperationException", "This is a new conversation, the ID wasn't set yet, you have to call run() first"); } return this._conversationId; } requiredActions() { if (!this._actionRequests) { throwError("InvalidOperationException", "You must call run() first."); } return this._actionRequests; } addActionResponse(toolId, actionResponse) { if (!toolId) throwError("InvalidArgumentException", "toolId cannot be empty"); if (actionResponse == null) throwError("InvalidArgumentException", `Action response for '${toolId}' cannot be null.`); if (typeof actionResponse === "string") { this._actionResponses.push({ toolId, content: actionResponse }); return; } this._actionResponses.push({ toolId, content: JSON.stringify(actionResponse) }); } setUserPrompt(userPrompt) { if (!userPrompt) throwError("InvalidArgumentException", "userPrompt cannot be empty"); this._userPrompt = userPrompt; } handle(actionName, action, aiHandleError = AiHandleErrorStrategy.SendErrorsToModel) { const wrappedAction = action.length === 1 ? (_request, args) => action(args) : action; this.receive(actionName, async (req, args) => { const result = await wrappedAction(req, args); this.addActionResponse(req.toolId, result); }, aiHandleError); } receive(actionName, action, aiHandleError = AiHandleErrorStrategy.SendErrorsToModel) { if (this._invocations.has(actionName)) { throwError("InvalidOperationException", `Action '${actionName}' already exists.`); } const inv = async (request) => { try { const args = this._parseArgs(request.arguments); await action(request, args); } catch (e) { if (aiHandleError === AiHandleErrorStrategy.SendErrorsToModel) { this.addActionResponse(request.toolId, this._createErrorMessageForLlm(e)); } else { throw e; } } }; this._invocations.set(actionName, inv); } async run() { // eslint-disable-next-line no-constant-condition while (true) { const r = await this._runInternal(); if (r.status === "Done") { return r; } if (!this._actionRequests || this._actionRequests.length === 0) { throwError("InvalidOperationException", `There are no action requests to process, but Status was ${r.status}, should not be possible.`); } for (const action of this._actionRequests) { const invocation = this._invocations.get(action.name); if (invocation) { await invocation(action); } else if (this.onUnhandledAction) { await this.onUnhandledAction({ sender: this, action: action }); } else { throwError("InvalidOperationException", `There is no action defined for action '${action.name}' on agent '${this._agentId}' (${this._conversationId}), ` + `but it was invoked by the model with: ${action.arguments}. ` + `Did you forget to call receive() or handle()? You can also handle unexpected action invocations using the onUnhandledAction event.`); } } if (this._actionResponses.length === 0) { return r; // ActionsRequired, nothing to send back yet } } } /** * Executes one "turn" of the conversation with streaming enabled. * Streams the specified property's value in real-time by invoking the callback with each chunk. * * @param streamPropertyPath - The property path of the answer to stream (e.g., "suggestedReward") * @param streamCallback - Callback invoked with each streamed chunk * @returns A promise that resolves to the full answer after streaming completes * * @example * ```typescript * const answer = await chat.stream<TAnswer>("propertyName", async (chunk) => { * console.log("Received chunk:", chunk); * }); * ``` */ async stream(streamPropertyPath, streamCallback) { if (StringUtil.isNullOrEmpty(streamPropertyPath)) { throwError("InvalidArgumentException", "streamPropertyPath cannot be empty"); } if (!streamCallback) { throwError("InvalidArgumentException", "streamCallback cannot be null"); } // eslint-disable-next-line no-constant-condition while (true) { const r = await this._runInternal(streamPropertyPath, streamCallback); if (r.status === "Done") { return r; } if (!this._actionRequests || this._actionRequests.length === 0) { throwError("InvalidOperationException", `There are no action requests to process, but Status was ${r.status}, should not be possible.`); } for (const action of this._actionRequests) { const invocation = this._invocations.get(action.name); if (invocation) { await invocation(action); } else if (this.onUnhandledAction) { await this.onUnhandledAction({ sender: this, action: action }); } else { throwError("InvalidOperationException", `There is no action defined for action '${action.name}' on agent '${this._agentId}' (${this._conversationId}), ` + `but it was invoked by the model with: ${action.arguments}. ` + `Did you forget to call receive() or handle()? You can also handle unexpected action invocations using the onUnhandledAction event.`); } } if (this._actionResponses.length === 0) { return r; // ActionsRequired, nothing to send back yet } } } async _runInternal(streamPropertyPath, streamCallback) { if (this._actionRequests != null && !this._userPrompt && this._actionResponses.length === 0) { return { status: "Done" }; } const op = new RunConversationOperation(this._agentId, this._conversationId, this._userPrompt, this._actionResponses, this._options, this._changeVector, streamPropertyPath, streamCallback); try { const res = await this._store.maintenance.send(op); this._changeVector = res.changeVector; this._conversationId = res.conversationId; this._actionRequests = res.actionRequests; return { answer: res.response, status: (this._actionRequests.length > 0) ? "ActionRequired" : "Done" }; } finally { // clear prompt and responses after running the conversation this._userPrompt = undefined; this._actionResponses.length = 0; } } _parseArgs(argsJson) { // If TArgs is string, return as-is try { return JSON.parse(argsJson); } catch { // fall back to raw string when not a JSON return argsJson; } } _createErrorMessageForLlm(e) { const lines = []; let curr = e; let indent = 0; while (curr) { const pad = " ".repeat(indent); const name = curr?.name || curr?.constructor?.name || "Error"; const msg = curr?.message || String(curr); lines.push(`${pad}${name}: ${msg}`); curr = curr?.cause; // Node 20 supports error cause indent++; } return lines.join("\n"); } } //# sourceMappingURL=AiConversation.js.map