引言

在大语言模型(LLM)技术飞速发展的今天,如何将AI能力真正落地到企业应用场景中,成为众多开发团队面临的挑战。本文将深入介绍一个基于Spring Boot 3.4构建的企业级智能应用平台,展示从底层LLM客户端封装、向量检索、知识切片到可视化工作流编排的完整技术栈。


一、项目概览

这是一个模块化设计的企业级智能知识管理平台,采用微服务架构思想,将不同功能拆分为独立模块:

核心模块

  • farm2-llm: 大语言模型客户端抽象层
  • farm2-ai-flow: AI工作流引擎与组件化编排
  • farm2-skc-know: 知识库管理系统
  • farm2-lucene: 全文检索与向量混合检索
  • farm2-auth: 统一认证与权限管理
  • farm2-base: 基础工具与通用组件

技术栈

  • 后端框架: Spring Boot 3.4 + JDK 17
  • 持久层: MyBatis 3.0.4 + MySQL 8.0
  • 搜索引擎: Apache Lucene 9.9.2
  • 缓存: Ehcache 2.10.8
  • JSON处理: Fastjson2 2.0.53
  • 文档处理: Apache POI + PDFBox
  • AI SDK:
    • PlexPT ChatGPT SDK 5.1.1
    • 阿里云 DashScope SDK 2.21.16
    • Ollama 原生HTTP客户端

二、LLM客户端抽象层:统一多模型接入

设计思路

在实际应用中,企业可能需要同时使用多个大模型提供商的服务(如阿里云通义千问、Ollama本地部署、OpenAI等)。为了屏蔽不同API的差异,我们设计了统一的客户端接口体系。

核心接口定义

public interface Farm2LlmClientInter extends Farm2ClientInter {
    /**
     * 初始化客户端配置
     */
    void init(LlmClient client);
    
    /**
     * 获取最大Token数量
     */
    int getTokenSize();
    
    /**
     * 发送消息(支持流式响应)
     */
    LlmSendInfo sendMsg(AiTextPrompt msg, Farm2LlmMessageHandleInter handle);
}

支持的模型类型

系统通过LlmFuncKeyEnum枚举区分不同类型的模型:

  1. 对话模型 (TALK): 用于智能问答、文本生成
  2. 向量模型 (EMBEDDING): 用于文本向量化,支持语义检索
  3. 重排序模型 (RERANK): 用于检索结果的相关性排序

客户端实现示例

1. Ollama原生HTTP客户端
@Component
public class Farm2LlmOllamaClientImpl implements Farm2LlmClientInter {
    private static final ObjectMapper objectMapper = new ObjectMapper();
    
    @Override
    public LlmSendInfo sendMsg(AiTextPrompt msg, Farm2LlmMessageHandleInter handle) {
        // 构建请求体
        Map<String, Object> requestBody = new HashMap<>();
        requestBody.put("model", paras.getModelkey());
        requestBody.put("messages", buildMessages(msg));
        requestBody.put("stream", true); // 启用流式输出
        
        // 发送HTTP请求并处理SSE流式响应
        OkHttpClient client = new OkHttpClient.Builder()
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(120, TimeUnit.SECONDS)
            .build();
            
        Request request = new Request.Builder()
            .url(paras.getBaseurl() + "/api/chat")
            .post(RequestBody.create(
                MediaType.parse("application/json"),
                objectMapper.writeValueAsString(requestBody)
            ))
            .build();
            
        // 异步处理流式响应
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onResponse(Call call, Response response) {
                try (BufferedReader reader = new BufferedReader(
                    response.body().charStream())) {
                    String line;
                    while ((line = reader.readLine()) != null) {
                        if (line.startsWith("data: ")) {
                            String jsonStr = line.substring(6);
                            JsonNode node = objectMapper.readTree(jsonStr);
                            String content = node.path("message")
                                .path("content").asText();
                            handle.onMsg(content); // 回调处理
                        }
                    }
                }
            }
        });
    }
}
2. 阿里云DashScope客户端

使用PlexPT SDK封装阿里云通义千问模型,支持更高级的功能如函数调用、思维链等。

动态客户端管理

系统支持在运行时动态切换和选择模型:

// 自动选择最优可用客户端
LlmClient client = llmClientService.getAutoClient(
    LlmFuncKeyEnum.TALK, true);
    
