Add Claude AI support as alternative to Mistral
- Create claudeClient.js service for Claude API integration - Add CLAUDE_API_KEY to .env configuration - Claude uses identical prompt structure and section extraction logic - Supports claude-3-5-sonnet-20241022 model Next: Add provider selection to orchestrator and routes
This commit is contained in:
parent
5a2ca101f8
commit
60fdc9a66f
144
backend/src/services/claudeClient.js
Normal file
144
backend/src/services/claudeClient.js
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
import dotenv from 'dotenv'
|
||||||
|
|
||||||
|
dotenv.config()
|
||||||
|
|
||||||
|
const CLAUDE_API_KEY = process.env.CLAUDE_API_KEY
|
||||||
|
const CLAUDE_API_URL = 'https://api.anthropic.com/v1/messages'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic AI prompt for collaborative document editing
|
||||||
|
*/
|
||||||
|
function getAgentPrompt(agentName) {
|
||||||
|
return `You are an AI assistant named ${agentName} collaborating on a technical document design.
|
||||||
|
|
||||||
|
Your responsibilities:
|
||||||
|
1. Review the current document structure
|
||||||
|
2. Either:
|
||||||
|
a) Modify ONE existing section (identified by #, ##, ###, #### headers), OR
|
||||||
|
b) Create a NEW section if you think it's needed, OR
|
||||||
|
c) Delete a section if you think it's redundant or not useful
|
||||||
|
3. Provide your thinking process and reasoning
|
||||||
|
4. Return ONLY the section (modified or new) with its header, or command to delete, or confirm it's good as-is
|
||||||
|
|
||||||
|
CRITICAL RULES - FOLLOW THESE EXACTLY:
|
||||||
|
- Work on exactly ONE section only
|
||||||
|
- NEVER return the entire document
|
||||||
|
- NEVER return multiple sections
|
||||||
|
- Return ONLY the section you're working on, not the whole document
|
||||||
|
- You CAN create a new section if document is missing important content
|
||||||
|
- You CAN delete a section if it's redundant, duplicate, or not useful
|
||||||
|
- To delete a section, respond: "DELETE: ## Section Name" (with exact header)
|
||||||
|
- If section is good, respond: "Section is good, no changes needed"
|
||||||
|
- Think step-by-step about what could be improved or removed
|
||||||
|
- Share your reasoning process
|
||||||
|
|
||||||
|
RESPONSE FORMAT - FOLLOW THIS EXACTLY:
|
||||||
|
THINKING: [Your analysis and reasoning about the current document]
|
||||||
|
DECISION: [Exactly what you will do: modify section X, create new section Y, delete section Z, or keep as-is]
|
||||||
|
SECTION:
|
||||||
|
[ONLY ONE: Either a markdown section starting with # or ##, a DELETE command, or the text "Section is good, no changes needed"]
|
||||||
|
|
||||||
|
EXAMPLE OF CORRECT RESPONSE:
|
||||||
|
THINKING: The Overview section is too brief and doesn't explain the main purpose.
|
||||||
|
DECISION: I will modify the Overview section to be more comprehensive.
|
||||||
|
SECTION:
|
||||||
|
## Overview
|
||||||
|
This is a technical document for designing system architecture...
|
||||||
|
|
||||||
|
EXAMPLE OF INCORRECT RESPONSE (DO NOT DO THIS):
|
||||||
|
[The entire document repeated here] <- WRONG!`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call Claude API
|
||||||
|
*/
|
||||||
|
async function callClaudeAPI(messages) {
|
||||||
|
const response = await fetch(CLAUDE_API_URL, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'x-api-key': CLAUDE_API_KEY,
|
||||||
|
'anthropic-version': '2023-06-01'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
model: 'claude-3-5-sonnet-20241022',
|
||||||
|
max_tokens: 2048,
|
||||||
|
system: messages[0].content,
|
||||||
|
messages: messages.slice(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.text()
|
||||||
|
throw new Error(`Claude API error: ${error}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate agent response using Claude
|
||||||
|
*/
|
||||||
|
export async function generateAgentResponseSync(agentName, prompt, currentDocument = '') {
|
||||||
|
const systemPrompt = getAgentPrompt(agentName)
|
||||||
|
|
||||||
|
const messages = [
|
||||||
|
{ role: 'user', content: systemPrompt },
|
||||||
|
{ role: 'user', content: `Project description: ${prompt}\n\nCurrent document:\n${currentDocument}` }
|
||||||
|
]
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await callClaudeAPI(messages)
|
||||||
|
const data = await response.json()
|
||||||
|
|
||||||
|
if (!data.content?.[0]?.text) {
|
||||||
|
throw new Error('Invalid response from Claude API')
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.content[0].text
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error generating response from ${agentName}:`, error)
|
||||||
|
return `Error: ${error.message}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract section from AI response
|
||||||
|
* Validates that we get a proper section, not the entire document
|
||||||
|
*/
|
||||||
|
export function extractSection(aiResponse) {
|
||||||
|
const sectionMatch = aiResponse.match(/SECTION:\s*([\s\S]*?)(?:$)/)
|
||||||
|
if (sectionMatch) {
|
||||||
|
const extracted = sectionMatch[1].trim()
|
||||||
|
|
||||||
|
// Validate: extracted should be either:
|
||||||
|
// 1. A markdown section starting with # (DELETE: or "Section is good...")
|
||||||
|
// 2. OR a single section with < 5000 chars (not entire document)
|
||||||
|
const isMarkdownSection = /^(#|DELETE:|Section is good)/.test(extracted)
|
||||||
|
const isShortEnough = extracted.length < 5000 // Single section should be < 5KB
|
||||||
|
|
||||||
|
if (isMarkdownSection || isShortEnough) {
|
||||||
|
return extracted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: if no SECTION: tag found, treat entire response as section
|
||||||
|
// but only if it starts with markdown header or is a command
|
||||||
|
if (/^(#|DELETE:|Section is good)/.test(aiResponse)) {
|
||||||
|
return aiResponse.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
// If none of the above, return error indicator
|
||||||
|
return "ERROR: Response does not contain a valid section format"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract thinking from AI response
|
||||||
|
*/
|
||||||
|
export function extractThinking(aiResponse) {
|
||||||
|
const thinkingMatch = aiResponse.match(/THINKING:\s*([\s\S]*?)(?:DECISION:|SECTION:)/)
|
||||||
|
if (thinkingMatch) {
|
||||||
|
return thinkingMatch[1].trim()
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user