18586361686
2025-04-21 4453533319a3e21ace5d041a94a10212afffde4e
feat: Add third-party call to intelligent agent interface and interfaec docs
2个文件已修改
9个文件已添加
504 ■■■■■ 已修改文件
aiflowy-commons/aiflowy-common-ai/src/main/java/tech/aiflowy/common/ai/util/UUIDGenerator.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/controller/AiBotApiKeyController.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/controller/AiBotController.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/entity/AiBotApiKey.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/entity/base/AiBotApiKeyBase.java 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/mapper/AiBotApiKeyMapper.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/service/AiBotApiKeyService.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/service/impl/AiBotApiKeyServiceImpl.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
aiflowy-ui-react/src/pages/ai/AiBotApiKey.tsx 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/.vitepress/config.mts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/zh/development/ai/apiKey.md 156 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
aiflowy-commons/aiflowy-common-ai/src/main/java/tech/aiflowy/common/ai/util/UUIDGenerator.java
New file
@@ -0,0 +1,24 @@
package tech.aiflowy.common.ai.util;
import java.util.UUID;
public class UUIDGenerator {
    /**
     * 生成32位不带"-"的UUID字符串
     * @return 32位不重复的UUID字符串
     */
    public static String generateUUID() {
        // 使用UUID类生成标准的UUID
        UUID uuid = UUID.randomUUID();
        // 去掉"-"并转换为小写,返回32位的字符串
        return uuid.toString().replace("-", "").toLowerCase();
    }
    public static void main(String[] args) {
        // 测试生成32位UUID
        for (int i = 0; i < 5; i++) {
            System.out.println(generateUUID());
        }
    }
}
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/controller/AiBotApiKeyController.java
New file
@@ -0,0 +1,47 @@
package tech.aiflowy.ai.controller;
import com.mybatisflex.core.table.TableInfo;
import com.mybatisflex.core.table.TableInfoFactory;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.aiflowy.ai.entity.AiBotApiKey;
import tech.aiflowy.ai.service.AiBotApiKeyService;
import tech.aiflowy.common.ai.util.UUIDGenerator;
import tech.aiflowy.common.domain.Result;
import tech.aiflowy.common.web.controller.BaseCurdController;
import java.time.LocalDateTime;
/**
 *  控制层。
 *
 * @author wangGangQiang
 * @since 2025-04-18
 */
@RestController
@RequestMapping("/api/v1/aiBotApiKey")
public class AiBotApiKeyController extends BaseCurdController<AiBotApiKeyService, AiBotApiKey> {
    public AiBotApiKeyController(AiBotApiKeyService service) {
        super(service);
    }
    /**
     * 添加(保存)数据
     *
     * @return {@code Result.errorCode == 0} 添加成功,否则添加失败
     */
    @PostMapping("/key/save")
    public Result save() {
        String apiKey = UUIDGenerator.generateUUID();
        AiBotApiKey entity = new AiBotApiKey();
        entity.setApiKey(apiKey);
        entity.setCreated(LocalDateTime.now());
        entity.setStatus(1);
        boolean success = service.save(entity);
        onSaveOrUpdateAfter(entity, true);
        TableInfo tableInfo = TableInfoFactory.ofEntityClass(entity.getClass());
        Object[] pkArgs = tableInfo.buildPkSqlArgs(entity);
        return Result.create(success).set("id", pkArgs);
    }
}
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/controller/AiBotController.java
@@ -6,6 +6,7 @@
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import tech.aiflowy.ai.entity.*;
import tech.aiflowy.ai.mapper.AiBotApiKeyMapper;
import tech.aiflowy.ai.mapper.AiBotConversationMessageMapper;
import tech.aiflowy.ai.service.*;
import tech.aiflowy.common.ai.ChatManager;
@@ -55,6 +56,8 @@
    private final AiBotWorkflowService aiBotWorkflowService;
    private final AiBotKnowledgeService aiBotKnowledgeService;
    private final AiBotMessageService aiBotMessageService;
    @Resource
    private AiBotApiKeyMapper aiBotApiKeyMapper;
    @Resource
    private AiBotConversationMessageService aiBotConversationMessageService;
    @Resource
@@ -224,15 +227,24 @@
        // 获取 API Key 和 Bot 信息
        String apiKey = request.getHeader("Authorization");
        QueryWrapper queryWrapper = QueryWrapper.create()
                .select("apiKey")
                .from("tb_ai_bot_api_key")
                .where("apikey = ? ", apiKey);
        long count =  aiBotApiKeyMapper.selectCountByQuery(queryWrapper);
        if (count <= 0){
            return createResponse(stream, JSON.toJSONString(errorRespnseMsg(1,"该apiKey不存在")));
        }
        AiBot aiBot = service.getById(botId);
        if (aiBot == null) {
            return createResponse(stream, "机器人不存在");
            return createResponse(stream, JSON.toJSONString(errorRespnseMsg(2,"机器人不存在")));
        }
        Map<String, Object> llmOptions = aiBot.getLlmOptions();
        AiLlm aiLlm = aiLlmService.getById(aiBot.getLlmId());
        if (aiLlm == null) {
            return createResponse(stream, "LLM不存在");
            return createResponse(stream, JSON.toJSONString(errorRespnseMsg(3, "LLM不存在")));
        }
        Llm llm = aiLlm.toLlm();
