Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 0 additions & 21 deletions LICENSE.MIT

This file was deleted.

45 changes: 28 additions & 17 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions strands-py-wasm/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ name = "strands-agents"
version = "2.0.0a1"
description = "A model-driven approach to building AI agents in just a few lines of code"
requires-python = ">=3.10"
license = "Apache-2.0 OR MIT"
license-files = ["LICENSE.APACHE", "LICENSE.MIT"]
license = "Apache-2.0"
license-files = ["LICENSE.APACHE"]
authors = [
{name = "AWS", email = "opensource@amazon.com"},
]
Expand Down
31 changes: 25 additions & 6 deletions strands-ts/src/models/__tests__/anthropic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -376,17 +376,36 @@ describe('AnthropicModel', () => {
await expect(collectIterator(provider.stream(messages))).rejects.toThrow('API Error')
})

it('maps overload error to ContextWindowOverflowError', async () => {
const mockClient = createMockClient(async function* () {
yield { type: 'ping' } // Satisfy linter require-yield
throw new Error('prompt is too long')
})
const provider = new AnthropicModel({ client: mockClient })
const messages = [new Message({ role: 'user', content: [new TextBlock('Hi')] })]

await expect(collectIterator(provider.stream(messages))).rejects.toThrow(ContextWindowOverflowError)
})

it.each([
'PROMPT IS TOO LONG: request exceeds context window',
'max_tokens exceeded',
'input too long',
'input is too long',
'input length exceeds context window',
'input and output tokens exceed your context limit',
])('maps context overflow error "%s" to ContextWindowOverflowError', async (message) => {
])('maps overflow phrase %p to ContextWindowOverflowError', async (phrase) => {
const mockClient = createMockClient(async function* () {
yield { type: 'ping' } // Satisfy linter require-yield
throw new Error(message)
yield { type: 'ping' }
throw new Error(phrase)
})
const provider = new AnthropicModel({ client: mockClient })
const messages = [new Message({ role: 'user', content: [new TextBlock('Hi')] })]

await expect(collectIterator(provider.stream(messages))).rejects.toThrow(ContextWindowOverflowError)
})

