-
Notifications
You must be signed in to change notification settings - Fork 16.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(Anthropic Chat Model Node): Fetch models dynamically & support t…
…hinking (#13543)
- Loading branch information
1 parent
615a42a
commit 461df37
Showing
5 changed files
with
316 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
105 changes: 105 additions & 0 deletions
105
...es/@n8n/nodes-langchain/nodes/llms/LMChatAnthropic/methods/__tests__/searchModels.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import type { ILoadOptionsFunctions } from 'n8n-workflow'; | ||
|
||
import { searchModels, type AnthropicModel } from '../searchModels'; | ||
|
||
describe('searchModels', () => { | ||
let mockContext: jest.Mocked<ILoadOptionsFunctions>; | ||
|
||
const mockModels: AnthropicModel[] = [ | ||
{ | ||
id: 'claude-3-opus-20240229', | ||
display_name: 'Claude 3 Opus', | ||
type: 'model', | ||
created_at: '2024-02-29T00:00:00Z', | ||
}, | ||
{ | ||
id: 'claude-3-sonnet-20240229', | ||
display_name: 'Claude 3 Sonnet', | ||
type: 'model', | ||
created_at: '2024-02-29T00:00:00Z', | ||
}, | ||
{ | ||
id: 'claude-3-haiku-20240307', | ||
display_name: 'Claude 3 Haiku', | ||
type: 'model', | ||
created_at: '2024-03-07T00:00:00Z', | ||
}, | ||
{ | ||
id: 'claude-2.1', | ||
display_name: 'Claude 2.1', | ||
type: 'model', | ||
created_at: '2023-11-21T00:00:00Z', | ||
}, | ||
{ | ||
id: 'claude-2.0', | ||
display_name: 'Claude 2.0', | ||
type: 'model', | ||
created_at: '2023-07-11T00:00:00Z', | ||
}, | ||
]; | ||
|
||
beforeEach(() => { | ||
mockContext = { | ||
helpers: { | ||
httpRequestWithAuthentication: jest.fn().mockResolvedValue({ | ||
data: mockModels, | ||
}), | ||
}, | ||
} as unknown as jest.Mocked<ILoadOptionsFunctions>; | ||
}); | ||
|
||
afterEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
it('should fetch models from Anthropic API', async () => { | ||
const result = await searchModels.call(mockContext); | ||
|
||
expect(mockContext.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('anthropicApi', { | ||
url: 'https://api.anthropic.com/v1/models', | ||
headers: { | ||
'anthropic-version': '2023-06-01', | ||
}, | ||
}); | ||
expect(result.results).toHaveLength(5); | ||
}); | ||
|
||
it('should sort models by created_at date, most recent first', async () => { | ||
const result = await searchModels.call(mockContext); | ||
const sortedResults = result.results; | ||
|
||
expect(sortedResults[0].value).toBe('claude-3-haiku-20240307'); | ||
expect(sortedResults[1].value).toBe('claude-3-opus-20240229'); | ||
expect(sortedResults[2].value).toBe('claude-3-sonnet-20240229'); | ||
expect(sortedResults[3].value).toBe('claude-2.1'); | ||
expect(sortedResults[4].value).toBe('claude-2.0'); | ||
}); | ||
|
||
it('should filter models based on search term', async () => { | ||
const result = await searchModels.call(mockContext, 'claude-3'); | ||
|
||
expect(result.results).toHaveLength(3); | ||
expect(result.results).toEqual([ | ||
{ name: 'Claude 3 Haiku', value: 'claude-3-haiku-20240307' }, | ||
{ name: 'Claude 3 Opus', value: 'claude-3-opus-20240229' }, | ||
{ name: 'Claude 3 Sonnet', value: 'claude-3-sonnet-20240229' }, | ||
]); | ||
}); | ||
|
||
it('should handle case-insensitive search', async () => { | ||
const result = await searchModels.call(mockContext, 'CLAUDE-3'); | ||
|
||
expect(result.results).toHaveLength(3); | ||
expect(result.results).toEqual([ | ||
{ name: 'Claude 3 Haiku', value: 'claude-3-haiku-20240307' }, | ||
{ name: 'Claude 3 Opus', value: 'claude-3-opus-20240229' }, | ||
{ name: 'Claude 3 Sonnet', value: 'claude-3-sonnet-20240229' }, | ||
]); | ||
}); | ||
|
||
it('should handle when no models match the filter', async () => { | ||
const result = await searchModels.call(mockContext, 'nonexistent-model'); | ||
|
||
expect(result.results).toHaveLength(0); | ||
}); | ||
}); |
60 changes: 60 additions & 0 deletions
60
packages/@n8n/nodes-langchain/nodes/llms/LMChatAnthropic/methods/searchModels.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import type { | ||
ILoadOptionsFunctions, | ||
INodeListSearchItems, | ||
INodeListSearchResult, | ||
} from 'n8n-workflow'; | ||
|
||
export interface AnthropicModel { | ||
id: string; | ||
display_name: string; | ||
type: string; | ||
created_at: string; | ||
} | ||
|
||
export async function searchModels( | ||
this: ILoadOptionsFunctions, | ||
filter?: string, | ||
): Promise<INodeListSearchResult> { | ||
const response = (await this.helpers.httpRequestWithAuthentication.call(this, 'anthropicApi', { | ||
url: 'https://api.anthropic.com/v1/models', | ||
headers: { | ||
'anthropic-version': '2023-06-01', | ||
}, | ||
})) as { data: AnthropicModel[] }; | ||
|
||
const models = response.data || []; | ||
let results: INodeListSearchItems[] = []; | ||
|
||
if (filter) { | ||
for (const model of models) { | ||
if (model.id.toLowerCase().includes(filter.toLowerCase())) { | ||
results.push({ | ||
name: model.display_name, | ||
value: model.id, | ||
}); | ||
} | ||
} | ||
} else { | ||
results = models.map((model) => ({ | ||
name: model.display_name, | ||
value: model.id, | ||
})); | ||
} | ||
|
||
// Sort models with more recent ones first (claude-3 before claude-2) | ||
results = results.sort((a, b) => { | ||
const modelA = models.find((m) => m.id === a.value); | ||
const modelB = models.find((m) => m.id === b.value); | ||
|
||
if (!modelA || !modelB) return 0; | ||
|
||
// Sort by created_at date, most recent first | ||
const dateA = new Date(modelA.created_at); | ||
const dateB = new Date(modelB.created_at); | ||
return dateB.getTime() - dateA.getTime(); | ||
}); | ||
|
||
return { | ||
results, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.