@@ -420,6 +432,13 @@
        return JSON.toJSONString(messageContent);
    }
    private Map<String, Object> errorRespnseMsg(int errorCode, String message){
        HashMap<String, Object> result =  new HashMap<>();
        result.put("error", errorCode);
        result.put("message", message);
        return result;
    }
    private AiBotExternalMsgJsonResult handleMessageStreamJsonResult(AiMessage message) {
        AiBotExternalMsgJsonResult result = new AiBotExternalMsgJsonResult();
        AiBotExternalMsgJsonResult.Choice choice = new AiBotExternalMsgJsonResult.Choice();
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/entity/AiBotApiKey.java
New file
@@ -0,0 +1,15 @@
package tech.aiflowy.ai.entity;
import com.mybatisflex.annotation.Table;
import tech.aiflowy.ai.entity.base.AiBotApiKeyBase;
/**
 *  实体类。
 *
 * @author Administrator
 * @since 2025-04-18
 */
@Table("tb_ai_bot_api_key")
public class AiBotApiKey extends AiBotApiKeyBase {
}
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/entity/base/AiBotApiKeyBase.java
New file
@@ -0,0 +1,71 @@
package tech.aiflowy.ai.entity.base;
import com.mybatisflex.annotation.Column;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.KeyType;
import java.io.Serializable;
import java.time.LocalDateTime;
public class AiBotApiKeyBase implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * id
     */
    @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "id")
    private Long id;
    /**
     * apiKey
     */
    @Id(comment = "apiKey")@Column(value = "apiKey", comment = "apiKey")
    private String apiKey;
    /**
     * 创建时间
     */
    @Column(comment = "创建时间")
    private LocalDateTime created;
    /**
     * 状态1启用 2失效
     */
    @Column(comment = "状态1启用 2失效")
    private Integer status;
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getApiKey() {
        return apiKey;
    }
    public void setApiKey(String apiKey) {
        this.apiKey = apiKey;
    }
    public LocalDateTime getCreated() {
        return created;
    }
    public void setCreated(LocalDateTime created) {
        this.created = created;
    }
    public Integer getStatus() {
        return status;
    }
    public void setStatus(Integer status) {
        this.status = status;
    }
}
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/mapper/AiBotApiKeyMapper.java
New file
@@ -0,0 +1,14 @@
package tech.aiflowy.ai.mapper;
import com.mybatisflex.core.BaseMapper;
import tech.aiflowy.ai.entity.AiBotApiKey;
/**
 *  映射层。
 *
 * @author Administrator
 * @since 2025-04-18
 */
public interface AiBotApiKeyMapper extends BaseMapper<AiBotApiKey> {
}
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/service/AiBotApiKeyService.java
New file
@@ -0,0 +1,14 @@
package tech.aiflowy.ai.service;
import com.mybatisflex.core.service.IService;
import tech.aiflowy.ai.entity.AiBotApiKey;
/**
 *  服务层。
 *
 * @author Administrator
 * @since 2025-04-18
 */
public interface AiBotApiKeyService extends IService<AiBotApiKey> {
}
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/service/impl/AiBotApiKeyServiceImpl.java
New file
@@ -0,0 +1,18 @@
package tech.aiflowy.ai.service.impl;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import tech.aiflowy.ai.entity.AiBotApiKey;
import tech.aiflowy.ai.mapper.AiBotApiKeyMapper;
import tech.aiflowy.ai.service.AiBotApiKeyService;
/**
 *  服务层实现。
 *
 * @author Administrator
 * @since 2025-04-18
 */