// 根据ID获取指定客户端
Farm2LlmClientInter llmClient = 
    llmClientService.getTalkClientById(clientId);

这种设计使得:

  • ✅ 可以无缝切换不同的模型提供商
  • ✅ 支持模型的灰度发布和A/B测试
  • ✅ 实现负载均衡和故障转移

三、RAG架构:检索增强生成

什么是RAG?

RAG(Retrieval-Augmented Generation)是一种结合信息检索和文本生成的技术架构。它通过以下步骤提升大模型的回答质量:

  1. 检索: 从知识库中查找相关文档
  2. 增强: 将检索到的内容作为上下文注入提示词
  3. 生成: 大模型基于增强后的上下文生成回答

系统架构

用户提问
   ↓
查询理解与改写
   ↓
┌─────────────┬──────────────┐
│  向量检索    │  全文检索     │
│ (Semantic)  │ (Keyword)    │
└──────┬──────┴──────┬───────┘
       ↓             ↓
   混合检索结果融合
       ↓
   重排序 (Rerank)
       ↓
   上下文组装
       ↓
   LLM生成回答
       ↓
   流式返回前端

1. 知识切片策略

高质量的切片是RAG系统的基石。系统提供了多种切片器:

LLM增强切片器 (LLMEnhancedChunker)

这是最智能的切片方式,利用大模型进行语义理解和结构化提取:

public class LLMEnhancedChunker implements ChunkerInter {
    @Override
    public List<SkcChunk> chunk(String text, Map<String, Object> params) {
        // 分批处理长文本
        int batchSize = (Integer) params.get("batch_size");
        
        // 调用LLM进行智能切片
        String prompt = buildPrompt(text, batchSize);
        String response = AiSyncUtils.sendMsgSync(llmClient, 
            new AiTextPrompt(prompt, systemPrompt, LlmCommandModelEnum.NONE),
            timeout, handler);
        
        // 解析JSON格式的切片结果
        List<SkcChunk> chunks = FarmJsons.toList(response, SkcChunk.class);
        
        // 每个切片包含:
        // - content: 清洗后的正文
        // - keywords: 核心关键词
        // - search_aliases: 同义词扩展
        // - summary: 一句话摘要
        // - full_path: 分类路径
        
        return chunks;
    }
}

优势

  • 🎯 语义完整性:避免在句子中间切断
  • 🔍 元数据丰富:自动生成关键词、摘要
  • 📝 代词消解:将"本条规定"替换为具体实体名
  • 🌐 别名扩展:增加搜索命中率
其他切片器
  • OverlapWindowChunker: 滑动窗口切片,保留重叠部分以维持上下文
  • EmbeddingLoadChunker: 基于嵌入向量相似度动态调整切片边界

2. 混合检索实现

系统同时支持向量检索和全文检索,并通过加权融合提升召回率:

public List<SkcChunkIndexDoc> search(RagQuery ragQuery) {
    // 1. 向量检索
    float[] vector = embedClient.getEmbedding(ragQuery.getWord());
    DocumentResult vectorResult = searchService.search(
        new DocumentRuleByVector(
            "embedding", vector, topK, similarityThreshold
        )
    );
    
    // 2. 全文检索(Lucene)
    String keywordQuery = buildLuceneQuery(ragQuery.getSearchKey());
    DocumentResult textResult = luceneService.search(keywordQuery);
    
    // 3. 结果融合(RRF算法)
    List<SkcChunkIndexDoc> merged = reciprocalRankFusion(
        vectorResult.getDocs(),
        textResult.getDocs(),
        k = 60 // RRF参数
    );
    
    // 4. 重排序
    if (rerankClientId != null) {
        merged = rerankWithLLM(merged, ragQuery.getWord());
    }
    
    return merged;
}

关键技术点

  • Reciprocal Rank Fusion (RRF): 一种无需训练的融合算法,通过倒数排名加权合并多路检索结果
  • 权限过滤: 检索时自动过滤用户无权访问的知识
  • 多字段Boost: 标题、标签、正文赋予不同权重

3. 上下文补充机制

针对切片导致的上下文丢失问题,系统实现了智能上下文注入:

