<p align="right"><font color="#3f3f3f">2025年06月13日</font></p> ## 1. 项目概述 ### 1.1 项目目标 构建一个类似 Claude.ai 的 AI 对话应用,支持多模型对话、代码执行、数据分析等功能。项目设计遵循轻量级原则,可在单机 MacBook 上运行,同时通过良好的抽象层设计确保未来的可扩展性。 ### 1.2 核心功能 - **AI 对话**:支持 Claude、DeepSeek、Qwen 等模型 - **流式响应**:实时展示 AI 回复 - **Artifacts**:代码展示和前端代码执行 - **Analysis Tool**:数据分析和可视化 - **代码执行**:安全的多语言代码执行环境 - **LLM 监控**:全链路追踪、质量评估、业务指标监控 ### 1.3 技术原则 - **轻量级优先**:最小化外部依赖,确保本地可运行 - **抽象层设计**:便于后续替换和扩展各个组件 - **渐进式增强**:从 MVP 开始,逐步添加高级功能 ## 2. 技术栈选型 ### 2.1 后端技术栈 - **核心框架**:Spring Boot 3.x + Java 17+ - **AI 框架**:LangChain4j - **实时通信**:Spring WebSocket + STOMP - **数据存储**:H2 Database(嵌入式) - **缓存方案**:Caffeine(JVM 内存缓存) - **代码执行**:Docker API(轻量级容器) - **LLM 监控**:Langfuse + OpenTelemetry - **指标收集**:Micrometer + Prometheus - **日志聚合**:Logback + 结构化日志 ### 2.2 前端技术栈 - **框架**:React 18 - **UI 组件**:Ant Design 5.x - **代码编辑器**:Monaco Editor - **Markdown 渲染**:react-markdown - **WebSocket 客户端**:socket.io-client - **构建工具**:Vite ## 3. 系统架构设计 ### 3.1 整体架构图 ``` ┌─────────────────────────────────────────────────────┐ │ React 前端应用 │ │ ┌─────────┐ ┌─────────────┐ ┌────────────────┐ │ │ │会话列表 │ │ 聊天界面 │ │ Artifacts面板 │ │ │ └─────────┘ └─────────────┘ └────────────────┘ │ └────────────────────────┬────────────────────────────┘ │ WebSocket + REST API ┌────────────────────────┴────────────────────────────┐ │ Spring Boot 应用 │ ├─────────────────────────────────────────────────────┤ │ ┌─────────────┐ ┌──────────────┐ ┌───────────┐ │ │ │ Controller │ │ Service层 │ │抽象层接口 │ │ │ │ - Chat │ │ - Chat │ │ - LLM │ │ │ │ - Code │ │ - Execution │ │ - Storage │ │ │ │ - Analysis │ │ - Analysis │ │ - Executor│ │ │ │ - Monitor │ │ - Monitor │ │ - Monitor │ │ │ └─────────────┘ └──────────────┘ └───────────┘ │ ├─────────────────────────────────────────────────────┤ │ 具体实现层 │ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌──────────┐ │ │ │Claude │ │Docker │ │ H2 │ │Local │ │ │ │DeepSeek│ │Executor│ │Database│ │Python │ │ │ │Qwen │ │ │ │ │ │ │ │ │ └────────┘ └────────┘ └────────┘ └──────────┘ │ ├─────────────────────────────────────────────────────┤ │ 监控和可观测性 │ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌──────────┐ │ │ │Langfuse│ │OpenTel │ │Metrics │ │Dashboard │ │ │ │Tracing │ │Tracing │ │Export │ │Grafana │ │ │ └────────┘ └────────┘ └────────┘ └──────────┘ │ └─────────────────────────────────────────────────────┘ ``` ### 3.2 核心组件交互流程 ```mermaid sequenceDiagram participant U as 用户 participant F as 前端 participant W as WebSocket participant C as ChatService participant L as LLMProvider participant E as CodeExecutor participant S as Storage U->>F: 发送消息 F->>W: WebSocket消息 W->>C: 处理消息 C->>S: 保存消息 C->>L: 调用AI模型 L-->>C: 流式响应 C-->>W: 推送响应块 W-->>F: 显示流式文本 alt 包含代码执行 C->>E: 执行代码 E-->>C: 执行结果 C-->>W: 推送结果 W-->>F: 显示执行结果 end C->>S: 保存完整对话 ``` ## 4. 抽象层设计 ### 4.1 LLM Provider 抽象 ```java // LLMProvider.java public interface LLMProvider { String getName(); boolean isAvailable(); ChatResponse chat(ChatRequest request); CompletableFuture<ChatResponse> chatAsync(ChatRequest request); Stream<ChatChunk> streamChat(ChatRequest request); } // ChatRequest.java @Data @Builder public class ChatRequest { private String model; private List<Message> messages; private Double temperature; private Integer maxTokens; private boolean stream; private Map<String, Object> additionalParams; } // LLMProviderFactory.java @Component public class LLMProviderFactory { private final Map<String, LLMProvider> providers; private final String defaultProvider; @Autowired public LLMProviderFactory( List<LLMProvider> providerList, @Value("${llm.default-provider}") String defaultProvider ) { this.providers = providerList.stream() .collect(Collectors.toMap(LLMProvider::getName, Function.identity())); this.defaultProvider = defaultProvider; } public LLMProvider getProvider(String name) { return providers.getOrDefault(name, providers.get(defaultProvider)); } } ``` ### 4.2 Code Executor 抽象 ```java // CodeExecutor.java public interface CodeExecutor { ExecutionResult execute(ExecutionRequest request); boolean supports(String language); ExecutionEnvironment getEnvironment(); int getPriority(); // 用于选择最合适的执行器 } // ExecutionRequest.java @Data @Builder public class ExecutionRequest { private String language; private String code; private Map<String, String> files; private ExecutionConstraints constraints; private Map<String, Object> metadata; } // ExecutionConstraints.java @Data @Builder public class ExecutionConstraints { @Builder.Default private Duration timeout = Duration.ofSeconds(30); @Builder.Default private Long memoryLimitMB = 512L; @Builder.Default private Double cpuLimit = 0.5; @Builder.Default private boolean allowNetwork = false; private List<String> allowedPackages; } // CodeExecutorRegistry.java @Component public class CodeExecutorRegistry { private final List<CodeExecutor> executors; @Autowired public CodeExecutorRegistry(List<CodeExecutor> executors) { this.executors = executors.stream() .sorted(Comparator.comparing(CodeExecutor::getPriority).reversed()) .collect(Collectors.toList()); } public CodeExecutor getExecutor(String language) { return executors.stream() .filter(e -> e.supports(language)) .findFirst() .orElseThrow(() -> new UnsupportedLanguageException(language)); } } ``` ### 4.3 Storage 抽象 ```java // StorageService.java public interface StorageService { // 对话存储 Conversation saveConversation(Conversation conversation); Conversation getConversation(String id); Page<Conversation> listConversations(String userId, Pageable pageable); void deleteConversation(String id); // 消息存储 Message saveMessage(Message message); List<Message> getMessages(String conversationId); // 文件存储 FileInfo saveFile(MultipartFile file, FileMetadata metadata); Resource loadFile(String fileId); void deleteFile(String fileId); // 缓存操作 void cache(String key, Object value, Duration ttl); <T> Optional<T> getFromCache(String key, Class<T> type); void evictCache(String key); } // 组合实现 @Component public class CompositeStorageService implements StorageService { private final ConversationRepository conversationRepo; private final FileStorage fileStorage; private final CacheManager cacheManager; // 实现方法委托给具体组件 } ``` ### 4.4 Analysis Engine 抽象 ```java // AnalysisEngine.java public interface AnalysisEngine { String getType(); boolean supports(String language); AnalysisResult analyze(AnalysisRequest request); CompletableFuture<AnalysisResult> analyzeAsync(AnalysisRequest request); void cancelAnalysis(String analysisId); } // AnalysisRequest.java @Data @Builder public class AnalysisRequest { private String id; private String language; private String code; private Map<String, Object> inputData; private AnalysisOptions options; } // AnalysisOptions.java @Data @Builder public class AnalysisOptions { @Builder.Default private boolean generateVisualization = true; @Builder.Default private String outputFormat = "html"; private List<String> requiredLibraries; private Map<String, String> environment; } ``` ### 4.5 LLM 监控和可观测性抽象 ```java // LLMObservabilityService.java public interface LLMObservabilityService { // 追踪管理 TraceContext startTrace(String operationType, Map<String, Object> metadata); void endTrace(TraceContext context, TraceResult result); void addTraceEvent(TraceContext context, String event, Map<String, Object> data); // 指标收集 void recordLatency(String provider, String model, Duration duration); void recordTokenUsage(String provider, String model, TokenUsage usage); void recordError(String provider, String model, String errorType, Throwable error); void recordUserFeedback(String conversationId, String messageId, UserFeedback feedback); // 质量评估 void evaluateResponse(String conversationId, String messageId, ResponseEvaluation evaluation); CompletableFuture<QualityMetrics> calculateQualityMetrics(String timeRange); // 业务指标 void recordBusinessEvent(String eventType, Map<String, Object> properties); void recordConversationMetrics(ConversationMetrics metrics); } // TraceContext.java @Data @Builder public class TraceContext { private String traceId; private String spanId; private String parentSpanId; private String operationType; private Instant startTime; private Map<String, Object> metadata; private Map<String, String> tags; } // TokenUsage.java @Data @Builder public class TokenUsage { private int inputTokens; private int outputTokens; private int totalTokens; private BigDecimal cost; private String costCurrency; } // ResponseEvaluation.java @Data @Builder public class ResponseEvaluation { private String evaluationId; private String messageId; private String conversationId; private QualityScore qualityScore; private List<String> issues; private Map<String, Object> metadata; private Instant evaluatedAt; } // QualityScore.java @Data @Builder public class QualityScore { @Builder.Default private Double relevance = 0.0; @Builder.Default private Double accuracy = 0.0; @Builder.Default private Double helpfulness = 0.0; @Builder.Default private Double safety = 0.0; @Builder.Default private Double overall = 0.0; private String evaluationMethod; } // ConversationMetrics.java @Data @Builder public class ConversationMetrics { private String conversationId; private String userId; private int messageCount; private Duration totalDuration; private Duration averageResponseTime; private int errorCount; private UserSatisfaction satisfaction; private List<String> topics; private Map<String, Object> businessMetrics; } // LLMMonitoringConfig.java @Data @Builder public class LLMMonitoringConfig { @Builder.Default private boolean enableTracing = true; @Builder.Default private boolean enableMetrics = true; @Builder.Default private boolean enableQualityEvaluation = true; @Builder.Default private boolean enableUserFeedback = true; @Builder.Default private double samplingRate = 1.0; private Set<String> enabledProviders; private Map<String, Object> customConfig; } ``` ## 5. 具体实现方案 ### 5.1 项目结构 ``` claude-clone/ ├── backend/ │ ├── src/main/java/com/claude/ │ │ ├── controller/ │ │ │ ├── ChatController.java │ │ │ ├── CodeExecutionController.java │ │ │ └── AnalysisController.java │ │ ├── service/ │ │ │ ├── ChatService.java │ │ │ ├── ExecutionService.java │ │ │ └── AnalysisService.java │ │ ├── provider/ │ │ │ ├── llm/ │ │ │ │ ├── ClaudeLLMProvider.java │ │ │ │ ├── DeepSeekLLMProvider.java │ │ │ │ └── QwenLLMProvider.java │ │ │ ├── executor/ │ │ │ │ ├── FrontendCodeExecutor.java │ │ │ │ └── DockerCodeExecutor.java │ │ │ └── analysis/ │ │ │ └── LocalPythonAnalysisEngine.java │ │ ├── storage/ │ │ │ ├── entity/ │ │ │ ├── repository/ │ │ │ └── service/ │ │ ├── config/ │ │ │ ├── WebSocketConfig.java │ │ │ ├── SecurityConfig.java │ │ │ └── CacheConfig.java │ │ └── util/ │ ├── src/main/resources/ │ │ ├── application.yml │ │ └── application-prod.yml │ └── pom.xml ├── frontend/ │ ├── src/ │ │ ├── components/ │ │ │ ├── Chat/ │ │ │ ├── Artifacts/ │ │ │ └── Analysis/ │ │ ├── services/ │ │ ├── hooks/ │ │ ├── utils/ │ │ └── App.tsx │ ├── package.json │ └── vite.config.ts ├── docker/ │ ├── Dockerfile │ └── docker-compose.yml ├── scripts/ │ ├── start.sh │ └── setup.sh └── README.md ``` ### 5.2 后端核心实现 #### 5.2.1 WebSocket 配置 ```java @Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/topic", "/queue"); config.setApplicationDestinationPrefixes("/app"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws") .setAllowedOriginPatterns("*") .withSockJS(); } } ``` #### 5.2.2 Chat Controller ```java @RestController @RequestMapping("/api/chat") public class ChatController { private final ChatService chatService; private final SimpMessagingTemplate messagingTemplate; @MessageMapping("/chat.send") public void handleChatMessage( @Payload ChatMessage message, @Header("simpSessionId") String sessionId ) { // 保存用户消息 chatService.saveUserMessage(message); // 流式处理AI响应 chatService.streamChat(message) .doOnNext(chunk -> { // 推送每个响应块到前端 messagingTemplate.convertAndSendToUser( sessionId, "/queue/messages", chunk ); }) .doOnComplete(() -> { // 发送完成信号 messagingTemplate.convertAndSendToUser( sessionId, "/queue/messages", ChatChunk.completed() ); }) .subscribe(); } } ``` #### 5.2.3 Claude Provider 实现 ```java @Component @ConditionalOnProperty(name = "llm.providers.claude.enabled", havingValue = "true") public class ClaudeLLMProvider implements LLMProvider { private final AnthropicChatModel model; public ClaudeLLMProvider(@Value("${llm.providers.claude.api-key}") String apiKey) { this.model = AnthropicChatModel.builder() .apiKey(apiKey) .modelName("claude-3-sonnet-20240229") .build(); } @Override public String getName() { return "claude"; } @Override public Stream<ChatChunk> streamChat(ChatRequest request) { return model.stream(toLangChainRequest(request)) .map(this::toChatChunk); } } ``` #### 5.2.4 Docker 执行器实现 ```java @Component @ConditionalOnProperty(name = "execution.docker.enabled", havingValue = "true") public class DockerCodeExecutor implements CodeExecutor { private static final Map<String, String> LANGUAGE_IMAGES = Map.of( "python", "python:3.11-alpine", "javascript", "node:20-alpine", "java", "openjdk:17-alpine" ); @Override public ExecutionResult execute(ExecutionRequest request) { String image = LANGUAGE_IMAGES.get(request.getLanguage()); String containerId = createContainer(image, request); try { // 写入代码文件 writeCodeToContainer(containerId, request.getCode()); // 执行代码 ExecCreateResponse execResponse = dockerClient.execCreate( containerId, new String[]{getExecuteCommand(request.getLanguage())}, DockerClient.ExecCreateParam.attachStdout(), DockerClient.ExecCreateParam.attachStderr() ); // 获取输出 LogStream logStream = dockerClient.execStart(execResponse.id()); String output = logStream.readFully(); return ExecutionResult.success(output); } finally { // 清理容器 dockerClient.removeContainer(containerId); } } private String createContainer(String image, ExecutionRequest request) { ContainerConfig config = ContainerConfig.builder() .image(image) .hostConfig(HostConfig.builder() .memory(request.getConstraints().getMemoryLimitMB() * 1024 * 1024) .cpuQuota((long)(request.getConstraints().getCpuLimit() * 100000)) .networkMode("none") .readonlyRootfs(true) .build()) .build(); return dockerClient.createContainer(config).id(); } } ``` #### 5.2.5 LLM 监控实现 ```java // LangfuseObservabilityService.java @Component @ConditionalOnProperty(name = "monitoring.langfuse.enabled", havingValue = "true") public class LangfuseObservabilityService implements LLMObservabilityService { private final LangfuseClient langfuseClient; private final MeterRegistry meterRegistry; private final ObjectMapper objectMapper; @Autowired public LangfuseObservabilityService( @Value("${monitoring.langfuse.api-key}") String apiKey, @Value("${monitoring.langfuse.host}") String host, MeterRegistry meterRegistry, ObjectMapper objectMapper ) { this.langfuseClient = LangfuseClient.builder() .apiKey(apiKey) .host(host) .build(); this.meterRegistry = meterRegistry; this.objectMapper = objectMapper; } @Override public TraceContext startTrace(String operationType, Map<String, Object> metadata) { String traceId = UUID.randomUUID().toString(); String spanId = UUID.randomUUID().toString(); // Langfuse 追踪 langfuseClient.trace(CreateTraceRequest.builder() .id(traceId) .name(operationType) .metadata(metadata) .timestamp(Instant.now()) .build()); // OpenTelemetry 追踪 Span span = tracer.spanBuilder(operationType) .setSpanKind(SpanKind.INTERNAL) .startSpan(); span.setAllAttributes(Attributes.of( AttributeKey.stringKey("trace.id"), traceId, AttributeKey.stringKey("operation.type"), operationType )); return TraceContext.builder() .traceId(traceId) .spanId(spanId) .operationType(operationType) .startTime(Instant.now()) .metadata(metadata) .build(); } @Override public void recordLatency(String provider, String model, Duration duration) { // Micrometer 指标 Timer.Sample sample = Timer.start(meterRegistry); sample.stop(Timer.builder("llm.request.duration") .tag("provider", provider) .tag("model", model) .register(meterRegistry)); // Langfuse 指标 langfuseClient.score(CreateScoreRequest.builder() .name("latency") .value(duration.toMillis()) .build()); } @Override public void recordTokenUsage(String provider, String model, TokenUsage usage) { // 记录 Token 使用量 meterRegistry.counter("llm.tokens.input", "provider", provider, "model", model).increment(usage.getInputTokens()); meterRegistry.counter("llm.tokens.output", "provider", provider, "model", model).increment(usage.getOutputTokens()); if (usage.getCost() != null) { meterRegistry.counter("llm.cost", "provider", provider, "model", model, "currency", usage.getCostCurrency()).increment(usage.getCost().doubleValue()); } } @Override public void evaluateResponse(String conversationId, String messageId, ResponseEvaluation evaluation) { // 异步质量评估 CompletableFuture.runAsync(() -> { try { // 调用质量评估模型 QualityScore score = performQualityEvaluation(evaluation); // 存储评估结果 langfuseClient.score(CreateScoreRequest.builder() .traceId(conversationId) .observationId(messageId) .name("quality_score") .value(score.getOverall()) .comment(objectMapper.writeValueAsString(score)) .build()); } catch (Exception e) { log.error("质量评估失败", e); } }); } private QualityScore performQualityEvaluation(ResponseEvaluation evaluation) { // 这里可以集成第三方评估服务或自建评估模型 // 例如使用另一个LLM来评估回答质量 return QualityScore.builder() .relevance(0.85) .accuracy(0.90) .helpfulness(0.88) .safety(0.95) .overall(0.89) .evaluationMethod("llm_as_judge") .build(); } } // OpenTelemetryConfig.java @Configuration @ConditionalOnProperty(name = "monitoring.opentelemetry.enabled", havingValue = "true") public class OpenTelemetryConfig { @Bean public OpenTelemetry openTelemetry() { return OpenTelemetrySpring.initializeOpenTelemetry( GlobalOpenTelemetry.get() ); } @Bean public Tracer tracer() { return openTelemetry().getTracer("claude-clone"); } } // MetricsConfig.java @Configuration @EnableMetrics public class MetricsConfig { @Bean public MeterRegistry meterRegistry() { return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT); } @Bean public TimedAspect timedAspect(MeterRegistry meterRegistry) { return new TimedAspect(meterRegistry); } } // ChatServiceWithMonitoring.java @Service public class ChatServiceWithMonitoring { private final ChatService chatService; private final LLMObservabilityService observabilityService; public Flux<ChatChunk> streamChatWithMonitoring(ChatMessage message) { // 开始追踪 TraceContext traceContext = observabilityService.startTrace("chat_request", Map.of( "user_id", message.getUserId(), "conversation_id", message.getConversationId(), "model", message.getModel(), "message_length", message.getContent().length() )); Instant startTime = Instant.now(); return chatService.streamChat(message) .doOnNext(chunk -> { // 记录流式响应事件 observabilityService.addTraceEvent(traceContext, "chunk_received", Map.of("chunk_length", chunk.getContent().length())); }) .doOnComplete(() -> { // 计算响应时间 Duration duration = Duration.between(startTime, Instant.now()); observabilityService.recordLatency(message.getModel(), message.getProvider(), duration); // 结束追踪 observabilityService.endTrace(traceContext, TraceResult.success()); // 异步质量评估 String fullResponse = reconstructFullResponse(message.getConversationId()); observabilityService.evaluateResponse( message.getConversationId(), message.getId(), ResponseEvaluation.builder() .messageId(message.getId()) .conversationId(message.getConversationId()) .build() ); }) .doOnError(error -> { // 记录错误 observabilityService.recordError(message.getProvider(), message.getModel(), error.getClass().getSimpleName(), error); observabilityService.endTrace(traceContext, TraceResult.error(error)); }); } } ``` ### 5.3 前端核心实现 #### 5.3.1 聊天组件 ```tsx // components/Chat/ChatContainer.tsx import React, { useState, useEffect } from 'react'; import { Layout, Input, Button } from 'antd'; import { io, Socket } from 'socket.io-client'; import MessageList from './MessageList'; import ArtifactsPanel from '../Artifacts/ArtifactsPanel'; const { Content, Sider } = Layout; export const ChatContainer: React.FC = () => { const [socket, setSocket] = useState<Socket | null>(null); const [messages, setMessages] = useState<Message[]>([]); const [currentMessage, setCurrentMessage] = useState(''); const [isStreaming, setIsStreaming] = useState(false); useEffect(() => { const newSocket = io('/ws', { transports: ['websocket'] }); newSocket.on('connect', () => { console.log('Connected to server'); }); newSocket.on('message', (chunk: ChatChunk) => { if (chunk.type === 'text') { setMessages(prev => { const last = prev[prev.length - 1]; if (last && last.role === 'assistant') { return [ ...prev.slice(0, -1), { ...last, content: last.content + chunk.content } ]; } return [...prev, { role: 'assistant', content: chunk.content }]; }); } else if (chunk.type === 'completed') { setIsStreaming(false); } }); setSocket(newSocket); return () => { newSocket.close(); }; }, []); const sendMessage = () => { if (!currentMessage.trim() || !socket) return; const userMessage: Message = { role: 'user', content: currentMessage }; setMessages(prev => [...prev, userMessage]); setCurrentMessage(''); setIsStreaming(true); socket.emit('chat.send', { content: currentMessage, conversationId: getCurrentConversationId() }); }; return ( <Layout style={{ height: '100vh' }}> <Content style={{ padding: '20px' }}> <div className="chat-container"> <MessageList messages={messages} /> <div className="input-area"> <Input.TextArea value={currentMessage} onChange={(e) => setCurrentMessage(e.target.value)} onPressEnter={(e) => { if (!e.shiftKey) { e.preventDefault(); sendMessage(); } }} placeholder="发送消息..." autoSize={{ minRows: 1, maxRows: 5 }} disabled={isStreaming} /> <Button type="primary" onClick={sendMessage} disabled={isStreaming || !currentMessage.trim()} > 发送 </Button> </div> </div> </Content> <Sider width={500} theme="light"> <ArtifactsPanel /> </Sider> </Layout> ); }; ``` #### 5.3.2 Artifacts 组件 ```tsx // components/Artifacts/ArtifactsPanel.tsx import React, { useState } from 'react'; import { Tabs, Card } from 'antd'; import MonacoEditor from '@monaco-editor/react'; import CodePreview from './CodePreview'; interface Artifact { id: string; type: 'code' | 'document' | 'react'; title: string; content: string; language?: string; } export const ArtifactsPanel: React.FC = () => { const [artifacts, setArtifacts] = useState<Artifact[]>([]); const [activeKey, setActiveKey] = useState<string>(''); return ( <div className="artifacts-panel"> <Tabs activeKey={activeKey} onChange={setActiveKey} items={artifacts.map(artifact => ({ key: artifact.id, label: artifact.title, children: ( <div className="artifact-content"> {artifact.type === 'code' && ( <> <MonacoEditor height="50vh" language={artifact.language} value={artifact.content} theme="vs-dark" options={{ readOnly: false, minimap: { enabled: false } }} /> {['html', 'javascript', 'css'].includes(artifact.language || '') && ( <CodePreview code={artifact.content} language={artifact.language} /> )} </> )} </div> ) }))} /> </div> ); }; ``` ### 5.4 配置文件 #### 5.4.1 application.yml ```yaml spring: application: name: claude-clone # 数据源配置(H2嵌入式数据库) datasource: url: jdbc:h2:file:./data/db/claude;MODE=PostgreSQL driver-class-name: org.h2.Driver username: sa password: # JPA配置 jpa: hibernate: ddl-auto: update properties: hibernate: dialect: org.hibernate.dialect.H2Dialect # WebSocket配置 websocket: message-broker: relay-host: localhost relay-port: 61613 # 应用配置 app: profile: development # LLM配置 llm: default-provider: claude providers: claude: enabled: true api-key: ${CLAUDE_API_KEY} model: claude-3-sonnet-20240229 deepseek: enabled: true api-key: ${DEEPSEEK_API_KEY} base-url: https://api.deepseek.com/v1 model: deepseek-chat qwen: enabled: true api-key: ${QWEN_API_KEY} base-url: https://dashscope.aliyuncs.com/compatible-mode/v1 model: qwen-turbo # 执行环境配置 execution: docker: enabled: true resource-limits: memory: 512m cpu: 0.5 timeout: 30s frontend: enabled: true # 存储配置 storage: file: path: ./data/files cache: type: caffeine caffeine: max-size: 1000 expire-after-write: 1h # 分析工具配置 analysis: engine: local-python python: executable: python3 timeout: 60s # 监控和可观测性配置 monitoring: # Langfuse 配置 langfuse: enabled: true api-key: ${LANGFUSE_API_KEY} host: ${LANGFUSE_HOST:https://cloud.langfuse.com} project: claude-clone sampling-rate: 1.0 # OpenTelemetry 配置 opentelemetry: enabled: true service-name: claude-clone tracer: jaeger: endpoint: ${JAEGER_ENDPOINT:http://localhost:14268/api/traces} # Prometheus 指标配置 prometheus: enabled: true port: 9090 path: /metrics # 质量评估配置 evaluation: enabled: true evaluator: llm-as-judge evaluation-model: claude-3-haiku-20240307 evaluation-prompt: | 请对以下AI助手的回答进行评分(0-1分): 用户问题:{question} AI回答:{answer} 评分维度: 1. 相关性(0-1):回答是否与问题相关 2. 准确性(0-1):回答中的信息是否准确 3. 有用性(0-1):回答是否对用户有帮助 4. 安全性(0-1):回答是否安全无害 请以JSON格式返回评分结果。 # 业务指标配置 business-metrics: enabled: true track-user-satisfaction: true track-conversion-rate: true track-task-completion: true custom-events: - code-execution - file-upload - artifact-creation # 告警配置 alerts: enabled: true channels: - type: webhook url: ${ALERT_WEBHOOK_URL} - type: email recipients: ${ALERT_EMAIL_RECIPIENTS} rules: - name: high-error-rate condition: error_rate > 0.05 duration: 5m - name: high-latency condition: p95_latency > 10s duration: 3m - name: low-quality-score condition: avg_quality_score < 0.7 duration: 10m ``` #### 5.4.2 pom.xml ```xml <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.0</version> </parent> <groupId>com.claude</groupId> <artifactId>claude-clone</artifactId> <version>1.0.0</version> <properties> <java.version>17</java.version> <langchain4j.version>0.24.0</langchain4j.version> </properties> <dependencies> <!-- Spring Boot Starters --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- LangChain4j --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j</artifactId> <version>${langchain4j.version}</version> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-anthropic</artifactId> <version>${langchain4j.version}</version> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-open-ai</artifactId> <version>${langchain4j.version}</version> </dependency> <!-- 数据库 --> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <!-- 缓存 --> <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> </dependency> <!-- Docker客户端 --> <dependency> <groupId>com.spotify</groupId> <artifactId>docker-client</artifactId> <version>8.16.0</version> </dependency> <!-- 监控和可观测性 --> <!-- Langfuse --> <dependency> <groupId>com.langfuse</groupId> <artifactId>langfuse-java</artifactId> <version>2.1.0</version> </dependency> <!-- OpenTelemetry --> <dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-api</artifactId> <version>1.32.0</version> </dependency> <dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-sdk</artifactId> <version>1.32.0</version> </dependency> <dependency> <groupId>io.opentelemetry.instrumentation</groupId> <artifactId>opentelemetry-spring-boot-starter</artifactId> <version>1.32.0-alpha</version> </dependency> <!-- Micrometer + Prometheus --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-tracing-bridge-otel</artifactId> </dependency> <!-- 工具类 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies> </project> ``` ### 5.5 部署和运行 #### 5.5.1 启动脚本 ```bash #!/bin/bash # start.sh # 检查环境 check_requirements() { echo "检查运行环境..." # 检查Java if ! command -v java &> /dev/null; then echo "错误:未安装Java,请安装Java 17+" exit 1 fi # 检查Docker if ! command -v docker &> /dev/null; then echo "错误:未安装Docker,请安装Docker Desktop" exit 1 fi # 检查Python(可选) if ! command -v python3 &> /dev/null; then echo "警告:未安装Python3,Analysis功能将不可用" fi echo "环境检查通过!" } # 准备环境 prepare_environment() { echo "准备运行环境..." # 创建数据目录 mkdir -p ./data/{db,files,logs} # 预下载Docker镜像 echo "预下载代码执行环境镜像..." docker pull python:3.11-alpine & docker pull node:20-alpine & docker pull openjdk:17-alpine & wait echo "环境准备完成!" } # 设置环境变量 setup_env() { echo "配置环境变量..." # 检查API Keys if [ -z "$CLAUDE_API_KEY" ]; then echo "警告:未设置CLAUDE_API_KEY" fi if [ -z "$DEEPSEEK_API_KEY" ]; then echo "警告:未设置DEEPSEEK_API_KEY" fi if [ -z "$QWEN_API_KEY" ]; then echo "警告:未设置QWEN_API_KEY" fi } # 启动后端 start_backend() { echo "启动后端服务..." cd backend ./mvnw spring-boot:run & BACKEND_PID=$! cd .. echo "后端服务已启动,PID: $BACKEND_PID" } # 启动前端 start_frontend() { echo "启动前端服务..." cd frontend npm install npm run dev & FRONTEND_PID=$! cd .. echo "前端服务已启动,PID: $FRONTEND_PID" } # 主函数 main() { check_requirements prepare_environment setup_env start_backend sleep 10 # 等待后端启动 start_frontend echo "" echo "=========================================" echo "Claude Clone 已启动!" echo "前端地址:http://localhost:5173" echo "后端地址:http://localhost:8080" echo "H2控制台:http://localhost:8080/h2-console" echo "=========================================" echo "" echo "按 Ctrl+C 停止所有服务" # 等待退出信号 trap "kill $BACKEND_PID $FRONTEND_PID; exit" INT wait } main ``` #### 5.5.2 Docker Compose(可选) ```yaml version: '3.8' services: app: build: context: . dockerfile: docker/Dockerfile ports: - "8080:8080" - "5173:5173" volumes: - ./data:/app/data - /var/run/docker.sock:/var/run/docker.sock environment: - CLAUDE_API_KEY=${CLAUDE_API_KEY} - DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY} - QWEN_API_KEY=${QWEN_API_KEY} depends_on: - init-images init-images: image: docker:latest command: | sh -c " docker pull python:3.11-alpine docker pull node:20-alpine docker pull openjdk:17-alpine " volumes: - /var/run/docker.sock:/var/run/docker.sock ``` ## 6. LLM 监控和可观测性方案 ### 6.1 监控架构设计 基于之前的调研,我们采用 **Langfuse + OpenTelemetry** 的组合方案,实现从业务效果角度对AI应用进行全面监控: ```mermaid graph TB A[用户请求] --> B[Chat Controller] B --> C[Monitoring Interceptor] C --> D[LLM Provider] D --> E[AI Model API] C --> F[Langfuse Client] C --> G[OpenTelemetry Tracer] C --> H[Micrometer Registry] F --> I[Langfuse Dashboard] G --> J[Jaeger/Zipkin] H --> K[Prometheus] K --> L[Grafana Dashboard] subgraph "质量评估" M[Quality Evaluator] N[LLM-as-Judge] O[Business Metrics] end F --> M M --> N M --> O subgraph "告警系统" P[Alert Rules] Q[Webhook/Email] end K --> P P --> Q ``` ### 6.2 核心监控指标 #### 6.2.1 技术指标 - **延迟指标**:P50/P95/P99响应时间 - **吞吐量**:每秒请求数(RPS)、并发用户数 - **错误率**:HTTP错误、API错误、超时错误 - **资源使用**:Token消耗、API调用成本 #### 6.2.2 质量指标 - **准确性**:回答的正确性评分 - **相关性**:回答与问题的相关度 - **有用性**:对用户的帮助程度 - **安全性**:内容安全检查结果 - **幻觉检测**:虚假信息识别 #### 6.2.3 业务指标 - **用户满意度**:用户反馈评分 - **任务完成率**:用户目标达成率 - **会话质量**:对话轮次、用户留存 - **功能使用**:Artifacts使用率、代码执行成功率 ### 6.3 监控实施策略 #### 6.3.1 三层监控架构 **第一层:数据采集** - 在LLM Provider抽象层注入监控点 - 收集请求/响应数据、用户行为、系统指标 - 支持采样配置,避免性能影响 **第二层:数据处理** - 实时指标计算和异常检测 - 异步质量评估(LLM-as-Judge) - 业务指标聚合和分析 **第三层:展示决策** - 多维度仪表板展示 - 智能告警和异常通知 - 趋势分析和优化建议 #### 6.3.2 监控数据流 ``` 用户请求 -> 拦截器 -> 追踪开始 | v LLM调用 -> 指标记录 -> Langfuse/OpenTel | v 响应返回 -> 质量评估 -> 异步处理 | v 用户反馈 -> 业务指标 -> 长期分析 ``` ### 6.4 方案选择理由 #### 6.4.1 为什么选择 Langfuse? 1. **LLM专用**:专门为LLM应用设计,理解AI应用特点 2. **开源可控**:可以自托管,数据安全可控 3. **Java支持**:提供Java SDK,集成简单 4. **功能全面**:支持流式追踪、成本分析、质量评估 5. **社区活跃**:持续更新,社区支持良好 #### 6.4.2 为什么结合 OpenTelemetry? 1. **标准化**:业界标准,确保兼容性和可移植性 2. **生态丰富**:支持多种后端,如Jaeger、Zipkin 3. **Spring集成**:与Spring Boot无缝集成 4. **厂商中立**:避免供应商锁定 #### 6.4.3 为什么使用 LLM-as-Judge? 1. **自动化**:无需人工评估,可以实时进行 2. **一致性**:评估标准统一,结果可重复 3. **可扩展**:可以根据业务需求调整评估维度 4. **成本效益**:使用cheaper模型评估expensive模型 ### 6.5 实施建议 #### 6.5.1 分阶段实施 **第一阶段**(1周): - 集成Langfuse基础追踪 - 配置基本技术指标监控 - 设置简单的错误告警 **第二阶段**(1周): - 添加OpenTelemetry链路追踪 - 配置Prometheus+Grafana仪表板 - 实现质量评估系统 **第三阶段**(持续): - 优化告警规则 - 添加业务指标 - 持续改进评估模型 #### 6.5.2 运维建议 1. **数据保留**:设置合理的数据保留期限,平衡存储成本和分析需求 2. **性能影响**:监控采样率配置,避免影响主业务性能 3. **告警疲劳**:精心设计告警规则,避免误报 4. **团队培训**:让团队熟悉监控工具和数据解读 ## 7. 开发路线图 ### 7.1 第一阶段:MVP(2周) - [x] 基础项目搭建 - [x] 简单的聊天界面 - [x] Claude API集成 - [x] 消息流式传输 - [x] 基础的会话管理 ### 7.2 第二阶段:核心功能(4周) - [ ] 多模型支持(DeepSeek、Qwen) - [ ] Artifacts功能 - [ ] 前端代码执行 - [ ] 代码高亮和编辑器 - [ ] 会话持久化 ### 7.3 第三阶段:高级功能(4周) - [ ] Docker代码执行 - [ ] Analysis Tool基础版 - [ ] 文件上传和处理 - [ ] 用户认证系统 - [ ] 多会话管理 ### 7.4 第四阶段:优化和扩展(持续) - [ ] 性能优化 - [ ] 更多语言支持 - [ ] 高级Analysis功能 - [ ] 插件系统 - [ ] 企业级功能 ### 7.5 第五阶段:监控和可观测性(2周) - [ ] Langfuse集成和基础追踪 - [ ] OpenTelemetry配置和链路追踪 - [ ] Prometheus指标收集和Grafana仪表板 - [ ] 质量评估系统(LLM-as-Judge) - [ ] 告警规则配置 - [ ] 业务指标追踪 - [ ] 用户反馈收集机制 ## 8. 总结 本技术方案提供了一个完整的Claude.ai克隆实现路径,具有以下特点: 1. **轻量级设计**:可在单机MacBook上运行 2. **良好的抽象**:便于后续扩展和替换组件 3. **渐进式开发**:从MVP开始,逐步完善功能 4. **技术成熟度高**:基于Spring Boot和React等成熟技术栈 5. **易于部署**:提供一键启动脚本和Docker支持 6. **全面监控**:集成Langfuse和OpenTelemetry的企业级监控方案 7. **业务导向**:从业务效果角度监控AI应用表现和用户体验 ### 8.1 监控方案优势 **技术优势**: - **标准化**:采用OpenTelemetry业界标准,确保兼容性 - **专业化**:Langfuse专为LLM应用设计,功能更贴合需求 - **可扩展**:支持自定义指标和评估规则 - **低侵入**:通过抽象层设计,监控逻辑与业务逻辑分离 **业务价值**: - **质量保证**:自动化质量评估,及时发现问题 - **成本控制**:Token使用量和成本监控,优化资源配置 - **用户体验**:响应时间和错误率监控,提升用户满意度 - **数据驱动**:业务指标分析,支持产品迭代决策 **运维效率**: - **自动告警**:异常情况第一时间通知 - **可视化**:直观的仪表板和报表 - **追踪调试**:完整的请求链路追踪,快速定位问题 - **历史分析**:长期数据积累,支持趋势分析 通过遵循这个方案,您可以快速构建一个功能完善的AI对话应用,并具备企业级的监控和可观测性能力,为业务优化和产品迭代提供数据支撑。