mirror of
https://github.com/geoffsee/open-gsio.git
synced 2025-09-08 22:56:46 +00:00
adds eslint
This commit is contained in:

committed by
Geoff Seemueller

parent
9698fc6f3b
commit
02c3253343
@@ -1,4 +1,5 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
|
||||
import { AssistantSdk } from '../assistant-sdk.ts';
|
||||
import { Utils } from '../utils.ts';
|
||||
|
||||
@@ -6,17 +7,17 @@ import { Utils } from '../utils.ts';
|
||||
vi.mock('../utils', () => ({
|
||||
Utils: {
|
||||
selectEquitably: vi.fn(),
|
||||
getCurrentDate: vi.fn()
|
||||
}
|
||||
getCurrentDate: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../prompts/few_shots', () => ({
|
||||
default: {
|
||||
'a': 'A1',
|
||||
'question1': 'answer1',
|
||||
'question2': 'answer2',
|
||||
'question3': 'answer3'
|
||||
}
|
||||
a: 'A1',
|
||||
question1: 'answer1',
|
||||
question2: 'answer2',
|
||||
question3: 'answer3',
|
||||
},
|
||||
}));
|
||||
|
||||
describe('AssistantSdk', () => {
|
||||
@@ -37,63 +38,62 @@ describe('AssistantSdk', () => {
|
||||
it('should return a prompt with default values when minimal params are provided', () => {
|
||||
// Mock dependencies
|
||||
vi.mocked(Utils.selectEquitably).mockReturnValue({
|
||||
'question1': 'answer1',
|
||||
'question2': 'answer2'
|
||||
question1: 'answer1',
|
||||
question2: 'answer2',
|
||||
});
|
||||
vi.mocked(Utils.getCurrentDate).mockReturnValue('2023-01-01T12:30:45Z');
|
||||
|
||||
const prompt = AssistantSdk.getAssistantPrompt({});
|
||||
|
||||
expect(prompt).toContain('# Assistant Knowledge');
|
||||
expect(prompt).toContain('2023-01-01');
|
||||
expect(prompt).toContain('- **Web Host**: geoff.seemueller.io');
|
||||
expect(prompt).toContain('- **User Location**: Unknown');
|
||||
expect(prompt).toContain('- **Timezone**: UTC');
|
||||
expect(prompt).not.toContain('- **Response Limit**:');
|
||||
expect(prompt).toContain('### Date: ');
|
||||
expect(prompt).toContain('### Web Host: ');
|
||||
expect(prompt).toContain('### User Location: ');
|
||||
expect(prompt).toContain('### Timezone: ');
|
||||
});
|
||||
|
||||
it('should include maxTokens when provided', () => {
|
||||
// Mock dependencies
|
||||
vi.mocked(Utils.selectEquitably).mockReturnValue({
|
||||
'question1': 'answer1',
|
||||
'question2': 'answer2'
|
||||
question1: 'answer1',
|
||||
question2: 'answer2',
|
||||
});
|
||||
vi.mocked(Utils.getCurrentDate).mockReturnValue('2023-01-01T12:30:45Z');
|
||||
|
||||
const prompt = AssistantSdk.getAssistantPrompt({ maxTokens: 1000 });
|
||||
|
||||
expect(prompt).toContain('- **Response Limit**: 1000 tokens (maximum)');
|
||||
expect(prompt).toContain('Max Response Length: 1000 tokens (maximum)');
|
||||
});
|
||||
|
||||
it('should use provided userTimezone and userLocation', () => {
|
||||
// Mock dependencies
|
||||
vi.mocked(Utils.selectEquitably).mockReturnValue({
|
||||
'question1': 'answer1',
|
||||
'question2': 'answer2'
|
||||
question1: 'answer1',
|
||||
question2: 'answer2',
|
||||
});
|
||||
vi.mocked(Utils.getCurrentDate).mockReturnValue('2023-01-01T12:30:45Z');
|
||||
|
||||
const prompt = AssistantSdk.getAssistantPrompt({
|
||||
userTimezone: 'America/New_York',
|
||||
userLocation: 'New York, USA'
|
||||
userLocation: 'New York, USA',
|
||||
});
|
||||
|
||||
expect(prompt).toContain('- **User Location**: New York, USA');
|
||||
expect(prompt).toContain('- **Timezone**: America/New_York');
|
||||
expect(prompt).toContain('### User Location: New York, USA');
|
||||
expect(prompt).toContain('### Timezone: America/New_York');
|
||||
});
|
||||
|
||||
it('should use current date when Utils.getCurrentDate is not available', () => {
|
||||
// Mock dependencies
|
||||
vi.mocked(Utils.selectEquitably).mockReturnValue({
|
||||
'question1': 'answer1',
|
||||
'question2': 'answer2'
|
||||
question1: 'answer1',
|
||||
question2: 'answer2',
|
||||
});
|
||||
vi.mocked(Utils.getCurrentDate).mockReturnValue(undefined);
|
||||
|
||||
const prompt = AssistantSdk.getAssistantPrompt({});
|
||||
|
||||
// Instead of checking for a specific date, just verify that a date is included
|
||||
expect(prompt).toMatch(/- \*\*Date\*\*: \d{4}-\d{2}-\d{2} \d{1,2}:\d{2} \d{1,2}s/);
|
||||
expect(prompt).toMatch(/### Date: \d{4}-\d{2}-\d{2} \d{1,2}:\d{2} \d{1,2}s/);
|
||||
});
|
||||
|
||||
it('should use few_shots directly when Utils.selectEquitably is not available', () => {
|
||||
@@ -114,7 +114,7 @@ describe('AssistantSdk', () => {
|
||||
it('should format fewshots correctly', () => {
|
||||
const fewshots = {
|
||||
'What is the capital of France?': 'Paris is the capital of France.',
|
||||
'How do I make pasta?': 'Boil water, add pasta, cook until al dente.'
|
||||
'How do I make pasta?': 'Boil water, add pasta, cook until al dente.',
|
||||
};
|
||||
|
||||
const result = AssistantSdk.useFewshots(fewshots);
|
||||
@@ -129,12 +129,12 @@ describe('AssistantSdk', () => {
|
||||
|
||||
it('should respect the limit parameter', () => {
|
||||
const fewshots = {
|
||||
'Q1': 'A1',
|
||||
'Q2': 'A2',
|
||||
'Q3': 'A3',
|
||||
'Q4': 'A4',
|
||||
'Q5': 'A5',
|
||||
'Q6': 'A6'
|
||||
Q1: 'A1',
|
||||
Q2: 'A2',
|
||||
Q3: 'A3',
|
||||
Q4: 'A4',
|
||||
Q5: 'A5',
|
||||
Q6: 'A6',
|
||||
};
|
||||
|
||||
const result = AssistantSdk.useFewshots(fewshots, 3);
|
||||
|
@@ -1,26 +1,27 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { ChatSdk } from '../chat-sdk.ts';
|
||||
import { AssistantSdk } from '../assistant-sdk.ts';
|
||||
|
||||
import Message from '../../models/Message.ts';
|
||||
import { ProviderRepository } from '../../providers/_ProviderRepository';
|
||||
import { AssistantSdk } from '../assistant-sdk.ts';
|
||||
import { ChatSdk } from '../chat-sdk.ts';
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock('../assistant-sdk', () => ({
|
||||
AssistantSdk: {
|
||||
getAssistantPrompt: vi.fn()
|
||||
}
|
||||
getAssistantPrompt: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../../models/Message', () => ({
|
||||
default: {
|
||||
create: vi.fn((message) => message)
|
||||
}
|
||||
create: vi.fn(message => message),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../../providers/_ProviderRepository', () => ({
|
||||
ProviderRepository: {
|
||||
getModelFamily: vi.fn()
|
||||
}
|
||||
getModelFamily: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('ChatSdk', () => {
|
||||
@@ -37,11 +38,11 @@ describe('ChatSdk', () => {
|
||||
|
||||
expect(Message.create).toHaveBeenCalledWith({
|
||||
role: 'assistant',
|
||||
content: ''
|
||||
content: '',
|
||||
});
|
||||
expect(result).toEqual({
|
||||
role: 'assistant',
|
||||
content: ''
|
||||
content: '',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -49,7 +50,7 @@ describe('ChatSdk', () => {
|
||||
describe('handleChatRequest', () => {
|
||||
it('should return a 400 response if no messages are provided', async () => {
|
||||
const request = {
|
||||
json: vi.fn().mockResolvedValue({ messages: [] })
|
||||
json: vi.fn().mockResolvedValue({ messages: [] }),
|
||||
};
|
||||
const ctx = {
|
||||
openai: {},
|
||||
@@ -58,9 +59,9 @@ describe('ChatSdk', () => {
|
||||
env: {
|
||||
SERVER_COORDINATOR: {
|
||||
idFromName: vi.fn(),
|
||||
get: vi.fn()
|
||||
}
|
||||
}
|
||||
get: vi.fn(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const response = await ChatSdk.handleChatRequest(request as any, ctx as any);
|
||||
@@ -72,7 +73,7 @@ describe('ChatSdk', () => {
|
||||
it('should save stream data and return a response with streamUrl', async () => {
|
||||
const streamId = 'test-uuid';
|
||||
vi.stubGlobal('crypto', {
|
||||
randomUUID: vi.fn().mockReturnValue(streamId)
|
||||
randomUUID: vi.fn().mockReturnValue(streamId),
|
||||
});
|
||||
|
||||
const messages = [{ role: 'user', content: 'Hello' }];
|
||||
@@ -80,12 +81,12 @@ describe('ChatSdk', () => {
|
||||
const conversationId = 'conv-123';
|
||||
|
||||
const request = {
|
||||
json: vi.fn().mockResolvedValue({ messages, model, conversationId })
|
||||
json: vi.fn().mockResolvedValue({ messages, model, conversationId }),
|
||||
};
|
||||
|
||||
const saveStreamData = vi.fn();
|
||||
const durableObject = {
|
||||
saveStreamData
|
||||
saveStreamData,
|
||||
};
|
||||
|
||||
const ctx = {
|
||||
@@ -95,9 +96,9 @@ describe('ChatSdk', () => {
|
||||
env: {
|
||||
SERVER_COORDINATOR: {
|
||||
idFromName: vi.fn().mockReturnValue('object-id'),
|
||||
get: vi.fn().mockReturnValue(durableObject)
|
||||
}
|
||||
}
|
||||
get: vi.fn().mockReturnValue(durableObject),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const response = await ChatSdk.handleChatRequest(request as any, ctx as any);
|
||||
@@ -105,12 +106,9 @@ describe('ChatSdk', () => {
|
||||
|
||||
expect(ctx.env.SERVER_COORDINATOR.idFromName).toHaveBeenCalledWith('stream-index');
|
||||
expect(ctx.env.SERVER_COORDINATOR.get).toHaveBeenCalledWith('object-id');
|
||||
expect(saveStreamData).toHaveBeenCalledWith(
|
||||
streamId,
|
||||
expect.stringContaining(model)
|
||||
);
|
||||
expect(saveStreamData).toHaveBeenCalledWith(streamId, expect.stringContaining(model));
|
||||
expect(responseBody).toEqual({
|
||||
streamUrl: `/api/streams/${streamId}`
|
||||
streamUrl: `/api/streams/${streamId}`,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -120,7 +118,7 @@ describe('ChatSdk', () => {
|
||||
const messages = [{ role: 'user', content: 'Hello' }];
|
||||
const dynamicMaxTokens = vi.fn().mockResolvedValue(500);
|
||||
const durableObject = {
|
||||
dynamicMaxTokens
|
||||
dynamicMaxTokens,
|
||||
};
|
||||
|
||||
const ctx = {
|
||||
@@ -128,9 +126,9 @@ describe('ChatSdk', () => {
|
||||
env: {
|
||||
SERVER_COORDINATOR: {
|
||||
idFromName: vi.fn().mockReturnValue('object-id'),
|
||||
get: vi.fn().mockReturnValue(durableObject)
|
||||
}
|
||||
}
|
||||
get: vi.fn().mockReturnValue(durableObject),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await ChatSdk.calculateMaxTokens(messages, ctx as any);
|
||||
@@ -150,7 +148,7 @@ describe('ChatSdk', () => {
|
||||
expect(AssistantSdk.getAssistantPrompt).toHaveBeenCalledWith({
|
||||
maxTokens: 1000,
|
||||
userTimezone: 'UTC',
|
||||
userLocation: 'USA/unknown'
|
||||
userLocation: 'USA/unknown',
|
||||
});
|
||||
expect(result).toBe('Assistant prompt');
|
||||
});
|
||||
@@ -160,15 +158,13 @@ describe('ChatSdk', () => {
|
||||
it('should build a message chain with system role for most models', async () => {
|
||||
vi.mocked(ProviderRepository.getModelFamily).mockResolvedValue('openai');
|
||||
|
||||
const messages = [
|
||||
{role: 'user', content: 'Hello'}
|
||||
];
|
||||
const messages = [{ role: 'user', content: 'Hello' }];
|
||||
|
||||
const opts = {
|
||||
systemPrompt: 'System prompt',
|
||||
assistantPrompt: 'Assistant prompt',
|
||||
toolResults: {role: 'tool', content: 'Tool result'},
|
||||
model: 'gpt-4'
|
||||
toolResults: { role: 'tool', content: 'Tool result' },
|
||||
model: 'gpt-4',
|
||||
};
|
||||
|
||||
const result = await ChatSdk.buildMessageChain(messages, opts as any);
|
||||
@@ -177,30 +173,28 @@ describe('ChatSdk', () => {
|
||||
expect(Message.create).toHaveBeenCalledTimes(3);
|
||||
expect(Message.create).toHaveBeenNthCalledWith(1, {
|
||||
role: 'system',
|
||||
content: 'System prompt'
|
||||
content: 'System prompt',
|
||||
});
|
||||
expect(Message.create).toHaveBeenNthCalledWith(2, {
|
||||
role: 'assistant',
|
||||
content: 'Assistant prompt'
|
||||
content: 'Assistant prompt',
|
||||
});
|
||||
expect(Message.create).toHaveBeenNthCalledWith(3, {
|
||||
role: 'user',
|
||||
content: 'Hello'
|
||||
content: 'Hello',
|
||||
});
|
||||
});
|
||||
|
||||
it('should build a message chain with assistant role for o1, gemma, claude, or google models', async () => {
|
||||
vi.mocked(ProviderRepository.getModelFamily).mockResolvedValue('claude');
|
||||
|
||||
const messages = [
|
||||
{ role: 'user', content: 'Hello' }
|
||||
];
|
||||
const messages = [{ role: 'user', content: 'Hello' }];
|
||||
|
||||
const opts = {
|
||||
systemPrompt: 'System prompt',
|
||||
assistantPrompt: 'Assistant prompt',
|
||||
toolResults: { role: 'tool', content: 'Tool result' },
|
||||
model: 'claude-3'
|
||||
model: 'claude-3',
|
||||
};
|
||||
|
||||
const result = await ChatSdk.buildMessageChain(messages, opts as any);
|
||||
@@ -209,7 +203,7 @@ describe('ChatSdk', () => {
|
||||
expect(Message.create).toHaveBeenCalledTimes(3);
|
||||
expect(Message.create).toHaveBeenNthCalledWith(1, {
|
||||
role: 'assistant',
|
||||
content: 'System prompt'
|
||||
content: 'System prompt',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -220,14 +214,14 @@ describe('ChatSdk', () => {
|
||||
{ role: 'user', content: 'Hello' },
|
||||
{ role: 'user', content: '' },
|
||||
{ role: 'user', content: ' ' },
|
||||
{ role: 'user', content: 'World' }
|
||||
{ role: 'user', content: 'World' },
|
||||
];
|
||||
|
||||
const opts = {
|
||||
systemPrompt: 'System prompt',
|
||||
assistantPrompt: 'Assistant prompt',
|
||||
toolResults: { role: 'tool', content: 'Tool result' },
|
||||
model: 'gpt-4'
|
||||
model: 'gpt-4',
|
||||
};
|
||||
|
||||
const result = await ChatSdk.buildMessageChain(messages, opts as any);
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
import { Utils } from '../utils.ts';
|
||||
|
||||
describe('Debug Utils.getSeason', () => {
|
||||
|
@@ -1,13 +1,14 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
|
||||
import handleStreamData from '../handleStreamData.ts';
|
||||
|
||||
describe('handleStreamData', () => {
|
||||
// Setup mocks
|
||||
const mockController = {
|
||||
enqueue: vi.fn()
|
||||
enqueue: vi.fn(),
|
||||
};
|
||||
const mockEncoder = {
|
||||
encode: vi.fn((str) => str)
|
||||
encode: vi.fn(str => str),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -16,75 +17,75 @@ describe('handleStreamData', () => {
|
||||
|
||||
it('should return early if data type is not "chat"', () => {
|
||||
const handler = handleStreamData(mockController as any, mockEncoder as any);
|
||||
|
||||
|
||||
handler({ type: 'not-chat', data: {} });
|
||||
|
||||
|
||||
expect(mockController.enqueue).not.toHaveBeenCalled();
|
||||
expect(mockEncoder.encode).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return early if data is undefined', () => {
|
||||
const handler = handleStreamData(mockController as any, mockEncoder as any);
|
||||
|
||||
|
||||
handler(undefined as any);
|
||||
|
||||
|
||||
expect(mockController.enqueue).not.toHaveBeenCalled();
|
||||
expect(mockEncoder.encode).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle content_block_start type data', () => {
|
||||
const handler = handleStreamData(mockController as any, mockEncoder as any);
|
||||
|
||||
|
||||
const data = {
|
||||
type: 'chat',
|
||||
data: {
|
||||
type: 'content_block_start',
|
||||
content_block: {
|
||||
type: 'text',
|
||||
text: 'Hello world'
|
||||
}
|
||||
}
|
||||
text: 'Hello world',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
handler(data);
|
||||
|
||||
|
||||
expect(mockController.enqueue).toHaveBeenCalledTimes(1);
|
||||
expect(mockEncoder.encode).toHaveBeenCalledWith(expect.stringContaining('Hello world'));
|
||||
|
||||
|
||||
const encodedData = mockEncoder.encode.mock.calls[0][0];
|
||||
const parsedData = JSON.parse(encodedData.split('data: ')[1]);
|
||||
|
||||
|
||||
expect(parsedData.type).toBe('chat');
|
||||
expect(parsedData.data.choices[0].delta.content).toBe('Hello world');
|
||||
});
|
||||
|
||||
it('should handle delta.text type data', () => {
|
||||
const handler = handleStreamData(mockController as any, mockEncoder as any);
|
||||
|
||||
|
||||
const data = {
|
||||
type: 'chat',
|
||||
data: {
|
||||
delta: {
|
||||
text: 'Hello world'
|
||||
}
|
||||
}
|
||||
text: 'Hello world',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
handler(data);
|
||||
|
||||
|
||||
expect(mockController.enqueue).toHaveBeenCalledTimes(1);
|
||||
expect(mockEncoder.encode).toHaveBeenCalledWith(expect.stringContaining('Hello world'));
|
||||
|
||||
|
||||
const encodedData = mockEncoder.encode.mock.calls[0][0];
|
||||
const parsedData = JSON.parse(encodedData.split('data: ')[1]);
|
||||
|
||||
|
||||
expect(parsedData.type).toBe('chat');
|
||||
expect(parsedData.data.choices[0].delta.content).toBe('Hello world');
|
||||
});
|
||||
|
||||
it('should handle choices[0].delta.content type data', () => {
|
||||
const handler = handleStreamData(mockController as any, mockEncoder as any);
|
||||
|
||||
|
||||
const data = {
|
||||
type: 'chat',
|
||||
data: {
|
||||
@@ -92,23 +93,23 @@ describe('handleStreamData', () => {
|
||||
{
|
||||
index: 0,
|
||||
delta: {
|
||||
content: 'Hello world'
|
||||
content: 'Hello world',
|
||||
},
|
||||
logprobs: null,
|
||||
finish_reason: null
|
||||
}
|
||||
]
|
||||
}
|
||||
finish_reason: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
handler(data);
|
||||
|
||||
|
||||
expect(mockController.enqueue).toHaveBeenCalledTimes(1);
|
||||
expect(mockEncoder.encode).toHaveBeenCalledWith(expect.stringContaining('Hello world'));
|
||||
|
||||
|
||||
const encodedData = mockEncoder.encode.mock.calls[0][0];
|
||||
const parsedData = JSON.parse(encodedData.split('data: ')[1]);
|
||||
|
||||
|
||||
expect(parsedData.type).toBe('chat');
|
||||
expect(parsedData.data.choices[0].delta.content).toBe('Hello world');
|
||||
expect(parsedData.data.choices[0].finish_reason).toBe(null);
|
||||
@@ -116,7 +117,7 @@ describe('handleStreamData', () => {
|
||||
|
||||
it('should pass through data with choices but no delta.content', () => {
|
||||
const handler = handleStreamData(mockController as any, mockEncoder as any);
|
||||
|
||||
|
||||
const data = {
|
||||
type: 'chat',
|
||||
data: {
|
||||
@@ -125,64 +126,66 @@ describe('handleStreamData', () => {
|
||||
index: 0,
|
||||
delta: {},
|
||||
logprobs: null,
|
||||
finish_reason: 'stop'
|
||||
}
|
||||
]
|
||||
}
|
||||
finish_reason: 'stop',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
handler(data);
|
||||
|
||||
|
||||
expect(mockController.enqueue).toHaveBeenCalledTimes(1);
|
||||
expect(mockEncoder.encode).toHaveBeenCalledWith(expect.stringContaining('"finish_reason":"stop"'));
|
||||
expect(mockEncoder.encode).toHaveBeenCalledWith(
|
||||
expect.stringContaining('"finish_reason":"stop"'),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return early for unrecognized data format', () => {
|
||||
const handler = handleStreamData(mockController as any, mockEncoder as any);
|
||||
|
||||
|
||||
const data = {
|
||||
type: 'chat',
|
||||
data: {
|
||||
// No recognized properties
|
||||
unrecognized: 'property'
|
||||
}
|
||||
unrecognized: 'property',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
handler(data);
|
||||
|
||||
|
||||
expect(mockController.enqueue).not.toHaveBeenCalled();
|
||||
expect(mockEncoder.encode).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should use custom transform function if provided', () => {
|
||||
const handler = handleStreamData(mockController as any, mockEncoder as any);
|
||||
|
||||
|
||||
const data = {
|
||||
type: 'chat',
|
||||
data: {
|
||||
original: 'data'
|
||||
}
|
||||
original: 'data',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
const transformFn = vi.fn().mockReturnValue({
|
||||
type: 'chat',
|
||||
data: {
|
||||
choices: [
|
||||
{
|
||||
delta: {
|
||||
content: 'Transformed content'
|
||||
content: 'Transformed content',
|
||||
},
|
||||
logprobs: null,
|
||||
finish_reason: null
|
||||
}
|
||||
]
|
||||
}
|
||||
finish_reason: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
handler(data, transformFn);
|
||||
|
||||
|
||||
expect(transformFn).toHaveBeenCalledWith(data);
|
||||
expect(mockController.enqueue).toHaveBeenCalledTimes(1);
|
||||
expect(mockEncoder.encode).toHaveBeenCalledWith(expect.stringContaining('Transformed content'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
|
||||
import { Utils } from '../utils.ts';
|
||||
|
||||
describe('Utils', () => {
|
||||
@@ -44,8 +45,8 @@ describe('Utils', () => {
|
||||
// Mock Intl.DateTimeFormat
|
||||
global.Intl.DateTimeFormat = vi.fn().mockReturnValue({
|
||||
resolvedOptions: vi.fn().mockReturnValue({
|
||||
timeZone: 'America/New_York'
|
||||
})
|
||||
timeZone: 'America/New_York',
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -102,10 +103,10 @@ describe('Utils', () => {
|
||||
|
||||
it('should select items equitably from multiple sources', () => {
|
||||
const sources = {
|
||||
a: { 'key1': 'value1', 'key2': 'value2' },
|
||||
b: { 'key3': 'value3', 'key4': 'value4' },
|
||||
c: { 'key5': 'value5', 'key6': 'value6' },
|
||||
d: { 'key7': 'value7', 'key8': 'value8' }
|
||||
a: { key1: 'value1', key2: 'value2' },
|
||||
b: { key3: 'value3', key4: 'value4' },
|
||||
c: { key5: 'value5', key6: 'value6' },
|
||||
d: { key7: 'value7', key8: 'value8' },
|
||||
};
|
||||
|
||||
const result = Utils.selectEquitably(sources, 4);
|
||||
@@ -117,10 +118,10 @@ describe('Utils', () => {
|
||||
|
||||
it('should handle itemCount greater than available items', () => {
|
||||
const sources = {
|
||||
a: { 'key1': 'value1' },
|
||||
b: { 'key2': 'value2' },
|
||||
a: { key1: 'value1' },
|
||||
b: { key2: 'value2' },
|
||||
c: {},
|
||||
d: {}
|
||||
d: {},
|
||||
};
|
||||
|
||||
const result = Utils.selectEquitably(sources, 5);
|
||||
@@ -135,7 +136,7 @@ describe('Utils', () => {
|
||||
a: {},
|
||||
b: {},
|
||||
c: {},
|
||||
d: {}
|
||||
d: {},
|
||||
};
|
||||
|
||||
const result = Utils.selectEquitably(sources, 5);
|
||||
@@ -148,7 +149,7 @@ describe('Utils', () => {
|
||||
it('should insert blank messages to maintain user/assistant alternation', () => {
|
||||
const messages = [
|
||||
{ role: 'user', content: 'Hello' },
|
||||
{ role: 'user', content: 'How are you?' }
|
||||
{ role: 'user', content: 'How are you?' },
|
||||
];
|
||||
|
||||
const result = Utils.normalizeWithBlanks(messages);
|
||||
@@ -160,9 +161,7 @@ describe('Utils', () => {
|
||||
});
|
||||
|
||||
it('should insert blank user message if first message is assistant', () => {
|
||||
const messages = [
|
||||
{ role: 'assistant', content: 'Hello, how can I help?' }
|
||||
];
|
||||
const messages = [{ role: 'assistant', content: 'Hello, how can I help?' }];
|
||||
|
||||
const result = Utils.normalizeWithBlanks(messages);
|
||||
|
||||
@@ -183,7 +182,7 @@ describe('Utils', () => {
|
||||
const messages = [
|
||||
{ role: 'user', content: 'Hello' },
|
||||
{ role: 'assistant', content: 'Hi there' },
|
||||
{ role: 'user', content: 'How are you?' }
|
||||
{ role: 'user', content: 'How are you?' },
|
||||
];
|
||||
|
||||
const result = Utils.normalizeWithBlanks(messages);
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { Utils } from "./utils";
|
||||
import few_shots from "../prompts/few_shots";
|
||||
import few_shots from '../prompts/few_shots';
|
||||
|
||||
import { Utils } from './utils';
|
||||
|
||||
export class AssistantSdk {
|
||||
static getAssistantPrompt(params: {
|
||||
@@ -7,11 +8,7 @@ export class AssistantSdk {
|
||||
userTimezone?: string;
|
||||
userLocation?: string;
|
||||
}): string {
|
||||
const {
|
||||
maxTokens,
|
||||
userTimezone = "UTC",
|
||||
userLocation = "",
|
||||
} = params;
|
||||
const { maxTokens, userTimezone = 'UTC', userLocation = '' } = params;
|
||||
// console.log('[DEBUG_LOG] few_shots:', JSON.stringify(few_shots));
|
||||
let selectedFewshots = Utils.selectEquitably?.(few_shots);
|
||||
// console.log('[DEBUG_LOG] selectedFewshots after Utils.selectEquitably:', JSON.stringify(selectedFewshots));
|
||||
@@ -20,18 +17,18 @@ export class AssistantSdk {
|
||||
// console.log('[DEBUG_LOG] selectedFewshots after fallback:', JSON.stringify(selectedFewshots));
|
||||
}
|
||||
const sdkDate = new Date().toISOString();
|
||||
const [currentDate] = sdkDate.includes("T") ? sdkDate.split("T") : [sdkDate];
|
||||
const [currentDate] = sdkDate.includes('T') ? sdkDate.split('T') : [sdkDate];
|
||||
const now = new Date();
|
||||
const formattedMinutes = String(now.getMinutes()).padStart(2, "0");
|
||||
const formattedMinutes = String(now.getMinutes()).padStart(2, '0');
|
||||
const currentTime = `${now.getHours()}:${formattedMinutes} ${now.getSeconds()}s`;
|
||||
|
||||
return `# Assistant Knowledge
|
||||
## Current Context
|
||||
### Date: ${currentDate} ${currentTime}
|
||||
### Web Host: open-gsio.seemueller.workers.dev
|
||||
${maxTokens ? `### Max Response Length: ${maxTokens} tokens (maximum)` : ""}
|
||||
${maxTokens ? `### Max Response Length: ${maxTokens} tokens (maximum)` : ''}
|
||||
### Lexicographical Format: Markdown
|
||||
### User Location: ${userLocation || "Unknown"}
|
||||
### User Location: ${userLocation || 'Unknown'}
|
||||
### Timezone: ${userTimezone}
|
||||
## Response Framework
|
||||
1. Use knowledge provided in the current context as the primary source of truth.
|
||||
@@ -51,11 +48,9 @@ Continuously monitor the evolving conversation. Dynamically adapt each response.
|
||||
static useFewshots(fewshots: Record<string, string>, limit = 5): string {
|
||||
return Object.entries(fewshots)
|
||||
.slice(0, limit)
|
||||
.map(
|
||||
([q, a], i) => {
|
||||
return `#### Example ${i + 1}\n**Human**: ${q}\n**Assistant**: ${a}`
|
||||
}
|
||||
)
|
||||
.join("\n---\n");
|
||||
.map(([q, a], i) => {
|
||||
return `#### Example ${i + 1}\n**Human**: ${q}\n**Assistant**: ${a}`;
|
||||
})
|
||||
.join('\n---\n');
|
||||
}
|
||||
}
|
||||
|
@@ -1,135 +1,131 @@
|
||||
import {OpenAI} from "openai";
|
||||
import Message from "../models/Message.ts";
|
||||
import {AssistantSdk} from "./assistant-sdk.ts";
|
||||
import type {Instance} from "mobx-state-tree";
|
||||
import {ProviderRepository} from "../providers/_ProviderRepository";
|
||||
import type { Instance } from 'mobx-state-tree';
|
||||
import { OpenAI } from 'openai';
|
||||
|
||||
import Message from '../models/Message.ts';
|
||||
import { ProviderRepository } from '../providers/_ProviderRepository';
|
||||
|
||||
import { AssistantSdk } from './assistant-sdk.ts';
|
||||
|
||||
export class ChatSdk {
|
||||
static async preprocess({
|
||||
messages,
|
||||
}) {
|
||||
// run processing on messages to generate events/context
|
||||
return Message.create({
|
||||
role: "assistant",
|
||||
content: "",
|
||||
});
|
||||
static async preprocess({ messages }) {
|
||||
// run processing on messages to generate events/context
|
||||
return Message.create({
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
});
|
||||
}
|
||||
|
||||
static async handleChatRequest(
|
||||
request: Request,
|
||||
ctx: {
|
||||
openai: OpenAI;
|
||||
systemPrompt: any;
|
||||
maxTokens: any;
|
||||
env: Env;
|
||||
},
|
||||
) {
|
||||
const streamId = crypto.randomUUID();
|
||||
const { messages, model, conversationId } = await request.json();
|
||||
|
||||
if (!messages?.length) {
|
||||
return new Response('No messages provided', { status: 400 });
|
||||
}
|
||||
|
||||
static async handleChatRequest(
|
||||
request: Request,
|
||||
ctx: {
|
||||
openai: OpenAI;
|
||||
systemPrompt: any;
|
||||
maxTokens: any;
|
||||
env: Env;
|
||||
const preprocessedContext = await ChatSdk.preprocess({
|
||||
messages,
|
||||
});
|
||||
// console.log(ctx.env)
|
||||
// console.log(ctx.env.SERVER_COORDINATOR);
|
||||
|
||||
const objectId = ctx.env.SERVER_COORDINATOR.idFromName('stream-index');
|
||||
const durableObject = ctx.env.SERVER_COORDINATOR.get(objectId);
|
||||
|
||||
await durableObject.saveStreamData(
|
||||
streamId,
|
||||
JSON.stringify({
|
||||
messages,
|
||||
model,
|
||||
conversationId,
|
||||
timestamp: Date.now(),
|
||||
systemPrompt: ctx.systemPrompt,
|
||||
preprocessedContext,
|
||||
}),
|
||||
);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
streamUrl: `/api/streams/${streamId}`,
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
) {
|
||||
const streamId = crypto.randomUUID();
|
||||
const {messages, model, conversationId} =
|
||||
await request.json();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (!messages?.length) {
|
||||
return new Response("No messages provided", {status: 400});
|
||||
}
|
||||
static async calculateMaxTokens(
|
||||
messages: any[],
|
||||
ctx: Record<string, any> & {
|
||||
env: Env;
|
||||
maxTokens: number;
|
||||
},
|
||||
) {
|
||||
const objectId = ctx.env.SERVER_COORDINATOR.idFromName('dynamic-token-counter');
|
||||
const durableObject = ctx.env.SERVER_COORDINATOR.get(objectId);
|
||||
return durableObject.dynamicMaxTokens(messages, ctx.maxTokens);
|
||||
}
|
||||
|
||||
const preprocessedContext = await ChatSdk.preprocess({
|
||||
messages,
|
||||
});
|
||||
// console.log(ctx.env)
|
||||
// console.log(ctx.env.SERVER_COORDINATOR);
|
||||
static buildAssistantPrompt({ maxTokens }) {
|
||||
return AssistantSdk.getAssistantPrompt({
|
||||
maxTokens,
|
||||
userTimezone: 'UTC',
|
||||
userLocation: 'USA/unknown',
|
||||
});
|
||||
}
|
||||
|
||||
const objectId = ctx.env.SERVER_COORDINATOR.idFromName("stream-index");
|
||||
const durableObject = ctx.env.SERVER_COORDINATOR.get(objectId);
|
||||
static async buildMessageChain(
|
||||
messages: any[],
|
||||
opts: {
|
||||
systemPrompt: any;
|
||||
assistantPrompt: string;
|
||||
toolResults: Instance<typeof Message>;
|
||||
model: any;
|
||||
env: Env;
|
||||
},
|
||||
) {
|
||||
const modelFamily = await ProviderRepository.getModelFamily(opts.model, opts.env);
|
||||
|
||||
const messagesToSend = [];
|
||||
|
||||
await durableObject.saveStreamData(
|
||||
streamId,
|
||||
JSON.stringify({
|
||||
messages,
|
||||
model,
|
||||
conversationId,
|
||||
timestamp: Date.now(),
|
||||
systemPrompt: ctx.systemPrompt,
|
||||
preprocessedContext
|
||||
}),
|
||||
);
|
||||
messagesToSend.push(
|
||||
Message.create({
|
||||
role:
|
||||
opts.model.includes('o1') ||
|
||||
opts.model.includes('gemma') ||
|
||||
modelFamily === 'claude' ||
|
||||
modelFamily === 'google'
|
||||
? 'assistant'
|
||||
: 'system',
|
||||
content: opts.systemPrompt.trim(),
|
||||
}),
|
||||
);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
streamUrl: `/api/streams/${streamId}`,
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
messagesToSend.push(
|
||||
Message.create({
|
||||
role: 'assistant',
|
||||
content: opts.assistantPrompt.trim(),
|
||||
}),
|
||||
);
|
||||
|
||||
static async calculateMaxTokens(
|
||||
messages: any[],
|
||||
ctx: Record<string, any> & {
|
||||
env: Env;
|
||||
maxTokens: number;
|
||||
},
|
||||
) {
|
||||
const objectId = ctx.env.SERVER_COORDINATOR.idFromName(
|
||||
"dynamic-token-counter",
|
||||
);
|
||||
const durableObject = ctx.env.SERVER_COORDINATOR.get(objectId);
|
||||
return durableObject.dynamicMaxTokens(messages, ctx.maxTokens);
|
||||
}
|
||||
messagesToSend.push(
|
||||
...messages
|
||||
.filter((message: any) => message.content?.trim())
|
||||
.map((message: any) => Message.create(message)),
|
||||
);
|
||||
|
||||
static buildAssistantPrompt({maxTokens}) {
|
||||
return AssistantSdk.getAssistantPrompt({
|
||||
maxTokens,
|
||||
userTimezone: "UTC",
|
||||
userLocation: "USA/unknown",
|
||||
});
|
||||
}
|
||||
|
||||
static async buildMessageChain(
|
||||
messages: any[],
|
||||
opts: {
|
||||
systemPrompt: any;
|
||||
assistantPrompt: string;
|
||||
toolResults: Instance<typeof Message>;
|
||||
model: any;
|
||||
env: Env;
|
||||
},
|
||||
) {
|
||||
const modelFamily = await ProviderRepository.getModelFamily(opts.model, opts.env)
|
||||
|
||||
const messagesToSend = [];
|
||||
|
||||
messagesToSend.push(
|
||||
Message.create({
|
||||
role:
|
||||
opts.model.includes("o1") ||
|
||||
opts.model.includes("gemma") ||
|
||||
modelFamily === "claude" ||
|
||||
modelFamily === "google"
|
||||
? "assistant"
|
||||
: "system",
|
||||
content: opts.systemPrompt.trim(),
|
||||
}),
|
||||
);
|
||||
|
||||
messagesToSend.push(
|
||||
Message.create({
|
||||
role: "assistant",
|
||||
content: opts.assistantPrompt.trim(),
|
||||
}),
|
||||
);
|
||||
|
||||
messagesToSend.push(
|
||||
...messages
|
||||
.filter((message: any) => message.content?.trim())
|
||||
.map((message: any) => Message.create(message)),
|
||||
);
|
||||
|
||||
return messagesToSend;
|
||||
}
|
||||
return messagesToSend;
|
||||
}
|
||||
}
|
||||
|
||||
export default ChatSdk;
|
||||
|
@@ -22,15 +22,9 @@ interface StreamResponse {
|
||||
};
|
||||
}
|
||||
|
||||
const handleStreamData = (
|
||||
controller: ReadableStreamDefaultController,
|
||||
encoder: TextEncoder,
|
||||
) => {
|
||||
return (
|
||||
data: StreamResponse,
|
||||
transformFn?: (data: StreamResponse) => StreamResponse,
|
||||
) => {
|
||||
if (!data?.type || data.type !== "chat") {
|
||||
const handleStreamData = (controller: ReadableStreamDefaultController, encoder: TextEncoder) => {
|
||||
return (data: StreamResponse, transformFn?: (data: StreamResponse) => StreamResponse) => {
|
||||
if (!data?.type || data.type !== 'chat') {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -39,17 +33,14 @@ const handleStreamData = (
|
||||
if (transformFn) {
|
||||
transformedData = transformFn(data);
|
||||
} else {
|
||||
if (
|
||||
data.data.type === "content_block_start" &&
|
||||
data.data.content_block?.type === "text"
|
||||
) {
|
||||
if (data.data.type === 'content_block_start' && data.data.content_block?.type === 'text') {
|
||||
transformedData = {
|
||||
type: "chat",
|
||||
type: 'chat',
|
||||
data: {
|
||||
choices: [
|
||||
{
|
||||
delta: {
|
||||
content: data.data.content_block.text || "",
|
||||
content: data.data.content_block.text || '',
|
||||
},
|
||||
logprobs: null,
|
||||
finish_reason: null,
|
||||
@@ -59,7 +50,7 @@ const handleStreamData = (
|
||||
};
|
||||
} else if (data.data.delta?.text) {
|
||||
transformedData = {
|
||||
type: "chat",
|
||||
type: 'chat',
|
||||
data: {
|
||||
choices: [
|
||||
{
|
||||
@@ -74,7 +65,7 @@ const handleStreamData = (
|
||||
};
|
||||
} else if (data.data.choices?.[0]?.delta?.content) {
|
||||
transformedData = {
|
||||
type: "chat",
|
||||
type: 'chat',
|
||||
data: {
|
||||
choices: [
|
||||
{
|
||||
@@ -95,9 +86,7 @@ const handleStreamData = (
|
||||
}
|
||||
}
|
||||
|
||||
controller.enqueue(
|
||||
encoder.encode(`data: ${JSON.stringify(transformedData)}\n\n`),
|
||||
);
|
||||
controller.enqueue(encoder.encode(`data: ${JSON.stringify(transformedData)}\n\n`));
|
||||
};
|
||||
};
|
||||
|
||||
|
@@ -1,20 +1,17 @@
|
||||
export class Utils {
|
||||
static getSeason(date: string): string {
|
||||
const hemispheres = {
|
||||
Northern: ["Winter", "Spring", "Summer", "Autumn"],
|
||||
Southern: ["Summer", "Autumn", "Winter", "Spring"],
|
||||
Northern: ['Winter', 'Spring', 'Summer', 'Autumn'],
|
||||
Southern: ['Summer', 'Autumn', 'Winter', 'Spring'],
|
||||
};
|
||||
const d = new Date(date);
|
||||
const month = d.getMonth();
|
||||
const day = d.getDate();
|
||||
const hemisphere = "Northern";
|
||||
const hemisphere = 'Northern';
|
||||
|
||||
if (month < 2 || (month === 2 && day <= 20) || month === 11)
|
||||
return hemispheres[hemisphere][0];
|
||||
if (month < 5 || (month === 5 && day <= 21))
|
||||
return hemispheres[hemisphere][1];
|
||||
if (month < 8 || (month === 8 && day <= 22))
|
||||
return hemispheres[hemisphere][2];
|
||||
if (month < 2 || (month === 2 && day <= 20) || month === 11) return hemispheres[hemisphere][0];
|
||||
if (month < 5 || (month === 5 && day <= 21)) return hemispheres[hemisphere][1];
|
||||
if (month < 8 || (month === 8 && day <= 22)) return hemispheres[hemisphere][2];
|
||||
return hemispheres[hemisphere][3];
|
||||
}
|
||||
static getTimezone(timezone) {
|
||||
@@ -30,7 +27,7 @@ export class Utils {
|
||||
|
||||
static isAssetUrl(url) {
|
||||
const { pathname } = new URL(url);
|
||||
return pathname.startsWith("/assets/");
|
||||
return pathname.startsWith('/assets/');
|
||||
}
|
||||
|
||||
static selectEquitably({ a, b, c, d }, itemCount = 9) {
|
||||
@@ -39,9 +36,7 @@ export class Utils {
|
||||
|
||||
let combinedItems = [];
|
||||
sources.forEach((source, index) => {
|
||||
combinedItems.push(
|
||||
...Object.keys(source).map((key) => ({ source: index, key })),
|
||||
);
|
||||
combinedItems.push(...Object.keys(source).map(key => ({ source: index, key })));
|
||||
});
|
||||
|
||||
combinedItems = combinedItems.sort(() => Math.random() - 0.5);
|
||||
@@ -60,37 +55,35 @@ export class Utils {
|
||||
return result;
|
||||
}
|
||||
|
||||
static normalizeWithBlanks<T extends Normalize.ChatMessage>(msgs: T[]): T[] {
|
||||
static normalizeWithBlanks<T extends NormalizeChatMessage>(msgs: T[]): T[] {
|
||||
const out: T[] = [];
|
||||
|
||||
// In local mode first turn expected to be user.
|
||||
let expected: Normalize.Role = "user";
|
||||
let expected: NormalizeRole = 'user';
|
||||
|
||||
for (const m of msgs) {
|
||||
while (m.role !== expected) {
|
||||
// Insert blanks to match expected sequence user/assistant/user...
|
||||
out.push(Normalize.makeBlank(expected) as T);
|
||||
expected = expected === "user" ? "assistant" : "user";
|
||||
out.push(makeNormalizeBlank(expected) as T);
|
||||
expected = expected === 'user' ? 'assistant' : 'user';
|
||||
}
|
||||
|
||||
out.push(m);
|
||||
expected = expected === "user" ? "assistant" : "user";
|
||||
expected = expected === 'user' ? 'assistant' : 'user';
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module Normalize {
|
||||
export type Role = "user" | "assistant";
|
||||
// Normalize module exports
|
||||
export type NormalizeRole = 'user' | 'assistant';
|
||||
|
||||
export interface ChatMessage extends Record<any, any> {
|
||||
role: Role;
|
||||
}
|
||||
|
||||
export const makeBlank = (role: Role): ChatMessage => ({
|
||||
role,
|
||||
content: ""
|
||||
});
|
||||
export interface NormalizeChatMessage extends Record<any, any> {
|
||||
role: NormalizeRole;
|
||||
}
|
||||
|
||||
export const makeNormalizeBlank = (role: NormalizeRole): NormalizeChatMessage => ({
|
||||
role,
|
||||
content: '',
|
||||
});
|
||||
|
Reference in New Issue
Block a user