public class FarmAppComponentInjectLlmTalkImpl 
    implements FarmAppComponentInter {
    
    @Override
    public void run(Map<String, Object> params, 
                   AiAppRunnerContext context) {
        // 1. 获取当前检索到的chunks
        List<SkcChunkIndexDoc> docs = context.getParas("search_results");
        
        // 2. 使用LLM判断哪些chunk需要补充上下文
        for (SkcChunkIndexDoc doc : docs) {
            String judgment = llmJudgeNeedContext(doc.getContent());
            
            if ("NEED".equals(judgment)) {
                // 3. 从数据库加载相邻chunks
                List<AiflowChunkInfo> adjacentChunks = 
                    chunkInfoDao.findAdjacent(
                        doc.getAppId(),
                        doc.getPartNo(),
                        forwardCount = 2,
                        backwardCount = 2
                    );
                
                // 4. 合并到原文档
                doc.setContent(mergeWithContext(doc, adjacentChunks));
            }
        }
        
        // 5. 更新上下文变量
        context.setParas("enhanced_docs", docs);
    }
}

这种机制有效解决了:

  • ❓ 指代不明:"该政策"指的是什么?
  • 📖 逻辑断裂:前提条件在前一个chunk中
  • 🔗 关联缺失:相关概念分散在多个chunk

四、AI工作流引擎:可视化编排智能应用

设计理念

传统的AI应用开发需要将业务逻辑硬编码在代码中,缺乏灵活性。我们设计了基于组件化的工作流引擎,允许用户通过拖拽方式编排复杂的AI业务流程。

核心概念

1. 应用 (AiApp)

一个完整的AI应用,由多个组件节点组成,例如:

  • 智能客服助手
  • 文档摘要生成器
  • 代码审查机器人
2. 组件 (AppComponent)

工作流的基本执行单元,每个组件完成特定功能:

组件类型 功能描述 示例
RAG检索 从知识库检索相关内容 查询相关政策条款
LLM对话 调用大模型生成文本 生成回答、总结
布尔判断 LLM返回true/false 判断是否敏感内容
变量转换 数据处理与格式转换 JSON解析、字符串拼接
消息发送 向客户端推送消息 流式输出、进度提示
上下文注入 补充缺失的上下文 加载相邻文档片段
3. 流程定义 (Flow Graph)

使用JSON格式存储有向无环图(DAG):

{
  "nodes": [
    {
      "id": "start",
      "type": "start",
      "position": {"x": 100, "y": 100}
    },
    {
      "id": "node_rag_search",
      "type": "TYPE-APPCOM-RAGSEARCH",
      "data": {
        "clientId": "emb_client_001",
        "topK": 5,
        "textLength": 2000
      },
      "position": {"x": 300, "y": 100}
    },
    {
      "id": "node_llm_talk",
      "type": "TYPE-APPCOM-LLMTALK-TEXT",
      "data": {
        "llmClientId": "llm_client_001",
        "prompt": "基于以下资料回答问题:\n{{search_results}}\n\n用户问题:{{user_msg}}",
        "sysPrompt": "你是一个专业的知识助手"
      },
      "position": {"x": 500, "y": 100}
    },
    {
      "id": "end",
      "type": "end",
      "position": {"x": 700, "y": 100}
    }
  ],
  "edges": [
    {"source": "start", "target": "node_rag_search"},
    {"source": "node_rag_search", "target": "node_llm_talk"},
    {"source": "node_llm_talk", "target": "end"}
  ]
}

工作流执行引擎

@Service
public class FarmAiAppRunnerImpl implements FarmAiAppRunnerInter {
    
    @Override
    public void runApp(String appId, AiAppRunnerContext context) {
        // 1. 加载应用配置
        AiflowApp app = aiflowAppService.getById(appId);
        GraphData graph = FlowJsonUtils.parseGraph(app.getFlowJson());
        
        // 2. 从开始节点出发
        Node currentNode = FlowGraphUtils.getStartNode(graph);
        
        // 3. 遍历执行节点
        while (currentNode != null && !currentNode.isEnd()) {
            // 执行当前节点
            executeNode(currentNode, context);
            
            // 根据边关系找到下一个节点
            currentNode = FlowGraphUtils.getNextNode(
                graph, currentNode, context
            );
        }
        
        // 4. 完成处理
        context.complete();
    }
    
