Skip to content

Commit a62ae3c

Browse files
authored
Merge pull request #114 from ajcwebdev/new-llms
Add DeepSeek and Grok Models
2 parents da448a2 + d57735e commit a62ae3c

File tree

9 files changed

+282
-17
lines changed

9 files changed

+282
-17
lines changed

src/commander.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ program
5959
.option('--claude [model]', 'Use Claude for processing with optional model specification')
6060
.option('--gemini [model]', 'Use Gemini for processing with optional model specification')
6161
.option('--cohere [model]', 'Use Cohere for processing with optional model specification')
62-
.option('--mistral [model]', 'Use Mistral for processing')
62+
.option('--mistral [model]', 'Use Mistral for processing with optional model specification')
63+
.option('--deepseek [model]', 'Use DeepSeek for processing with optional model specification')
64+
.option('--grok [model]', 'Use Grok for processing with optional model specification')
6365
.option('--fireworks [model]', 'Use Fireworks AI for processing with optional model specification')
6466
.option('--together [model]', 'Use Together AI for processing with optional model specification')
6567
.option('--groq [model]', 'Use Groq for processing with optional model specification')
@@ -76,6 +78,7 @@ program
7678
.option('--geminiApiKey <key>', 'Specify Gemini API key (overrides .env variable)')
7779
.option('--cohereApiKey <key>', 'Specify Cohere API key (overrides .env variable)')
7880
.option('--mistralApiKey <key>', 'Specify Mistral API key (overrides .env variable)')
81+
.option('--deepseekApiKey <key>', 'Specify DeepSeek API key (overrides .env variable)')
7982
.option('--grokApiKey <key>', 'Specify GROK API key (overrides .env variable)')
8083
.option('--togetherApiKey <key>', 'Specify Together API key (overrides .env variable)')
8184
.option('--fireworksApiKey <key>', 'Specify Fireworks API key (overrides .env variable)')

src/llms/deepseek.ts

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// src/llms/deepseek.ts
2+
3+
/**
4+
* @file Provides integration with the DeepSeek API, compatible with the OpenAI Node.js SDK.
5+
* @packageDocumentation
6+
*/
7+
8+
import { env } from 'node:process'
9+
import { OpenAI } from 'openai'
10+
import { err, logLLMCost } from '../utils/logging'
11+
import { DEEPSEEK_MODELS } from '../utils/globals/llms'
12+
import type { DeepSeekModelType } from '../utils/types/llms'
13+
14+
/**
15+
* Main function to call DeepSeek API via an OpenAI-compatible SDK.
16+
*
17+
* @param prompt - The prompt or instructions to process.
18+
* @param transcript - The transcript text to be appended to the prompt.
19+
* @param model - (optional) The DeepSeek model to use (e.g., 'DEEPSEEK_CHAT' or 'DEEPSEEK_REASONER').
20+
* Defaults to 'DEEPSEEK_CHAT'.
21+
* @returns A Promise that resolves with the generated text from DeepSeek.
22+
* @throws Will throw an error if the DEEPSEEK_API_KEY environment variable is not set, or if no valid response is returned.
23+
*/
24+
export async function callDeepSeek(
25+
prompt: string,
26+
transcript: string,
27+
model: string = 'DEEPSEEK_CHAT'
28+
): Promise<string> {
29+
if (!env['DEEPSEEK_API_KEY']) {
30+
throw new Error('DEEPSEEK_API_KEY environment variable is not set. Please set it to your DeepSeek API key.')
31+
}
32+
33+
const openai = new OpenAI({
34+
baseURL: 'https://api.deepseek.com',
35+
apiKey: env['DEEPSEEK_API_KEY']
36+
})
37+
38+
try {
39+
const actualModel = (DEEPSEEK_MODELS[model as DeepSeekModelType] || DEEPSEEK_MODELS.DEEPSEEK_CHAT).modelId
40+
const combinedPrompt = `${prompt}\n${transcript}`
41+
42+
const response = await openai.chat.completions.create({
43+
model: actualModel,
44+
messages: [{ role: 'user', content: combinedPrompt }]
45+
})
46+
47+
const firstChoice = response.choices[0]
48+
if (!firstChoice || !firstChoice.message?.content) {
49+
throw new Error('No valid response received from the DeepSeek API')
50+
}
51+
52+
const content = firstChoice.message.content
53+
54+
logLLMCost({
55+
modelName: actualModel,
56+
stopReason: firstChoice.finish_reason ?? 'unknown',
57+
tokenUsage: {
58+
input: response.usage?.prompt_tokens,
59+
output: response.usage?.completion_tokens,
60+
total: response.usage?.total_tokens
61+
}
62+
})
63+
64+
return content
65+
} catch (error) {
66+
err(`Error in callDeepSeek: ${(error as Error).message}`)
67+
throw error
68+
}
69+
}

