Browse Source

feat(controller): 新增 CopilotKit 控制器并重构数据访问逻辑

- 添加 CopilotKitController 实现 SSE 流式响应
- 重构 SysEntityController 和 DynamicDataController 移除 creatorId 过滤
- 新增 AgentResponse 和 Result 统一响应结构
- 扩展 CrmAgent 支持同步和流式聊天接口
- 更新 application.yml 添加 streaming-chat-model 配置
- 完善 CrmTools 工具类增加更新和查询功能
main
Boom 1 week ago
parent
commit
80662a06f0
  1. 21
      .antigravity/BASELINE.md
  2. 22
      src/main/java/com/example/ainative/ai/AgentResponse.java
  3. 33
      src/main/java/com/example/ainative/ai/CrmAgent.java
  4. 47
      src/main/java/com/example/ainative/ai/CrmTools.java
  5. 22
      src/main/java/com/example/ainative/common/Result.java
  6. 9
      src/main/java/com/example/ainative/controller/AgentController.java
  7. 171
      src/main/java/com/example/ainative/controller/CopilotKitController.java
  8. 17
      src/main/java/com/example/ainative/controller/DynamicDataController.java
  9. 6
      src/main/java/com/example/ainative/controller/SysEntityController.java
  10. 6
      src/main/resources/application.yml
  11. 6
      target/classes/application.yml

21
.antigravity/BASELINE.md

@ -7,7 +7,8 @@
- **关键组件** - **关键组件**
- `CrmTools.java`: 系统的“消息枢纽”,包含所有 `@Tool` 方法(Define, Save, Delete),直接与 Repository 交互。 - `CrmTools.java`: 系统的“消息枢纽”,包含所有 `@Tool` 方法(Define, Save, Delete),直接与 Repository 交互。
- `AgentController.java`: 主要入口,负责处理自然语言指令或业务事实报告。 - `AgentController.java`: 主要入口,负责处理自然语言指令或业务事实报告。
- **隔离机制**:实现了基于 Header `X-Creator-Id` 的简易用户 Session 隔离,所有数据查询和变更均强制绑定 `creatorId` - `CopilotKitController.java`: **新增**,充当 CopilotKit 的后端运行时,统一通过 LangChain4j 调用 AI。
- [x] **业务模块共享**:移除了 `/api/entities``creatorId` 过滤,所有用户可共享业务实体定义 (Schema),但业务数据记录目前仍保留隔离。
## 💼 业务逻辑 (Business Logic) ## 💼 业务逻辑 (Business Logic)
- **动态模块**:系统支持通过 AI 指令动态创建业务实体(Entities),并在数据库中自动维护。 - **动态模块**:系统支持通过 AI 指令动态创建业务实体(Entities),并在数据库中自动维护。
@ -16,17 +17,25 @@
## 📈 当前开发进度 (Current Progress) ## 📈 当前开发进度 (Current Progress)
- [x] **AI 原生 CRUD**:已实现 Agent 驱动的插入、更新和删除。 - [x] **AI 原生 CRUD**:已实现 Agent 驱动的插入、更新和删除。
- [x] **实体关联**:支持通过 `x-link-entity` 扩展 Schema 实现实体间关联选择。 - [x] **实体关联**:支持通过 `x-link-entity` 扩展 Schema 实现实体间关联选择。
- [x] **用户会话隔离**:已完成后端拦截与过滤逻辑,确保多用户数据不串线。 - [x] **用户会话隔离**:已完成后端拦截与过滤逻辑。
- [x] **CopilotKit 后端转发**:已实现 AI 交互的后端中转,提升了安全性。
- [x] **CORS 配置**:已解决前后端跨域通信问题。 - [x] **CORS 配置**:已解决前后端跨域通信问题。
- [x] **CopilotKit v1 协议适配**: 修复了由于后端控制器 DTO 结构不匹配导致的 Runtime 协议消息解析异常(messages 嵌套在 body 中)。
- [x] **Bug Fix**: 修复了 `CopilotKitController` 处理空请求时的 `NullPointerException`
- [x] **接口扩充**: 新增了 `/api/copilotkit/info` 发现接口,以适配前端 CopilotKit 库的启动流程。
- [x] **流式配置修复**: 修复了 `CrmAgent` 在处理流式请求时因缺失 `StreamingChatLanguageModel` 导致的 `IllegalArgumentException`
- [x] **流式协议修正**: 将 `ResponseBodyEmitter` 恢复为 `SseEmitter`。并在流开始时强制发送 `threadId``runId` 初始事件,确保前端 `@copilotkit/core` 能正确挂载响应流。同时移除了末尾冗余的 `result` 类型消息以避免解析冲突。
- [x] **流式数据格式修正**: 修复了由于 `SseEmitter` 自动追加 `data:` 前缀导致前端 `@copilotkit/runtime` 无法解析流的问题,将实现替换为 `ResponseBodyEmitter` 结合手动追加 `\n` 的 NDJSON(换行分隔 JSON)格式。
- [x] **Runtime Info 修复**: 修复了 `/api/copilotkit/info` 的响应结构,将 `agents` 从列表改为 Map (Object),并显式声明了 `default` agent。
## ⚠ 尚未解决的隐患 (Known Issues/Risks) ## ⚠ 尚未解决的隐患 (Known Issues/Risks)
- **并发冲突**:动态 Schema 变更在极高并发下的稳定性待验证。 - **共享冲突**:当多用户同时修改同一个公共实体的 Schema 时,可能产生覆盖风险
- **权限细化**:目前的 `X-Creator-Id` 仅做基础隔离,缺乏细粒度的 RBAC。 - **权限细化**:目前的 `X-Creator-Id` 仅做基础隔离,缺乏细粒度的 RBAC。
## 🚀 下一步计划 (Next Steps) ## 🚀 下一步计划 (Next Steps)
1. **AI 增强搜索**:实现通过 AI 生成复杂查询逻辑。 1. **数据共享开关**:支持针对特定实体配置是否跨用户共享数据
2. **仪表盘分析**:开发动态数据的 AI 汇总分析模块 2. **AI 增强搜索**:实现通过 AI 生成复杂查询逻辑
3. **性能监控**:针对 AI Tool 调用的耗时进行埋点监控 3. **仪表盘分析**:开发动态数据的 AI 汇总分析模块
--- ---
*Last Updated: 2026-03-25 by Antigravity* *Last Updated: 2026-03-25 by Antigravity*

