创建一个使用Spring AI结合MCP(Model Context Protocol)和Dify构建智能客服系统的简单案例

项目概述

构建一个智能客服系统,集成Spring AI作为应用框架,使用MCP协议连接外部工具,通过Dify管理AI工作流。

项目结构

css 复制代码
intelligent-customer-service/
├── src/main/java/
│   ├── com/example/service/
│   │   ├── CustomerServiceApplication.java
│   │   ├── config/
│   │   │   ├── SpringAIConfig.java
│   │   │   ├── MCPConfig.java
│   │   │   └── DifyConfig.java
│   │   ├── controller/
│   │   │   └── ChatController.java
│   │   ├── service/
│   │   │   ├── ChatService.java
│   │   │   ├── MCPToolService.java
│   │   │   └── DifyWorkflowService.java
│   │   ├── model/
│   │   │   ├── ChatRequest.java
│   │   │   ├── ChatResponse.java
│   │   │   └── CustomerInfo.java
│   │   └── mcp/
│   │       ├── MCPClient.java
│   │       ├── MCPTool.java
│   │       └── tools/
│   │           ├── DatabaseTool.java
│   │           └── EmailTool.java
├── src/main/resources/
│   ├── application.yml
│   └── mcp-tools.json
└── pom.xml

1. Maven依赖配置

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<pom.xml>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>intelligent-customer-service</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
        <relativePath/>
    </parent>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <spring-ai.version>0.8.0</spring-ai.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-data-jpa</artifactId>
        </dependency>

        <!-- Spring AI -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
            <version>${spring-ai.version}</version>
        </dependency>

        <!-- HTTP Client for MCP and Dify -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>

        <!-- JSON Processing -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>

        <!-- Database -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!-- Validation -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
    </dependencies>
</pom.xml>

2. 配置文件

yaml 复制代码
# application.yml
server:
  port: 8080

spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY:your-openai-api-key}
      chat:
        model: gpt-4
        temperature: 0.7
  
  datasource:
    url: jdbc:h2:mem:testdb
    driver-class-name: org.h2.Driver
    username: sa
    password: password
  
  jpa:
    database-platform: org.hibernate.dialect.H2Dialect
    hibernate:
      ddl-auto: create-drop

# MCP Configuration
mcp:
  server:
    url: ws://localhost:3001
    timeout: 30000
  tools:
    database:
      enabled: true
      connection-url: jdbc:h2:mem:testdb
    email:
      enabled: true
      smtp-host: smtp.gmail.com
      smtp-port: 587

# Dify Configuration  
dify:
  api:
    base-url: https://api.dify.ai/v1
    key: ${DIFY_API_KEY:your-dify-api-key}
  workflow:
    customer-service: ${DIFY_WORKFLOW_ID:workflow-id}

logging:
  level:
    com.example.service: DEBUG
    org.springframework.ai: DEBUG

3. 核心配置类

java 复制代码
// SpringAIConfig.java
package com.example.service.config;

import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.openai.OpenAiChatClient;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SpringAIConfig {

    @Value("${spring.ai.openai.api-key}")
    private String apiKey;

    @Bean
    public OpenAiApi openAiApi() {
        return new OpenAiApi(apiKey);
    }

    @Bean
    public ChatClient chatClient(OpenAiApi openAiApi) {
        return OpenAiChatClient.builder(openAiApi)
            .withDefaultOptions(OpenAiChatOptions.builder()
                .withModel("gpt-4")
                .withTemperature(0.7f)
                .withMaxTokens(1000)
                .build())
            .build();
    }
}
// MCPConfig.java
package com.example.service.config;

import com.example.service.mcp.MCPClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient;
import org.springframework.web.reactive.socket.client.WebSocketClient;

@Configuration
public class MCPConfig {

    @Value("${mcp.server.url}")
    private String mcpServerUrl;

    @Value("${mcp.server.timeout}")
    private int timeout;

    @Bean
    public WebSocketClient webSocketClient() {
        return new ReactorNettyWebSocketClient();
    }

    @Bean
    public MCPClient mcpClient(WebSocketClient webSocketClient) {
        return new MCPClient(webSocketClient, mcpServerUrl, timeout);
    }
}
// DifyConfig.java
package com.example.service.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;