src/llms/grok.ts

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// src/llms/grok.ts
2+
3+
/**
4+
* @file Provides integration with the Grok LLM via xAI's REST API endpoint.
5+
* @packageDocumentation
6+
*/
7+
8+
import { env } from 'node:process'
9+
import { OpenAI } from 'openai'
10+
import { err, logLLMCost } from '../utils/logging'
11+
import type { GroqChatCompletionResponse, GrokModelType } from '../utils/types/llms'
12+
13+
/**
14+
* Calls the Grok API to generate a response to a prompt and transcript.
15+
* Uses the xAI-compatible OpenAI interface with a custom baseURL.
16+
*
17+
* @async
18+
* @function callGrok
19+
* @param {string} prompt - The prompt or instructions for Grok
20+
* @param {string} transcript - The transcript or additional context to process
21+
* @param {GrokModelType | string | { modelId: string } | boolean} [model='GROK_2_LATEST'] - The Grok model to use (defaults to GROK_2_LATEST).
22+
* Note: a boolean may appear if the CLI is used like `--grok` with no model specified. We handle that by defaulting to 'grok-2-latest'.
23+
* @throws Will throw an error if GROK_API_KEY is not set or if the API call fails
24+
* @returns {Promise<string>} The generated text from Grok
25+
*/
26+
export async function callGrok(
27+
prompt: string,
28+
transcript: string,
29+
model: GrokModelType | string | { modelId: string } | boolean = 'GROK_2_LATEST'
30+
): Promise<string> {
31+
if (!env['GROK_API_KEY']) {
32+
throw new Error('GROK_API_KEY environment variable is not set. Please set it to your xAI Grok API key.')
33+
}
34+
35+
// Safely parse the model parameter, since it can be a string, object, or boolean
36+
const modelId = typeof model === 'boolean'
37+
? 'grok-2-latest'
38+
: typeof model === 'object'
39+
? model?.modelId ?? 'grok-2-latest'
40+
: typeof model === 'string'
41+
? model
42+
: 'grok-2-latest'
43+
44+
const openai = new OpenAI({
45+
apiKey: env['GROK_API_KEY'],
46+
baseURL: 'https://api.x.ai/v1',
47+
})
48+
49+
try {
50+
const combinedPrompt = `${prompt}\n${transcript}`
51+
52+
const response = await openai.chat.completions.create({
53+
model: modelId,
54+
messages: [
55+
{
56+
role: 'user',
57+
content: combinedPrompt
58+
}
59+
],
60+
}) as GroqChatCompletionResponse
61+
62+
const firstChoice = response.choices[0]
63+
if (!firstChoice || !firstChoice.message?.content) {
64+
throw new Error('No valid response received from Grok API')
65+
}
66+
67+
const content = firstChoice.message.content
68+
69+
if (response.usage) {
70+
logLLMCost({
71+
modelName: modelId,
72+
stopReason: firstChoice.finish_reason ?? 'unknown',
73+
tokenUsage: {
74+
input: response.usage.prompt_tokens,
75+
output: response.usage.completion_tokens,
76+
total: response.usage.total_tokens
77+
}
78+
})
79+
}
80+
81+
return content
82+
} catch (error) {
83+
err(`Error in callGrok: ${(error as Error).message}`)
84+
throw error
85+
}
86+
}

src/server/tests/fetch-all.ts