22
src/main/java/com/example/ainative/ai/AgentResponse.java

@ -0,0 +1,22 @@
package com.example.ainative.ai;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class AgentResponse {
/**
* AI 生成的对话内容
*/
private String content;
/**
* 业务状态标识 (比如: SUCCESS, NEED_INFO, ERROR)
*/
private String status;
}

33
src/main/java/com/example/ainative/ai/CrmAgent.java

@ -4,6 +4,7 @@ import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.SystemMessage; import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage; import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V; import dev.langchain4j.service.V;
import dev.langchain4j.service.TokenStream;
import dev.langchain4j.service.spring.AiService; import dev.langchain4j.service.spring.AiService;
@AiService(tools = "crmTools") @AiService(tools = "crmTools")
@ -34,5 +35,35 @@ public interface CrmAgent {
当前租户上下文 ID: {{userContext}} 当前租户上下文 ID: {{userContext}}
""") """)
String chat(@MemoryId String memoryId, @UserMessage String userMessage, @V("userContext") String userContext); TokenStream chat(@MemoryId String memoryId, @UserMessage String userMessage, @V("userContext") String userContext);
// 用于普通同步调用 (重载方法,返回 String)
@SystemMessage("""
## 角色与愿景
你是一个 AI 原生 CRM 系统的核心引擎你的任务是通过自然语言管理业务模块Schema和动态数据替代传统的 REST 接口
## 核心运行准则严禁违反
1. **执行重于对话**系统已禁用传统的增删改接口所有数据变更必须通过调用工具实现
2. **禁止虚假模拟**严禁在未成功调用工具的情况下回复任何暗示操作已完成的内容已保存已创建
3. **事实驱动**前端会报告业务事实用户提交了表单内容你必须根据事实判断该调用哪个工具insertDynamicData
## 建模协议 (defineDynamicModule)
当定义或更新模块结构时生成的 JSON Schema 需遵循
- **实体关联**若字段需关联其他实体必须在属性中加入 `"x-link-entity": "目标实体Code"`
- **约束增强**优先使用 `enum` (下拉选项)`pattern` (正则校验)`format: "date"` 等标准 JSON Schema 约束
- **命名规范**entityCode 采用下划线命名法student_course
## 数据操作协议
- **入库 (insert)**将用户提供的或事实报告中的信息提取为 JSON 字符串
- **物理删除 (delete)**仅在用户明确指令或业务流程需要时执行
## 输出风格
- 保持简洁
- 必须在工具执行成功后再简要反馈执行结果或后续 UI 操作建议
- **结构化输出**请直接以 JSON 格式返回包含 `content` (对话内容) `status` (SUCCESS/ERROR/NEED_INFO) 字段
当前租户上下文 ID: {{userContext}}
""")
AgentResponse chatSync(@MemoryId String memoryId, @UserMessage String userMessage, @V("userContext") String userContext);
} }