    private void executeNode(Node node, AiAppRunnerContext context) {
        // 获取组件实现类
        String componentType = node.getType();
        FarmAppComponentInter component = 
            AppComponentUtils.getComponent(componentType);
        
        // 解析组件参数
        Map<String, Object> params = node.getData();
        
        // 执行组件
        try {
            component.run(params, context);
            logSuccess(node.getId());
        } catch (Exception e) {
            logError(node.getId(), e);
            context.sendToClientMsg("执行失败: " + e.getMessage(), 
                AiAppMsgTypeEnum.ERROR);
            throw e;
        }
    }
}

上下文管理

工作流中的节点通过AiAppRunnerContext共享数据:

public class AiAppRunnerContext {
    private final String id; // 会话ID
    private SseEmitter emitter; // SSE连接
    private List<AiMsgDto> hisMsgs; // 历史消息
    private Map<String, Object> paras; // 参数存储
    
    // 设置变量(供后续节点使用)
    public void setParas(String key, Object value) {
        paras.put(key, value);
    }
    
    // 获取变量(支持模板替换)
    public String getParaString(String key) {
        return (String) paras.get(key);
    }
    
    // 向客户端推送消息
    public void sendToClientMsg(String msg, AiAppMsgTypeEnum type) {
        AiMsgDto aiMsg = new AiMsgDto();
        aiMsg.setType(type);
        aiMsg.setContent(msg);
        emitter.send(SseEmitter.event().data(aiMsg));
    }
}

变量引用语法

在组件配置中可以使用{{variable_name}}引用上下文变量:

{
  "prompt": "请总结以下内容:\n{{rag_results}}"
}

执行时会自动替换为实际值。

典型应用场景

场景1:智能问答机器人
[开始] 
  ↓
[RAG检索] → 检索相关知识
  ↓
[上下文注入] → 补充相邻文档
  ↓
[LLM生成] → 基于知识生成回答
  ↓
[发送消息] → 流式返回给用户
  ↓
[结束]
场景2:文档审核工作流
[开始]
  ↓
[LLM判断] → 是否包含敏感内容?
  ↓
[条件分支]
  ├─ YES → [标记违规] → [通知管理员]
  └─ NO  → [LLM摘要] → [存档]
  ↓
[结束]
场景3:多轮对话记忆
[开始]
  ↓
[加载历史] → 从数据库读取对话历史
  ↓
[RAG检索] → 检索相关知识
  ↓
[上下文组装] → 合并历史+知识+当前问题
  ↓
[LLM对话] → 生成回复
  ↓
[保存历史] → 持久化对话记录
  ↓
[发送消息] → 返回给用户
  ↓
[结束]

五、实战案例:构建智能客服系统

需求分析

某企业需要构建一个智能客服系统,能够:

  1. 自动回答产品相关问题
  2. 从知识库中检索准确的政策条款
  3. 支持多轮对话,记住上下文
  4. 对于不确定的问题转人工

系统设计

1. 知识库准备
-- 知识分类表
CREATE TABLE SKC_TYPE (
    ID VARCHAR(32) PRIMARY KEY,
    NAME VARCHAR(256),
    PARENT_ID VARCHAR(32),
    CODE_PATH VARCHAR(1024) -- 分类路径,如:产品/手机/iPhone
);

-- 知识表
CREATE TABLE SKC_KNOW (
    ID VARCHAR(32) PRIMARY KEY,
    TITLE VARCHAR(512),
    CONTENT TEXT,
    TYPE_ID VARCHAR(32),
    STATE VARCHAR(2) -- 1:草稿 2:待审 3:发布
);

