Skip to content

Commit

Permalink
fix (ai/core): throw error when accessing output when no output is de…
Browse files Browse the repository at this point in the history
…fined in generateText (#4260)
  • Loading branch information
lgrammel authored Jan 3, 2025
1 parent ed4cde1 commit 0823899
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 17 deletions.
5 changes: 5 additions & 0 deletions .changeset/orange-plums-know.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'ai': patch
---

fix (ai/core): throw error when accessing output when no output is defined in generateText (breaking/experimental)
6 changes: 4 additions & 2 deletions packages/ai/core/generate-text/generate-text.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1177,7 +1177,7 @@ describe('options.messages', () => {

describe('options.output', () => {
describe('no output', () => {
it('should have undefined output', async () => {
it('should throw error when accessing output', async () => {
const result = await generateText({
model: new MockLanguageModelV1({
doGenerate: async () => ({
Expand All @@ -1188,7 +1188,9 @@ describe('options.output', () => {
prompt: 'prompt',
});

expect(result.experimental_output).toBeUndefined();
expect(() => {
result.experimental_output;
}).toThrow('No output specified');
});
});

Expand Down
38 changes: 24 additions & 14 deletions packages/ai/core/generate-text/generate-text.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { createIdGenerator } from '@ai-sdk/provider-utils';
import { Tracer } from '@opentelemetry/api';
import { InvalidArgumentError, ToolExecutionError } from '../../errors';
import { InvalidArgumentError } from '../../errors/invalid-argument-error';
import { NoOutputSpecifiedError } from '../../errors/no-output-specified-error';
import { ToolExecutionError } from '../../errors/tool-execution-error';
import { CoreAssistantMessage, CoreMessage, CoreToolMessage } from '../prompt';
import { CallSettings } from '../prompt/call-settings';
import { convertToLanguageModelPrompt } from '../prompt/convert-to-language-model-prompt';
Expand Down Expand Up @@ -511,16 +513,16 @@ A function that attempts to repair a tool call that failed to parse.

return new DefaultGenerateTextResult({
text,
output:
output == null
? (undefined as never)
: output.parseOutput(
{ text },
{
response: currentModelResponse.response,
usage,
},
),
outputResolver: () => {
if (output == null) {
throw new NoOutputSpecifiedError();
}

return output.parseOutput(
{ text },
{ response: currentModelResponse.response, usage },
);
},
toolCalls: currentToolCalls,
toolResults: currentToolResults,
finishReason: currentModelResponse.finishReason,
Expand Down Expand Up @@ -649,7 +651,8 @@ class DefaultGenerateTextResult<TOOLS extends Record<string, CoreTool>, OUTPUT>
>['experimental_providerMetadata'];
readonly response: GenerateTextResult<TOOLS, OUTPUT>['response'];
readonly request: GenerateTextResult<TOOLS, OUTPUT>['request'];
readonly experimental_output: GenerateTextResult<

private readonly outputResolver: () => GenerateTextResult<
TOOLS,
OUTPUT
>['experimental_output'];
Expand All @@ -669,7 +672,10 @@ class DefaultGenerateTextResult<TOOLS extends Record<string, CoreTool>, OUTPUT>
>['experimental_providerMetadata'];
response: GenerateTextResult<TOOLS, OUTPUT>['response'];
request: GenerateTextResult<TOOLS, OUTPUT>['request'];
output: GenerateTextResult<TOOLS, OUTPUT>['experimental_output'];
outputResolver: () => GenerateTextResult<
TOOLS,
OUTPUT
>['experimental_output'];
}) {
this.text = options.text;
this.toolCalls = options.toolCalls;
Expand All @@ -682,6 +688,10 @@ class DefaultGenerateTextResult<TOOLS extends Record<string, CoreTool>, OUTPUT>
this.steps = options.steps;
this.experimental_providerMetadata = options.providerMetadata;
this.logprobs = options.logprobs;
this.experimental_output = options.output;
this.outputResolver = options.outputResolver;
}

get experimental_output() {
return this.outputResolver();
}
}
29 changes: 28 additions & 1 deletion packages/ai/core/generate-text/stream-text.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { createMockServerResponse } from '../test/mock-server-response';
import { MockTracer } from '../test/mock-tracer';
import { mockValues } from '../test/mock-values';
import { CoreTool, tool } from '../tool/tool';
import { object } from './output';
import { object, text } from './output';
import { StepResult } from './step-result';
import { streamText } from './stream-text';
import { StreamTextResult, TextStreamPart } from './stream-text-result';
Expand Down Expand Up @@ -3588,6 +3588,33 @@ describe('options.output', () => {
});
});

describe('text output', () => {
it('should send partial output stream', async () => {
const result = streamText({
model: createTestModel({
stream: convertArrayToReadableStream([
{ type: 'text-delta', textDelta: 'Hello, ' },
{ type: 'text-delta', textDelta: ',' },
{ type: 'text-delta', textDelta: ' world!' },
{
type: 'finish',
finishReason: 'stop',
usage: { completionTokens: 10, promptTokens: 3 },
},
]),
}),
experimental_output: text(),
prompt: 'prompt',
});

expect(
await convertAsyncIterableToArray(
result.experimental_partialOutputStream,
),
).toStrictEqual(['Hello, ', 'Hello, ,', 'Hello, , world!']);
});
});

describe('object output', () => {
it('should set responseFormat to json and send schema as part of the responseFormat', async () => {
let callOptions!: LanguageModelV1CallOptions;
Expand Down

0 comments on commit 0823899

Please sign in to comment.