feat: Add third-party call to intelligent agent interface and interfaec docs
| New file |
| | |
| | | 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()); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | 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); |
| | | } |
| | | } |
| | |
| | | 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; |
| | |
| | | private final AiBotWorkflowService aiBotWorkflowService; |
| | | private final AiBotKnowledgeService aiBotKnowledgeService; |
| | | private final AiBotMessageService aiBotMessageService; |
| | | @Resource |
| | | private AiBotApiKeyMapper aiBotApiKeyMapper; |
| | | @Resource |
| | | private AiBotConversationMessageService aiBotConversationMessageService; |
| | | @Resource |
| | |
| | | |
| | | // 获取 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(); |
| | |
| | | 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(); |
| New file |
| | |
| | | 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 { |
| | | } |
| New file |
| | |
| | | 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; |
| | | } |
| | | |
| | | } |
| New file |
| | |
| | | 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> { |
| | | |
| | | } |
| New file |
| | |
| | | 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> { |
| | | |
| | | } |
| New file |
| | |
| | | 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{ |
| | | |
| | | } |
| New file |
| | |
| | | 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 |
| | | }; |
| | |
| | | { text: 'Bot 应用', link: '/ai/bot-application' }, |
| | | { text: '插件', link: '/ai/plugin' }, |
| | | { text: '知识库', link: '/ai/knowledge' }, |
| | | { text: 'apiKey', link: '/ai/apiKey' }, |
| | | { text: 'Ollama', link: '/ai/ollama' }, |
| | | ] |
| | | }, |
| New file |
| | |
| | | # 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 |
| | | } |
| | | } |
| | | ``` |