@Configuration
public class DifyConfig {

    @Value("${dify.api.base-url}")
    private String baseUrl;

    @Value("${dify.api.key}")
    private String apiKey;

    @Bean
    public WebClient difyWebClient() {
        return WebClient.builder()
            .baseUrl(baseUrl)
            .defaultHeader("Authorization", "Bearer " + apiKey)
            .defaultHeader("Content-Type", "application/json")
            .build();
    }
}

4. 数据模型

java 复制代码
// ChatRequest.java
package com.example.service.model;

import javax.validation.constraints.NotBlank;

public class ChatRequest {
    @NotBlank
    private String message;
    private String sessionId;
    private String userId;
    private String context;

    // Constructors
    public ChatRequest() {}

    public ChatRequest(String message, String sessionId, String userId) {
        this.message = message;
        this.sessionId = sessionId;
        this.userId = userId;
    }

    // Getters and Setters
    public String getMessage() { return message; }
    public void setMessage(String message) { this.message = message; }

    public String getSessionId() { return sessionId; }
    public void setSessionId(String sessionId) { this.sessionId = sessionId; }

    public String getUserId() { return userId; }
    public void setUserId(String userId) { this.userId = userId; }

    public String getContext() { return context; }
    public void setContext(String context) { this.context = context; }
}
// ChatResponse.java
package com.example.service.model;

import java.time.LocalDateTime;
import java.util.List;

public class ChatResponse {
    private String response;
    private String sessionId;
    private LocalDateTime timestamp;
    private List<String> suggestedActions;
    private boolean requiresHumanIntervention;

    // Constructors
    public ChatResponse() {
        this.timestamp = LocalDateTime.now();
    }

    public ChatResponse(String response, String sessionId) {
        this();
        this.response = response;
        this.sessionId = sessionId;
    }

    // Getters and Setters
    public String getResponse() { return response; }
    public void setResponse(String response) { this.response = response; }

    public String getSessionId() { return sessionId; }
    public void setSessionId(String sessionId) { this.sessionId = sessionId; }

    public LocalDateTime getTimestamp() { return timestamp; }
    public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; }

    public List<String> getSuggestedActions() { return suggestedActions; }
    public void setSuggestedActions(List<String> suggestedActions) { 
        this.suggestedActions = suggestedActions; 
    }

    public boolean isRequiresHumanIntervention() { return requiresHumanIntervention; }
    public void setRequiresHumanIntervention(boolean requiresHumanIntervention) { 
        this.requiresHumanIntervention = requiresHumanIntervention; 
    }
}

5. MCP客户端实现

java 复制代码
// MCPClient.java
package com.example.service.mcp;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.socket.WebSocketMessage;
import org.springframework.web.reactive.socket.WebSocketSession;
import org.springframework.web.reactive.socket.client.WebSocketClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

@Component
public class MCPClient {
    private static final Logger logger = LoggerFactory.getLogger(MCPClient.class);
    
    private final WebSocketClient client;
    private final String serverUrl;
    private final int timeout;
    private final ObjectMapper objectMapper;
    private final AtomicLong messageIdCounter;
    private final Map<String, Mono<JsonNode>> pendingRequests;
    
    private WebSocketSession session;

    public MCPClient(WebSocketClient client, String serverUrl, int timeout) {
        this.client = client;
        this.serverUrl = serverUrl;
        this.timeout = timeout;
        this.objectMapper = new ObjectMapper();
        this.messageIdCounter = new AtomicLong();
        this.pendingRequests = new ConcurrentHashMap<>();
    }

    public Mono<Void> connect() {
        return client.execute(URI.create(serverUrl), this::handleSession);
    }

    private Mono<Void> handleSession(WebSocketSession session) {
        this.session = session;
        
        Mono<Void> input = session.receive()
            .map(WebSocketMessage::getPayloadAsText)
            .doOnNext(this::handleMessage)
            .then();

        Mono<Void> output = session.send(Flux.never());

        return Mono.zip(input, output).then();
    }

    private void handleMessage(String message) {
        try {
            JsonNode jsonMessage = objectMapper.readTree(message);
            String id = jsonMessage.get("id").asText();
            
            if (pendingRequests.containsKey(id)) {
                pendingRequests.get(id).subscribe(result -> {
                    // Handle response
                });
                pendingRequests.remove(id);
            }
        } catch (Exception e) {
            logger.error("Error handling MCP message", e);
        }
    }