-- 切片表
CREATE TABLE AIFLOW_CHUNK_INFO (
    ID VARCHAR(32) PRIMARY KEY,
    APP_ID VARCHAR(32), -- 关联的知识ID
    PART_NO INT, -- 切片序号
    CONTENT TEXT, -- 切片内容
    EMBEDDING BLOB, -- 向量数据
    KEYWORDS VARCHAR(1024), -- 关键词
    SUMMARY VARCHAR(512) -- 摘要
);
2. 切片处理流程
// 重建所有知识的切片索引
@PostMapping("/reRagChunk")
public FarmResponseResult reRagChunk(@RequestBody KeyValueDto key) {
    new Thread(() -> {
        if ("KNOW".equals(key.getKey())) {
            // 遍历所有已发布的知识
            DataResult result = skcKnowService.searchSkcKnow(
                DataQuery.getInstance()
                    .addRule(new DBRule("STATE", "3", "="))
            );
            
            for (SkcKnow know : result.getData()) {
                // 读取知识内容
                String text = FileUtils.readText(know.getContentFile());
                
                // 执行切片流程
                AiflowChunkFlow flow = chunkFlowService.getFlow(
                    new SkcChunkApp(F2EObjectT.KNOW, know.getId(), ...)
                );
                
                // 调用配置的切片器
                List<SkcChunk> chunks = chunkerService.executeFlow(
                    flow.getId(), text
                );
                
                // 生成向量并存储
                for (SkcChunk chunk : chunks) {
                    float[] embedding = embedClient.getEmbedding(
                        chunk.getContent()
                    );
                    chunkInfoService.saveChunk(know.getId(), chunk, embedding);
                }
            }
        }
    }).start();
    
    return FarmResponseResult.success();
}
3. 工作流配置

创建一个名为"智能客服"的应用,配置如下工作流:

节点1:RAG检索

{
  "type": "TYPE-APPCOM-RAGSEARCH",
  "data": {
    "clientId": "emb_qwen_v3",
    "topK": 5,
    "textLength": 3000,
    "searchIndexWordVarKey": "user_msg",
    "searchVectorWordVarKey": "user_msg"
  }
}

节点2:上下文注入

{
  "type": "TYPE-APPCOM-LLMTALK-INJECT",
  "data": {
    "llm_client_id": "llm_qwen_plus",
    "max_check_chunks": 5,
    "forward_count": 1,
    "backward_count": 1,
    "prompt": "判断以下文本是否需要补充上下文才能完整理解..."
  }
}

节点3:LLM生成回答

{
  "type": "TYPE-APPCOM-LLMTALK-TEXT",
  "data": {
    "llmClientId": "llm_qwen_max",
    "sysPrompt": "你是专业的客服助手,基于提供的资料准确回答问题。如果资料中没有相关信息,请明确告知用户。",
    "prompt": "参考资料:\n{{enhanced_docs}}\n\n用户问题:{{user_msg}}\n\n请给出专业、准确的回答:"
  }
}

节点4:发送消息

{
  "type": "TYPE-APPCOM-SENDMSG",
  "data": {
    "msgVarKey": "llm_response",
    "msgType": "ANSWER"
  }
}
4. 前端调用
// 发起智能对话
const eventSource = new EventSource(
  `/api/aiflowrunner/run?appId=${appId}&userMsg=${encodeURIComponent(question)}`
);

eventSource.onmessage = (event) => {
  const data = JSON.parse(event.data);
  
  if (data.type === 'ANSWER') {
    // 流式显示回答
    appendToChatBox(data.content);
  } else if (data.type === 'SYS_MSG') {
    // 显示系统消息
    showSystemMessage(data.content);
  } else if (data.type === 'COMPLETE') {
    // 对话完成
    eventSource.close();
  }
};

eventSource.onerror = (error) => {
  console.error('SSE错误:', error);
  eventSource.close();
};

效果展示

用户提问

iPhone 15的保修期是多久?

系统执行过程

  1. RAG检索:从知识库中找到3个相关切片

    • 切片1:iPhone系列产品保修政策概述
    • 切片2:iPhone 15具体保修条款
    • 切片3:保修范围说明
  2. 上下文注入:LLM判断切片2需要补充上下文,加载了前后相邻的切片

  3. LLM生成

    根据苹果官方政策,iPhone 15的保修期为:
    
    1. 整机保修:自购买之日起1年
    2. 主要部件(主板、显示屏等):2年
    3. 电池:如果电池容量低于原始容量的80%,且在保修期内,可免费更换
    
    温馨提示:
    - 保修需要提供购买凭证
    - 人为损坏不在保修范围内
    - 建议购买AppleCare+延长保修服务
    
  4. 流式返回:文字逐字显示,用户体验流畅


六、性能优化与实践建议

1. 向量检索优化

问题:随着知识库增长,向量检索速度下降

解决方案

  • ✅ 使用HNSW索引加速近似最近邻搜索
  • ✅ 对热门知识预计算向量缓存
  • ✅ 分页批量处理,避免一次性加载过多数据