47
src/main/java/com/example/ainative/ai/CrmTools.java

@ -75,9 +75,25 @@ public class CrmTools {
} }
} }
/** @Transactional
* AI 工具封装删除指定的业务数据 @Tool("核心业务处理:根据记录 ID 更新已有的动态业务数据。参数:id (记录的唯一 UUID), jsonData (更新后的完整 JSON 数据)。")
*/ public String updateDynamicData(String id, String jsonData) {
log.info("Agent Tool 正在处理消息:更新记录 ID: {}", id);
try {
java.util.UUID uuid = java.util.UUID.fromString(id);
return sysDynamicDataRepository.findById(uuid)
.map(record -> {
record.setData(jsonData);
sysDynamicDataRepository.save(record);
return "AI 已成功更新记录 (ID: " + id + ")。";
})
.orElse("AI 无法找到 ID 为 [" + id + "] 的记录,可能已被删除。");
} catch (Exception e) {
log.error("AI 更新数据失败: {}", e.getMessage());
return "更新操作遇到技术故障: " + e.getMessage();
}
}
@Transactional @Transactional
@Tool("核心业务处理:根据 ID 物理删除指定的业务记录。参数:id (记录的唯一 UUID)。") @Tool("核心业务处理:根据 ID 物理删除指定的业务记录。参数:id (记录的唯一 UUID)。")
public String deleteDynamicData(String id) { public String deleteDynamicData(String id) {
@ -94,6 +110,31 @@ public class CrmTools {
return "删除操作遇到技术故障: " + e.getMessage(); return "删除操作遇到技术故障: " + e.getMessage();
} }
} }
@Tool("核心业务处理:查询指定模块下的所有业务数据。参数:entityCode (模块代码)。返回该模块下当前用户的所有记录 JSON 数组。")
public String queryDynamicData(String entityCode) {
log.info("Agent Tool 正在处理消息:查询模块数据: Code={}", entityCode);
try {
SysEntity entity = sysEntityRepository.findByEntityCode(entityCode);
if (entity == null) {
return "AI 无法找到模块 [" + entityCode + "]。";
}
java.util.List<SysDynamicData> records = sysDynamicDataRepository.findByEntityIdAndCreatorId(
entity.getId(), UserContextHolder.getCurrentUser());
if (records.isEmpty()) {
return "模块 [" + entityCode + "] 下暂无数据。";
}
StringBuilder sb = new StringBuilder();
sb.append("模块 [").append(entityCode).append("] 共 ").append(records.size()).append(" 条记录:\n");
for (SysDynamicData r : records) {
sb.append("- ID: ").append(r.getId()).append(", 数据: ").append(r.getData()).append("\n");
}
return sb.toString();
} catch (Exception e) {
log.error("AI 查询数据失败: {}", e.getMessage());
return "查询失败: " + e.getMessage();
}
}
} }

22
src/main/java/com/example/ainative/common/Result.java

@ -0,0 +1,22 @@
package com.example.ainative.common;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result<T> {
private Integer code;
private String msg;
private T data;
public static <T> Result<T> success(T data) {
return new Result<>(200, "success", data);
}
public static <T> Result<T> error(String msg) {
return new Result<>(500, msg, null);
}
}