    public Mono<JsonNode> callTool(String toolName, Map<String, Object> parameters) {
        String messageId = String.valueOf(messageIdCounter.incrementAndGet());
        
        Map<String, Object> request = Map.of(
            "jsonrpc", "2.0",
            "id", messageId,
            "method", "tools/call",
            "params", Map.of(
                "name", toolName,
                "arguments", parameters
            )
        );

        try {
            String requestJson = objectMapper.writeValueAsString(request);
            
            Mono<JsonNode> responseMono = Mono.<JsonNode>create(sink -> {
                pendingRequests.put(messageId, Mono.just(sink));
            }).timeout(Duration.ofMillis(timeout));

            if (session != null) {
                session.send(Mono.just(session.textMessage(requestJson)))
                    .subscribe();
            }

            return responseMono;
        } catch (Exception e) {
            logger.error("Error calling MCP tool: " + toolName, e);
            return Mono.error(e);
        }
    }
}

6. MCP工具实现

java 复制代码
// DatabaseTool.java
package com.example.service.mcp.tools;

import com.example.service.mcp.MCPClient;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import java.util.Map;

@Component
public class DatabaseTool {
    
    @Autowired
    private MCPClient mcpClient;

    public Mono<String> queryCustomerInfo(String customerId) {
        Map<String, Object> params = Map.of(
            "query", "SELECT * FROM customers WHERE id = ?",
            "parameters", new String[]{customerId}
        );

        return mcpClient.callTool("database_query", params)
            .map(this::extractResult)
            .onErrorReturn("Customer information not found");
    }

    public Mono<String> updateCustomerStatus(String customerId, String status) {
        Map<String, Object> params = Map.of(
            "query", "UPDATE customers SET status = ? WHERE id = ?",
            "parameters", new String[]{status, customerId}
        );

        return mcpClient.callTool("database_update", params)
            .map(result -> "Customer status updated successfully")
            .onErrorReturn("Failed to update customer status");
    }

    private String extractResult(JsonNode response) {
        if (response.has("result")) {
            return response.get("result").toString();
        }
        return "No data found";
    }
}

7. Dify工作流服务

java 复制代码
// DifyWorkflowService.java
package com.example.service.service;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

import java.util.Map;

@Service
public class DifyWorkflowService {
    private static final Logger logger = LoggerFactory.getLogger(DifyWorkflowService.class);

    @Autowired
    private WebClient difyWebClient;

    @Value("${dify.workflow.customer-service}")
    private String workflowId;

    private final ObjectMapper objectMapper = new ObjectMapper();

    public Mono<String> executeWorkflow(String userMessage, Map<String, Object> context) {
        Map<String, Object> requestBody = Map.of(
            "inputs", Map.of(
                "user_message", userMessage,
                "context", context
            ),
            "response_mode", "blocking",
            "user", context.getOrDefault("userId", "anonymous")
        );

        return difyWebClient.post()
            .uri("/workflows/run")
            .bodyValue(requestBody)
            .retrieve()
            .bodyToMono(JsonNode.class)
            .map(this::extractWorkflowResult)
            .doOnNext(result -> logger.info("Dify workflow result: {}", result))
            .onErrorResume(error -> {
                logger.error("Error executing Dify workflow", error);
                return Mono.just("I apologize, but I'm experiencing technical difficulties. Please try again later.");
            });
    }

    private String extractWorkflowResult(JsonNode response) {
        if (response.has("data") && response.get("data").has("outputs")) {
            JsonNode outputs = response.get("data").get("outputs");
            if (outputs.has("answer")) {
                return outputs.get("answer").asText();
            }
        }
        return "No response from workflow";
    }

    public Mono<Boolean> isWorkflowHealthy() {
        return difyWebClient.get()
            .uri("/workflows/{workflowId}/status", workflowId)
            .retrieve()
            .bodyToMono(JsonNode.class)
            .map(response -> response.get("status").asText().equals("active"))
            .onErrorReturn(false);
    }
}

8. 主要服务层

java 复制代码
// ChatService.java
package com.example.service.service;

