feat: upgrade to deepseek-reasoner with Chain of Thought

- Switch to deepseek-reasoner model for improved analysis
- Add Chain of Thought (CoT) reasoning capabilities
- Update types to support reasoning_content in responses
- Update second-opinion tool to showcase CoT reasoning
- Add max_tokens configuration for response length control
- Update documentation and examples

The deepseek-reasoner model provides step-by-step reasoning before
generating final responses, improving the quality and transparency
of the analysis. The reasoning is now exposed in tool responses
under the 'reasoning' field.
This commit is contained in:
cyanheads
2025-01-25 19:14:26 -08:00
parent a6edebbf8e
commit ed5afdd56c
5 changed files with 100 additions and 34 deletions

View File

@@ -1,7 +1,10 @@
# Deepseek API Configuration
DEEPSEEK_API_KEY=your_deepseek_api_key_here
DEEPSEEK_API_BASE_URL=https://api.deepseek.com
DEEPSEEK_MODEL=deepseek-chat
# Deepseek Reasoner Settings
# Maximum length of the final response after Chain of Thought (4K-8K)
DEEPSEEK_MAX_TOKENS=4096
DEEPSEEK_MAX_RETRIES=3
DEEPSEEK_TIMEOUT=30000

View File

@@ -1,5 +1,5 @@
import OpenAI from 'openai';
import type { LLMResponse } from '../../types/index.js';
import type { LLMResponse, ChatMessage } from '../../types/index.js';
import { config } from '../../config.js';
import { sanitizeInput } from '../../utils/prompt.js';
@@ -42,7 +42,7 @@ class RateLimiter {
}
/**
* Deepseek API client class using OpenAI SDK
* Deepseek API client class using OpenAI SDK with deepseek-reasoner model
*/
class DeepseekClient {
private readonly client: OpenAI;
@@ -52,8 +52,7 @@ class DeepseekClient {
this.client = new OpenAI({
baseURL: config.api.baseUrl || 'https://api.deepseek.com',
apiKey: config.api.apiKey,
defaultQuery: { model: config.api.model || 'deepseek-chat' },
defaultHeaders: { 'api-key': config.api.apiKey }
defaultQuery: { model: 'deepseek-reasoner' }
});
this.rateLimiter = new RateLimiter(
@@ -90,11 +89,12 @@ class DeepseekClient {
}
/**
* Makes a call to the Deepseek API
* Makes a call to the Deepseek API using the reasoner model
*/
public async makeApiCall(
prompt: string,
systemPrompt: string = 'You are a helpful AI assistant.'
systemPrompt: string = 'You are a helpful AI assistant.',
previousMessages: ChatMessage[] = []
): Promise<LLMResponse> {
// Check rate limit
if (!this.rateLimiter.tryConsume()) {
@@ -110,22 +110,33 @@ class DeepseekClient {
const sanitizedPrompt = sanitizeInput(prompt);
const sanitizedSystemPrompt = sanitizeInput(systemPrompt);
// Prepare messages, filtering out any previous reasoning_content
const messages = [
{ role: 'system' as const, content: sanitizedSystemPrompt },
...previousMessages.map(msg => ({
role: msg.role,
content: msg.content
})),
{ role: 'user' as const, content: sanitizedPrompt }
];
const response = await this.retryWithExponentialBackoff(async () => {
const completion = await this.client.chat.completions.create({
messages: [
{ role: 'system', content: sanitizedSystemPrompt },
{ role: 'user', content: sanitizedPrompt }
],
model: config.api.model || 'deepseek-chat',
temperature: 0.7,
max_tokens: 2048
model: 'deepseek-reasoner',
messages,
max_tokens: config.api.maxTokens || 4096
});
return completion;
});
// Extract both the reasoning and final content
const reasoningContent = (response.choices[0]?.message as any)?.reasoning_content || '';
const finalContent = response.choices[0]?.message?.content || '';
return {
text: response.choices[0]?.message?.content || '',
text: finalContent,
reasoning: reasoningContent,
isError: false,
};
} catch (error) {
@@ -158,7 +169,10 @@ class DeepseekClient {
export const deepseekClient = new DeepseekClient();
// Export the main interface functions
export const makeDeepseekAPICall = (prompt: string, systemPrompt?: string) =>
deepseekClient.makeApiCall(prompt, systemPrompt);
export const makeDeepseekAPICall = (
prompt: string,
systemPrompt?: string,
previousMessages: ChatMessage[] = []
) => deepseekClient.makeApiCall(prompt, systemPrompt, previousMessages);
export const checkRateLimit = () => deepseekClient.checkRateLimit();

View File

@@ -15,9 +15,10 @@ function requireEnv(name: string): string {
const apiConfig: APIConfig = {
apiKey: requireEnv('DEEPSEEK_API_KEY'),
baseUrl: process.env.DEEPSEEK_API_BASE_URL || 'https://api.deepseek.com',
model: process.env.DEEPSEEK_MODEL || 'deepseek-chat',
model: 'deepseek-reasoner', // Always use the reasoner model
maxRetries: parseInt(process.env.DEEPSEEK_MAX_RETRIES || '3', 10),
timeout: parseInt(process.env.DEEPSEEK_TIMEOUT || '30000', 10),
maxTokens: parseInt(process.env.DEEPSEEK_MAX_TOKENS || '4096', 10), // Default to 4K tokens for final response
};
export const config: ServerConfig = {

View File

@@ -6,11 +6,19 @@ import { createPrompt, PromptTemplate } from '../../utils/prompt.js';
* System prompt for the second opinion tool
*/
const SYSTEM_PROMPT = `You are an expert mentor providing second opinions on user requests.
Your role is to analyze requests and identify critical considerations that might be overlooked.
Focus on modern practices, potential pitfalls, and important factors for success.
Your role is to analyze requests and identify critical considerations that might be overlooked.
Format your response as a clear, non-numbered list of points, focusing on what's most relevant
to the specific request. Each point should be concise but informative.`;
First, reason through the request step by step:
1. Understand the core request and its implications
2. Consider the context and domain
3. Identify potential challenges and pitfalls
4. Think about prerequisites and dependencies
5. Evaluate resource requirements
6. Consider maintenance and scalability
7. Think about security and performance implications
Then, provide a concise list of critical considerations based on your reasoning.
Focus on modern practices, potential pitfalls, and important factors for success.`;
/**
* Prompt template for generating second opinions
@@ -18,18 +26,17 @@ to the specific request. Each point should be concise but informative.`;
const PROMPT_TEMPLATE: PromptTemplate = {
template: `User Request: {user_request}
Task: List the critical considerations for this user request:
Please analyze this request carefully. Consider:
- Core problem/concept to address
- Common pitfalls or edge cases
- Security/performance implications (if applicable)
- Prerequisites or dependencies
- Resource constraints and requirements to consider
- Prerequisites and dependencies
- Resource constraints and requirements
- Advanced topics that could add value
- Maintenance/scalability factors
Reminder: You are not fulfilling the user request, only generating a plain text, non-numbered list of non-obvious points of consideration.
Format: Brief, clear points in plain text. Focus on what's most relevant to the specific request.`,
First, reason through your analysis step by step.
Then, provide a clear, non-numbered list of critical considerations.`,
systemPrompt: SYSTEM_PROMPT
};
@@ -55,9 +62,9 @@ export const definition: ToolDefinition = {
* Handles the execution of the second opinion tool
*
* @param args - Tool arguments containing the user request
* @returns Tool response containing the generated second opinion
* @returns Tool response containing the generated second opinion with reasoning
*/
export async function handler(args: SecondOpinionArgs) {
export async function handler(args: unknown) {
// Check rate limit first
if (!checkRateLimit()) {
return {
@@ -67,13 +74,30 @@ export async function handler(args: SecondOpinionArgs) {
text: 'Rate limit exceeded. Please try again later.',
},
],
isError: true,
};
}
try {
// Create the complete prompt using the template
// Type guard for SecondOpinionArgs
if (!args || typeof args !== 'object' || !('user_request' in args) ||
typeof args.user_request !== 'string') {
return {
content: [
{
type: 'text',
text: 'Missing or invalid user_request parameter.',
},
],
isError: true,
};
}
const typedArgs = args as SecondOpinionArgs;
// Create the complete prompt
const prompt = createPrompt(PROMPT_TEMPLATE, {
user_request: args.user_request
user_request: typedArgs.user_request
});
// Make the API call
@@ -87,17 +111,27 @@ export async function handler(args: SecondOpinionArgs) {
text: `Error generating second opinion: ${response.errorMessage || 'Unknown error'}`,
},
],
isError: true,
};
}
// Format the response
// Return both the reasoning and the final response
return {
content: [
{
type: 'text',
text: `<internal_thoughts>\n${response.text}\n</internal_thoughts>`,
text: response.text,
},
],
// Include the Chain of Thought reasoning if available
...(response.reasoning ? {
reasoning: [
{
type: 'text',
text: `<reasoning>\n${response.reasoning}\n</reasoning>`,
},
],
} : {}),
};
} catch (error) {
console.error('Second opinion tool error:', error);
@@ -108,6 +142,7 @@ export async function handler(args: SecondOpinionArgs) {
text: `Error processing request: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
}

View File

@@ -18,11 +18,13 @@ export interface ToolContent {
export interface ToolResponse {
content: ToolContent[];
reasoning?: ToolContent[]; // Added to expose reasoning content
isError?: boolean;
}
export interface LLMResponse {
text: string;
reasoning?: string; // Added for Chain of Thought content
isError: boolean;
errorMessage?: string;
}
@@ -38,6 +40,7 @@ export interface APIConfig {
model: string;
maxRetries: number;
timeout: number;
maxTokens?: number; // Added for deepseek-reasoner max_tokens parameter
}
export interface ServerConfig {
@@ -111,4 +114,14 @@ export function isBrainstormEnhancementsArgs(args: unknown): args is BrainstormE
const a = args as Record<string, unknown>;
return 'concept' in a && typeof a.concept === 'string';
}
// Message types for OpenAI/Deepseek chat
export interface ChatMessage {
role: 'system' | 'user' | 'assistant';
content: string;
}
export interface ChatHistory {
messages: ChatMessage[];
}