9
src/main/java/com/example/ainative/controller/AgentController.java

@ -1,6 +1,8 @@
package com.example.ainative.controller; package com.example.ainative.controller;
import com.example.ainative.ai.CrmAgent; import com.example.ainative.ai.CrmAgent;
import com.example.ainative.ai.AgentResponse;
import com.example.ainative.common.Result;
import com.example.ainative.config.UserContextHolder; import com.example.ainative.config.UserContextHolder;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -20,15 +22,16 @@ public class AgentController {
* *
* @param sessionId 唯一会话识别与底层的 ChatMemory 持久化紧密对应确保 AI 记住对话 * @param sessionId 唯一会话识别与底层的 ChatMemory 持久化紧密对应确保 AI 记住对话
* @param message 用户的原始需求 (比如"帮我建一个客户关系系统的线索录入表,有姓名和手机号") * @param message 用户的原始需求 (比如"帮我建一个客户关系系统的线索录入表,有姓名和手机号")
* @return AI 所思所想或执行完 @Tool 后的结果字符串反馈 * @return AI 所思所想或执行完 @Tool 后的结果结构化反馈
*/ */
@PostMapping("/chat") @PostMapping("/chat")
public String chat(@RequestParam String sessionId, @RequestBody String message) { public Result<AgentResponse> chat(@RequestParam String sessionId, @RequestBody String message) {
// 先从拦截器上下文中拿到刚才设置的调用者身份 // 先从拦截器上下文中拿到刚才设置的调用者身份
String currentUser = UserContextHolder.getCurrentUser(); String currentUser = UserContextHolder.getCurrentUser();
log.info("message:{}",message); log.info("message:{}",message);
// 传递给底层的 Agent:这背后的复杂工作流完全交由 Langchain4J 托管! // 传递给底层的 Agent:这背后的复杂工作流完全交由 Langchain4J 托管!
// 如果用户的 message 意图命中“建表”,框架会在返回前自己拦截调去跑 CrmTools 里的 defineDynamicModule。 // 如果用户的 message 意图命中“建表”,框架会在返回前自己拦截调去跑 CrmTools 里的 defineDynamicModule。
return crmAgent.chat(sessionId, message, currentUser); AgentResponse res = crmAgent.chatSync(sessionId, message, currentUser);
return Result.success(res);
} }
} }

171
src/main/java/com/example/ainative/controller/CopilotKitController.java