import com.example.service.model.ChatRequest;
import com.example.service.model.ChatResponse;
import com.example.service.mcp.tools.DatabaseTool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.chat.ChatResponse;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class ChatService {
    private static final Logger logger = LoggerFactory.getLogger(ChatService.class);

    @Autowired
    private ChatClient chatClient;

    @Autowired
    private DifyWorkflowService difyWorkflowService;

    @Autowired
    private DatabaseTool databaseTool;

    private static final String SYSTEM_PROMPT = """
        You are an intelligent customer service assistant. You have access to:
        1. Customer database through MCP tools
        2. Dify workflows for complex business logic
        3. Email and notification systems
        
        Your responsibilities:
        - Provide accurate and helpful customer support
        - Use appropriate tools when needed
        - Escalate to human agents when necessary
        - Maintain a professional and friendly tone
        
        If you need to access customer information, use the database tools.
        For complex workflows like order processing or refunds, use Dify workflows.
        """;

    public Mono<com.example.service.model.ChatResponse> processChat(ChatRequest request) {
        logger.info("Processing chat request from user: {}", request.getUserId());

        // First, try to determine if we need customer data
        return determineRequiredTools(request.getMessage())
            .flatMap(toolsNeeded -> {
                if (toolsNeeded.contains("customer_data")) {
                    return enrichWithCustomerData(request);
                } else {
                    return handleWithAI(request, Map.of());
                }
            })
            .map(this::createChatResponse)
            .doOnNext(response -> logger.info("Generated response for session: {}", response.getSessionId()));
    }

    private Mono<List<String>> determineRequiredTools(String message) {
        // Simple keyword-based tool detection
        // In production, this could be more sophisticated
        if (message.toLowerCase().contains("order") || 
            message.toLowerCase().contains("account") ||
            message.toLowerCase().contains("profile")) {
            return Mono.just(Arrays.asList("customer_data"));
        }
        return Mono.just(Arrays.asList());
    }

    private Mono<String> enrichWithCustomerData(ChatRequest request) {
        if (request.getUserId() != null) {
            return databaseTool.queryCustomerInfo(request.getUserId())
                .flatMap(customerInfo -> {
                    Map<String, Object> context = Map.of(
                        "customer_info", customerInfo,
                        "user_id", request.getUserId()
                    );
                    return handleWithAI(request, context);
                });
        } else {
            return handleWithAI(request, Map.of());
        }
    }

    private Mono<String> handleWithAI(ChatRequest request, Map<String, Object> context) {
        // First try Dify workflow for complex scenarios
        if (isComplexScenario(request.getMessage())) {
            return difyWorkflowService.executeWorkflow(request.getMessage(), context)
                .onErrorResume(error -> {
                    logger.warn("Dify workflow failed, falling back to Spring AI", error);
                    return handleWithSpringAI(request, context);
                });
        } else {
            return handleWithSpringAI(request, context);
        }
    }

    private boolean isComplexScenario(String message) {
        return message.toLowerCase().contains("refund") ||
               message.toLowerCase().contains("return") ||
               message.toLowerCase().contains("exchange") ||
               message.toLowerCase().contains("cancel order");
    }

    private Mono<String> handleWithSpringAI(ChatRequest request, Map<String, Object> context) {
        return Mono.fromCallable(() -> {
            List<Message> messages = Arrays.asList(
                new SystemMessage(SYSTEM_PROMPT + buildContextPrompt(context)),
                new UserMessage(request.getMessage())
            );

            Prompt prompt = new Prompt(messages);
            ChatResponse response = chatClient.call(prompt);
            
            return response.getResult().getOutput().getContent();
        });
    }

    private String buildContextPrompt(Map<String, Object> context) {
        if (context.isEmpty()) {
            return "";
        }

        StringBuilder contextPrompt = new StringBuilder("\n\nAdditional Context:\n");
        context.forEach((key, value) -> {
            contextPrompt.append("- ").append(key).append(": ").append(value).append("\n");
        });

        return contextPrompt.toString();
    }

    private com.example.service.model.ChatResponse createChatResponse(String aiResponse) {
        com.example.service.model.ChatResponse response = 
            new com.example.service.model.ChatResponse();
        response.setResponse(aiResponse);
        
        // Analyze response for suggested actions
        if (aiResponse.toLowerCase().contains("contact support") ||
            aiResponse.toLowerCase().contains("human agent")) {
            response.setRequiresHumanIntervention(true);
        }

        return response;
    }
}

