package com.yunhai.agent.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * 智能体配置:大模型 + 各业务模块基地址 + 角色权限映射。 * *

全部落在 {@code application.yml} 的 {@code agent.*} 下,便于运维按环境改配置而不动代码。 */ @Data @Component @ConfigurationProperties(prefix = "agent") public class AgentSettings { /** 大模型配置 */ private Llm llm = new Llm(); /** 编排参数 */ private Loop loop = new Loop(); /** 角色权限映射(学生 / 教师 / 管理员 的角色号) */ private Roles roles = new Roles(); /** * 业务模块基地址表:key = 模块标识(工具定义里引用),value = 该模块的根地址(不含 /api)。 *

例:{@code homeleave: http://base.yunhaixingtong.com/home-leave} *

本地联调把 value 改成 {@code http://localhost:8106} 之类即可。 */ private Map modules = new LinkedHashMap<>(); /** * 各业务模块的「默认落地页」——即该模块前端的一个路由(如 {@code /my})。 * *

★ 深链兜底(2026-10 新增):业务模块的用户态接口 {@code /<模块>/api/*} 只认 * 浏览器 CAS 会话,服务端调用拿不到,必然回 {@code code=302}(详见 README 第 11 节)。 * 此时智能体不再只丢一句报错,而是把模块页面地址随轨迹回传前端, * 让用户用自己的浏览器登录态一键打开真实页面看数据 —— 这比"死等打通"务实得多。 * *

取值已按各模块前端 {@code src/router/routes.ts} 的真实路由核对(2026-10-10): *

     * homeleave        → /my             我的请假
     * campusnotice     → /              我的公告(收件箱)
     * dormnotice       → /par/notices   宿舍通知箱
     * familyactivity   → /par/activities 活动广场
     * coursehomework   → /stu/today     今日作业
     * resourcelibrary  → /resource      资源管理
     * profession       → /home          发展首页
     * 
* 运维可在 {@code application.yml} 用 {@code agent.module-landing.<模块>=<路由>} 覆盖单个模块; * 工具自身声明的 {@code landingPath}({@code write(...)} / {@code readAt(...)})优先级更高。 */ private Map moduleLanding = defaultModuleLanding(); /** 模块默认落地页的出厂值(见 {@link #moduleLanding}) */ private static Map defaultModuleLanding() { Map m = new LinkedHashMap<>(); m.put("homeleave", "/my"); m.put("campusnotice", "/"); m.put("dormnotice", "/par/notices"); m.put("familyactivity", "/par/activities"); m.put("coursehomework", "/stu/today"); m.put("resourcelibrary", "/resource"); m.put("profession", "/home"); return m; } /** * 是否按「用户已授权模块」过滤工具(modId 维度)。 *

默认 false:不向模型隐藏工具,越权与否由各业务模块按透传身份自行裁决。 *

置 true 时:用平台 GetUserMod 的 modId 与 {@code ToolDef.moduleId} 比对,未授权模块的工具不暴露。 */ private boolean filterToolsByAuthorizedModule = false; /** * 是否开启「角色强校验」。 *

默认 true:模型若调用了当前角色无权使用的工具,服务端直接拒绝执行(不发真实请求), * 并在轨迹里留下 FORBIDDEN 记录 —— 这是「分学生、教师权限」的硬约束。 *

置 false 可临时放开(排障用),此时仅按角色过滤列表,不做执行拦截。 */ private boolean enforceRolePermission = true; /** * 浏览器代执行(方案 A,2026-10-12 新增)。 * *

业务模块的用户态接口只认浏览器 CAS 会话,服务端调用必然 {@code code=302} * (README 第 11 节的四条实测证据)。开启后,模块类工具不再由服务端发请求,而是: *

    *
  1. 模型决定调用 → 服务端挂起,把「接口描述(方法/路径/参数)」下发前端;
  2. *
  3. 前端用用户自己的登录态调用模块接口(同站部署 ⇒ 同源,Cookie 自动携带);
  4. *
  5. 结果回传服务端 → 喂回模型 → 生成最终回答。
  6. *
* 本地 dev(localhost:8081)因跨域取不到 base 域会话,代执行会失败并自动落回 * 「去系统里查看」的深链兜底;同站部署后即真实生效。 */ private BrowserExec browserExec = new BrowserExec(); @Data public static class BrowserExec { /** 是否启用浏览器代执行(false = 维持服务端直调的旧行为) */ private boolean enabled = true; } @Data public static class Llm { /** OpenAI 兼容的 chat/completions 地址(DeepSeek 官方:https://api.deepseek.com/chat/completions) */ private String host = "https://api.deepseek.com/chat/completions"; /** 多模态/推理兼用的兜底地址(可选,默认同上) */ private String fallbackHost; private String apiKey; /** 默认模型:deepseek-flash(DeepSeek-V4.1-Flash,支持 function calling)。 * 以官方实时清单为准:GET https://api.deepseek.com/models(2026-10-10:deepseek-flash / deepseek-v4-pro) */ private String model = "deepseek-flash"; private int maxTokens = 2048; private double temperature = 0.3; /** 单次 HTTP 超时(毫秒)。⚠️ 实际出站超时已由平台统一客户端接管,见 {@code yh.http.read-timeout-ms};本字段保留仅作文档参考 */ private int timeout = 60000; /** 是否开启深度思考(旧的 deepseek-reasoner 独立模型已下线;V4 系列改为在请求里带 effort 档位,本开关暂未接线) */ private boolean thinking = false; } @Data public static class Loop { /** 单轮对话内最多允许的「模型思考 → 调工具」循环次数,防死循环 */ private int maxRounds = 6; /** 每个工具调用结果塞回模型时的最大字符数(防撑爆上下文) */ private int toolResultMaxChars = 6000; /** 会话上下文最多保留的历史消息条数 */ private int historyMaxMessages = 20; /** 会话空闲过期时间(分钟) */ private int sessionTtlMinutes = 120; } /** * 角色映射:UserBase 的角色号(roleId)→ 智能体角色。 *

★ 上线前必须按本校 UserBase 的角色体系校准: * 平台 {@code BoaUser.getType() == 1} 恒为超管/校管;其余按 {@code roleIds} 命中下面的集合判定。 * 一个用户可同时命中多个(如既是教师又是管理员),能力取并集。 */ @Data public static class Roles { /** 学生角色号(默认 1;请按 UserBase 校准) */ private List student = new ArrayList<>(Arrays.asList(1)); /** 教师角色号(默认 2=教师、3=班主任;请按 UserBase 校准) */ private List teacher = new ArrayList<>(Arrays.asList(2, 3)); /** 管理员角色号(默认 4=校管、5=分管领导、6=教务、7=校长;请按 UserBase 校准) */ private List admin = new ArrayList<>(Arrays.asList(4, 5, 6, 7)); /** 管理员是否天然拥有教师能力(默认 true:校管/领导可用教师侧工具) */ private boolean adminAlsoTeacher = true; } }