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:
Augustin ROUX 2025-10-19 16:32:48 +02:00
parent 5a2ca101f8
commit 60fdc9a66f

View 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 ''
}