// 批量生成向量
int batchSize = 100;
for (int i = 0; i < chunks.size(); i += batchSize) {
    List<SkcChunk> batch = chunks.subList(i, 
        Math.min(i + batchSize, chunks.size()));
    
    // 并行生成向量
    batch.parallelStream().forEach(chunk -> {
        float[] embedding = embedClient.getEmbedding(chunk.getContent());
        chunk.setEmbedding(embedding);
    });
    
    // 批量入库
    chunkInfoService.batchSave(batch);
    
    // 更新进度
    FarmProcessUtils.setProcess(processKey, 
        i * 100 / chunks.size(), "处理中...");
}

2. LLM调用成本控制

策略

  • 🎯 小模型优先:简单任务使用低成本模型
  • 💾 结果缓存:相同问题直接返回缓存答案
  • ⏱️ 超时控制:避免长时间等待
  • 📊 Token监控:统计每次调用的Token消耗
// 智能选择模型
LlmClient selectModel(String taskComplexity) {
    if ("SIMPLE".equals(taskComplexity)) {
        // 简单任务:使用快速便宜的模型
        return llmClientService.getClientByLevel(1);
    } else if ("COMPLEX".equals(taskComplexity)) {
        // 复杂任务:使用高质量模型
        return llmClientService.getClientByLevel(3);
    }
    return llmClientService.getAutoClient(LlmFuncKeyEnum.TALK, true);
}

3. 并发与异步处理

场景:大量用户同时发起AI请求

方案

  • 使用线程池隔离不同任务
  • SSE流式输出减少等待焦虑
  • 后台异步处理耗时操作(如重建索引)
// 异步重建索引
@Async("taskExecutor")
public void rebuildIndexAsync(List<String> knowIds) {
    String processKey = UUID.randomUUID().toString();
    FarmProcessUtils.setProcess(processKey, 0, "开始重建");
    
    try {
        for (String knowId : knowIds) {
            // 处理单个知识
            processKnowledge(knowId);
            
            // 更新进度
            updateProgress(processKey);
        }
        
        FarmProcessUtils.setProcessEnd(processKey, "完成");
    } catch (Exception e) {
        FarmProcessUtils.setProcessError(processKey, e.getMessage());
    }
}

4. 安全性考虑

  • 🔐 API密钥管理:加密存储,不硬编码
  • 🛡️ 输入验证:防止提示词注入攻击
  • 👤 权限控制:基于角色的知识访问控制
  • 📝 审计日志:记录所有AI交互行为
// 权限过滤
Set<String> userReadKnowIds = permissionService.getUserReadKnowIds(currentUser);

// 检索时自动过滤
query.addRule(new WhereInRule("APP_ID", userReadKnowIds));

七、未来展望

技术演进方向

  1. 多模态支持

    • 图片理解与生成
    • 语音交互
    • 视频内容分析
  2. Agent智能体

    • 自主规划与决策
    • 工具调用(搜索、计算、API)
    • 多Agent协作
  3. 个性化推荐

    • 基于用户行为的智能推荐
    • 自适应学习路径
  4. 实时知识更新

    • 增量索引构建
    • 热更新机制
    • 版本管理

最佳实践总结

模块化设计:清晰的职责划分,便于维护和扩展
抽象接口:屏蔽底层差异,提高可移植性
可视化编排:降低使用门槛,提升开发效率
混合检索:结合向量与关键词,提升召回率
流式输出:改善用户体验,减少等待焦虑
权限控制:保障数据安全,符合企业规范


结语

本文详细介绍了一个企业级智能应用平台的核心架构与实现细节。通过LLM客户端抽象层、RAG检索增强、可视化工作流编排等技术,我们能够快速构建灵活、高效的AI应用。

关键成功因素:

  1. 良好的架构设计:分层清晰,职责明确
  2. 灵活的扩展机制:插件化组件,易于定制
  3. 完善的工程实践:性能优化、安全控制、监控告警

希望这篇文章能为正在探索AI应用落地的团队提供一些参考和启发。AI技术仍在快速发展,保持学习和创新的心态,才能在变革中抓住机遇。


产品地址: 添加链接描述
技术栈: Spring Boot 3.4 + JDK 17 + MyBatis + Lucene

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