9. 控制器

java 复制代码
// ChatController.java
package com.example.service.controller;

import com.example.service.model.ChatRequest;
import com.example.service.model.ChatResponse;
import com.example.service.service.ChatService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;

import javax.validation.Valid;

@RestController
@RequestMapping("/api/chat")
@CrossOrigin(origins = "*")
public class ChatController {

    @Autowired
    private ChatService chatService;

    @PostMapping("/message")
    public Mono<ResponseEntity<ChatResponse>> sendMessage(@Valid @RequestBody ChatRequest request) {
        return chatService.processChat(request)
            .map(ResponseEntity::ok)
            .onErrorReturn(ResponseEntity.internalServerError().build());
    }

    @GetMapping("/health")
    public ResponseEntity<String> health() {
        return ResponseEntity.ok("Chat service is running");
    }

    @PostMapping("/session/{sessionId}/end")
    public ResponseEntity<String> endSession(@PathVariable String sessionId) {
        // Implementation for ending chat session
        return ResponseEntity.ok("Session ended: " + sessionId);
    }
}

10. 主应用类

java 复制代码
// CustomerServiceApplication.java
package com.example.service;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@SpringBootApplication
@EnableAspectJAutoProxy
public class CustomerServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(CustomerServiceApplication.class, args);
    }
}

11. MCP工具配置

json 复制代码
// mcp-tools.json
{
  "tools": [
    {
      "name": "database_query",
      "description": "Query customer database",
      "parameters": {
        "type": "object",
        "properties": {
          "query": {
            "type": "string",
            "description": "SQL query to execute"
          },
          "parameters": {
            "type": "array",
            "description": "Query parameters"
          }
        },
        "required": ["query"]
      }
    },
    {
      "name": "send_email",
      "description": "Send email to customer",
      "parameters": {
        "type": "object",
        "properties": {
          "to": {
            "type": "string",
            "description": "Recipient email address"
          },
          "subject": {
            "type": "string",
            "description": "Email subject"
          },
          "body": {
            "type": "string",
            "description": "Email body"
          }
        },
        "required": ["to", "subject", "body"]
      }
    }
  ]
}

12. 使用示例

bash 复制代码
# 启动应用
mvn spring-boot:run

# 发送聊天请求
curl -X POST http://localhost:8080/api/chat/message \
  -H "Content-Type: application/json" \
  -d '{
    "message": "I need help with my order #12345",
    "sessionId": "session_123",
    "userId": "user_456"
  }'

总结

这个完整的案例展示了如何将Spring AI、MCP和Dify集成到一个智能客服系统中:

  1. Spring AI: 提供基础的AI对话能力
  2. MCP: 通过工具扩展AI的能力(数据库查询、邮件发送等)
  3. Dify: 处理复杂的业务流程和工作流
相关推荐
架构师沉默1 小时前
我用一个 Postgres 实现一整套后端架构!
java·spring boot·程序人生·架构·tdd
xiucai_cs1 小时前
布隆过滤器原理与Spring Boot实战
java·spring boot·后端·布隆过滤器
巴拉巴巴巴拉1 小时前
Spring Boot 中 YAML 配置文件详解
spring boot
向阳花自开2 小时前
Spring Boot 常用注解速查表
java·spring boot·后端
cpp加油站3 小时前
打脸来的太快了,又发现一个Trae的宝藏功能--内置浏览器可以指定机型来显示前端界面
前端·ai编程·trae
win4r3 小时前
🚀Cursor CLI+GPT-5保姆级教程+编程能力测评!Cursor CLI零成本免费使用GPT-5!Claude Code的劲敌来了!从安装到实战演示
gpt·aigc·openai
꧁༺摩༒西༻꧂4 小时前
Spring Boot Actuator 监控功能的简介及禁用
java·数据库·spring boot
jzy37114 小时前
京东开源王炸!JoyAgent-JDGenie 通用智能体一键部署指南,DeepSeek 大模型完美适配
后端·openai·ai编程
POLOAPI4 小时前
Claude Opus:从智能升级到场景落地的旗舰模型进阶之路
人工智能·ai编程·claude