+18
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,24 @@ const requests = [
289289
endpoint: '/process',
290290
outputFiles: ['FILE_29.md'],
291291
},
292+
{
293+
data: {
294+
type: 'video',
295+
url: 'https://www.youtube.com/watch?v=MORMZXEaONk',
296+
llm: 'deepseek',
297+
},
298+
endpoint: '/process',
299+
outputFiles: ['FILE_30.md'],
300+
},
301+
{
302+
data: {
303+
type: 'video',
304+
url: 'https://www.youtube.com/watch?v=MORMZXEaONk',
305+
llm: 'grok',
306+
},
307+
endpoint: '/process',
308+
outputFiles: ['FILE_30.md'],
309+
},
292310
{
293311
data: {
294312
type: 'video',

src/utils/globals/llms.ts

+48-4
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,27 @@
1-
// src/utils/llm-globals.ts
1+
// src/utils/globals/llms.ts
22

33
import { callOllama } from '../../llms/ollama'
44
import { callChatGPT } from '../../llms/chatgpt'
55
import { callClaude } from '../../llms/claude'
66
import { callGemini } from '../../llms/gemini'
77
import { callCohere } from '../../llms/cohere'
88
import { callMistral } from '../../llms/mistral'
9+
import { callDeepSeek } from '../../llms/deepseek'
10+
import { callGrok } from '../../llms/grok'
911
import { callFireworks } from '../../llms/fireworks'
1012
import { callTogether } from '../../llms/together'
1113
import { callGroq } from '../../llms/groq'
1214

1315
import type {
1416
ModelConfig,
17+
OllamaModelType,
1518
ChatGPTModelType,
1619
ClaudeModelType,
1720
CohereModelType,
1821
GeminiModelType,
1922
MistralModelType,
20-
OllamaModelType,
23+
DeepSeekModelType,
24+
GrokModelType,
2125
TogetherModelType,
2226
FireworksModelType,
2327
GroqModelType,
@@ -43,6 +47,8 @@ export const LLM_SERVICES: Record<string, LLMServiceConfig> = {
4347
GEMINI: { name: 'Google Gemini', value: 'gemini' },
4448
COHERE: { name: 'Cohere', value: 'cohere' },
4549
MISTRAL: { name: 'Mistral', value: 'mistral' },
50+
DEEPSEEK: { name: 'DeepSeek', value: 'deepseek' },
51+
GROK: { name: 'Grok', value: 'grok' },
4652
FIREWORKS: { name: 'Fireworks AI', value: 'fireworks' },
4753
TOGETHER: { name: 'Together AI', value: 'together' },
4854
GROQ: { name: 'Groq', value: 'groq' },
@@ -105,6 +111,8 @@ export const LLM_FUNCTIONS = {
105111
gemini: callGemini,
106112
cohere: callCohere,
107113
mistral: callMistral,
114+
deepseek: callDeepSeek,
115+
grok: callGrok,
108116
fireworks: callFireworks,
109117
together: callTogether,
110118
groq: callGroq,
@@ -458,17 +466,53 @@ export const GROQ_MODELS: ModelConfig<GroqModelType> = {
458466
},
459467
}
460468

469+
/**
470+
* Configuration for Grok models, mapping model types to their display names and identifiers.
471+
* Pricing is hypothetical or as provided by xAI docs
472+
* @type {ModelConfig<GrokModelType>}
473+
*/
474+
export const GROK_MODELS: ModelConfig<GrokModelType> = {
475+
GROK_2_LATEST: {
476+
name: 'Grok 2 Latest',
477+
modelId: 'grok-2-latest',
478+
inputCostPer1M: 2.00,
479+
outputCostPer1M: 10.00
480+
},
481+
}
482+
483+
/**
484+
* Configuration for DeepSeek models, mapping model types to their display names and identifiers.
485+
* Pricing is based on publicly listed rates for DeepSeek.
486+
* @type {ModelConfig<DeepSeekModelType>}
487+
*/
488+
export const DEEPSEEK_MODELS: ModelConfig<DeepSeekModelType> = {
489+
DEEPSEEK_CHAT: {
490+
name: 'DeepSeek Chat',
491+
modelId: 'deepseek-chat',
492+
inputCostPer1M: 0.07,
493+
outputCostPer1M: 1.10
494+
},
495+
DEEPSEEK_REASONER: {
496+
name: 'DeepSeek Reasoner',
497+
modelId: 'deepseek-reasoner',
498+
inputCostPer1M: 0.14,
499+
outputCostPer1M: 2.19
500+
},
501+
}
502+
461503
/**
462504
* All available model configurations combined
463505
*/
464506
export const ALL_MODELS: { [key: string]: ModelConfigValue } = {
507+
...OLLAMA_MODELS,
465508
...GPT_MODELS,
466509
...CLAUDE_MODELS,
467510
...GEMINI_MODELS,
468511
...COHERE_MODELS,
469512
...MISTRAL_MODELS,
470-
...OLLAMA_MODELS,
513+
...DEEPSEEK_MODELS,
514+
...GROK_MODELS,
471515
...FIREWORKS_MODELS,
472516
...TOGETHER_MODELS,
473-
...GROQ_MODELS
517+
...GROQ_MODELS,
474518
}

src/utils/types/llms.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export type ModelConfigValue = {
1818
/**
1919
* Options for Language Models (LLMs) that can be used in the application.
2020
*/
21-
export type LLMServices = 'chatgpt' | 'claude' | 'cohere' | 'mistral' | 'ollama' | 'gemini' | 'fireworks' | 'together' | 'groq'
21+
export type LLMServices = 'chatgpt' | 'claude' | 'cohere' | 'mistral' | 'ollama' | 'gemini' | 'deepseek' | 'fireworks' | 'together' | 'groq' | 'grok'
2222

2323
export type LLMServiceConfig = {
2424
name: string
@@ -84,11 +84,22 @@ export type TogetherModelType = 'LLAMA_3_2_3B' | 'LLAMA_3_1_405B' | 'LLAMA_3_1_7
8484
*/
8585
export type GroqModelType = 'LLAMA_3_2_1B_PREVIEW' | 'LLAMA_3_2_3B_PREVIEW' | 'LLAMA_3_3_70B_VERSATILE' | 'LLAMA_3_1_8B_INSTANT' | 'MIXTRAL_8X7B_INSTRUCT'
8686

87+
/**
88+
* Available Grok models.
89+
*/
90+
export type GrokModelType = 'GROK_2_LATEST'
91+
8792
/**
8893
* Local model with Ollama.
8994
*/
9095
export type OllamaModelType = 'LLAMA_3_2_1B' | 'LLAMA_3_2_3B' | 'GEMMA_2_2B' | 'PHI_3_5' | 'QWEN_2_5_0B' | 'QWEN_2_5_1B' | 'QWEN_2_5_3B'
9196

97+
/**
98+
* @typedef DeepSeekModelType
99+
* Represents the possible DeepSeek model IDs used within the application.
100+
*/
101+
export type DeepSeekModelType = 'DEEPSEEK_CHAT' | 'DEEPSEEK_REASONER'
102+
92103
// API Response Types
93104
/**
94105
* Response structure from Fireworks AI API.

test/all.test.ts

+12
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,18 @@ const commands = [
216216
expectedFile: 'audio-mistral-shownotes.md',
217217
newName: '42-mistral-shownotes.md'
218218
},
219+
{
220+
// process file using DeepSeek for LLM operations
221+
cmd: 'npm run as -- --file "content/audio.mp3" --deepseek',
222+
expectedFile: 'audio-deepseek-shownotes.md',
223+
newName: '41-deepsek-shownotes.md'
224+
},
225+
{
226+
// process file using Grok for LLM operations
227+
cmd: 'npm run as -- --file "content/audio.mp3" --grok',
228+
expectedFile: 'audio-grok-shownotes.md',
229+
newName: '41-grok-shownotes.md'
230+
},
219231
{
220232
// process file using Fireworks for LLM operations
221233
cmd: 'npm run as -- --file "content/audio.mp3" --fireworks',

test/docker.test.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -71,20 +71,30 @@ const commands = [
7171
expectedFile: 'audio-mistral-shownotes.md',
7272
newName: '14-docker-three-prompts-mistral-shownotes.md'
7373
},
74+
{
75+
cmd: 'npm run docker-cli -- --file "content/audio.mp3" --prompt titles summary shortChapters --whisper base --deepseek',
76+
expectedFile: 'audio-deepseek-shownotes.md',
77+
newName: '15-docker-three-prompts-grok-shownotes.md'
78+
},
79+
{
80+
cmd: 'npm run docker-cli -- --file "content/audio.mp3" --prompt titles summary shortChapters --whisper base --grok',
81+
expectedFile: 'audio-grok-shownotes.md',
82+
newName: '15-docker-three-prompts-grok-shownotes.md'
83+
},
7484
{
7585
cmd: 'npm run docker-cli -- --file "content/audio.mp3" --prompt titles summary shortChapters --whisper base --fireworks',
7686
expectedFile: 'audio-fireworks-shownotes.md',
77-
newName: '15-docker-three-prompts-fireworks-shownotes.md'
87+
newName: '16-docker-three-prompts-fireworks-shownotes.md'
7888
},
7989
{
8090
cmd: 'npm run docker-cli -- --file "content/audio.mp3" --prompt titles summary shortChapters --whisper base --together',
8191
expectedFile: 'audio-together-shownotes.md',
82-
newName: '16-docker-three-prompts-together-shownotes.md'
92+
newName: '17-docker-three-prompts-together-shownotes.md'
8393
},
8494
{
8595
cmd: 'npm run docker-cli -- --file "content/audio.mp3" --prompt titles summary shortChapters --whisper base --groq',
8696
expectedFile: 'audio-groq-shownotes.md',
87-
newName: '17-docker-three-prompts-groq-shownotes.md'
97+
newName: '18-docker-three-prompts-groq-shownotes.md'
8898
},
8999
]
90100

0 commit comments

Comments
 (0)