@ -0,0 +1,171 @@
package com.example.ainative.controller;
import com.example.ainative.ai.CrmAgent;
import com.example.ainative.config.UserContextHolder;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@RestController
@RequestMapping("/api/copilotkit")
@RequiredArgsConstructor
@Slf4j
@CrossOrigin(origins = "*")
public class CopilotKitController {
private final CrmAgent crmAgent;
private static final ObjectMapper objectMapper = new ObjectMapper();
@GetMapping({"/info", ""})
public Map<String, Object> getInfo() {
return Map.of(
"actions", List.of(),
"agents", Map.of(
"default", Map.of("description", "Default Agent")
)
);
}
@PostMapping(value = "", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public ResponseEntity<Object> handleSingleRoute(@RequestBody CopilotSingleRouteRequest request) {
if ("info".equals(request.getMethod())) {
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(getInfo());
}
log.info("CopilotKit single-route request: method={}", request.getMethod());
AgentRunInput input = request.getBody();
if (input == null) {
input = new AgentRunInput();
}
return buildStreamResponse(input);
}
@PostMapping(value = "/agent/{agentId}/run", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public ResponseEntity<SseEmitter> handleRestRoute(
@PathVariable String agentId,
@RequestBody AgentRunInput input) {
log.info("CopilotKit REST request: agentId={}", agentId);
if (input == null) {
input = new AgentRunInput();
}
return buildStreamResponse(input);
}
private <T> ResponseEntity<T> buildStreamResponse(AgentRunInput input) {
SseEmitter emitter = new SseEmitter(180_000L);
String threadId = input.getThreadId() != null ? input.getThreadId() : UUID.randomUUID().toString();
String runId = input.getRunId() != null ? input.getRunId() : UUID.randomUUID().toString();
String messageId = UUID.randomUUID().toString();
List<Map<String, Object>> messages = input.getMessages();
if (messages == null || messages.isEmpty()) {
sendEvent(emitter, aguiEvent("RUN_STARTED", "threadId", threadId, "runId", runId));
sendEvent(emitter, aguiEvent("RUN_FINISHED", "threadId", threadId, "runId", runId));
emitter.complete();
@SuppressWarnings("unchecked")
ResponseEntity<T> resp = (ResponseEntity<T>) ResponseEntity.ok()
.contentType(MediaType.TEXT_EVENT_STREAM)
.body(emitter);
return resp;
}
String lastUserMessage = messages.stream()
.filter(m -> "user".equals(m.get("role")))
.reduce((first, second) -> second)
.map(m -> {
Object content = m.get("content");
if (content instanceof String) {
return (String) content;
}
if (content instanceof List<?> parts) {
StringBuilder sb = new StringBuilder();
for (Object part : parts) {
if (part instanceof Map<?, ?> partMap && "text".equals(partMap.get("type"))) {
sb.append(partMap.get("text"));
}
}
return sb.toString();
}
return "";
})
.orElse("");
log.info("User message: {}", lastUserMessage);
String currentUser = UserContextHolder.getCurrentUser();
sendEvent(emitter, aguiEvent("RUN_STARTED", "threadId", threadId, "runId", runId));
sendEvent(emitter, aguiEvent("TEXT_MESSAGE_START", "messageId", messageId, "role", "assistant"));
crmAgent.chat("copilot-streaming-session", lastUserMessage, currentUser)
.onPartialResponse(token -> {
sendEvent(emitter, aguiEvent("TEXT_MESSAGE_CONTENT", "messageId", messageId, "delta", token));
})
.onCompleteResponse(response -> {
sendEvent(emitter, aguiEvent("TEXT_MESSAGE_END", "messageId", messageId));
sendEvent(emitter, aguiEvent("RUN_FINISHED", "threadId", threadId, "runId", runId));
emitter.complete();
})
.onError(err -> {
log.error("AI streaming error:", err);
sendEvent(emitter, aguiEvent("TEXT_MESSAGE_END", "messageId", messageId));
sendEvent(emitter, aguiEvent("RUN_ERROR", "message", err.getMessage() != null ? err.getMessage() : "Unknown error"));
sendEvent(emitter, aguiEvent("RUN_FINISHED", "threadId", threadId, "runId", runId));
emitter.completeWithError(err);
})
.start();
@SuppressWarnings("unchecked")
ResponseEntity<T> resp = (ResponseEntity<T>) ResponseEntity.ok()
.contentType(MediaType.TEXT_EVENT_STREAM)
.body(emitter);
return resp;
}
private Map<String, Object> aguiEvent(String type, String... kvPairs) {
Map<String, Object> event = new LinkedHashMap<>();
event.put("type", type);
for (int i = 0; i < kvPairs.length; i += 2) {
event.put(kvPairs[i], kvPairs[i + 1]);
}
return event;
}
private void sendEvent(SseEmitter emitter, Map<String, Object> data) {
try {
emitter.send(SseEmitter.event().data(data));
} catch (Exception e) {
log.warn("Failed to send SSE event: {}", e.getMessage());
}
}
@Data
public static class CopilotSingleRouteRequest {
private String method;
private Map<String, Object> params;
private AgentRunInput body;
}
@Data
public static class AgentRunInput {
private String threadId;
private String runId;
private List<Map<String, Object>> messages;
private List<Map<String, Object>> context;
private List<Map<String, Object>> tools;
private Map<String, Object> forwardedProps;
private Map<String, Object> state;
}
}

17
src/main/java/com/example/ainative/controller/DynamicDataController.java

@ -1,6 +1,5 @@
package com.example.ainative.controller; package com.example.ainative.controller;
import com.example.ainative.ai.CrmTools;
import com.example.ainative.config.UserContextHolder; import com.example.ainative.config.UserContextHolder;
import com.example.ainative.entity.SysDynamicData; import com.example.ainative.entity.SysDynamicData;
import com.example.ainative.entity.SysEntity; import com.example.ainative.entity.SysEntity;
@ -12,10 +11,6 @@ import org.springframework.web.bind.annotation.*;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
/**
* 动态数据交互控制器
* 负责接收 AI 生成的 UI 提交过来的所有结构不固定的 JSON 数据
*/
@RestController @RestController
@RequestMapping("/api/dynamic") @RequestMapping("/api/dynamic")
@RequiredArgsConstructor @RequiredArgsConstructor
@ -25,22 +20,16 @@ public class DynamicDataController {
private final SysDynamicDataRepository dataRepository; private final SysDynamicDataRepository dataRepository;
private final SysEntityRepository entityRepository; private final SysEntityRepository entityRepository;
/**
* 根据模块 UUID 获取数据列表
*/
@GetMapping("/{entityId}") @GetMapping("/{entityId}")
public List<SysDynamicData> getRecords(@PathVariable UUID entityId) { public List<SysDynamicData> getRecords(@PathVariable UUID entityId) {
return dataRepository.findByEntityIdAndCreatorId(entityId, UserContextHolder.getCurrentUser()); return dataRepository.findByEntityId(entityId);
} }
/**
* 根据模块 code 获取数据列表
*/
@GetMapping("/code/{entityCode}") @GetMapping("/code/{entityCode}")
public List<SysDynamicData> getRecordsByCode(@PathVariable String entityCode) { public List<SysDynamicData> getRecordsByCode(@PathVariable String entityCode) {
SysEntity entity = entityRepository.findByEntityCodeAndCreatorId(entityCode, UserContextHolder.getCurrentUser()); SysEntity entity = entityRepository.findByEntityCode(entityCode);
if (entity != null) { if (entity != null) {
return dataRepository.findByEntityIdAndCreatorId(entity.getId(), UserContextHolder.getCurrentUser()); return dataRepository.findByEntityId(entity.getId());
} }
return List.of(); return List.of();
} }

6
src/main/java/com/example/ainative/controller/SysEntityController.java

@ -21,7 +21,8 @@ public class SysEntityController {
*/ */
@GetMapping @GetMapping
public List<SysEntity> getAllEntities() { public List<SysEntity> getAllEntities() {
return sysEntityRepository.findByCreatorId(com.example.ainative.config.UserContextHolder.getCurrentUser()); // 全量返回,各用户可共用实体定义
return sysEntityRepository.findAll();
} }
/** /**
@ -29,7 +30,8 @@ public class SysEntityController {
*/ */
@GetMapping("/code/{entityCode}") @GetMapping("/code/{entityCode}")
public SysEntity getEntityByCode(@PathVariable String entityCode) { public SysEntity getEntityByCode(@PathVariable String entityCode) {
return sysEntityRepository.findByEntityCodeAndCreatorId(entityCode, com.example.ainative.config.UserContextHolder.getCurrentUser()); // 按 code 全量查询,不再限制创建者
return sysEntityRepository.findByEntityCode(entityCode);
} }
} }

6
src/main/resources/application.yml

@ -23,6 +23,12 @@ langchain4j:
model-name: glm-4-plus model-name: glm-4-plus
log-requests: true log-requests: true
log-responses: true log-responses: true
streaming-chat-model:
api-key: ${ZHIPU_API_KEY:61833436cbd642ed844d0128a99b2e89.PTbpERpysO3qSf8w}
base-url: https://open.bigmodel.cn/api/paas/v4/
model-name: glm-4-plus
log-requests: true
log-responses: true
server: server:
port: 8080 port: 8080

6
target/classes/application.yml

@ -23,6 +23,12 @@ langchain4j:
model-name: glm-4-plus model-name: glm-4-plus
log-requests: true log-requests: true
log-responses: true log-responses: true
streaming-chat-model:
api-key: ${ZHIPU_API_KEY:61833436cbd642ed844d0128a99b2e89.PTbpERpysO3qSf8w}
base-url: https://open.bigmodel.cn/api/paas/v4/
model-name: glm-4-plus
log-requests: true
log-responses: true
server: server:
port: 8080 port: 8080

Loading…
Cancel
Save