it('matches overflow phrases case-insensitively', async () => {
const mockClient = createMockClient(async function* () {
yield { type: 'ping' }
throw new Error('PROMPT IS TOO LONG: 200000 tokens')
})
const provider = new AnthropicModel({ client: mockClient })
const messages = [new Message({ role: 'user', content: [new TextBlock('Hi')] })]
Expand Down
7 changes: 5 additions & 2 deletions strands-ts/src/models/anthropic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import { logger } from '../logging/logger.js'
import { warnOnce } from '../logging/warn-once.js'
import { MODEL_DEFAULTS, defaultMaxTokensWarningMessage, defaultModelWarningMessage } from './defaults.js'

// Union of overflow phrases observed across Anthropic responses, matched
// case-insensitively. Kept in lowercase so the comparison is a single
// ``toLowerCase`` on the error message.
const CONTEXT_WINDOW_OVERFLOW_ERRORS = [
'prompt is too long',
'max_tokens exceeded',
Expand Down Expand Up @@ -277,8 +280,8 @@ export class AnthropicModel extends Model<AnthropicModelConfig> {
} catch (unknownError) {
const error = normalizeError(unknownError)

const lowerMessage = error.message.toLowerCase()
if (CONTEXT_WINDOW_OVERFLOW_ERRORS.some((msg) => lowerMessage.includes(msg))) {
const lowered = error.message.toLowerCase()
if (CONTEXT_WINDOW_OVERFLOW_ERRORS.some((msg) => lowered.includes(msg))) {
throw new ContextWindowOverflowError(error.message)
}

Expand Down
12 changes: 2 additions & 10 deletions strands-ts/src/models/openai/__tests__/chat.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1643,20 +1643,12 @@ describe('OpenAIModel', () => {
}).rejects.toThrow(ContextWindowOverflowError)
})

it.each([
'maximum context length exceeded',
'context_length_exceeded',
'too many tokens',
'context length',
'Input is too long for requested model',
'input length and `max_tokens` exceed context limit',
'too many total text bytes',
])('throws ContextWindowOverflowError for error message pattern "%s"', async (message) => {
it('throws ContextWindowOverflowError for error with message pattern', async () => {
const mockClient = {
chat: {
completions: {
create: vi.fn(async () => {
throw new Error(message)
throw new Error('maximum context length exceeded')
}),
},
},
Expand Down
34 changes: 34 additions & 0 deletions strands-ts/src/models/openai/__tests__/errors.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { describe, it, expect } from 'vitest'
import { classifyOpenAIError } from '../errors.js'

describe('classifyOpenAIError', () => {
it.each([
'maximum context length exceeded',
'context_length_exceeded',
'too many tokens',
'context length',
'Input is too long for requested model',
'input length and `max_tokens` exceed context limit',
'too many total text bytes',
])('classifies overflow phrase %p as contextOverflow', (phrase) => {
expect(classifyOpenAIError(new Error(phrase))).toBe('contextOverflow')
})

it('matches overflow phrases case-insensitively', () => {
expect(classifyOpenAIError(new Error('MAXIMUM CONTEXT LENGTH EXCEEDED'))).toBe('contextOverflow')
})

it('classifies a structured context_length_exceeded code as contextOverflow', () => {
const err = Object.assign(new Error('something opaque'), { code: 'context_length_exceeded' })
expect(classifyOpenAIError(err)).toBe('contextOverflow')
})

it('matches structured codes case-insensitively', () => {
const err = Object.assign(new Error('something opaque'), { code: 'Context_Length_Exceeded' })
expect(classifyOpenAIError(err)).toBe('contextOverflow')
})

it('returns undefined for unrelated errors', () => {
expect(classifyOpenAIError(new Error('a totally unrelated failure'))).toBeUndefined()
})
})
22 changes: 0 additions & 22 deletions strands-ts/src/models/openai/__tests__/responses.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -719,28 +719,6 @@ describe("OpenAIModel (api: 'responses')", () => {
).rejects.toBeInstanceOf(ContextWindowOverflowError)
})

it.each([
'maximum context length exceeded',
'context_length_exceeded',
'too many tokens',
'context length',
'Input is too long for requested model',
'input length and `max_tokens` exceed context limit',
'too many total text bytes',
])('wraps context overflow message pattern "%s" as ContextWindowOverflowError', async (message) => {
const client: any = {
responses: {
create: vi.fn(async () => {
throw new Error(message)
}),
},
}
const model = new OpenAIModel({ api: 'responses', client })
await expect(
collectIterator(model.stream([new Message({ role: 'user', content: [new TextBlock('x')] })]))
).rejects.toBeInstanceOf(ContextWindowOverflowError)
})

it('rethrows unknown errors untouched', async () => {
const client: any = {
responses: {
Expand Down
10 changes: 5 additions & 5 deletions strands-ts/src/models/openai/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@
*
* @see https://platform.openai.com/docs/guides/error-codes
*/
// Union of overflow phrases observed across OpenAI responses, matched
// case-insensitively. Lowercased here so the comparison is a single
// ``toLowerCase`` on the error message.
const CONTEXT_WINDOW_OVERFLOW_PATTERNS = [
'maximum context length',
'context_length_exceeded',
'too many tokens',
'context length',
'Input is too long for requested model',
'input is too long for requested model',
'input length and `max_tokens` exceed context limit',
'too many total text bytes',
]
Expand All @@ -41,10 +44,7 @@ export function classifyOpenAIError(err: Error & { status?: number; code?: strin
return 'throttling'
}

if (
code === 'context_length_exceeded' ||
CONTEXT_WINDOW_OVERFLOW_PATTERNS.some((pattern) => message.includes(pattern.toLowerCase()))
) {
if (code === 'context_length_exceeded' || CONTEXT_WINDOW_OVERFLOW_PATTERNS.some((p) => message.includes(p))) {
return 'contextOverflow'
}

Expand Down
Loading