@Service
public class AiBotApiKeyServiceImpl extends ServiceImpl<AiBotApiKeyMapper, AiBotApiKey>  implements AiBotApiKeyService{
}
aiflowy-ui-react/src/pages/ai/AiBotApiKey.tsx
New file
@@ -0,0 +1,121 @@
import React, {useState} from 'react';
import {ActionConfig, ColumnsConfig} from "../../components/AntdCrud";
import CrudPage from "../../components/CrudPage";
import {EditLayout} from "../../components/AntdCrud/EditForm.tsx";
import {dateFormat} from "../../libs/utils.ts";
import {Button, message, Modal} from "antd";
import {useGetManual, usePostManual} from "../../hooks/useApis.ts";
//字段配置
const columnsConfig: ColumnsConfig<any> = [
    {
        hidden: true,
        form: {
            type: "hidden"
        },
        dataIndex: "id",
        key: "id"
    },
    {
        form: {
            type: "input"
        },
        dataIndex: "apiKey",
        title: "apiKey",
        key: "apiKey",
        supportSearch: true,
        editCondition: (data) => {
            return data?.id === undefined;
        }
    },
    {
        form: {
            type: "select"
        },
        dataIndex: "status",
        title: "状态",
        key: "status",
        supportSearch: true,
        dict: {
            name: "dataStatus"
        }
    },
    {
        form: {
            type: "hidden"
        },
        dataIndex: "created",
        render: (value) => {
            return <span>{dateFormat(value, "YYYY-MM-DD HH:mm:ss")}</span>
        },
        title: "创建时间",
        key: "created",
        editCondition: (data) => {
            return data?.id === undefined;
        }
    }
];
//编辑页面设置
const editLayout = {
    labelLayout: "horizontal",
    labelWidth: 80,
    columnsCount: 1,
    openType: "modal",
} as EditLayout;
//操作列配置
const actionConfig = {
    detailButtonEnable: false,
    deleteButtonEnable: true,
    editButtonEnable: true,
    hidden: false,
    width: "200px",
} as ActionConfig<any>
export const AiBotApiKey: React.FC = () => {
    const {doPost: useApiKeyPost} = usePostManual('/api/v1/aiBotApiKey/key/save');
    const {doGet: doPage} = useGetManual('/api/v1/aiBotApiKey/page');
    const [queryParam] = useState({
        pageNum: 1,
        pageSize: 10
    });
    return (
        <CrudPage columnsConfig={columnsConfig} tableAlias="aiBotApiKey" addButtonEnable={false}
                  customButton={() => {
                      return <><Button type="primary" onClick={() => {
                              Modal.confirm({
                                  title: '提示',
                                  content: '该操作会生成一个apiKey,请确认是否生成',
                                  okText: '确定',
                                  cancelText: '取消',
                                  onOk() {
                                      console.log('OK');
                                      useApiKeyPost().then((res) => {
                                          console.log('res');
                                          console.log(res);
                                          if (res.data.errorCode === 0) {
                                              message.success("apiKey生成成功");
                                              doPage({params: {
                                                  ...queryParam,
                                                  }}).then((res) => {
                                                  console.log('res');
                                                  console.log(res);
                                              })
                                          }
                                      })
                                  },
                              });
                      }}>新增</Button></>
        }}
            actionConfig={actionConfig} editLayout={editLayout}/>
    )
};
export default {
    path: "/ai/aiBotApiKey",
    element:  AiBotApiKey
};
docs/.vitepress/config.mts
@@ -80,6 +80,7 @@
        { text: 'Bot 应用', link: '/ai/bot-application' },
        { text: '插件', link: '/ai/plugin' },
        { text: '知识库', link: '/ai/knowledge' },
        { text: 'apiKey', link: '/ai/apiKey' },
        { text: 'Ollama', link: '/ai/ollama' },
      ]
    },
docs/zh/development/ai/apiKey.md
New file
@@ -0,0 +1,156 @@
# API Key
apiKey 的作用是 用于身份认证,通过 apiKey 第三方可以携带对应的参数访问到 AIFlowy 对应的Bot进行对话。
## 第三方接入 Bot 聊天地址:
请求方式: POST
请求地址: http://127.0.0.1:8080/api/v1/aiBot/externalChat
Headers: {
Authorization: apiKey
}
请求格式参数说明:
botId: 第三方想要请求的 BotId
### 默认请求方式以 JSON格式返回
```
messages: 消息体
```
```json
{
    "messages": [
        {
            "role": "user",
            "content": "你好"
        },
          {
            "role": "assistant",
            "content": "你好我是科大讯飞模型"
        },
         {
            "role": "user",
            "content": "帮我翻译 what's your name ?"
        }
    ],
    "botId": "267848016181075968"
}
```
请求成功示例:
响应参数说明:
status: END 表示本次对话正常结束
created: 创建时间
message: 消息体
```json
{
    "status": "END",
    "usage": {
        "completionTokens": 3,
        "promptTokens": 22,
        "totalTokens": 25
    },
    "created": 1745205152874,
    "choices": {
        "index": 0,
        "message": {
            "content": "你叫什么名字?",
            "role": "assistant"
        }
    }
}
```
### stream 格式返回
参数说明:
```
stream: true 表示以流式返回
```
请求示例:
```json
{
    "stream": true,
    "messages": [
        {
            "role": "user",
            "content": "你好"
        },
          {
            "role": "assistant",
            "content": "你好我是科大讯飞模型"
        },
         {
            "role": "user",
            "content": "你叫什么名字?"
        }
    ],
    "botId": "267848016181075968"
}
```
返回参数说明:
```
status: START 表示本次对话开始
status: MIDDLE 表示本次对话进行中
status: END 表示本次对话正常结束
```
请求成功示例:
```json
{
    "status": "START",
    "created": 1745205750472,
    "choices": {
        "delta": {
            "content": "我的名字叫讯",
            "role": "assistant"
        },
        "index": 0
    }
}
```
```json
{
    "status": "MIDDLE",
    "created": 1745205750750,
    "choices": {
        "delta": {
            "content": "认知大模型,很高兴",
            "role": "assistant"
        },
        "index": 0
    }
}
```
```json
{
    "status": "END",
    "created": 1745205750901,
    "choices": {
        "delta": {
            "content": "为你服务。",
            "role": "assistant"
        },
        "index": 0
    }
}
```