diff --git a/docker/nginx/conf.d/dify.conf b/docker/nginx/conf.d/dify.conf new file mode 100644 index 00000000..bef3c96c --- /dev/null +++ b/docker/nginx/conf.d/dify.conf @@ -0,0 +1,51 @@ +# Dify 服务代理配置 + +# Dify Web 前端 +location /dify/ { + proxy_pass http://dify-web:3000/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # 支持 WebSocket + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + # 超时配置 + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; +} + +# Dify API +location /dify/api/ { + proxy_pass http://dify-api:5001/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # 支持 WebSocket (SSE) + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + # 超时配置(AI 响应可能较慢) + proxy_connect_timeout 300s; + proxy_send_timeout 300s; + proxy_read_timeout 300s; + + # 禁用缓冲(支持流式响应) + proxy_buffering off; +} + +# Dify App (公开应用) +location /dify/app/ { + proxy_pass http://dify-web:3000/app/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; +} diff --git a/urbanLifelineServ/.bin/database/postgres/sql/createTableAI.sql b/urbanLifelineServ/.bin/database/postgres/sql/createTableAI.sql new file mode 100644 index 00000000..dd7eb252 --- /dev/null +++ b/urbanLifelineServ/.bin/database/postgres/sql/createTableAI.sql @@ -0,0 +1,166 @@ +CREATE SCHEMA IF NOT EXISTS ai; + +-- AI智能体配置 +DROP TABLE IF EXISTS ai.tb_agent CASCADE; +CREATE TABLE ai.tb_agent( + optsn VARCHAR(50) NOT NULL, -- 流水号 + agent_id VARCHAR(50) NOT NULL, -- 智能体ID + name VARCHAR(50) NOT NULL, -- 智能体名称 + description VARCHAR(500) DEFAULT NULL, -- 智能体描述 + link VARCHAR(500) DEFAULT NULL, -- 智能体url + api_key VARCHAR(500) NOT NULL, -- dify智能体APIKEY + introduce VARCHAR(500) NOT NULL, -- 引导词 + prompt_cards JSONB DEFAULT '[]'::jsonb, -- 提示卡片数组 [{file_id:'', prompt:''}] + category VARCHAR(50) NOT NULL, -- 分类 + creator VARCHAR(50) DEFAULT NULL, -- 创建者 + updater VARCHAR(50) DEFAULT NULL, -- 更新者 + create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间 + update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间 + delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间 + deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除 + PRIMARY KEY (agent_id), + UNIQUE (optsn), + UNIQUE (api_key) +); + +-- AI智能体对话 +DROP TABLE IF EXISTS ai.tb_chat CASCADE; +CREATE TABLE ai.tb_chat( + optsn VARCHAR(50) NOT NULL, -- 流水号 + chat_id VARCHAR(50) NOT NULL, -- 对话ID + agent_id VARCHAR(50) NOT NULL, -- 智能体ID + user_id VARCHAR(50) NOT NULL, -- 用户ID + title VARCHAR(500) NOT NULL, -- 对话标题 + create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间 + update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间 + delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间 + deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除 + PRIMARY KEY (chat_id), + UNIQUE (optsn) +); + +-- AI智能体对话消息 +DROP TABLE IF EXISTS ai.tb_chat_message CASCADE; +CREATE TABLE ai.tb_chat_message( + optsn VARCHAR(50) NOT NULL, -- 流水号 + message_id VARCHAR(50) NOT NULL, -- 消息ID + chat_id VARCHAR(50) NOT NULL, -- 对话ID + role VARCHAR(50) NOT NULL, -- 角色:user-用户/assistant-智能体/recipient-客服 + content TEXT NOT NULL, -- 消息内容 + files VARCHAR(50)[] DEFAULT NULL, -- 文件id数组 + create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间 + update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间 + delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间 + deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除 + PRIMARY KEY (message_id), + UNIQUE (optsn) +); + + +-- 知识库配置 bidding和workcase2个服务使用 +DROP TABLE IF EXISTS ai.tb_knowledge CASCADE; +CREATE TABLE ai.tb_knowledge( + -- 知识库dify相关配置 + optsn VARCHAR(50) NOT NULL, -- 流水号 + knowledge_id VARCHAR(50) NOT NULL, -- 知识库ID + title VARCHAR(255) NOT NULL, -- 知识库标题 + avatar VARCHAR(255) DEFAULT NULL, -- 知识库头像 + description VARCHAR(500) DEFAULT NULL, -- 知识库描述 + dify_dataset_id VARCHAR(100) DEFAULT NULL, -- Dify知识库ID(Dataset ID) + dify_indexing_technique VARCHAR(50) DEFAULT 'high_quality', -- Dify索引方式(high_quality/economy) + embedding_model VARCHAR(100) DEFAULT NULL, -- 向量模型名称 + embedding_model_provider VARCHAR(100) DEFAULT NULL, -- 向量模型提供商 + rerank_model VARCHAR(100) DEFAULT NULL, -- Rerank模型名称 + rerank_model_provider VARCHAR(100) DEFAULT NULL, -- Rerank模型提供商 + reranking_enable BOOLEAN DEFAULT false, -- 是否启用Rerank + retrieval_top_k INTEGER DEFAULT 2, -- 检索Top K(返回前K个结果) + retrieval_score_threshold DECIMAL(3,2) DEFAULT 0.00, -- 检索分数阈值(0.00-1.00) + document_count INTEGER DEFAULT 0, -- 文档数量 + total_chunks INTEGER DEFAULT 0, -- 总分段数 + -- 下面是服务使用 + service VARCHAR(50) DEFAULT NULL, -- 所属服务 workcase、bidding + project_id VARCHAR(50) DEFAULT NULL, -- bidding所属项目ID + category VARCHAR(50) DEFAULT NULL, -- 所属分类 workcase 内部知识库、外部知识库 + creator VARCHAR(50) NOT NULL, -- 创建者(用户ID) + dept_path VARCHAR(50) DEFAULT NULL, -- 创建者部门路径 + updater VARCHAR(50) DEFAULT NULL, -- 更新者 + create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间 + update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间 + delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间 + deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除 + PRIMARY KEY (optsn), + UNIQUE (knowledge_id), + UNIQUE (dify_dataset_id) +); + +-- 知识库配置表字段注释 +COMMENT ON TABLE ai.tb_knowledge IS '知识库配置表'; +COMMENT ON COLUMN ai.tb_knowledge.optsn IS '流水号'; +COMMENT ON COLUMN ai.tb_knowledge.knowledge_id IS '知识库ID'; +COMMENT ON COLUMN ai.tb_knowledge.title IS '知识库标题'; +COMMENT ON COLUMN ai.tb_knowledge.avatar IS '知识库头像'; +COMMENT ON COLUMN ai.tb_knowledge.description IS '知识库描述'; +COMMENT ON COLUMN ai.tb_knowledge.dify_dataset_id IS 'Dify知识库ID(Dataset ID)'; +COMMENT ON COLUMN ai.tb_knowledge.dify_indexing_technique IS 'Dify索引方式(high_quality/economy)'; +COMMENT ON COLUMN ai.tb_knowledge.embedding_model IS '向量模型名称'; +COMMENT ON COLUMN ai.tb_knowledge.embedding_model_provider IS '向量模型提供商'; +COMMENT ON COLUMN ai.tb_knowledge.rerank_model IS 'Rerank模型名称'; +COMMENT ON COLUMN ai.tb_knowledge.rerank_model_provider IS 'Rerank模型提供商'; +COMMENT ON COLUMN ai.tb_knowledge.reranking_enable IS '是否启用Rerank'; +COMMENT ON COLUMN ai.tb_knowledge.retrieval_top_k IS '检索Top K(返回前K个结果)'; +COMMENT ON COLUMN ai.tb_knowledge.retrieval_score_threshold IS '检索分数阈值(0.00-1.00)'; +COMMENT ON COLUMN ai.tb_knowledge.document_count IS '文档数量'; +COMMENT ON COLUMN ai.tb_knowledge.total_chunks IS '总分段数'; +COMMENT ON COLUMN ai.tb_knowledge.service IS '所属服务 workcase、bidding'; +COMMENT ON COLUMN ai.tb_knowledge.project_id IS 'bidding所属项目ID'; +COMMENT ON COLUMN ai.tb_knowledge.category IS '所属分类 workcase 内部知识库、外部知识库'; +COMMENT ON COLUMN ai.tb_knowledge.creator IS '创建者(用户ID)'; +COMMENT ON COLUMN ai.tb_knowledge.dept_path IS '创建者部门路径'; +COMMENT ON COLUMN ai.tb_knowledge.updater IS '更新者'; +COMMENT ON COLUMN ai.tb_knowledge.create_time IS '创建时间'; +COMMENT ON COLUMN ai.tb_knowledge.update_time IS '更新时间'; +COMMENT ON COLUMN ai.tb_knowledge.delete_time IS '删除时间'; +COMMENT ON COLUMN ai.tb_knowledge.deleted IS '是否删除'; +-- bidding知识库根据project等变化 +-- workcase知识库固定8个 +-- workcase外部知识库:4个知识库: +-- 1. 设备操作指南 +-- 2. 常见故障解决方案 +-- 3. 三包外服务政策 +-- 4. 配件咨询话术 +-- workcase内部知识库:4个知识库: +-- 1. 技术维修手册 +-- 2. 产品参数明细 +-- 3. 内部服务流程规范 +-- 4. 客户服务话术模板 + +-- 知识库文件 文件上传dify知识库,对dify内的文件修改不生成新版本, 只有重新上传才生成新版本 +DROP TABLE IF EXISTS ai.tb_knowledge_file CASCADE; +CREATE TABLE ai.tb_knowledge_file( + optsn VARCHAR(50) NOT NULL, -- 流水号 + knowledge_id VARCHAR(50) NOT NULL, -- 知识库ID + file_root_id VARCHAR(50) NOT NULL, -- 文件根ID + file_id VARCHAR(50) NOT NULL, -- 文件ID + dify_file_id VARCHAR(50) NOT NULL, -- dify文件ID + version VARCHAR(50) NOT NULL, -- 文件版本 + create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间 + update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间 + delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间 + deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除 + PRIMARY KEY (optsn), + UNIQUE (knowledge_id, file_id) +); + +-- 知识库文件表字段注释 +COMMENT ON TABLE ai.tb_knowledge_file IS '知识库文件表'; +COMMENT ON COLUMN ai.tb_knowledge_file.optsn IS '流水号'; +COMMENT ON COLUMN ai.tb_knowledge_file.knowledge_id IS '知识库ID'; +COMMENT ON COLUMN ai.tb_knowledge_file.file_root_id IS '文件根ID'; +COMMENT ON COLUMN ai.tb_knowledge_file.file_id IS '文件ID'; +COMMENT ON COLUMN ai.tb_knowledge_file.dify_file_id IS 'dify文件ID'; +COMMENT ON COLUMN ai.tb_knowledge_file.version IS '文件版本'; +COMMENT ON COLUMN ai.tb_knowledge_file.create_time IS '创建时间'; +COMMENT ON COLUMN ai.tb_knowledge_file.update_time IS '更新时间'; +COMMENT ON COLUMN ai.tb_knowledge_file.delete_time IS '删除时间'; +COMMENT ON COLUMN ai.tb_knowledge_file.deleted IS '是否删除'; + diff --git a/urbanLifelineServ/.bin/database/postgres/sql/createTableAgent.sql b/urbanLifelineServ/.bin/database/postgres/sql/createTableAgent.sql deleted file mode 100644 index ac8897b4..00000000 --- a/urbanLifelineServ/.bin/database/postgres/sql/createTableAgent.sql +++ /dev/null @@ -1,306 +0,0 @@ --- -- ============================= --- -- 智能体管理和平台基础设施模块 --- -- 支持:智能体广场、API集成管理、智能体运维监控 --- -- ============================= --- CREATE SCHEMA IF NOT EXISTS agent; - --- -- 智能体定义表 --- DROP TABLE IF EXISTS agent.tb_agent CASCADE; --- CREATE TABLE agent.tb_agent ( --- optsn VARCHAR(50) NOT NULL, -- 流水号 --- agent_id VARCHAR(50) NOT NULL, -- 智能体ID --- agent_code VARCHAR(100) NOT NULL, -- 智能体编码(唯一标识) --- agent_name VARCHAR(255) NOT NULL, -- 智能体名称 --- agent_type VARCHAR(50) NOT NULL, -- 智能体类型:bidding-招投标/customer_service-客服/knowledge_assistant-知识助手/custom-自定义 --- display_name VARCHAR(255) NOT NULL, -- 展示名称 --- description TEXT, -- 智能体描述 --- icon VARCHAR(500), -- 图标URL --- banner VARCHAR(500), -- Banner图URL --- version VARCHAR(20) DEFAULT '1.0.0', -- 版本号 --- model_provider VARCHAR(50), -- 模型提供商:openai/anthropic/baidu/aliyun/custom --- model_name VARCHAR(100), -- 模型名称 --- model_config JSONB, -- 模型配置(温度、最大tokens等) --- prompt_template TEXT, -- 提示词模板 --- system_prompt TEXT, -- 系统提示词 --- capabilities TEXT[], -- 能力列表 --- access_level VARCHAR(20) DEFAULT 'private', -- 访问级别:public-公开/private-私有/internal-内部 --- is_published BOOLEAN DEFAULT false, -- 是否发布到智能体广场 --- usage_count INTEGER DEFAULT 0, -- 使用次数 --- rating DECIMAL(3,2) DEFAULT 0, -- 评分(0-5) --- rating_count INTEGER DEFAULT 0, -- 评分人数 --- tags TEXT[], -- 标签数组 --- category VARCHAR(100), -- 分类 --- dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径 --- owner_user_id VARCHAR(50), -- 所有者用户ID --- status VARCHAR(20) DEFAULT 'active', -- 状态:active-激活/inactive-停用/under_maintenance-维护中 --- creator VARCHAR(50) DEFAULT NULL, -- 创建者 --- updater VARCHAR(50) DEFAULT NULL, -- 更新者 --- create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间 --- update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间 --- delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间 --- deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除 --- PRIMARY KEY (agent_id), --- UNIQUE (optsn), --- UNIQUE (agent_code) --- ); - --- CREATE INDEX idx_agent_type ON agent.tb_agent(agent_type) WHERE deleted = false; --- CREATE INDEX idx_agent_published ON agent.tb_agent(is_published) WHERE deleted = false AND is_published = true; --- CREATE INDEX idx_agent_category ON agent.tb_agent(category) WHERE deleted = false; - --- COMMENT ON TABLE agent.tb_agent IS '智能体定义表'; --- COMMENT ON COLUMN agent.tb_agent.agent_type IS '智能体类型:bidding/customer_service/knowledge_assistant/custom'; - --- -- 智能体会话表 --- DROP TABLE IF EXISTS agent.tb_agent_session CASCADE; --- CREATE TABLE agent.tb_agent_session ( --- optsn VARCHAR(50) NOT NULL, -- 流水号 --- session_id VARCHAR(50) NOT NULL, -- 会话ID --- agent_id VARCHAR(50) NOT NULL, -- 智能体ID --- user_id VARCHAR(50) NOT NULL, -- 用户ID --- session_type VARCHAR(30) DEFAULT 'chat', -- 会话类型:chat-对话/task-任务/workflow-工作流 --- session_name VARCHAR(255), -- 会话名称 --- context JSONB, -- 会话上下文 --- session_status VARCHAR(20) DEFAULT 'active', -- 会话状态:active-活跃/paused-暂停/ended-结束 --- start_time TIMESTAMPTZ DEFAULT now(), -- 开始时间 --- end_time TIMESTAMPTZ, -- 结束时间 --- message_count INTEGER DEFAULT 0, -- 消息数量 --- token_usage INTEGER DEFAULT 0, -- Token使用量 --- cost DECIMAL(10,4) DEFAULT 0, -- 成本 --- dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径 --- creator VARCHAR(50) DEFAULT NULL, -- 创建者 --- create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间 --- update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间 --- deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除 --- PRIMARY KEY (session_id), --- UNIQUE (optsn), --- FOREIGN KEY (agent_id) REFERENCES agent.tb_agent(agent_id) --- ); - --- CREATE INDEX idx_session_agent ON agent.tb_agent_session(agent_id) WHERE deleted = false; --- CREATE INDEX idx_session_user ON agent.tb_agent_session(user_id) WHERE deleted = false; - --- COMMENT ON TABLE agent.tb_agent_session IS '智能体会话表'; - --- -- 智能体消息表 --- DROP TABLE IF EXISTS agent.tb_agent_message CASCADE; --- CREATE TABLE agent.tb_agent_message ( --- optsn VARCHAR(50) NOT NULL, -- 流水号 --- message_id VARCHAR(50) NOT NULL, -- 消息ID --- session_id VARCHAR(50) NOT NULL, -- 会话ID --- agent_id VARCHAR(50) NOT NULL, -- 智能体ID --- role VARCHAR(20) NOT NULL, -- 角色:user-用户/assistant-助手/system-系统/function-函数 --- content TEXT, -- 消息内容 --- content_type VARCHAR(30) DEFAULT 'text', -- 内容类型:text-文本/image-图片/file-文件/structured-结构化数据 --- function_call JSONB, -- 函数调用(JSON格式) --- function_response JSONB, -- 函数响应 --- token_count INTEGER, -- Token数量 --- model_name VARCHAR(100), -- 使用的模型 --- kb_references JSONB, -- 知识库引用(JSON数组) --- metadata JSONB, -- 消息元数据 --- dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径 --- create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间 --- deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除 --- PRIMARY KEY (message_id), --- UNIQUE (optsn), --- FOREIGN KEY (session_id) REFERENCES agent.tb_agent_session(session_id), --- FOREIGN KEY (agent_id) REFERENCES agent.tb_agent(agent_id) --- ); - --- CREATE INDEX idx_msg_session ON agent.tb_agent_message(session_id, create_time) WHERE deleted = false; --- CREATE INDEX idx_msg_agent ON agent.tb_agent_message(agent_id) WHERE deleted = false; - --- COMMENT ON TABLE agent.tb_agent_message IS '智能体消息表'; - --- -- 智能体工具表 --- DROP TABLE IF EXISTS agent.tb_agent_tool CASCADE; --- CREATE TABLE agent.tb_agent_tool ( --- optsn VARCHAR(50) NOT NULL, -- 流水号 --- tool_id VARCHAR(50) NOT NULL, -- 工具ID --- tool_code VARCHAR(100) NOT NULL, -- 工具编码 --- tool_name VARCHAR(255) NOT NULL, -- 工具名称 --- tool_type VARCHAR(50) NOT NULL, -- 工具类型:api-API调用/function-函数/plugin-插件/integration-集成 --- description TEXT, -- 工具描述 --- function_schema JSONB, -- 函数Schema(OpenAI function calling格式) --- api_endpoint VARCHAR(500), -- API端点 --- api_method VARCHAR(10), -- API方法:GET/POST/PUT/DELETE --- api_headers JSONB, -- API请求头 --- auth_type VARCHAR(30), -- 认证类型:none/api_key/oauth2/bearer --- auth_config JSONB, -- 认证配置(加密存储) --- request_template TEXT, -- 请求模板 --- response_template TEXT, -- 响应模板 --- timeout_seconds INTEGER DEFAULT 30, -- 超时时间(秒) --- retry_count INTEGER DEFAULT 3, -- 重试次数 --- is_enabled BOOLEAN DEFAULT true, -- 是否启用 --- usage_count INTEGER DEFAULT 0, -- 使用次数 --- dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径 --- creator VARCHAR(50) DEFAULT NULL, -- 创建者 --- updater VARCHAR(50) DEFAULT NULL, -- 更新者 --- create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间 --- update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间 --- delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间 --- deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除 --- PRIMARY KEY (tool_id), --- UNIQUE (optsn), --- UNIQUE (tool_code) --- ); - --- CREATE INDEX idx_tool_type ON agent.tb_agent_tool(tool_type) WHERE deleted = false; - --- COMMENT ON TABLE agent.tb_agent_tool IS '智能体工具表'; - --- -- API集成注册表 --- DROP TABLE IF EXISTS agent.tb_api_integration CASCADE; --- CREATE TABLE agent.tb_api_integration ( --- optsn VARCHAR(50) NOT NULL, -- 流水号 --- integration_id VARCHAR(50) NOT NULL, -- 集成ID --- integration_name VARCHAR(255) NOT NULL, -- 集成名称 --- integration_type VARCHAR(50) NOT NULL, -- 集成类型:rest_api/soap/graphql/webhook/mq --- provider VARCHAR(100), -- 提供商 --- base_url VARCHAR(500), -- 基础URL --- version VARCHAR(20), -- API版本 --- auth_type VARCHAR(30) DEFAULT 'none', -- 认证类型 --- auth_config JSONB, -- 认证配置(加密存储) --- endpoints JSONB, -- 端点列表(JSON数组) --- rate_limit INTEGER, -- 速率限制(请求/秒) --- timeout_seconds INTEGER DEFAULT 30, -- 超时时间 --- retry_config JSONB, -- 重试配置 --- health_check_url VARCHAR(500), -- 健康检查URL --- health_status VARCHAR(20) DEFAULT 'unknown', -- 健康状态:healthy-健康/unhealthy-不健康/unknown-未知 --- last_health_check TIMESTAMPTZ, -- 最后健康检查时间 --- documentation_url VARCHAR(500), -- 文档URL --- is_enabled BOOLEAN DEFAULT true, -- 是否启用 --- dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径 --- creator VARCHAR(50) DEFAULT NULL, -- 创建者 --- updater VARCHAR(50) DEFAULT NULL, -- 更新者 --- create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间 --- update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间 --- delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间 --- deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除 --- PRIMARY KEY (integration_id), --- UNIQUE (optsn) --- ); - --- CREATE INDEX idx_integration_type ON agent.tb_api_integration(integration_type) WHERE deleted = false; --- CREATE INDEX idx_integration_health ON agent.tb_api_integration(health_status) WHERE deleted = false; - --- COMMENT ON TABLE agent.tb_api_integration IS 'API集成注册表'; - --- -- API调用日志表 --- DROP TABLE IF EXISTS agent.tb_api_call_log CASCADE; --- CREATE TABLE agent.tb_api_call_log ( --- optsn VARCHAR(50) NOT NULL, -- 流水号 --- log_id VARCHAR(50) NOT NULL, -- 日志ID --- integration_id VARCHAR(50), -- 集成ID --- tool_id VARCHAR(50), -- 工具ID --- agent_id VARCHAR(50), -- 智能体ID --- session_id VARCHAR(50), -- 会话ID --- user_id VARCHAR(50), -- 用户ID --- endpoint VARCHAR(500) NOT NULL, -- 请求端点 --- method VARCHAR(10) NOT NULL, -- 请求方法 --- request_headers JSONB, -- 请求头 --- request_body TEXT, -- 请求体 --- response_status INTEGER, -- 响应状态码 --- response_headers JSONB, -- 响应头 --- response_body TEXT, -- 响应体 --- duration_ms INTEGER, -- 请求耗时(毫秒) --- is_success BOOLEAN, -- 是否成功 --- error_message TEXT, -- 错误信息 --- retry_count INTEGER DEFAULT 0, -- 重试次数 --- ip_address VARCHAR(45), -- IP地址 --- dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径 --- create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间 --- PRIMARY KEY (log_id), --- UNIQUE (optsn) --- ); - --- CREATE INDEX idx_api_log_integration ON agent.tb_api_call_log(integration_id, create_time DESC); --- CREATE INDEX idx_api_log_agent ON agent.tb_api_call_log(agent_id, create_time DESC); --- CREATE INDEX idx_api_log_status ON agent.tb_api_call_log(is_success, create_time DESC); - --- COMMENT ON TABLE agent.tb_api_call_log IS 'API调用日志表'; - --- -- 智能体监控指标表 --- DROP TABLE IF EXISTS agent.tb_agent_metrics CASCADE; --- CREATE TABLE agent.tb_agent_metrics ( --- optsn VARCHAR(50) NOT NULL, -- 流水号 --- metric_id VARCHAR(50) NOT NULL, -- 指标ID --- agent_id VARCHAR(50) NOT NULL, -- 智能体ID --- metric_date DATE NOT NULL, -- 指标日期 --- metric_hour INTEGER, -- 指标小时(0-23) --- total_sessions INTEGER DEFAULT 0, -- 总会话数 --- active_sessions INTEGER DEFAULT 0, -- 活跃会话数 --- total_messages INTEGER DEFAULT 0, -- 总消息数 --- total_tokens BIGINT DEFAULT 0, -- 总Token数 --- total_cost DECIMAL(10,4) DEFAULT 0, -- 总成本 --- avg_response_time INTEGER, -- 平均响应时间(毫秒) --- success_rate DECIMAL(5,4), -- 成功率 --- error_count INTEGER DEFAULT 0, -- 错误次数 --- api_call_count INTEGER DEFAULT 0, -- API调用次数 --- avg_rating DECIMAL(3,2), -- 平均评分 --- rating_count INTEGER DEFAULT 0, -- 评分数量 --- dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径 --- create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间 --- update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间 --- PRIMARY KEY (metric_id), --- UNIQUE (optsn), --- UNIQUE (agent_id, metric_date, metric_hour), --- FOREIGN KEY (agent_id) REFERENCES agent.tb_agent(agent_id) --- ); - --- CREATE INDEX idx_metrics_agent_date ON agent.tb_agent_metrics(agent_id, metric_date DESC); - --- COMMENT ON TABLE agent.tb_agent_metrics IS '智能体监控指标表'; - --- -- 智能体异常日志表 --- DROP TABLE IF EXISTS agent.tb_agent_error_log CASCADE; --- CREATE TABLE agent.tb_agent_error_log ( --- optsn VARCHAR(50) NOT NULL, -- 流水号 --- log_id VARCHAR(50) NOT NULL, -- 日志ID --- agent_id VARCHAR(50) NOT NULL, -- 智能体ID --- session_id VARCHAR(50), -- 会话ID --- error_type VARCHAR(50) NOT NULL, -- 错误类型:model_error-模型错误/api_error-API错误/timeout-超时/rate_limit-限流/other-其他 --- error_code VARCHAR(50), -- 错误代码 --- error_message TEXT NOT NULL, -- 错误信息 --- stack_trace TEXT, -- 堆栈跟踪 --- request_context JSONB, -- 请求上下文 --- severity VARCHAR(20) DEFAULT 'error', -- 严重级别:critical-致命/error-错误/warning-警告 --- is_resolved BOOLEAN DEFAULT false, -- 是否已解决 --- resolution_notes TEXT, -- 解决方案备注 --- dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径 --- create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间 --- resolve_time TIMESTAMPTZ, -- 解决时间 --- PRIMARY KEY (log_id), --- UNIQUE (optsn), --- FOREIGN KEY (agent_id) REFERENCES agent.tb_agent(agent_id) --- ); - --- CREATE INDEX idx_error_agent ON agent.tb_agent_error_log(agent_id, create_time DESC); --- CREATE INDEX idx_error_severity ON agent.tb_agent_error_log(severity) WHERE is_resolved = false; - --- COMMENT ON TABLE agent.tb_agent_error_log IS '智能体异常日志表'; - --- -- 智能体评价表 --- DROP TABLE IF EXISTS agent.tb_agent_rating CASCADE; --- CREATE TABLE agent.tb_agent_rating ( --- optsn VARCHAR(50) NOT NULL, -- 流水号 --- rating_id VARCHAR(50) NOT NULL, -- 评价ID --- agent_id VARCHAR(50) NOT NULL, -- 智能体ID --- session_id VARCHAR(50), -- 会话ID --- user_id VARCHAR(50) NOT NULL, -- 用户ID --- rating INTEGER NOT NULL, -- 评分(1-5星) --- dimensions JSONB, -- 分维度评分(准确性、速度、友好度等) --- feedback TEXT, -- 评价反馈 --- tags TEXT[], -- 标签 --- is_anonymous BOOLEAN DEFAULT false, -- 是否匿名 --- dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径 --- create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间 --- deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除 --- PRIMARY KEY (rating_id), --- UNIQUE (optsn), --- FOREIGN KEY (agent_id) REFERENCES agent.tb_agent(agent_id) --- ); - --- CREATE INDEX idx_rating_agent ON agent.tb_agent_rating(agent_id) WHERE deleted = false; - --- COMMENT ON TABLE agent.tb_agent_rating IS '智能体评价表'; diff --git a/urbanLifelineServ/.bin/database/postgres/sql/createTableAll.sql b/urbanLifelineServ/.bin/database/postgres/sql/createTableAll.sql deleted file mode 100644 index 62abb2bc..00000000 --- a/urbanLifelineServ/.bin/database/postgres/sql/createTableAll.sql +++ /dev/null @@ -1,156 +0,0 @@ - -- ============================= --- 泰豪电源AI数智化平台 - 完整数据库初始化脚本 --- 包含所有业务模块的表结构 --- ============================= - --- 安装必要的扩展 -CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -- UUID生成 -CREATE EXTENSION IF NOT EXISTS "pg_trgm"; -- 全文搜索 -CREATE EXTENSION IF NOT EXISTS "btree_gin"; -- GIN索引支持 --- CREATE EXTENSION IF NOT EXISTS "vector"; -- pgvector向量检索(需要单独安装) - --- ============================= --- 1. 系统基础模块(权限、用户、部门、角色) --- ============================= -\i createTablePermission.sql -\i createTableUser.sql - --- ============================= --- 2. 文件管理模块 --- ============================= -\i createTableFile.sql - --- ============================= --- 3. 消息通知模块 --- ============================= -\i createTableMessage.sql - --- ============================= --- 4. 日志模块 --- ============================= -\i createTableLog.sql - --- ============================= --- 5. 配置管理模块 --- ============================= -\i createTableConfig.sql - --- ============================= --- 6. 知识库管理模块 --- ============================= -\i createTableKnowledge.sql - --- ============================= --- 7. 招投标智能体业务模块 --- ============================= -\i createTableBidding.sql - --- ============================= --- 8. 智能客服系统业务模块 --- ============================= -\i createTableCustomerService.sql - --- ============================= --- 9. 智能体管理和平台基础设施模块 --- ============================= -\i createTableAgent.sql - --- ============================= --- 创建视图 --- ============================= - --- 用户完整信息视图 -CREATE OR REPLACE VIEW sys.v_user_full_info AS -SELECT - u.user_id, - u.email, - u.phone, - u.wechat_id, - u.status, - ui.avatar, - ui.full_name, - ui.gender, - ui.level, - u.create_time, - u.update_time -FROM sys.tb_sys_user u -LEFT JOIN sys.tb_sys_user_info ui ON u.user_id = ui.user_id -WHERE u.deleted = false AND (ui.deleted = false OR ui.deleted IS NULL); - --- 用户角色权限视图 -CREATE OR REPLACE VIEW sys.v_user_role_permission AS -SELECT DISTINCT - ur.user_id, - r.role_id, - r.name AS role_name, - p.permission_id, - p.code AS permission_code, - p.name AS permission_name, - m.module_id, - m.name AS module_name -FROM sys.tb_sys_user_role ur -JOIN sys.tb_sys_role r ON ur.role_id = r.role_id -JOIN sys.tb_sys_role_permission rp ON r.role_id = rp.role_id -JOIN sys.tb_sys_permission p ON rp.permission_id = p.permission_id -LEFT JOIN sys.tb_sys_module m ON p.module_id = m.module_id -WHERE ur.deleted = false - AND r.deleted = false - AND rp.deleted = false - AND p.deleted = false; - --- 智能体使用统计视图 -CREATE OR REPLACE VIEW agent.v_agent_usage_stats AS -SELECT - a.agent_id, - a.agent_name, - a.agent_type, - COUNT(DISTINCT s.session_id) AS total_sessions, - COUNT(DISTINCT s.user_id) AS unique_users, - SUM(s.message_count) AS total_messages, - AVG(s.token_usage) AS avg_token_usage, - AVG(r.rating) AS avg_rating, - COUNT(r.rating_id) AS rating_count, - MAX(s.start_time) AS last_used_time -FROM agent.tb_agent a -LEFT JOIN agent.tb_agent_session s ON a.agent_id = s.agent_id AND s.deleted = false -LEFT JOIN agent.tb_agent_rating r ON a.agent_id = r.agent_id AND r.deleted = false -WHERE a.deleted = false -GROUP BY a.agent_id, a.agent_name, a.agent_type; - --- 客服工单统计视图 -CREATE OR REPLACE VIEW customer_service.v_ticket_stats AS -SELECT - t.ticket_status, - t.priority, - t.ticket_type, - COUNT(*) AS ticket_count, - AVG(EXTRACT(EPOCH FROM (t.resolution_time - t.create_time))/3600) AS avg_resolution_hours, - AVG(t.customer_rating) AS avg_rating, - COUNT(CASE WHEN t.is_overdue THEN 1 END) AS overdue_count -FROM customer_service.tb_ticket t -WHERE t.deleted = false -GROUP BY t.ticket_status, t.priority, t.ticket_type; - --- 招投标项目统计视图 -CREATE OR REPLACE VIEW bidding.v_project_stats AS -SELECT - p.project_status, - p.project_type, - COUNT(*) AS project_count, - SUM(p.budget_amount) AS total_budget, - SUM(CASE WHEN p.winning_status = 'won' THEN 1 ELSE 0 END) AS won_count, - SUM(CASE WHEN p.winning_status = 'won' THEN p.winning_amount ELSE 0 END) AS total_won_amount, - AVG(CASE WHEN p.winning_status = 'won' THEN p.winning_amount / NULLIF(p.budget_amount, 0) ELSE NULL END) AS avg_win_rate -FROM bidding.tb_bidding_project p -WHERE p.deleted = false -GROUP BY p.project_status, p.project_type; - -COMMENT ON VIEW sys.v_user_full_info IS '用户完整信息视图'; -COMMENT ON VIEW sys.v_user_role_permission IS '用户角色权限视图'; -COMMENT ON VIEW agent.v_agent_usage_stats IS '智能体使用统计视图'; -COMMENT ON VIEW customer_service.v_ticket_stats IS '客服工单统计视图'; -COMMENT ON VIEW bidding.v_project_stats IS '招投标项目统计视图'; - --- ============================= --- 数据库初始化完成 --- ============================= diff --git a/urbanLifelineServ/.bin/database/postgres/sql/createTableFile.sql b/urbanLifelineServ/.bin/database/postgres/sql/createTableFile.sql index d308c57d..b1bc95ac 100644 --- a/urbanLifelineServ/.bin/database/postgres/sql/createTableFile.sql +++ b/urbanLifelineServ/.bin/database/postgres/sql/createTableFile.sql @@ -15,6 +15,8 @@ CREATE TABLE file.tb_sys_file ( -- TbSysFileDTO 特有字段 file_id VARCHAR(50) NOT NULL, -- 文件ID (主键) + file_root_id VARCHAR(50) DEFAULT NULL, -- 文件根ID + version VARCHAR(50) DEFAULT NULL, -- 文件版本 name VARCHAR(255) NOT NULL, -- 文件名 path VARCHAR(500) NOT NULL, -- 文件路径 size BIGINT NOT NULL, -- 文件大小(字节) @@ -50,6 +52,8 @@ COMMENT ON COLUMN file.tb_sys_file.deleted IS '是否已删除(0-未删除,1 -- TbSysFileDTO 特有字段注释 COMMENT ON COLUMN file.tb_sys_file.file_id IS '文件ID (主键)'; +COMMENT ON COLUMN file.tb_sys_file.file_root_id IS '文件根ID'; +COMMENT ON COLUMN file.tb_sys_file.version IS '文件版本'; COMMENT ON COLUMN file.tb_sys_file.name IS '文件名'; COMMENT ON COLUMN file.tb_sys_file.path IS '文件路径'; COMMENT ON COLUMN file.tb_sys_file.size IS '文件大小(字节)'; @@ -72,49 +76,4 @@ CREATE INDEX idx_file_uploader ON file.tb_sys_file(uploader) WHERE deleted = 0; CREATE INDEX idx_file_bucket ON file.tb_sys_file(bucket_name) WHERE deleted = 0; CREATE INDEX idx_file_status ON file.tb_sys_file(status) WHERE deleted = 0; CREATE INDEX idx_file_create_time ON file.tb_sys_file(create_time) WHERE deleted = 0; -CREATE INDEX idx_file_md5 ON file.tb_sys_file(md5_hash) WHERE deleted = 0; - --- ============================= --- 文件关联表 --- ============================= -DROP TABLE IF EXISTS file.tb_file_relation CASCADE; -CREATE TABLE file.tb_file_relation ( - optsn VARCHAR(50) NOT NULL, - relation_id VARCHAR(50) NOT NULL, - file_id VARCHAR(50) NOT NULL, - object_type VARCHAR(50) NOT NULL, -- 对象类型:bidding_project/ticket/document等 - object_id VARCHAR(50) NOT NULL, -- 对象ID - relation_type VARCHAR(30) DEFAULT 'attachment', -- 关联类型:attachment-附件/avatar-头像/banner-横幅 - order_num INTEGER DEFAULT 0, -- 排序号 - service_type VARCHAR(50), -- 服务类型(继承自object_type) - creator VARCHAR(50) DEFAULT NULL, -- 创建者 - updater VARCHAR(50) DEFAULT NULL, -- 更新者 - create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间 - update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间 - delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间 - deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除 - PRIMARY KEY (relation_id), - UNIQUE (optsn), - FOREIGN KEY (file_id) REFERENCES file.tb_sys_file(file_id) -); - -COMMENT ON TABLE file.tb_file_relation IS '文件关联表'; -COMMENT ON COLUMN file.tb_file_relation.optsn IS '流水号'; -COMMENT ON COLUMN file.tb_file_relation.relation_id IS '关联ID'; -COMMENT ON COLUMN file.tb_file_relation.file_id IS '文件ID'; -COMMENT ON COLUMN file.tb_file_relation.object_type IS '对象类型:bidding_project/ticket/document等'; -COMMENT ON COLUMN file.tb_file_relation.object_id IS '对象ID'; -COMMENT ON COLUMN file.tb_file_relation.relation_type IS '关联类型:attachment-附件/avatar-头像/banner-横幅'; -COMMENT ON COLUMN file.tb_file_relation.order_num IS '排序号'; -COMMENT ON COLUMN file.tb_file_relation.service_type IS '服务类型(继承自object_type)'; -COMMENT ON COLUMN file.tb_file_relation.creator IS '创建者'; -COMMENT ON COLUMN file.tb_file_relation.updater IS '更新者'; -COMMENT ON COLUMN file.tb_file_relation.create_time IS '创建时间'; -COMMENT ON COLUMN file.tb_file_relation.update_time IS '更新时间'; -COMMENT ON COLUMN file.tb_file_relation.delete_time IS '删除时间'; -COMMENT ON COLUMN file.tb_file_relation.deleted IS '是否删除'; - --- 文件关联表索引 -CREATE INDEX idx_file_relation_object ON file.tb_file_relation(object_type, object_id) WHERE deleted = false; -CREATE INDEX idx_file_relation_file ON file.tb_file_relation(file_id) WHERE deleted = false; -CREATE INDEX idx_file_relation_service ON file.tb_file_relation(service_type) WHERE deleted = false; \ No newline at end of file +CREATE INDEX idx_file_md5 ON file.tb_sys_file(md5_hash) WHERE deleted = 0; \ No newline at end of file diff --git a/urbanLifelineServ/.bin/database/postgres/sql/createTableKnowledge.sql b/urbanLifelineServ/.bin/database/postgres/sql/createTableKnowledge.sql deleted file mode 100644 index a56b25da..00000000 --- a/urbanLifelineServ/.bin/database/postgres/sql/createTableKnowledge.sql +++ /dev/null @@ -1,153 +0,0 @@ --- ============================= --- 知识库管理模块 --- 支持:招投标知识库、客服知识库、企业内部知识库 --- ============================= -CREATE SCHEMA IF NOT EXISTS knowledge; - --- 知识库表(多租户知识库定义) -DROP TABLE IF EXISTS knowledge.tb_knowledge_base CASCADE; -CREATE TABLE knowledge.tb_knowledge_base ( - optsn VARCHAR(50) NOT NULL, -- 流水号 - agent_id VARCHAR(50) NOT NULL, -- 智能体id - knowledge_id VARCHAR(50) NOT NULL, -- 知识库ID - name VARCHAR(255) NOT NULL, -- 知识库名称 - kb_type VARCHAR(50) NOT NULL, -- 知识库类型:bidding-招投标/customer_service-客服/internal-内部协同 - access_level VARCHAR(20) NOT NULL DEFAULT 'private', -- 访问级别:public-公开/private-私有/internal-内部 - description TEXT, -- 知识库描述 - storage_path VARCHAR(500), -- 存储路径 - version VARCHAR(20) DEFAULT '1.0', -- 当前版本号 - config JSONB DEFAULT NULL, -- 知识库配置(JSON格式:索引配置、检索参数等) - service_type VARCHAR(50), -- 服务类型(bidding/customer_service/internal,冗余kb_type便于查询) - dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径 - status VARCHAR(20) NOT NULL DEFAULT 'active', -- 状态:active-激活/inactive-停用/archived-归档 - creator VARCHAR(50) DEFAULT NULL, -- 创建者 - updater VARCHAR(50) DEFAULT NULL, -- 更新者 - create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间 - update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间 - delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间 - deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除 - PRIMARY KEY (knowledge_id), - UNIQUE (optsn) -); - -CREATE INDEX idx_kb_type ON knowledge.tb_knowledge_base(kb_type) WHERE deleted = false; -CREATE INDEX idx_kb_service ON knowledge.tb_knowledge_base(service_type) WHERE deleted = false; -CREATE INDEX idx_kb_dept_path ON knowledge.tb_knowledge_base(dept_path) WHERE deleted = false; - -COMMENT ON TABLE knowledge.tb_knowledge_base IS '知识库表'; -COMMENT ON COLUMN knowledge.tb_knowledge_base.kb_type IS '知识库类型:bidding-招投标/customer_service-客服/internal-内部协同'; -COMMENT ON COLUMN knowledge.tb_knowledge_base.service_type IS '服务类型(冗余存储便于快速过滤,与kb_type保持一致)'; -COMMENT ON COLUMN knowledge.tb_knowledge_base.access_level IS '访问级别:public-公开/private-私有/internal-内部'; - --- 知识文档表 -DROP TABLE IF EXISTS knowledge.tb_knowledge_document CASCADE; -CREATE TABLE knowledge.tb_knowledge_document ( - optsn VARCHAR(50) NOT NULL, -- 流水号 - doc_id VARCHAR(50) NOT NULL, -- 文档ID - knowledge_id VARCHAR(50) NOT NULL, -- 所属知识库ID - title VARCHAR(500) NOT NULL, -- 文档标题 - doc_type VARCHAR(50) NOT NULL, -- 文档类型:text-文本/pdf/word/excel/image/video - category VARCHAR(100), -- 文档分类(自动或手动分类) - content TEXT, -- 文档内容(文本类型) - content_summary TEXT, -- 内容摘要(AI生成) - file_id VARCHAR(50), -- 关联文件表ID - file_path VARCHAR(500), -- 文件路径 - file_size BIGINT, -- 文件大小(字节) - mime_type VARCHAR(100), -- MIME类型 - version INTEGER DEFAULT 1, -- 文档版本号(仅做记录) - root_doc_id VARCHAR(50), -- 根文档ID(版本组标识,保留用于整体版本管理) - tags TEXT[], -- 文档标签数组 - keywords TEXT[], -- 关键词数组(AI提取) - embedding_status VARCHAR(20) DEFAULT 'pending', -- 向量化状态:pending-待处理/processing-处理中/completed-完成/failed-失败 - embedding_model VARCHAR(100), -- 使用的向量化模型 - chunk_count INTEGER DEFAULT 0, -- 切片数量 - metadata JSONB DEFAULT NULL, -- 文档元数据(JSON格式) - source_url VARCHAR(500), -- 来源URL - service_type VARCHAR(50), -- 服务类型(继承自知识库) - dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径 - status VARCHAR(20) NOT NULL DEFAULT 'active', -- 状态:active-激活/inactive-停用/archived-归档 - creator VARCHAR(50) DEFAULT NULL, -- 创建者 - updater VARCHAR(50) DEFAULT NULL, -- 更新者 - create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间 - update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间 - delete_time TIMESTAMPTZ DEFAULT NULL, -- 删除时间 - deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除 - PRIMARY KEY (doc_id), - UNIQUE (optsn) -); - -CREATE INDEX idx_doc_kb ON knowledge.tb_knowledge_document(knowledge_id) WHERE deleted = false; -CREATE INDEX idx_doc_service ON knowledge.tb_knowledge_document(service_type) WHERE deleted = false; -CREATE INDEX idx_doc_category ON knowledge.tb_knowledge_document(category) WHERE deleted = false; -CREATE INDEX idx_doc_embedding_status ON knowledge.tb_knowledge_document(embedding_status) WHERE deleted = false; -CREATE INDEX idx_doc_tags ON knowledge.tb_knowledge_document USING GIN(tags) WHERE deleted = false; -CREATE INDEX idx_doc_root ON knowledge.tb_knowledge_document(root_doc_id) WHERE deleted = false; - -COMMENT ON TABLE knowledge.tb_knowledge_document IS '知识文档表(文档级元数据,版本控制在chunk级别)'; -COMMENT ON COLUMN knowledge.tb_knowledge_document.service_type IS '服务类型(从知识库继承,用于服务间隔离)'; -COMMENT ON COLUMN knowledge.tb_knowledge_document.version IS '文档版本号(仅做记录,实际版本控制在chunk级别)'; -COMMENT ON COLUMN knowledge.tb_knowledge_document.root_doc_id IS '根文档ID(用于文档整体版本管理,可选)'; -COMMENT ON COLUMN knowledge.tb_knowledge_document.embedding_status IS '向量化状态:pending/processing/completed/failed'; - --- 知识文档片段表(用于RAG检索) -DROP TABLE IF EXISTS knowledge.tb_knowledge_chunk CASCADE; -CREATE TABLE knowledge.tb_knowledge_chunk ( - optsn VARCHAR(50) NOT NULL, -- 流水号 - chunk_id VARCHAR(50) NOT NULL, -- 片段ID - doc_id VARCHAR(50) NOT NULL, -- 所属文档ID - knowledge_id VARCHAR(50) NOT NULL, -- 所属知识库ID - chunk_index INTEGER NOT NULL, -- 片段索引(在文档中的顺序) - content TEXT NOT NULL, -- 片段内容 - content_length INTEGER, -- 内容长度 - embedding vector(1536), -- 向量嵌入(假设使用OpenAI 1536维) - chunk_type VARCHAR(20) DEFAULT 'text', -- 片段类型:text-文本/table-表格/image-图片 - version INTEGER DEFAULT 1, -- chunk版本号(每次修改自动+1) - root_chunk_id VARCHAR(50), -- 根chunk ID(同一chunk的不同版本共享此ID) - is_current BOOLEAN DEFAULT true, -- 是否当前使用的版本 - position_info JSONB, -- 位置信息(页码、坐标等) - metadata JSONB, -- 片段元数据 - dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径 - creator VARCHAR(50) DEFAULT NULL, -- 创建者 - create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间 - update_time TIMESTAMPTZ DEFAULT NULL, -- 更新时间 - deleted BOOLEAN NOT NULL DEFAULT false, -- 是否删除 - PRIMARY KEY (chunk_id), - UNIQUE (optsn) -); - -CREATE INDEX idx_chunk_doc ON knowledge.tb_knowledge_chunk(doc_id) WHERE deleted = false; -CREATE INDEX idx_chunk_kb ON knowledge.tb_knowledge_chunk(knowledge_id) WHERE deleted = false; -CREATE INDEX idx_chunk_root_current ON knowledge.tb_knowledge_chunk(root_chunk_id, is_current) WHERE deleted = false; -CREATE INDEX idx_chunk_current ON knowledge.tb_knowledge_chunk(is_current) WHERE deleted = false AND is_current = true; --- 向量检索索引(需要安装pgvector扩展,建议只索引当前版本) --- CREATE INDEX idx_chunk_embedding ON knowledge.tb_knowledge_chunk USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100) WHERE deleted = false AND is_current = true; - -COMMENT ON TABLE knowledge.tb_knowledge_chunk IS '知识文档片段表(RAG检索基本单位,支持chunk级版本控制)'; -COMMENT ON COLUMN knowledge.tb_knowledge_chunk.version IS 'chunk版本号(整数,每次修改自动+1)'; -COMMENT ON COLUMN knowledge.tb_knowledge_chunk.root_chunk_id IS '根chunk ID(同一chunk的所有版本共享此ID,首次创建时等于chunk_id)'; -COMMENT ON COLUMN knowledge.tb_knowledge_chunk.is_current IS '是否当前使用的版本(每个root_chunk_id只有一个is_current=true,RAG检索时只使用当前版本)'; -COMMENT ON COLUMN knowledge.tb_knowledge_chunk.embedding IS '向量嵌入(需要pgvector扩展,建议只为is_current=true的chunk生成)'; - --- 知识访问日志表 -DROP TABLE IF EXISTS knowledge.tb_knowledge_access_log CASCADE; -CREATE TABLE knowledge.tb_knowledge_access_log ( - optsn VARCHAR(50) NOT NULL, -- 流水号 - log_id VARCHAR(50) NOT NULL, -- 日志ID - knowledge_id VARCHAR(50), -- 知识库ID - doc_id VARCHAR(50), -- 文档ID - user_id VARCHAR(50) NOT NULL, -- 用户ID - access_type VARCHAR(20) NOT NULL, -- 访问类型:view-查看/download-下载/search-搜索/edit-编辑 - query_text TEXT, -- 搜索查询文本 - result_count INTEGER, -- 搜索结果数量 - ip_address VARCHAR(45), -- IP地址 - user_agent TEXT, -- 用户代理 - dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径 - create_time TIMESTAMPTZ NOT NULL DEFAULT now(), -- 创建时间 - PRIMARY KEY (log_id), - UNIQUE (optsn) -); - -CREATE INDEX idx_access_log_user ON knowledge.tb_knowledge_access_log(user_id, create_time DESC); -CREATE INDEX idx_access_log_kb ON knowledge.tb_knowledge_access_log(knowledge_id, create_time DESC); - -COMMENT ON TABLE knowledge.tb_knowledge_access_log IS '知识访问日志表'; diff --git a/urbanLifelineServ/.bin/database/postgres/sql/initAll.sql b/urbanLifelineServ/.bin/database/postgres/sql/initAll.sql index c0d81b5b..2e8b5549 100644 --- a/urbanLifelineServ/.bin/database/postgres/sql/initAll.sql +++ b/urbanLifelineServ/.bin/database/postgres/sql/initAll.sql @@ -24,8 +24,8 @@ -- 5. 配置管理模块 \i createTableConfig.sql --- 6. 知识库管理模块 -\i createTableKnowledge.sql +-- 6. AI模块 智能体+知识库 +\i createTableAI.sql -- 7. 招投标业务模块 \i createTableBidding.sql @@ -33,9 +33,6 @@ -- 8. 智能客服业务模块 \i createTableWorkcase.sql --- 9. 智能体模块(暂不启用) --- \i createTableAgent.sql - -- ============================= -- 第二阶段:初始化基础数据 -- ============================= diff --git a/urbanLifelineServ/.bin/database/postgres/sql/initDataConfig.sql b/urbanLifelineServ/.bin/database/postgres/sql/initDataConfig.sql index 48963205..a50b8327 100644 --- a/urbanLifelineServ/.bin/database/postgres/sql/initDataConfig.sql +++ b/urbanLifelineServ/.bin/database/postgres/sql/initDataConfig.sql @@ -62,6 +62,23 @@ INSERT INTO config.tb_sys_config ( ('CFG-0416', 'cfg_sms_tpl_register', 'sms.templateCode.register','注册验证码模板', 'SMS_491985030', 'String', 'input', '注册验证码模板编码', NULL, NULL, 'notify', 'mod_message', 130, 1, '注册验证码短信模板', 'system', NULL, NULL, now(), NULL, NULL, false), ('CFG-0417', 'cfg_sms_timeout', 'sms.timeout', '请求超时时间', '30000', 'INTEGER', 'input', '请求超时时间(毫秒)', NULL, NULL, 'notify', 'mod_message', 140, 1, 'API请求超时时间(5000-60000)', 'system', NULL, NULL, now(), NULL, NULL, false), +-- Dify AI 配置 +-- Dify 基础配置 +('CFG-0450', 'cfg_dify_api_base', 'dify.apiBaseUrl', 'Dify API地址', 'http://localhost:8000/v1', 'String', 'input', 'Dify API基础地址', NULL, NULL, 'dify', 'mod_agent', 10, 1, 'Dify服务的API基础地址,如 http://localhost/dify/api', 'system', NULL, NULL, now(), NULL, NULL, false), +('CFG-0452', 'cfg_dify_knowledge_key','dify.knowledgeApiKey', '知识库API密钥', 'dataset-nupqKP4LONpzdXmGthIrbjeJ', 'String', 'password', '知识库API密钥', NULL, NULL, 'dify', 'mod_agent', 30, 1, '用于访问Dify知识库的API密钥', 'system', NULL, NULL, now(), NULL, NULL, false), +('CFG-0453', 'cfg_dify_timeout', 'dify.timeout', '请求超时时间', '60', 'INTEGER', 'input', '请求超时时间(秒)', NULL, NULL, 'dify', 'mod_agent', 40, 1, 'API请求的超时时间(10-600秒)', 'system', NULL, NULL, now(), NULL, NULL, false), +('CFG-0454', 'cfg_dify_conn_timeout','dify.connectTimeout', '连接超时时间', '10', 'INTEGER', 'input', '连接超时时间(秒)', NULL, NULL, 'dify', 'mod_agent', 50, 1, 'API连接的超时时间(5-60秒)', 'system', NULL, NULL, now(), NULL, NULL, false), +('CFG-0455', 'cfg_dify_read_timeout','dify.readTimeout', '读取超时时间', '60', 'INTEGER', 'input', '读取超时时间(秒)', NULL, NULL, 'dify', 'mod_agent', 60, 1, 'API读取响应的超时时间(10-600秒)', 'system', NULL, NULL, now(), NULL, NULL, false), +('CFG-0456', 'cfg_dify_stream_timeout','dify.streamTimeout', '流式响应超时时间', '300', 'INTEGER', 'input', '流式响应超时时间(秒)', NULL, NULL, 'dify', 'mod_agent', 70, 1, '流式API响应的超时时间(30-1800秒)', 'system', NULL, NULL, now(), NULL, NULL, false), + +-- Dify 上传配置 +('CFG-0460', 'cfg_dify_upload_types','dify.upload.allowedTypes','允许的文件类型', 'pdf,txt,docx,doc,md,html,htm,xlsx,xls,csv', 'String', 'textarea', '上传文件允许的类型', NULL, NULL, 'dify', 'mod_agent', 80, 1, '支持上传的文件类型列表,逗号分隔', 'system', NULL, NULL, now(), NULL, NULL, false), +('CFG-0461', 'cfg_dify_upload_max', 'dify.upload.maxSize', '最大文件大小', '50', 'INTEGER', 'input', '最大文件大小(MB)', NULL, NULL, 'dify', 'mod_agent', 90, 1, '单个文件上传的最大大小限制(1-500MB)', 'system', NULL, NULL, now(), NULL, NULL, false), + +-- Dify 知识库配置 +('CFG-0470', 'cfg_dify_index_tech', 'dify.dataset.defaultIndexingTechnique','默认索引方式', 'high_quality', 'String', 'select', '默认索引方式', NULL, '["high_quality", "economy"]'::json, 'dify', 'mod_agent', 100, 1, '知识库文档的默认索引方式:high_quality(高质量)或 economy(经济)', 'system', NULL, NULL, now(), NULL, NULL, false), +('CFG-0471', 'cfg_dify_embed_model', 'dify.dataset.defaultEmbeddingModel','默认Embedding模型', 'text-embedding-ada-002', 'String', 'input', '默认Embedding模型', NULL, NULL, 'dify', 'mod_agent', 110, 1, '知识库使用的默认Embedding模型名称', 'system', NULL, NULL, now(), NULL, NULL, false), + -- 日志与审计 ('CFG-0501', 'cfg_log_level', 'log.level', '日志级别', 'INFO', 'String', 'select', '系统日志级别', NULL, '["DEBUG", "INFO", "WARN", "ERROR"]'::json, 'log', 'mod_system', 10, 0, 'DEBUG/INFO/WARN/ERROR', 'system', NULL, NULL, now(), NULL, NULL, false), ('CFG-0502', 'cfg_audit_retention', 'audit.retentionDays', '审计日志保留', '90', 'INTEGER', 'input', '审计日志保留天数', NULL, NULL, 'log', 'mod_system', 20, 0, '合规按需调整', 'system', NULL, NULL, now(), NULL, NULL, false), diff --git a/urbanLifelineServ/agent/src/main/java/org/xyzh/agent/client/DifyClient.java b/urbanLifelineServ/agent/src/main/java/org/xyzh/agent/client/DifyClient.java deleted file mode 100644 index eeaa0bc1..00000000 --- a/urbanLifelineServ/agent/src/main/java/org/xyzh/agent/client/DifyClient.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.xyzh.agent.client; - -public class DifyClient { - -} diff --git a/urbanLifelineServ/agent/pom.xml b/urbanLifelineServ/ai/pom.xml similarity index 59% rename from urbanLifelineServ/agent/pom.xml rename to urbanLifelineServ/ai/pom.xml index d4827c29..7f1f1db1 100644 --- a/urbanLifelineServ/agent/pom.xml +++ b/urbanLifelineServ/ai/pom.xml @@ -10,12 +10,25 @@ org.xyzh - agent + ai 1.0.0 21 21 + + + org.xyzh.apis + api-ai + ${urban-lifeline.version} + + + org.xyzh.apis + api-system + ${urban-lifeline.version} + + + \ No newline at end of file diff --git a/urbanLifelineServ/agent/src/main/java/org/xyzh/agent/AgentApp.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/AgentApp.java similarity index 97% rename from urbanLifelineServ/agent/src/main/java/org/xyzh/agent/AgentApp.java rename to urbanLifelineServ/ai/src/main/java/org/xyzh/ai/AgentApp.java index 906de32a..2854e8b2 100644 --- a/urbanLifelineServ/agent/src/main/java/org/xyzh/agent/AgentApp.java +++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/AgentApp.java @@ -1,4 +1,4 @@ -package org.xyzh.agent; +package org.xyzh.ai; import org.apache.dubbo.config.spring.context.annotation.EnableDubbo; import org.slf4j.Logger; diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/DifyApiClient.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/DifyApiClient.java new file mode 100644 index 00000000..ee62b8e3 --- /dev/null +++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/DifyApiClient.java @@ -0,0 +1,1031 @@ +package org.xyzh.ai.client; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; + +import jakarta.annotation.PostConstruct; +import okhttp3.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.xyzh.ai.client.dto.*; +import org.xyzh.ai.client.callback.StreamCallback; +import org.xyzh.ai.config.DifyConfig; +import org.xyzh.ai.exception.DifyException; +import org.xyzh.api.ai.dto.DifyFileInfo; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * @description Dify API客户端 - 封装所有Dify平台HTTP调用 + * @filename DifyApiClient.java + * @author AI Assistant + * @copyright xyzh + * @since 2025-12-15 + */ +@Component +public class DifyApiClient { + + private static final Logger logger = LoggerFactory.getLogger(DifyApiClient.class); + + @Autowired + private DifyConfig difyConfig; + + private OkHttpClient httpClient; + private OkHttpClient streamHttpClient; + + @PostConstruct + public void init() { + // 普通请求客户端 + this.httpClient = new OkHttpClient.Builder() + .connectTimeout(difyConfig.getConnectTimeout(), TimeUnit.SECONDS) + .readTimeout(difyConfig.getReadTimeout(), TimeUnit.SECONDS) + .writeTimeout(difyConfig.getTimeout(), TimeUnit.SECONDS) + .retryOnConnectionFailure(true) + .build(); + + // 流式请求客户端(更长的读取超时) + this.streamHttpClient = new OkHttpClient.Builder() + .connectTimeout(difyConfig.getConnectTimeout(), TimeUnit.SECONDS) + .readTimeout(difyConfig.getStreamTimeout(), TimeUnit.SECONDS) + .writeTimeout(difyConfig.getTimeout(), TimeUnit.SECONDS) + .retryOnConnectionFailure(false) // 流式不重试 + .build(); + + logger.info("DifyApiClient初始化完成,API地址: {}", difyConfig.getApiBaseUrl()); + } + + // ===================== 知识库管理 API ===================== + + /** + * 创建知识库(Dataset) + */ + public DatasetCreateResponse createDataset(DatasetCreateRequest request) { + String url = difyConfig.getFullApiUrl("/datasets"); + + try { + String jsonBody = JSON.toJSONString(request); + Request httpRequest = new Request.Builder() + .url(url) + .header("Authorization", "Bearer " + getKnowledgeApiKey()) + .header("Content-Type", "application/json") + .post(RequestBody.create(jsonBody, MediaType.parse("application/json"))) + .build(); + + try (Response response = httpClient.newCall(httpRequest).execute()) { + String responseBody = response.body() != null ? response.body().string() : ""; + + if (!response.isSuccessful()) { + logger.error("创建知识库失败: {} - {}", response.code(), responseBody); + throw new DifyException("创建知识库失败: " + responseBody); + } + + return JSON.parseObject(responseBody, DatasetCreateResponse.class); + } + } catch (IOException e) { + logger.error("创建知识库异常", e); + throw new DifyException("创建知识库异常: " + e.getMessage(), e); + } + } + + /** + * 查询知识库列表 + */ + public DatasetListResponse listDatasets(int page, int limit) { + String url = difyConfig.getFullApiUrl("/datasets?page=" + page + "&limit=" + limit); + + try { + Request httpRequest = new Request.Builder() + .url(url) + .header("Authorization", "Bearer " + getKnowledgeApiKey()) + .get() + .build(); + + try (Response response = httpClient.newCall(httpRequest).execute()) { + String responseBody = response.body() != null ? response.body().string() : ""; + + if (!response.isSuccessful()) { + logger.error("查询知识库列表失败: {} - {}", response.code(), responseBody); + throw new DifyException("查询知识库列表失败: " + responseBody); + } + + return JSON.parseObject(responseBody, DatasetListResponse.class); + } + } catch (IOException e) { + logger.error("查询知识库列表异常", e); + throw new DifyException("查询知识库列表异常: " + e.getMessage(), e); + } + } + + /** + * 查询知识库详情 + */ + public DatasetDetailResponse getDatasetDetail(String datasetId) { + String url = difyConfig.getFullApiUrl("/datasets/" + datasetId); + + try { + Request httpRequest = new Request.Builder() + .url(url) + .header("Authorization", "Bearer " + getKnowledgeApiKey()) + .get() + .build(); + + try (Response response = httpClient.newCall(httpRequest).execute()) { + String responseBody = response.body() != null ? response.body().string() : ""; + + if (!response.isSuccessful()) { + logger.error("查询知识库详情失败: {} - {}", response.code(), responseBody); + throw new DifyException("查询知识库详情失败: " + responseBody); + } + + return JSON.parseObject(responseBody, DatasetDetailResponse.class); + } + } catch (IOException e) { + logger.error("查询知识库详情异常", e); + throw new DifyException("查询知识库详情异常: " + e.getMessage(), e); + } + } + + + /** + * 更新知识库 + */ + public void updateDataset(String datasetId, DatasetUpdateRequest request) { + String url = difyConfig.getFullApiUrl("/datasets/" + datasetId); + + try { + String jsonBody = JSON.toJSONString(request); + Request httpRequest = new Request.Builder() + .url(url) + .header("Authorization", "Bearer " + getKnowledgeApiKey()) + .header("Content-Type", "application/json") + .patch(RequestBody.create(jsonBody, MediaType.parse("application/json"))) + .build(); + + try (Response response = httpClient.newCall(httpRequest).execute()) { + if (!response.isSuccessful()) { + String responseBody = response.body() != null ? response.body().string() : ""; + logger.error("更新知识库失败: {} - {}", response.code(), responseBody); + throw new DifyException("更新知识库失败: " + responseBody); + } + logger.info("知识库更新成功: {}", datasetId); + } + } catch (IOException e) { + logger.error("更新知识库异常", e); + throw new DifyException("更新知识库异常: " + e.getMessage(), e); + } + } + /** + * 删除知识库 + */ + public void deleteDataset(String datasetId) { + String url = difyConfig.getFullApiUrl("/datasets/" + datasetId); + + try { + Request httpRequest = new Request.Builder() + .url(url) + .header("Authorization", "Bearer " + getKnowledgeApiKey()) + .delete() + .build(); + + try (Response response = httpClient.newCall(httpRequest).execute()) { + if (!response.isSuccessful()) { + String responseBody = response.body() != null ? response.body().string() : ""; + logger.error("删除知识库失败: {} - {}", response.code(), responseBody); + throw new DifyException("删除知识库失败: " + responseBody); + } + logger.info("知识库删除成功: {}", datasetId); + } + } catch (IOException e) { + logger.error("删除知识库异常", e); + throw new DifyException("删除知识库异常: " + e.getMessage(), e); + } + } + + // ===================== 对话文件上传 API ===================== + + /** + * 上传文件用于对话(图文多模态) + * @param file 文件 + * @param originalFilename 原始文件名 + * @param user 用户标识 + * @param apiKey API密钥 + * @return 文件信息(包含id、name、size等) + */ + public DifyFileInfo uploadFileForChat(File file, String originalFilename, String user, String apiKey) { + String url = difyConfig.getFullApiUrl("/files/upload"); + + try { + MultipartBody.Builder bodyBuilder = new MultipartBody.Builder() + .setType(MultipartBody.FORM) + .addFormDataPart("file", originalFilename, + RequestBody.create(file, MediaType.parse("application/octet-stream"))) + .addFormDataPart("user", user); + + Request httpRequest = new Request.Builder() + .url(url) + .header("Authorization", "Bearer " + getApiKey(apiKey)) + .post(bodyBuilder.build()) + .build(); + + try (Response response = httpClient.newCall(httpRequest).execute()) { + String responseBody = response.body() != null ? response.body().string() : ""; + + if (!response.isSuccessful()) { + logger.error("上传对话文件失败: {} - {}", response.code(), responseBody); + throw new DifyException("上传对话文件失败: " + responseBody); + } + + return JSON.parseObject(responseBody, DifyFileInfo.class); + } + } catch (IOException e) { + logger.error("上传对话文件异常", e); + throw new DifyException("上传对话文件异常: " + e.getMessage(), e); + } + } + + // ===================== 文档管理 API ===================== + + /** + * 上传文档到知识库(通过文件) + * 根据 Dify API 文档: POST /datasets/{dataset_id}/document/create-by-file + */ + public DocumentUploadResponse uploadDocumentByFile( + String datasetId, + File file, + String originalFilename, + DocumentUploadRequest uploadRequest) { + + String url = difyConfig.getFullApiUrl("/datasets/" + datasetId + "/document/create-by-file"); + + try { + // 构建 data JSON 字符串(包含所有元数据) + Map dataMap = new HashMap<>(); + if (uploadRequest.getName() != null) { + dataMap.put("name", uploadRequest.getName()); + } + if (uploadRequest.getIndexingTechnique() != null) { + dataMap.put("indexing_technique", uploadRequest.getIndexingTechnique()); + } + + // process_rule 是必填字段,如果没有提供则使用默认配置 + if (uploadRequest.getProcessRule() != null) { + dataMap.put("process_rule", uploadRequest.getProcessRule()); + } else { + // 默认分段规则 + Map defaultProcessRule = new HashMap<>(); + defaultProcessRule.put("mode", "automatic"); + dataMap.put("process_rule", defaultProcessRule); + } + + // 默认设置文档形式和语言 + dataMap.put("doc_form", "text_model"); + dataMap.put("doc_language", "Chinese"); + + String dataJson = JSON.toJSONString(dataMap); + logger.info("上传文档到知识库: datasetId={}, file={}, data={}", datasetId, originalFilename, dataJson); + + // 构建 multipart/form-data 请求体 + MultipartBody.Builder bodyBuilder = new MultipartBody.Builder() + .setType(MultipartBody.FORM) + .addFormDataPart("file", originalFilename, + RequestBody.create(file, MediaType.parse("application/octet-stream"))) + .addFormDataPart("data", dataJson); + + Request httpRequest = new Request.Builder() + .url(url) + .header("Authorization", "Bearer " + getKnowledgeApiKey()) + .post(bodyBuilder.build()) + .build(); + + try (Response response = httpClient.newCall(httpRequest).execute()) { + String responseBody = response.body() != null ? response.body().string() : ""; + + if (!response.isSuccessful()) { + logger.error("上传文档失败: {} - {}", response.code(), responseBody); + throw new DifyException("上传文档失败: " + responseBody); + } + + logger.info("文档上传成功: datasetId={}, file={}", datasetId, originalFilename); + return JSON.parseObject(responseBody, DocumentUploadResponse.class); + } + } catch (IOException e) { + logger.error("上传文档异常", e); + throw new DifyException("上传文档异常: " + e.getMessage(), e); + } + } + + /** + * 查询文档处理状态 + */ + public DocumentStatusResponse getDocumentStatus(String datasetId, String batchId) { + String url = difyConfig.getFullApiUrl("/datasets/" + datasetId + "/documents/" + batchId + "/indexing-status"); + + try { + Request httpRequest = new Request.Builder() + .url(url) + .header("Authorization", "Bearer " + getKnowledgeApiKey()) + .get() + .build(); + + try (Response response = httpClient.newCall(httpRequest).execute()) { + String responseBody = response.body() != null ? response.body().string() : ""; + + if (!response.isSuccessful()) { + logger.error("查询文档状态失败: {} - {}", response.code(), responseBody); + throw new DifyException("查询文档状态失败: " + responseBody); + } + + return JSON.parseObject(responseBody, DocumentStatusResponse.class); + } + } catch (IOException e) { + logger.error("查询文档状态异常", e); + throw new DifyException("查询文档状态异常: " + e.getMessage(), e); + } + } + + /** + * 查询知识库文档列表 + */ + public DocumentListResponse listDocuments(String datasetId, int page, int limit) { + String url = difyConfig.getFullApiUrl("/datasets/" + datasetId + "/documents?page=" + page + "&limit=" + limit); + + try { + Request httpRequest = new Request.Builder() + .url(url) + .header("Authorization", "Bearer " + getKnowledgeApiKey()) + .get() + .build(); + + try (Response response = httpClient.newCall(httpRequest).execute()) { + String responseBody = response.body() != null ? response.body().string() : ""; + + if (!response.isSuccessful()) { + logger.error("查询文档列表失败: {} - {}", response.code(), responseBody); + throw new DifyException("查询文档列表失败: " + responseBody); + } + + return JSON.parseObject(responseBody, DocumentListResponse.class); + } + } catch (IOException e) { + logger.error("查询文档列表异常", e); + throw new DifyException("查询文档列表异常: " + e.getMessage(), e); + } + } + + /** + * 删除文档 + */ + public void deleteDocument(String datasetId, String documentId) { + String url = difyConfig.getFullApiUrl("/datasets/" + datasetId + "/documents/" + documentId); + + try { + Request httpRequest = new Request.Builder() + .url(url) + .header("Authorization", "Bearer " + getKnowledgeApiKey()) + .delete() + .build(); + + try (Response response = httpClient.newCall(httpRequest).execute()) { + if (!response.isSuccessful()) { + String responseBody = response.body() != null ? response.body().string() : ""; + logger.error("删除文档失败: {} - {}", response.code(), responseBody); + throw new DifyException("删除文档失败: " + responseBody); + } + logger.info("文档删除成功: {}", documentId); + } + } catch (IOException e) { + logger.error("删除文档异常", e); + throw new DifyException("删除文档异常: " + e.getMessage(), e); + } + } + + // ===================== 知识库检索 API ===================== + + /** + * 从知识库检索相关内容 + */ + public RetrievalResponse retrieveFromDataset(String datasetId, RetrievalRequest request) { + String url = difyConfig.getFullApiUrl("/datasets/" + datasetId + "/retrieve"); + + try { + String jsonBody = JSON.toJSONString(request); + Request httpRequest = new Request.Builder() + .url(url) + .header("Authorization", "Bearer " + getKnowledgeApiKey()) + .header("Content-Type", "application/json") + .post(RequestBody.create(jsonBody, MediaType.parse("application/json"))) + .build(); + + try (Response response = httpClient.newCall(httpRequest).execute()) { + String responseBody = response.body() != null ? response.body().string() : ""; + + if (!response.isSuccessful()) { + logger.error("知识库检索失败: {} - {}", response.code(), responseBody); + throw new DifyException("知识库检索失败: " + responseBody); + } + + return JSON.parseObject(responseBody, RetrievalResponse.class); + } + } catch (IOException e) { + logger.error("知识库检索异常", e); + throw new DifyException("知识库检索异常: " + e.getMessage(), e); + } + } + + // ===================== 对话 API ===================== + + /** + * 流式对话(SSE) + */ + public void streamChat(ChatRequest request, String apiKey, StreamCallback callback) { + String url = difyConfig.getFullApiUrl("/chat-messages"); + + try { + // 设置为流式模式 + request.setResponseMode("streaming"); + + String jsonBody = JSON.toJSONString(request); + Request httpRequest = new Request.Builder() + .url(url) + .header("Authorization", "Bearer " + getApiKey(apiKey)) + .header("Content-Type", "application/json") + .post(RequestBody.create(jsonBody, MediaType.parse("application/json"))) + .build(); + + streamHttpClient.newCall(httpRequest).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) throws IOException { + if (!response.isSuccessful()) { + String errorBody = response.body() != null ? response.body().string() : ""; + callback.onError(new DifyException("流式对话失败: " + errorBody)); + return; + } + + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(response.body().byteStream()))) { + + String line; + while ((line = reader.readLine()) != null) { + if (line.startsWith("data: ")) { + String data = line.substring(6).trim(); + + if ("[DONE]".equals(data)) { + callback.onComplete(); + break; + } + + if (!data.isEmpty()) { + // 使用Fastjson2解析SSE数据 + JSONObject jsonNode = JSON.parseObject(data); + String event = jsonNode.containsKey("event") ? jsonNode.getString("event") : ""; + + // 转发所有事件到回调(包含完整数据) + callback.onEvent(event, data); + + switch (event) { + case "message": + case "agent_message": + // 消息内容 + if (jsonNode.containsKey("answer")) { + callback.onMessage(jsonNode.getString("answer")); + } + break; + case "message_end": + // 消息结束,提取元数据 + callback.onMessageEnd(data); + break; + case "error": + // 错误事件 + String errorMsg = jsonNode.containsKey("message") ? + jsonNode.getString("message") : "未知错误"; + callback.onError(new DifyException(errorMsg)); + return; + // 其他事件(workflow_started、node_started、node_finished等) + // 已通过onEvent转发,这里不需要额外处理 + } + } + } + } + // 流正常读取完毕,如果没有收到 [DONE] 标记,也当作完成 + logger.info("SSE流读取完毕"); + callback.onComplete(); + } catch (java.io.EOFException e) { + // EOFException 通常表示流正常结束(Dify可能没有发送[DONE]标记) + logger.info("SSE流已结束(EOF)"); + callback.onComplete(); + } catch (Exception e) { + logger.error("流式响应处理异常", e); + callback.onError(e); + } + } + + @Override + public void onFailure(Call call, IOException e) { + logger.error("流式对话请求失败", e); + callback.onError(e); + } + }); + + } catch (Exception e) { + logger.error("流式对话异常", e); + callback.onError(e); + } + } + + /** + * 阻塞式对话(非流式) + */ + public ChatResponse blockingChat(ChatRequest request, String apiKey) { + String url = difyConfig.getFullApiUrl("/chat-messages"); + + try { + // 设置为阻塞模式 + request.setResponseMode("blocking"); + + String jsonBody = JSON.toJSONString(request); + Request httpRequest = new Request.Builder() + .url(url) + .header("Authorization", "Bearer " + getApiKey(apiKey)) + .header("Content-Type", "application/json") + .post(RequestBody.create(jsonBody, MediaType.parse("application/json"))) + .build(); + + try (Response response = httpClient.newCall(httpRequest).execute()) { + String responseBody = response.body() != null ? response.body().string() : ""; + + if (!response.isSuccessful()) { + logger.error("阻塞式对话失败: {} - {}", response.code(), responseBody); + throw new DifyException("阻塞式对话失败: " + responseBody); + } + + return JSON.parseObject(responseBody, ChatResponse.class); + } + } catch (IOException e) { + logger.error("阻塞式对话异常", e); + throw new DifyException("阻塞式对话异常: " + e.getMessage(), e); + } + } + + /** + * 停止对话生成 + */ + public void stopChatMessage(String taskId, String userId, String apiKey) { + String url = difyConfig.getFullApiUrl("/chat-messages/" + taskId + "/stop"); + + try { + String jsonBody = JSON.toJSONString(new StopRequest(userId)); + Request httpRequest = new Request.Builder() + .url(url) + .header("Authorization", "Bearer " + getApiKey(apiKey)) + .header("Content-Type", "application/json") + .post(RequestBody.create(jsonBody, MediaType.parse("application/json"))) + .build(); + + try (Response response = httpClient.newCall(httpRequest).execute()) { + if (!response.isSuccessful()) { + String responseBody = response.body() != null ? response.body().string() : ""; + logger.error("停止对话失败: {} - {}", response.code(), responseBody); + throw new DifyException("停止对话失败: " + responseBody); + } + logger.info("对话停止成功: {}", taskId); + } + } catch (IOException e) { + logger.error("停止对话异常", e); + throw new DifyException("停止对话异常: " + e.getMessage(), e); + } + } + + /** + * 提交消息反馈 + * @param messageId Dify消息ID + * @param rating 评分(like/dislike/null) + * @param userId 用户ID + * @param apiKey API密钥 + */ + public void submitMessageFeedback(String messageId, String rating, String userId, String feedback, String apiKey) { + String url = difyConfig.getFullApiUrl("/messages/" + messageId + "/feedbacks"); + + try { + FeedbackRequest feedbackRequest = new FeedbackRequest(rating, userId, feedback); + String jsonBody = JSON.toJSONString(feedbackRequest); + + Request httpRequest = new Request.Builder() + .url(url) + .header("Authorization", "Bearer " + getApiKey(apiKey)) + .header("Content-Type", "application/json") + .post(RequestBody.create(jsonBody, MediaType.parse("application/json"))) + .build(); + + try (Response response = httpClient.newCall(httpRequest).execute()) { + String responseBody = response.body() != null ? response.body().string() : ""; + + if (!response.isSuccessful()) { + logger.error("提交消息反馈失败: {} - {}", response.code(), responseBody); + throw new DifyException("提交消息反馈失败: " + responseBody); + } + if (responseBody!="success") { + logger.error("提交消息反馈失败: {} - {}", response.code(), responseBody); + throw new DifyException("提交消息反馈失败: " + responseBody); + } + logger.info("消息反馈提交成功: {} - {}", messageId, rating); + } + } catch (IOException e) { + logger.error("提交消息反馈异常", e); + throw new DifyException("提交消息反馈异常: " + e.getMessage(), e); + } + } + + // ===================== 对话历史 API ===================== + + /** + * 获取对话历史消息 + */ + public MessageHistoryResponse getMessageHistory( + String conversationId, + String userId, + String firstId, + Integer limit, + String apiKey) { + + StringBuilder urlBuilder = new StringBuilder(difyConfig.getFullApiUrl("/messages")); + urlBuilder.append("?user=").append(userId); + + if (conversationId != null && !conversationId.isEmpty()) { + urlBuilder.append("&conversation_id=").append(conversationId); + } + if (firstId != null && !firstId.isEmpty()) { + urlBuilder.append("&first_id=").append(firstId); + } + if (limit != null) { + urlBuilder.append("&limit=").append(limit); + } + + try { + Request httpRequest = new Request.Builder() + .url(urlBuilder.toString()) + .header("Authorization", "Bearer " + getApiKey(apiKey)) + .get() + .build(); + + try (Response response = httpClient.newCall(httpRequest).execute()) { + String responseBody = response.body() != null ? response.body().string() : ""; + + if (!response.isSuccessful()) { + logger.error("获取对话历史失败: {} - {}", response.code(), responseBody); + throw new DifyException("获取对话历史失败: " + responseBody); + } + + return JSON.parseObject(responseBody, MessageHistoryResponse.class); + } + } catch (IOException e) { + logger.error("获取对话历史异常", e); + throw new DifyException("获取对话历史异常: " + e.getMessage(), e); + } + } + + /** + * 获取对话列表 + */ + public ConversationListResponse getConversations( + String userId, + String lastId, + Integer limit, + String apiKey) { + + StringBuilder urlBuilder = new StringBuilder(difyConfig.getFullApiUrl("/conversations")); + urlBuilder.append("?user=").append(userId); + + if (lastId != null && !lastId.isEmpty()) { + urlBuilder.append("&last_id=").append(lastId); + } + if (limit != null) { + urlBuilder.append("&limit=").append(limit); + } + + try { + Request httpRequest = new Request.Builder() + .url(urlBuilder.toString()) + .header("Authorization", "Bearer " + getApiKey(apiKey)) + .get() + .build(); + + try (Response response = httpClient.newCall(httpRequest).execute()) { + String responseBody = response.body() != null ? response.body().string() : ""; + + if (!response.isSuccessful()) { + logger.error("获取对话列表失败: {} - {}", response.code(), responseBody); + throw new DifyException("获取对话列表失败: " + responseBody); + } + + return JSON.parseObject(responseBody, ConversationListResponse.class); + } + } catch (IOException e) { + logger.error("获取对话列表异常", e); + throw new DifyException("获取对话列表异常: " + e.getMessage(), e); + } + } + + // ===================== 通用 HTTP 方法(用于代理转发)===================== + + /** + * 通用 GET 请求 + * @param path API路径 + * @param apiKey API密钥(为null时使用知识库API Key) + * @return JSON响应字符串 + */ + public String get(String path, String apiKey) { + String url = difyConfig.getFullApiUrl(path); + + try { + // 如果apiKey为null,使用知识库API Key(因为通用方法主要用于知识库相关操作) + String actualApiKey = apiKey != null ? apiKey : getKnowledgeApiKey(); + + Request httpRequest = new Request.Builder() + .url(url) + .header("Authorization", "Bearer " + actualApiKey) + .get() + .build(); + + try (Response response = httpClient.newCall(httpRequest).execute()) { + String responseBody = response.body() != null ? response.body().string() : ""; + + if (!response.isSuccessful()) { + logger.error("GET请求失败: {} - {} - {}", url, response.code(), responseBody); + throw new DifyException("GET请求失败[" + response.code() + "]: " + responseBody); + } + + return responseBody; + } + } catch (IOException e) { + logger.error("GET请求异常: {}", url, e); + throw new DifyException("GET请求异常: " + e.getMessage(), e); + } + } + + /** + * 通用 POST 请求 + * @param path API路径 + * @param requestBody 请求体(JSON字符串或Map) + * @param apiKey API密钥(为null时使用知识库API Key) + * @return JSON响应字符串 + */ + public String post(String path, Object requestBody, String apiKey) { + String url = difyConfig.getFullApiUrl(path); + + try { + // 如果apiKey为null,使用知识库API Key + String actualApiKey = apiKey != null ? apiKey : getKnowledgeApiKey(); + + String jsonBody = requestBody instanceof String ? + (String) requestBody : JSON.toJSONString(requestBody); + + Request httpRequest = new Request.Builder() + .url(url) + .header("Authorization", "Bearer " + actualApiKey) + .header("Content-Type", "application/json") + .post(RequestBody.create(jsonBody, MediaType.parse("application/json"))) + .build(); + + try (Response response = httpClient.newCall(httpRequest).execute()) { + String responseBody = response.body() != null ? response.body().string() : ""; + + if (!response.isSuccessful()) { + logger.error("POST请求失败: {} - {} - {}", url, response.code(), responseBody); + throw new DifyException("POST请求失败[" + response.code() + "]: " + responseBody); + } + + return responseBody; + } + } catch (IOException e) { + logger.error("POST请求异常: {}", url, e); + throw new DifyException("POST请求异常: " + e.getMessage(), e); + } + } + + /** + * 通用 PATCH 请求 + * @param path API路径 + * @param requestBody 请求体(JSON字符串或Map) + * @param apiKey API密钥(为null时使用知识库API Key) + * @return JSON响应字符串 + */ + public String patch(String path, Object requestBody, String apiKey) { + String url = difyConfig.getFullApiUrl(path); + + try { + // 如果apiKey为null,使用知识库API Key + String actualApiKey = apiKey != null ? apiKey : getKnowledgeApiKey(); + + String jsonBody = requestBody instanceof String ? + (String) requestBody : JSON.toJSONString(requestBody); + + Request httpRequest = new Request.Builder() + .url(url) + .header("Authorization", "Bearer " + actualApiKey) + .header("Content-Type", "application/json") + .patch(RequestBody.create(jsonBody, MediaType.parse("application/json"))) + .build(); + + try (Response response = httpClient.newCall(httpRequest).execute()) { + String responseBody = response.body() != null ? response.body().string() : ""; + + if (!response.isSuccessful()) { + logger.error("PATCH请求失败: {} - {} - {}", url, response.code(), responseBody); + throw new DifyException("PATCH请求失败[" + response.code() + "]: " + responseBody); + } + + return responseBody; + } + } catch (IOException e) { + logger.error("PATCH请求异常: {}", url, e); + throw new DifyException("PATCH请求异常: " + e.getMessage(), e); + } + } + + /** + * 通用 DELETE 请求 + * @param path API路径 + * @param apiKey API密钥(为null时使用知识库API Key) + * @return JSON响应字符串 + */ + public String delete(String path, String apiKey) { + String url = difyConfig.getFullApiUrl(path); + + try { + // 如果apiKey为null,使用知识库API Key + String actualApiKey = apiKey != null ? apiKey : getKnowledgeApiKey(); + + Request httpRequest = new Request.Builder() + .url(url) + .header("Authorization", "Bearer " + actualApiKey) + .delete() + .build(); + + try (Response response = httpClient.newCall(httpRequest).execute()) { + String responseBody = response.body() != null ? response.body().string() : ""; + + if (!response.isSuccessful()) { + logger.error("DELETE请求失败: {} - {} - {}", url, response.code(), responseBody); + throw new DifyException("DELETE请求失败[" + response.code() + "]: " + responseBody); + } + + return responseBody; + } + } catch (IOException e) { + logger.error("DELETE请求异常: {}", url, e); + throw new DifyException("DELETE请求异常: " + e.getMessage(), e); + } + } + + // ===================== 工具方法 ===================== + + /** + * 获取API密钥(优先使用传入的密钥,否则使用配置的默认密钥) + * 用于智能体相关的API + */ + private String getApiKey(String apiKey) { + if (apiKey != null && !apiKey.trim().isEmpty()) { + return apiKey; + } + throw new DifyException("未配置Dify API密钥"); + } + + /** + * 获取知识库API密钥(统一使用配置中的knowledgeApiKey) + * 用于知识库相关的API + */ + private String getKnowledgeApiKey() { + if (difyConfig.getKnowledgeApiKey() != null && !difyConfig.getKnowledgeApiKey().trim().isEmpty()) { + return difyConfig.getKnowledgeApiKey(); + } + throw new DifyException("未配置Dify知识库API密钥"); + } + + /** + * 停止请求的内部类 + */ + private static class StopRequest { + private String user; + + public StopRequest(String user) { + this.user = user; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + } + + /** + * 反馈请求的内部类 + */ + private static class FeedbackRequest { + private String rating; + private String user; + + private String feedback; + + public FeedbackRequest(String rating, String user, String feedback) { + this.rating = rating; + this.user = user; + this.feedback = feedback; + } + + public String getRating() { + return rating; + } + + public void setRating(String rating) { + this.rating = rating; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public String getFeedback() { + return feedback; + } + + public void setFeedback(String feedback) { + this.feedback = feedback; + } + } + + // ===================== 模型管理 API ===================== + + /** + * 获取可用的嵌入模型列表 + */ + public EmbeddingModelResponse getAvailableEmbeddingModels() { + String url = difyConfig.getFullApiUrl("/workspaces/current/models/model-types/text-embedding"); + + try { + Request httpRequest = new Request.Builder() + .url(url) + .header("Authorization", "Bearer " + getKnowledgeApiKey()) + .get() + .build(); + + try (Response response = httpClient.newCall(httpRequest).execute()) { + String responseBody = response.body() != null ? response.body().string() : ""; + + if (!response.isSuccessful()) { + logger.error("获取嵌入模型列表失败: {} - {}", response.code(), responseBody); + throw new DifyException("获取嵌入模型列表失败: " + responseBody); + } + + return JSON.parseObject(responseBody, EmbeddingModelResponse.class); + } + } catch (IOException e) { + logger.error("获取嵌入模型列表异常", e); + throw new DifyException("获取嵌入模型列表异常: " + e.getMessage(), e); + } + } + + /** + * 获取可用的Rerank模型列表 + */ + public RerankModelResponse getAvailableRerankModels() { + String url = difyConfig.getFullApiUrl("/workspaces/current/models/model-types/rerank"); + + try { + Request httpRequest = new Request.Builder() + .url(url) + .header("Authorization", "Bearer " + getKnowledgeApiKey()) + .get() + .build(); + + try (Response response = httpClient.newCall(httpRequest).execute()) { + String responseBody = response.body() != null ? response.body().string() : ""; + + if (!response.isSuccessful()) { + logger.error("获取Rerank模型列表失败: {} - {}", response.code(), responseBody); + throw new DifyException("获取Rerank模型列表失败: " + responseBody); + } + + return JSON.parseObject(responseBody, RerankModelResponse.class); + } + } catch (IOException e) { + logger.error("获取Rerank模型列表异常", e); + throw new DifyException("获取Rerank模型列表异常: " + e.getMessage(), e); + } + } +} + diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/callback/StreamCallback.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/callback/StreamCallback.java new file mode 100644 index 00000000..f2c47744 --- /dev/null +++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/callback/StreamCallback.java @@ -0,0 +1,44 @@ +package org.xyzh.ai.client.callback; + +/** + * @description 流式响应回调接口 + * @filename StreamCallback.java + * @author AI Assistant + * @copyright xyzh + * @since 2025-11-04 + */ +public interface StreamCallback { + + /** + * 接收到消息片段 + * @param message 消息内容 + */ + void onMessage(String message); + + /** + * 消息结束(包含元数据) + * @param metadata JSON格式的元数据 + */ + void onMessageEnd(String metadata); + + /** + * 接收到Dify原始事件(用于转发完整事件数据) + * @param eventType 事件类型(如workflow_started、node_started等) + * @param eventData 完整的事件JSON数据 + */ + default void onEvent(String eventType, String eventData) { + // 默认实现:不处理 + } + + /** + * 流式响应完成 + */ + void onComplete(); + + /** + * 发生错误 + * @param error 错误对象 + */ + void onError(Throwable error); +} + diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/ChatRequest.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/ChatRequest.java new file mode 100644 index 00000000..9a685d39 --- /dev/null +++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/ChatRequest.java @@ -0,0 +1,75 @@ +package org.xyzh.ai.client.dto; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; +import java.util.List; +import java.util.Map; +import org.xyzh.api.ai.dto.DifyFileInfo; + +/** + * @description 对话请求 + * @filename ChatRequest.java + * @author AI Assistant + * @copyright xyzh + * @since 2025-12-15 + */ +@Data +public class ChatRequest { + + /** + * 输入变量 + */ + private Map inputs; + + /** + * 用户问题 + */ + private String query; + + /** + * 响应模式:streaming(流式)、blocking(阻塞) + */ + @JSONField(name = "response_mode") + private String responseMode = "streaming"; + + /** + * 对话ID(继续对话时传入) + */ + @JSONField(name = "conversation_id") + private String conversationId; + + /** + * 用户标识 + */ + private String user; + + /** + * 上传的文件列表 + */ + private List files; + + /** + * 自动生成标题 + */ + @JSONField(name = "auto_generate_name") + private Boolean autoGenerateName = true; + + /** + * 指定的数据集ID列表(知识库检索) + */ + @JSONField(name = "dataset_ids") + private List datasetIds; + + /** + * 温度参数(0.0-1.0) + */ + private Double temperature; + + /** + * 最大token数 + */ + @JSONField(name = "max_tokens") + private Integer maxTokens; + +} + diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/ChatResponse.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/ChatResponse.java new file mode 100644 index 00000000..8b166668 --- /dev/null +++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/ChatResponse.java @@ -0,0 +1,121 @@ +package org.xyzh.ai.client.dto; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; +import java.util.List; +import java.util.Map; + +/** + * @description 对话响应(阻塞模式) + * @filename ChatResponse.java + * @author AI Assistant + * @copyright xyzh + * @since 2025-11-04 + */ +@Data +public class ChatResponse { + + /** + * 消息ID + */ + @JSONField(name = "message_id") + private String messageId; + + /** + * 对话ID + */ + @JSONField(name = "conversation_id") + private String conversationId; + + /** + * 模式 + */ + private String mode; + + /** + * 回答内容 + */ + private String answer; + + /** + * 元数据 + */ + private Map metadata; + + /** + * 创建时间 + */ + @JSONField(name = "created_at") + private Long createdAt; + + /** + * Token使用情况 + */ + private Usage usage; + + /** + * 检索信息 + */ + @JSONField(name = "retrieval_info") + private List retrievalInfo; + + @Data + public static class Usage { + @JSONField(name = "prompt_tokens") + private Integer promptTokens; + + @JSONField(name = "prompt_unit_price") + private String promptUnitPrice; + + @JSONField(name = "prompt_price_unit") + private String promptPriceUnit; + + @JSONField(name = "prompt_price") + private String promptPrice; + + @JSONField(name = "completion_tokens") + private Integer completionTokens; + + @JSONField(name = "completion_unit_price") + private String completionUnitPrice; + + @JSONField(name = "completion_price_unit") + private String completionPriceUnit; + + @JSONField(name = "completion_price") + private String completionPrice; + + @JSONField(name = "total_tokens") + private Integer totalTokens; + + @JSONField(name = "total_price") + private String totalPrice; + + private String currency; + + private Double latency; + } + + @Data + public static class RetrievalInfo { + @JSONField(name = "dataset_id") + private String datasetId; + + @JSONField(name = "dataset_name") + private String datasetName; + + @JSONField(name = "document_id") + private String documentId; + + @JSONField(name = "document_name") + private String documentName; + + @JSONField(name = "segment_id") + private String segmentId; + + private Double score; + + private String content; + } +} + diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/ConversationListResponse.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/ConversationListResponse.java new file mode 100644 index 00000000..f177cfd7 --- /dev/null +++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/ConversationListResponse.java @@ -0,0 +1,50 @@ +package org.xyzh.ai.client.dto; + +import lombok.Data; +import java.util.List; + +import com.alibaba.fastjson2.annotation.JSONField; + +/** + * @description 对话列表响应 + * @filename ConversationListResponse.java + * @author AI Assistant + * @copyright xyzh + * @since 2025-11-04 + */ +@Data +public class ConversationListResponse { + + private Integer limit; + + @JSONField(name = "has_more") + private Boolean hasMore; + + private List data; + + @Data + public static class ConversationInfo { + private String id; + + private String name; + + private List inputs; + + private String status; + + private String introduction; + + @JSONField(name = "created_at") + private Long createdAt; + + @JSONField(name = "updated_at") + private Long updatedAt; + } + + @Data + public static class InputInfo { + private String key; + private String value; + } +} + diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DatasetCreateRequest.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DatasetCreateRequest.java new file mode 100644 index 00000000..c408cbf3 --- /dev/null +++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DatasetCreateRequest.java @@ -0,0 +1,55 @@ +package org.xyzh.ai.client.dto; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +/** + * @description 创建知识库请求 + * @filename DatasetCreateRequest.java + * @author AI Assistant + * @copyright xyzh + * @since 2025-11-04 + */ +@Data +public class DatasetCreateRequest { + + /** + * 知识库名称 + */ + private String name; + + /** + * 知识库描述 + */ + private String description; + + /** + * 索引方式:high_quality(高质量)、economy(经济) + */ + @JSONField(name = "indexing_technique") + private String indexingTechnique = "high_quality"; + + /** + * Embedding模型 + */ + @JSONField(name = "embedding_model") + private String embeddingModel; + + /** + * Embedding模型提供商 + */ + @JSONField(name = "embedding_model_provider") + private String embeddingModelProvider; + + /** + * 检索模型配置(包含 Rerank、Top K、Score 阈值等) + */ + @JSONField(name = "retrieval_model") + private RetrievalModel retrievalModel; + + /** + * 权限:only_me(仅自己)、all_team_members(团队所有成员) + */ + private String permission = "only_me"; +} + diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DatasetCreateResponse.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DatasetCreateResponse.java new file mode 100644 index 00000000..8f3b3f40 --- /dev/null +++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DatasetCreateResponse.java @@ -0,0 +1,55 @@ +package org.xyzh.ai.client.dto; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +/** + * @description 创建知识库响应 + * @filename DatasetCreateResponse.java + * @author AI Assistant + * @copyright xyzh + * @since 2025-11-04 + */ +@Data +public class DatasetCreateResponse { + + /** + * 知识库ID + */ + private String id; + + /** + * 知识库名称 + */ + private String name; + + /** + * 描述 + */ + private String description; + + /** + * 索引方式 + */ + @JSONField(name = "indexing_technique") + private String indexingTechnique; + + /** + * Embedding模型 + */ + @JSONField(name = "embedding_model") + private String embeddingModel; + + /** + * 创建时间 + */ + @JSONField(name = "created_at") + private Long createdAt; + + /** + * 创建人 + */ + @JSONField(name = "created_by") + private String createdBy; +} + diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DatasetDetailResponse.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DatasetDetailResponse.java new file mode 100644 index 00000000..33c2f947 --- /dev/null +++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DatasetDetailResponse.java @@ -0,0 +1,82 @@ +package org.xyzh.ai.client.dto; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +/** + * @description 知识库详情响应 + * @filename DatasetDetailResponse.java + * @author AI Assistant + * @copyright xyzh + * @since 2025-11-04 + */ +@Data +public class DatasetDetailResponse { + + private String id; + + private String name; + + private String description; + + @JSONField(name = "indexing_technique") + private String indexingTechnique; + + @JSONField(name = "embedding_model") + private String embeddingModel; + + @JSONField(name = "embedding_model_provider") + private String embeddingModelProvider; + + @JSONField(name = "embedding_available") + private Boolean embeddingAvailable; + + @JSONField(name = "retrieval_model_dict") + private RetrievalModelDict retrievalModelDict; + + @JSONField(name = "document_count") + private Integer documentCount; + + @JSONField(name = "word_count") + private Integer wordCount; + + @JSONField(name = "app_count") + private Integer appCount; + + @JSONField(name = "created_by") + private String createdBy; + + @JSONField(name = "created_at") + private Long createdAt; + + @JSONField(name = "updated_at") + private Long updatedAt; + + @Data + public static class RetrievalModelDict { + @JSONField(name = "search_method") + private String searchMethod; + + @JSONField(name = "reranking_enable") + private Boolean rerankingEnable; + + @JSONField(name = "reranking_model") + private RerankingModel rerankingModel; + + @JSONField(name = "top_k") + private Integer topK; + + @JSONField(name = "score_threshold_enabled") + private Boolean scoreThresholdEnabled; + } + + @Data + public static class RerankingModel { + @JSONField(name = "reranking_provider_name") + private String rerankingProviderName; + + @JSONField(name = "reranking_model_name") + private String rerankingModelName; + } +} + diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DatasetListResponse.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DatasetListResponse.java new file mode 100644 index 00000000..6ef4f821 --- /dev/null +++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DatasetListResponse.java @@ -0,0 +1,61 @@ +package org.xyzh.ai.client.dto; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; +import java.util.List; + +/** + * @description 知识库列表响应 + * @filename DatasetListResponse.java + * @author AI Assistant + * @copyright xyzh + * @since 2025-11-04 + */ +@Data +public class DatasetListResponse { + + /** + * 知识库列表 + */ + private List data; + + /** + * 是否有更多 + */ + @JSONField(name = "has_more") + private Boolean hasMore; + + /** + * 分页限制 + */ + private Integer limit; + + /** + * 总数 + */ + private Integer total; + + /** + * 当前页 + */ + private Integer page; + + @Data + public static class DatasetInfo { + private String id; + private String name; + private String description; + private String permission; + @JSONField(name = "document_count") + private Integer documentCount; + @JSONField(name = "word_count") + private Integer wordCount; + @JSONField(name = "created_by") + private String createdBy; + @JSONField(name = "created_at") + private Long createdAt; + @JSONField(name = "updated_at") + private Long updatedAt; + } +} + diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DatasetUpdateRequest.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DatasetUpdateRequest.java new file mode 100644 index 00000000..cd639539 --- /dev/null +++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DatasetUpdateRequest.java @@ -0,0 +1,50 @@ +package org.xyzh.ai.client.dto; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +/** + * @description Dify知识库更新请求 + * @filename DatasetUpdateRequest.java + * @author AI Assistant + * @copyright xyzh + * @since 2025-11-04 + */ +@Data +public class DatasetUpdateRequest { + + /** + * 知识库名称 + */ + private String name; + + /** + * 知识库描述 + */ + private String description; + + /** + * 索引方式(high_quality/economy) + */ + @JSONField(name = "indexing_technique") + private String indexingTechnique; + + /** + * Embedding模型 + */ + @JSONField(name = "embedding_model") + private String embeddingModel; + + /** + * Embedding模型提供商 + */ + @JSONField(name = "embedding_model_provider") + private String embeddingModelProvider; + + /** + * 检索模型配置(包含 Rerank、Top K、Score 阈值等) + */ + @JSONField(name = "retrieval_model") + private RetrievalModel retrievalModel; +} + diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DocumentListResponse.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DocumentListResponse.java new file mode 100644 index 00000000..cb4c9c5a --- /dev/null +++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DocumentListResponse.java @@ -0,0 +1,96 @@ +package org.xyzh.ai.client.dto; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +import java.util.List; + +/** + * @description Dify文档列表响应 + * @filename DocumentListResponse.java + * @author AI Assistant + * @copyright xyzh + * @since 2025-11-07 + */ +@Data +public class DocumentListResponse { + + private List data; + + @JSONField(name = "has_more") + private Boolean hasMore; + + private Integer limit; + + private Integer total; + + private Integer page; + + /** + * 文档信息 + */ + @Data + public static class Document { + private String id; + + private Integer position; + + @JSONField(name = "data_source_type") + private String dataSourceType; + + @JSONField(name = "data_source_info") + private DataSourceInfo dataSourceInfo; + + @JSONField(name = "dataset_process_rule_id") + private String datasetProcessRuleId; + + private String name; + + @JSONField(name = "created_from") + private String createdFrom; + + @JSONField(name = "created_by") + private String createdBy; + + @JSONField(name = "created_at") + private Long createdAt; + + private Integer tokens; + + @JSONField(name = "indexing_status") + private String indexingStatus; + + private String error; + + private Boolean enabled; + + @JSONField(name = "disabled_at") + private Long disabledAt; + + @JSONField(name = "disabled_by") + private String disabledBy; + + private Boolean archived; + + @JSONField(name = "display_status") + private String displayStatus; + + @JSONField(name = "word_count") + private Integer wordCount; + + @JSONField(name = "hit_count") + private Integer hitCount; + + @JSONField(name = "doc_form") + private String docForm; + } + + /** + * 数据源信息 + */ + @Data + public static class DataSourceInfo { + @JSONField(name = "upload_file_id") + private String uploadFileId; + } +} diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DocumentStatusResponse.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DocumentStatusResponse.java new file mode 100644 index 00000000..fa8ac9c2 --- /dev/null +++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DocumentStatusResponse.java @@ -0,0 +1,95 @@ +package org.xyzh.ai.client.dto; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; +import java.util.List; + +/** + * @description 文档处理状态响应 + * @filename DocumentStatusResponse.java + * @author AI Assistant + * @copyright xyzh + * @since 2025-11-04 + */ +@Data +public class DocumentStatusResponse { + + /** + * 文档列表 + */ + private List data; + + @Data + public static class DocumentStatus { + /** + * 文档ID + */ + private String id; + + /** + * 索引状态:waiting、parsing、cleaning、splitting、indexing、completed、error + */ + @JSONField(name = "indexing_status") + private String indexingStatus; + + /** + * 处理开始时间 + */ + @JSONField(name = "processing_started_at") + private Long processingStartedAt; + + /** + * 解析完成时间 + */ + @JSONField(name = "parsing_completed_at") + private Long parsingCompletedAt; + + /** + * 清洗完成时间 + */ + @JSONField(name = "cleaning_completed_at") + private Long cleaningCompletedAt; + + /** + * 分割完成时间 + */ + @JSONField(name = "splitting_completed_at") + private Long splittingCompletedAt; + + /** + * 完成时间 + */ + @JSONField(name = "completed_at") + private Long completedAt; + + /** + * 暂停时间 + */ + @JSONField(name = "paused_at") + private Long pausedAt; + + /** + * 错误信息 + */ + private String error; + + /** + * 停止时间 + */ + @JSONField(name = "stopped_at") + private Long stoppedAt; + + /** + * 分段数量 + */ + @JSONField(name = "completed_segments") + private Integer completedSegments; + + /** + * 总分段数 + */ + @JSONField(name = "total_segments") + private Integer totalSegments; + } +} + diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DocumentUploadRequest.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DocumentUploadRequest.java new file mode 100644 index 00000000..aca27ee9 --- /dev/null +++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DocumentUploadRequest.java @@ -0,0 +1,95 @@ +package org.xyzh.ai.client.dto; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +/** + * @description 文档上传请求 + * @filename DocumentUploadRequest.java + * @author AI Assistant + * @copyright xyzh + * @since 2025-11-04 + */ +@Data +public class DocumentUploadRequest { + + /** + * 文档名称 + */ + private String name; + + /** + * 索引方式 + */ + @JSONField(name = "indexing_technique") + private String indexingTechnique; + + /** + * 处理规则 + */ + @JSONField(name = "process_rule") + private ProcessRule processRule; + + @Data + public static class ProcessRule { + /** + * 分段模式:automatic(自动)、custom(自定义) + */ + private String mode = "automatic"; + + /** + * 预处理规则 + */ + private Rules rules; + + @Data + public static class Rules { + /** + * 自动分段配置 + */ + @JSONField(name = "pre_processing_rules") + private PreProcessingRules preProcessingRules; + + /** + * 分段配置 + */ + private Segmentation segmentation; + } + + @Data + public static class PreProcessingRules { + /** + * 移除额外空格 + */ + @JSONField(name = "remove_extra_spaces") + private Boolean removeExtraSpaces = true; + + /** + * 移除URL和邮箱 + */ + @JSONField(name = "remove_urls_emails") + private Boolean removeUrlsEmails = false; + } + + @Data + public static class Segmentation { + /** + * 分隔符 + */ + private String separator = "\\n"; + + /** + * 最大分段长度 + */ + @JSONField(name = "max_tokens") + private Integer maxTokens = 1000; + + /** + * 分段重叠长度 + */ + @JSONField(name = "chunk_overlap") + private Integer chunkOverlap = 50; + } + } +} + diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DocumentUploadResponse.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DocumentUploadResponse.java new file mode 100644 index 00000000..b46e4bc4 --- /dev/null +++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/DocumentUploadResponse.java @@ -0,0 +1,145 @@ +package org.xyzh.ai.client.dto; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +/** + * @description 文档上传响应(根据 Dify API 返回结构) + * @filename DocumentUploadResponse.java + * @author AI Assistant + * @copyright xyzh + * @since 2025-11-04 + */ +@Data +public class DocumentUploadResponse { + + /** + * 文档详细信息 + */ + private Document document; + + /** + * 批次ID(用于查询处理状态) + */ + private String batch; + + /** + * 文档详细信息 + */ + @Data + public static class Document { + /** + * 文档ID + */ + private String id; + + /** + * 文档名称 + */ + private String name; + + /** + * 位置(序号) + */ + private Integer position; + + /** + * 数据源类型 + */ + @JSONField(name = "data_source_type") + private String dataSourceType; + + /** + * 数据源信息 + */ + @JSONField(name = "data_source_info") + private Object dataSourceInfo; + + /** + * 数据集处理规则ID + */ + @JSONField(name = "dataset_process_rule_id") + private String datasetProcessRuleId; + + /** + * 创建来源 + */ + @JSONField(name = "created_from") + private String createdFrom; + + /** + * 创建人 + */ + @JSONField(name = "created_by") + private String createdBy; + + /** + * 创建时间(时间戳) + */ + @JSONField(name = "created_at") + private Long createdAt; + + /** + * Token数量 + */ + private Integer tokens; + + /** + * 索引状态 + */ + @JSONField(name = "indexing_status") + private String indexingStatus; + + /** + * 错误信息 + */ + private String error; + + /** + * 是否启用 + */ + private Boolean enabled; + + /** + * 禁用时间 + */ + @JSONField(name = "disabled_at") + private Long disabledAt; + + /** + * 禁用人 + */ + @JSONField(name = "disabled_by") + private String disabledBy; + + /** + * 是否归档 + */ + private Boolean archived; + + /** + * 显示状态 + */ + @JSONField(name = "display_status") + private String displayStatus; + + /** + * 字数 + */ + @JSONField(name = "word_count") + private Integer wordCount; + + /** + * 命中次数 + */ + @JSONField(name = "hit_count") + private Integer hitCount; + + /** + * 文档形式 + */ + @JSONField(name = "doc_form") + private String docForm; + } +} + diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/EmbeddingModelResponse.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/EmbeddingModelResponse.java new file mode 100644 index 00000000..0b1ebdaa --- /dev/null +++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/EmbeddingModelResponse.java @@ -0,0 +1,144 @@ +package org.xyzh.ai.client.dto; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +/** + * @description Dify嵌入模型响应 + * @filename EmbeddingModelResponse.java + * @author AI Assistant + * @since 2025-11-06 + */ +@Data +public class EmbeddingModelResponse { + + /** + * 模型提供商列表 + */ + @JSONField(name = "data") + private List data; + + /** + * 模型提供商 + */ + @Data + public static class ModelProvider { + /** + * 提供商标识 + */ + @JSONField(name = "provider") + private String provider; + + /** + * 提供商标签 + */ + @JSONField(name = "label") + private Map label; + + /** + * 小图标 + */ + @JSONField(name = "icon_small") + private Map iconSmall; + + /** + * 大图标 + */ + @JSONField(name = "icon_large") + private Map iconLarge; + + /** + * 状态 + */ + @JSONField(name = "status") + private String status; + + /** + * 模型列表 + */ + @JSONField(name = "models") + private List models; + } + + /** + * 模型详情 + */ + @Data + public static class Model { + /** + * 模型名称 + */ + @JSONField(name = "model") + private String model; + + /** + * 模型标签 + */ + @JSONField(name = "label") + private Map label; + + /** + * 模型类型 + */ + @JSONField(name = "model_type") + private String modelType; + + /** + * 特性列表 + */ + @JSONField(name = "features") + private List features; + + /** + * 获取来源 + */ + @JSONField(name = "fetch_from") + private String fetchFrom; + + /** + * 模型属性 + */ + @JSONField(name = "model_properties") + private ModelProperties modelProperties; + + /** + * 是否已弃用 + */ + @JSONField(name = "deprecated") + private Boolean deprecated; + + /** + * 状态 + */ + @JSONField(name = "status") + private String status; + + /** + * 是否启用负载均衡 + */ + @JSONField(name = "load_balancing_enabled") + private Boolean loadBalancingEnabled; + } + + /** + * 模型属性 + */ + @Data + public static class ModelProperties { + /** + * 上下文大小 + */ + @JSONField(name = "context_size") + private Integer contextSize; + + /** + * 最大分块数 + */ + @JSONField(name = "max_chunks") + private Integer maxChunks; + } +} + diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/MessageHistoryResponse.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/MessageHistoryResponse.java new file mode 100644 index 00000000..50e5c981 --- /dev/null +++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/MessageHistoryResponse.java @@ -0,0 +1,94 @@ +package org.xyzh.ai.client.dto; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; +import java.util.List; + +/** + * @description 消息历史响应 + * @filename MessageHistoryResponse.java + * @author AI Assistant + * @copyright xyzh + * @since 2025-11-04 + */ +@Data +public class MessageHistoryResponse { + + private Integer limit; + + @JSONField(name = "has_more") + private Boolean hasMore; + + private List data; + + @Data + public static class MessageInfo { + private String id; + + @JSONField(name = "conversation_id") + private String conversationId; + + private List inputs; + + private String query; + + private String answer; + + @JSONField(name = "message_files") + private List messageFiles; + + private Feedback feedback; + + @JSONField(name = "retriever_resources") + private List retrieverResources; + + @JSONField(name = "created_at") + private Long createdAt; + + @JSONField(name = "agent_thoughts") + private List agentThoughts; + } + + @Data + public static class MessageContent { + private String key; + private String value; + } + + @Data + public static class MessageFile { + private String id; + private String type; + private String url; + @JSONField(name = "belongs_to") + private String belongsTo; + } + + @Data + public static class Feedback { + private String rating; + } + + @Data + public static class RetrieverResource { + @JSONField(name = "dataset_id") + private String datasetId; + + @JSONField(name = "dataset_name") + private String datasetName; + + @JSONField(name = "document_id") + private String documentId; + + @JSONField(name = "document_name") + private String documentName; + + @JSONField(name = "segment_id") + private String segmentId; + + private Double score; + + private String content; + } +} + diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/RerankModelResponse.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/RerankModelResponse.java new file mode 100644 index 00000000..d1124bc6 --- /dev/null +++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/RerankModelResponse.java @@ -0,0 +1,56 @@ +package org.xyzh.ai.client.dto; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +/** + * @description Dify Rerank模型响应 + * @filename RerankModelResponse.java + * @author AI Assistant + * @copyright xyzh + * @since 2025-11-06 + */ +@Data +public class RerankModelResponse { + private List data; + + @Data + public static class ModelProvider { + private String provider; + private Map label; // e.g., {"en_US": "Cohere", "zh_Hans": "Cohere"} + @JSONField(name = "icon_small") + private String iconSmall; + @JSONField(name = "icon_large") + private String iconLarge; + private String status; // e.g., "active" + private List models; + } + + @Data + public static class Model { + private String model; // e.g., "rerank-multilingual-v3.0" + private Map label; + @JSONField(name = "model_type") + private String modelType; // e.g., "rerank" + private List features; + @JSONField(name = "fetch_from") + private String fetchFrom; + @JSONField(name = "model_properties") + private ModelProperties modelProperties; + private Boolean deprecated; + private String status; // e.g., "active" + private String provider; // 模型提供商(可能在 model 数据中) + } + + @Data + public static class ModelProperties { + @JSONField(name = "context_size") + private Integer contextSize; + @JSONField(name = "max_chunks") + private Integer maxChunks; + } +} + diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/RetrievalModel.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/RetrievalModel.java new file mode 100644 index 00000000..cad94eeb --- /dev/null +++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/RetrievalModel.java @@ -0,0 +1,76 @@ +package org.xyzh.ai.client.dto; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +/** + * @description Dify检索模型配置(Retrieval Model) + * @filename RetrievalModel.java + * @author AI Assistant + * @copyright xyzh + * @since 2025-11-06 + */ +@Data +public class RetrievalModel { + + /** + * 搜索方法:vector_search(向量搜索)、full_text_search(全文搜索)、hybrid_search(混合搜索) + */ + @JSONField(name = "search_method") + private String searchMethod; + + /** + * Rerank是否启用 + */ + @JSONField(name = "reranking_enable") + private Boolean rerankingEnable; + + /** + * Rerank模式(字符串,值为 "reranking_model") + */ + @JSONField(name = "reranking_mode") + private String rerankingMode; + + /** + * Rerank模型配置(当 reranking_enable=true 时必须设置) + */ + @JSONField(name = "reranking_model") + private RerankingModel rerankingModel; + + /** + * Top K(返回前K个结果) + */ + @JSONField(name = "top_k") + private Integer topK; + + /** + * 分数阈值(0.00-1.00) + */ + @JSONField(name = "score_threshold") + private Double scoreThreshold; + + /** + * 是否启用分数阈值 + */ + @JSONField(name = "score_threshold_enabled") + private Boolean scoreThresholdEnabled; + + /** + * Rerank模型配置(嵌套对象) + */ + @Data + public static class RerankingModel { + /** + * Rerank模型提供商 + */ + @JSONField(name = "reranking_provider_name") + private String rerankingProviderName; + + /** + * Rerank模型名称 + */ + @JSONField(name = "reranking_model_name") + private String rerankingModelName; + } +} + diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/RetrievalRequest.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/RetrievalRequest.java new file mode 100644 index 00000000..2eee2af4 --- /dev/null +++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/RetrievalRequest.java @@ -0,0 +1,33 @@ +package org.xyzh.ai.client.dto; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +/** + * @description 知识库检索请求 + * @filename RetrievalRequest.java + * @author AI Assistant + * @copyright xyzh + * @since 2025-11-04 + */ +@Data +public class RetrievalRequest { + + /** + * 查询文本 + */ + private String query; + + /** + * 返回的最相关结果数量 + */ + @JSONField(name = "top_k") + private Integer topK = 3; + + /** + * 相似度阈值(0-1) + */ + @JSONField(name = "score_threshold") + private Double scoreThreshold = 0.7; +} + diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/RetrievalResponse.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/RetrievalResponse.java new file mode 100644 index 00000000..e2724cf3 --- /dev/null +++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/client/dto/RetrievalResponse.java @@ -0,0 +1,88 @@ +package org.xyzh.ai.client.dto; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; +import java.util.List; +import java.util.Map; + +/** + * @description 知识库检索响应 + * @filename RetrievalResponse.java + * @author AI Assistant + * @copyright xyzh + * @since 2025-11-04 + */ +@Data +public class RetrievalResponse { + + /** + * 查询ID + */ + @JSONField(name = "query_id") + private String queryId; + + /** + * 检索结果列表 + */ + private List records; + + @Data + public static class RetrievalRecord { + /** + * 分段内容 + */ + private String content; + + /** + * 相似度分数 + */ + private Double score; + + /** + * 标题 + */ + private String title; + + /** + * 元数据 + */ + private Map metadata; + + /** + * 文档ID + */ + @JSONField(name = "document_id") + private String documentId; + + /** + * 文档名称 + */ + @JSONField(name = "document_name") + private String documentName; + + /** + * 分段ID + */ + @JSONField(name = "segment_id") + private String segmentId; + + /** + * 分段位置 + */ + @JSONField(name = "segment_position") + private Integer segmentPosition; + + /** + * 索引节点ID + */ + @JSONField(name = "index_node_id") + private String indexNodeId; + + /** + * 索引节点哈希 + */ + @JSONField(name = "index_node_hash") + private String indexNodeHash; + } +} + diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/config/DifyConfig.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/config/DifyConfig.java new file mode 100644 index 00000000..60adc60d --- /dev/null +++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/config/DifyConfig.java @@ -0,0 +1,170 @@ +package org.xyzh.ai.config; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.xyzh.api.system.service.SysConfigService; + +import jakarta.annotation.PostConstruct; + +/** + * @description Dify配置类 + * @filename DifyConfig.java + * @author AI Assistant + * @copyright xyzh + * @since 2025-11-04 + */ +@Slf4j +@Data +@Configuration +public class DifyConfig { + + @Autowired + private SysConfigService sysConfigService; + + /** + * Dify API基础地址 + */ + private String apiBaseUrl = "http://192.168.130.131/v1"; + + private String knowledgeApiKey="dataset-nupqKP4LONpzdXmGthIrbjeJ"; + + /** + * 请求超时时间(秒) + */ + private Integer timeout = 60; + + /** + * 连接超时时间(秒) + */ + private Integer connectTimeout = 10; + + /** + * 读取超时时间(秒) + */ + private Integer readTimeout = 60; + + /** + * 流式响应超时时间(秒) + */ + private Integer streamTimeout = 300; + + /** + * 上传文件配置 + */ + private Upload upload = new Upload(); + + /** + * 知识库配置 + */ + private Dataset dataset = new Dataset(); + + /** + * 初始化配置,从数据库加载 + */ + @PostConstruct + public void init() { + try { + log.info("开始从数据库加载Dify配置..."); + + // 基础配置 + loadStringConfig("dify.apiBaseUrl", val -> this.apiBaseUrl = val); + loadStringConfig("dify.knowledgeApiKey", val -> this.knowledgeApiKey = val); + loadIntegerConfig("dify.timeout", val -> this.timeout = val); + loadIntegerConfig("dify.connectTimeout", val -> this.connectTimeout = val); + loadIntegerConfig("dify.readTimeout", val -> this.readTimeout = val); + loadIntegerConfig("dify.streamTimeout", val -> this.streamTimeout = val); + + // Upload配置 + loadStringConfig("dify.upload.allowedTypes", val -> { + if (val != null && !val.trim().isEmpty()) { + this.upload.allowedTypes = val.split(","); + } + }); + loadIntegerConfig("dify.upload.maxSize", val -> this.upload.maxSize = val); + + // Dataset配置 + loadStringConfig("dify.dataset.defaultIndexingTechnique", val -> this.dataset.defaultIndexingTechnique = val); + loadStringConfig("dify.dataset.defaultEmbeddingModel", val -> this.dataset.defaultEmbeddingModel = val); + + log.info("Dify配置加载完成 - API地址: {}", apiBaseUrl); + } catch (Exception e) { + log.error("加载Dify配置失败,将使用默认值", e); + } + } + + /** + * 加载字符串配置 + */ + private void loadStringConfig(String key, java.util.function.Consumer setter) { + try { + String value = sysConfigService.getStringConfig(key); + if (value != null && !value.trim().isEmpty()) { + setter.accept(value); + log.debug("加载配置: {} = {}", key, value); + } + } catch (Exception e) { + log.warn("加载配置失败: {}, 错误: {}", key, e.getMessage()); + } + } + + /** + * 加载整数配置 + */ + private void loadIntegerConfig(String key, java.util.function.Consumer setter) { + try { + Integer value = sysConfigService.getIntConfig(key); + if (value != null) { + setter.accept(value); + log.debug("加载配置: {} = {}", key, value); + } + } catch (Exception e) { + log.warn("加载配置失败: {}, 错误: {}", key, e.getMessage()); + } + } + + @Data + public static class Upload { + /** + * 支持的文件类型 + */ + private String[] allowedTypes = {"pdf", "txt", "docx", "doc", "md", "html", "htm"}; + + /** + * 最大文件大小(MB) + */ + private Integer maxSize = 50; + } + + @Data + public static class Dataset { + /** + * 默认索引方式(high_quality/economy) + */ + private String defaultIndexingTechnique = "high_quality"; + + /** + * 默认Embedding模型 + */ + private String defaultEmbeddingModel = "text-embedding-ada-002"; + } + + /** + * 验证配置是否有效 + */ + public boolean isValid() { + return apiBaseUrl != null && !apiBaseUrl.trim().isEmpty(); + } + + /** + * 获取完整的API URL + */ + public String getFullApiUrl(String endpoint) { + String baseUrl = apiBaseUrl.endsWith("/") ? apiBaseUrl.substring(0, apiBaseUrl.length() - 1) : apiBaseUrl; + String path = endpoint.startsWith("/") ? endpoint : "/" + endpoint; + return baseUrl + path; + } +} + diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/exception/DifyException.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/exception/DifyException.java new file mode 100644 index 00000000..45b39e7e --- /dev/null +++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/exception/DifyException.java @@ -0,0 +1,42 @@ +package org.xyzh.ai.exception; + +/** + * @description Dify API调用异常 + * @filename DifyException.java + * @author AI Assistant + * @copyright xyzh + * @since 2025-11-04 + */ +public class DifyException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + private Integer code; + + public DifyException(String message) { + super(message); + } + + public DifyException(Integer code, String message) { + super(message); + this.code = code; + } + + public DifyException(String message, Throwable cause) { + super(message, cause); + } + + public DifyException(Integer code, String message, Throwable cause) { + super(message, cause); + this.code = code; + } + + public Integer getCode() { + return code; + } + + public void setCode(Integer code) { + this.code = code; + } +} + diff --git a/urbanLifelineServ/agent/src/main/resources/application.yml b/urbanLifelineServ/ai/src/main/resources/application.yml similarity index 100% rename from urbanLifelineServ/agent/src/main/resources/application.yml rename to urbanLifelineServ/ai/src/main/resources/application.yml diff --git a/urbanLifelineServ/agent/src/main/resources/log4j2.xml b/urbanLifelineServ/ai/src/main/resources/log4j2.xml similarity index 100% rename from urbanLifelineServ/agent/src/main/resources/log4j2.xml rename to urbanLifelineServ/ai/src/main/resources/log4j2.xml diff --git a/urbanLifelineServ/apis/api-agent/src/main/java/org/xyzh/api/agent/dto/KnowledgeBaseDTO.java b/urbanLifelineServ/apis/api-agent/src/main/java/org/xyzh/api/agent/dto/KnowledgeBaseDTO.java deleted file mode 100644 index 2d08a761..00000000 --- a/urbanLifelineServ/apis/api-agent/src/main/java/org/xyzh/api/agent/dto/KnowledgeBaseDTO.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.xyzh.api.agent.dto; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.xyzh.common.dto.BaseDTO; -import io.swagger.v3.oas.annotations.media.Schema; -import com.fasterxml.jackson.databind.JsonNode; - -/** - * 知识库DTO - * 用于创建和更新知识库 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@Schema(description = "知识库DTO") -public class KnowledgeBaseDTO extends BaseDTO { - private static final long serialVersionUID = 1L; - - @Schema(description = "知识库ID(更新时需要)") - private String knowledgeId; - - @Schema(description = "智能体ID") - private String agentId; - - @Schema(description = "知识库名称") - private String name; - - @Schema(description = "知识库类型:bidding-招投标/customer_service-客服/internal-内部协同") - private String kbType; - - @Schema(description = "访问级别:public-公开/private-私有/internal-内部") - private String accessLevel; - - @Schema(description = "知识库描述") - private String description; - - @Schema(description = "存储路径") - private String storagePath; - - @Schema(description = "当前版本号") - private String version; - - @Schema(description = "知识库配置") - private JsonNode config; - - @Schema(description = "服务类型") - private String serviceType; - - @Schema(description = "状态:active-激活/inactive-停用/archived-归档") - private String status; -} diff --git a/urbanLifelineServ/apis/api-agent/src/main/java/org/xyzh/api/agent/dto/KnowledgeChunkDTO.java b/urbanLifelineServ/apis/api-agent/src/main/java/org/xyzh/api/agent/dto/KnowledgeChunkDTO.java deleted file mode 100644 index 1d3bb4d9..00000000 --- a/urbanLifelineServ/apis/api-agent/src/main/java/org/xyzh/api/agent/dto/KnowledgeChunkDTO.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.xyzh.api.agent.dto; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.xyzh.common.dto.BaseDTO; -import io.swagger.v3.oas.annotations.media.Schema; -import com.fasterxml.jackson.databind.JsonNode; - -/** - * 知识文档片段DTO - * 用于创建和更新知识文档片段 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@Schema(description = "知识文档片段DTO") -public class KnowledgeChunkDTO extends BaseDTO { - private static final long serialVersionUID = 1L; - - @Schema(description = "片段ID(更新时需要)") - private String chunkId; - - @Schema(description = "所属文档ID") - private String docId; - - @Schema(description = "所属知识库ID") - private String knowledgeId; - - @Schema(description = "片段索引") - private Integer chunkIndex; - - @Schema(description = "片段内容") - private String content; - - @Schema(description = "片段类型:text-文本/table-表格/image-图片") - private String chunkType; - - @Schema(description = "根chunk ID") - private String rootChunkId; - - @Schema(description = "是否当前版本", defaultValue = "true") - private Boolean isCurrent; - - @Schema(description = "位置信息") - private JsonNode positionInfo; - - @Schema(description = "片段元数据") - private JsonNode metadata; -} diff --git a/urbanLifelineServ/apis/api-agent/src/main/java/org/xyzh/api/agent/dto/KnowledgeDocumentDTO.java b/urbanLifelineServ/apis/api-agent/src/main/java/org/xyzh/api/agent/dto/KnowledgeDocumentDTO.java deleted file mode 100644 index 86f7ed72..00000000 --- a/urbanLifelineServ/apis/api-agent/src/main/java/org/xyzh/api/agent/dto/KnowledgeDocumentDTO.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.xyzh.api.agent.dto; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.xyzh.common.dto.BaseDTO; -import io.swagger.v3.oas.annotations.media.Schema; -import com.fasterxml.jackson.databind.JsonNode; -import java.util.List; - -/** - * 知识文档DTO - * 用于创建和更新知识文档 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@Schema(description = "知识文档DTO") -public class KnowledgeDocumentDTO extends BaseDTO { - private static final long serialVersionUID = 1L; - - @Schema(description = "文档ID(更新时需要)") - private String docId; - - @Schema(description = "所属知识库ID") - private String knowledgeId; - - @Schema(description = "文档标题") - private String title; - - @Schema(description = "文档类型:text-文本/pdf/word/excel/image/video") - private String docType; - - @Schema(description = "文档分类") - private String category; - - @Schema(description = "文档内容(文本类型)") - private String content; - - @Schema(description = "关联文件ID") - private String fileId; - - @Schema(description = "文件路径") - private String filePath; - - @Schema(description = "文件大小") - private Long fileSize; - - @Schema(description = "MIME类型") - private String mimeType; - - @Schema(description = "根文档ID") - private String rootDocId; - - @Schema(description = "文档标签") - private List tags; - - @Schema(description = "来源URL") - private String sourceUrl; - - @Schema(description = "服务类型") - private String serviceType; - - @Schema(description = "文档元数据") - private JsonNode metadata; - - @Schema(description = "状态:active-激活/inactive-停用/archived-归档") - private String status; -} diff --git a/urbanLifelineServ/apis/api-agent/src/main/java/org/xyzh/api/agent/vo/KnowledgeBaseVO.java b/urbanLifelineServ/apis/api-agent/src/main/java/org/xyzh/api/agent/vo/KnowledgeBaseVO.java deleted file mode 100644 index 19331713..00000000 --- a/urbanLifelineServ/apis/api-agent/src/main/java/org/xyzh/api/agent/vo/KnowledgeBaseVO.java +++ /dev/null @@ -1,93 +0,0 @@ -package org.xyzh.api.agent.vo; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.xyzh.common.vo.BaseVO; -import io.swagger.v3.oas.annotations.media.Schema; -import com.alibaba.fastjson2.annotation.JSONField; -import com.fasterxml.jackson.databind.JsonNode; -import java.util.Date; - -/** - * 知识库VO - * 用于前端展示知识库信息 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@Schema(description = "知识库VO") -public class KnowledgeBaseVO extends BaseVO { - private static final long serialVersionUID = 1L; - - @Schema(description = "知识库ID") - private String knowledgeId; - - @Schema(description = "智能体ID") - private String agentId; - - @Schema(description = "智能体名称") - private String agentName; - - @Schema(description = "知识库名称") - private String name; - - @Schema(description = "知识库类型") - private String kbType; - - @Schema(description = "知识库类型名称") - private String kbTypeName; - - @Schema(description = "访问级别") - private String accessLevel; - - @Schema(description = "访问级别名称") - private String accessLevelName; - - @Schema(description = "知识库描述") - private String description; - - @Schema(description = "存储路径") - private String storagePath; - - @Schema(description = "当前版本号") - private String version; - - @Schema(description = "知识库配置") - private JsonNode config; - - @Schema(description = "服务类型") - private String serviceType; - - @Schema(description = "部门名称") - private String deptName; - - @Schema(description = "状态") - private String status; - - @Schema(description = "状态名称") - private String statusName; - - @Schema(description = "状态颜色") - private String statusColor; - - @Schema(description = "文档总数") - private Long documentCount; - - @Schema(description = "已向量化文档数") - private Long embeddedDocCount; - - @Schema(description = "chunk总数") - private Long chunkCount; - - @Schema(description = "总存储大小(字节)") - private Long totalSize; - - @Schema(description = "总存储大小格式化显示") - private String totalSizeFormatted; - - @Schema(description = "最后同步时间", format = "date-time") - @JSONField(format = "yyyy-MM-dd HH:mm:ss") - private Date lastSyncTime; - - @Schema(description = "创建者姓名") - private String creatorName; -} diff --git a/urbanLifelineServ/apis/api-agent/src/main/java/org/xyzh/api/agent/vo/KnowledgeChunkVO.java b/urbanLifelineServ/apis/api-agent/src/main/java/org/xyzh/api/agent/vo/KnowledgeChunkVO.java deleted file mode 100644 index f75c6d2a..00000000 --- a/urbanLifelineServ/apis/api-agent/src/main/java/org/xyzh/api/agent/vo/KnowledgeChunkVO.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.xyzh.api.agent.vo; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.xyzh.common.vo.BaseVO; -import io.swagger.v3.oas.annotations.media.Schema; -import com.alibaba.fastjson2.annotation.JSONField; -import com.fasterxml.jackson.databind.JsonNode; -import java.util.Date; - -/** - * 知识文档片段VO - * 用于前端展示知识文档片段信息 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@Schema(description = "知识文档片段VO") -public class KnowledgeChunkVO extends BaseVO { - private static final long serialVersionUID = 1L; - - @Schema(description = "片段ID") - private String chunkId; - - @Schema(description = "所属文档ID") - private String docId; - - @Schema(description = "文档标题") - private String docTitle; - - @Schema(description = "所属知识库ID") - private String knowledgeId; - - @Schema(description = "知识库名称") - private String knowledgeName; - - @Schema(description = "片段索引") - private Integer chunkIndex; - - @Schema(description = "片段内容") - private String content; - - @Schema(description = "内容长度") - private Integer contentLength; - - @Schema(description = "片段类型") - private String chunkType; - - @Schema(description = "片段类型名称") - private String chunkTypeName; - - @Schema(description = "版本号") - private Integer version; - - @Schema(description = "根chunk ID") - private String rootChunkId; - - @Schema(description = "是否当前版本", defaultValue = "false") - private Boolean isCurrent; - - @Schema(description = "位置信息") - private JsonNode positionInfo; - - @Schema(description = "片段元数据") - private JsonNode metadata; - - @Schema(description = "部门名称") - private String deptName; -} diff --git a/urbanLifelineServ/apis/api-agent/src/main/java/org/xyzh/api/agent/vo/KnowledgeDocumentVO.java b/urbanLifelineServ/apis/api-agent/src/main/java/org/xyzh/api/agent/vo/KnowledgeDocumentVO.java deleted file mode 100644 index 3076d323..00000000 --- a/urbanLifelineServ/apis/api-agent/src/main/java/org/xyzh/api/agent/vo/KnowledgeDocumentVO.java +++ /dev/null @@ -1,114 +0,0 @@ -package org.xyzh.api.agent.vo; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.xyzh.common.vo.BaseVO; -import io.swagger.v3.oas.annotations.media.Schema; -import com.alibaba.fastjson2.annotation.JSONField; -import com.fasterxml.jackson.databind.JsonNode; -import java.util.Date; -import java.util.List; - -/** - * 知识文档VO - * 用于前端展示知识文档信息 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@Schema(description = "知识文档VO") -public class KnowledgeDocumentVO extends BaseVO { - private static final long serialVersionUID = 1L; - - @Schema(description = "文档ID") - private String docId; - - @Schema(description = "所属知识库ID") - private String knowledgeId; - - @Schema(description = "知识库名称") - private String knowledgeName; - - @Schema(description = "文档标题") - private String title; - - @Schema(description = "文档类型") - private String docType; - - @Schema(description = "文档类型名称") - private String docTypeName; - - @Schema(description = "文档分类") - private String category; - - @Schema(description = "文档内容") - private String content; - - @Schema(description = "内容摘要") - private String contentSummary; - - @Schema(description = "关联文件ID") - private String fileId; - - @Schema(description = "文件路径") - private String filePath; - - @Schema(description = "文件下载URL") - private String fileUrl; - - @Schema(description = "文件大小") - private Long fileSize; - - @Schema(description = "文件大小格式化显示") - private String fileSizeFormatted; - - @Schema(description = "MIME类型") - private String mimeType; - - @Schema(description = "版本号") - private Integer version; - - @Schema(description = "根文档ID") - private String rootDocId; - - @Schema(description = "文档标签") - private List tags; - - @Schema(description = "关键词") - private List keywords; - - @Schema(description = "向量化状态") - private String embeddingStatus; - - @Schema(description = "向量化状态名称") - private String embeddingStatusName; - - @Schema(description = "使用的向量化模型") - private String embeddingModel; - - @Schema(description = "切片数量") - private Integer chunkCount; - - @Schema(description = "文档元数据") - private JsonNode metadata; - - @Schema(description = "来源URL") - private String sourceUrl; - - @Schema(description = "服务类型") - private String serviceType; - - @Schema(description = "部门名称") - private String deptName; - - @Schema(description = "状态") - private String status; - - @Schema(description = "状态名称") - private String statusName; - - @Schema(description = "创建者姓名") - private String creatorName; - - @Schema(description = "更新者姓名") - private String updaterName; -} diff --git a/urbanLifelineServ/apis/api-agent/pom.xml b/urbanLifelineServ/apis/api-ai/pom.xml similarity index 89% rename from urbanLifelineServ/apis/api-agent/pom.xml rename to urbanLifelineServ/apis/api-ai/pom.xml index 9a27dcd2..223cf561 100644 --- a/urbanLifelineServ/apis/api-agent/pom.xml +++ b/urbanLifelineServ/apis/api-ai/pom.xml @@ -9,8 +9,8 @@ 1.0.0 - org.xyzh - api-agent + org.xyzh.apis + api-ai 1.0.0 diff --git a/urbanLifelineServ/apis/api-agent/src/main/java/org/xyzh/api/agent/AgentService.java b/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/AgentService.java similarity index 78% rename from urbanLifelineServ/apis/api-agent/src/main/java/org/xyzh/api/agent/AgentService.java rename to urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/AgentService.java index 85aa3957..88321485 100644 --- a/urbanLifelineServ/apis/api-agent/src/main/java/org/xyzh/api/agent/AgentService.java +++ b/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/AgentService.java @@ -1,4 +1,4 @@ -package org.xyzh.api.agent; +package org.xyzh.api.ai; /** * Agent服务接口 diff --git a/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/dto/DifyFileInfo.java b/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/dto/DifyFileInfo.java new file mode 100644 index 00000000..0da42c19 --- /dev/null +++ b/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/dto/DifyFileInfo.java @@ -0,0 +1,99 @@ +package org.xyzh.api.ai.dto; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +/** + * @description Dify文件信息(用于对话文件上传和请求) + * @filename DifyFileInfo.java + * @author AI Assistant + * @copyright xyzh + * @since 2025-12-15 + */ +@Data +public class DifyFileInfo { + /** + * 文件ID(Dify返回) + */ + private String id; + + /** + * 文件名 + */ + private String name; + + /** + * 文件大小(字节) + */ + private Integer size; + + /** + * 文件扩展名 + */ + private String extension; + + /** + * 文件MIME类型 + */ + @JSONField(name = "mime_type") + private String mimeType; + + /** + * 上传人ID + */ + @JSONField(name = "created_by") + private String createdBy; + + /** + * 上传时间(时间戳) + */ + @JSONField(name = "created_at") + private Long createdAt; + + /** + * 预览URL + */ + @JSONField(name = "preview_url") + private String previewUrl; + + /** + * 源文件URL + */ + @JSONField(name = "source_url") + private String sourceUrl; + + /** + * 文件类型:image、document、audio、video、file + */ + private String type; + + /** + * 传输方式:remote_url、local_file + */ + @JSONField(name = "transfer_method") + private String transferMethod; + + /** + * 文件URL或ID + */ + private String url; + + /** + * 本地文件上传ID + */ + @JSONField(name = "upload_file_id") + private String uploadFileId; + + /** + * 系统文件ID + */ + @JSONField(name = "sys_file_id") + private String sysFileId; + + /** + * 文件路径(从系统文件表获取) + */ + @JSONField(name = "file_path") + private String filePath; +} + diff --git a/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/dto/PromptCard.java b/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/dto/PromptCard.java new file mode 100644 index 00000000..de2049fa --- /dev/null +++ b/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/dto/PromptCard.java @@ -0,0 +1,14 @@ +package org.xyzh.api.ai.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "智能体提示卡") +public class PromptCard { + @Schema(description = "文件ID") + private String fileId; + + @Schema(description = "提示词") + private String prompt; +} diff --git a/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/dto/TbAgent.java b/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/dto/TbAgent.java new file mode 100644 index 00000000..b19e52f4 --- /dev/null +++ b/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/dto/TbAgent.java @@ -0,0 +1,41 @@ +package org.xyzh.api.ai.dto; + +import java.util.List; + +import org.xyzh.common.dto.BaseDTO; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import io.swagger.v3.oas.annotations.media.Schema; + +@Data +@EqualsAndHashCode(callSuper = true) +@Schema(description = "系统文件DTO") +public class TbAgent extends BaseDTO{ + private static final long serialVersionUID = 1L; + + @Schema(description = "智能体ID") + private String agentId; + + @Schema(description = "智能体名称") + private String name; + + @Schema(description = "智能体描述") + private String description; + + @Schema(description = "智能体url") + private String link; + + @Schema(description = "智能体APIKEY") + private String apiKey; + + @Schema(description = "引导词") + private String introduce; + + @Schema(description = "提示卡片数组") + private List promptCards; + + @Schema(description = "分类") + private String category; + +} diff --git a/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/dto/TbChat.java b/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/dto/TbChat.java new file mode 100644 index 00000000..f579ccc2 --- /dev/null +++ b/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/dto/TbChat.java @@ -0,0 +1,25 @@ +package org.xyzh.api.ai.dto; + +import org.xyzh.common.dto.BaseDTO; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "AI智能体对话") +public class TbChat extends BaseDTO{ + private static final long serialVersionUID = 1L; + + @Schema(description = "对话ID") + private String chatId; + + @Schema(description = "智能体ID") + private String agentId; + + @Schema(description = "用户ID") + private String userId; + + @Schema(description = "对话标题") + private String title; + +} diff --git a/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/dto/TbChatMessage.java b/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/dto/TbChatMessage.java new file mode 100644 index 00000000..5f121acd --- /dev/null +++ b/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/dto/TbChatMessage.java @@ -0,0 +1,30 @@ +package org.xyzh.api.ai.dto; + +import java.util.List; + +import org.xyzh.common.dto.BaseDTO; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "AI智能体对话消息") +public class TbChatMessage extends BaseDTO{ + private static final long serialVersionUID = 1L; + + @Schema(description = "消息ID") + private String messageId; + + @Schema(description = "对话ID") + private String chatId; + + @Schema(description = "角色") + private String role; + + @Schema(description = "内容") + private String content; + + @Schema(description = "文件ID数组") + private List files; + +} diff --git a/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/dto/TbKnowledge.java b/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/dto/TbKnowledge.java new file mode 100644 index 00000000..0c668059 --- /dev/null +++ b/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/dto/TbKnowledge.java @@ -0,0 +1,68 @@ +package org.xyzh.api.ai.dto; + +import org.xyzh.common.dto.BaseDTO; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "知识库配置") +public class TbKnowledge extends BaseDTO{ + private static final long serialVersionUID = 1L; + + @Schema(description = "知识库ID") + private String knowledgeId; + + @Schema(description = "知识库标题") + private String title; + + @Schema(description = "知识库头像") + private String avatar; + + @Schema(description = "知识库描述") + private String description; + + @Schema(description = "Dify知识库ID(Dataset ID)") + private String difyDatasetId; + + @Schema(description = "Dify索引方式(high_quality/economy)") + private String difyIndexingTechnique; + + @Schema(description = "向量模型名称") + private String embeddingModel; + + @Schema(description = "向量模型提供商") + private String embeddingModelProvider; + + @Schema(description = "Rerank模型名称") + private String rerankModel; + + @Schema(description = "Rerank模型提供商") + private String rerankModelProvider; + + @Schema(description = "是否启用Rerank(0否 1是)") + private Integer rerankingEnable; + + @Schema(description = "检索Top K(返回前K个结果)") + private Integer retrievalTopK; + + @Schema(description = "检索分数阈值(0.00-1.00)") + private Double retrievalScoreThreshold; + + @Schema(description = "文档数量") + private Integer documentCount; + + @Schema(description = "总分段数") + private Integer totalChunks; + + @Schema(description = "所属服务 workcase、bidding") + private String service; + + @Schema(description = "bidding所属项目ID") + private String projectId; + + @Schema(description = "所属分类 workcase 内部知识库、外部知识库") + private String category; + + +} diff --git a/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/dto/TbKnowledgeFile.java b/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/dto/TbKnowledgeFile.java new file mode 100644 index 00000000..836251b9 --- /dev/null +++ b/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/dto/TbKnowledgeFile.java @@ -0,0 +1,28 @@ +package org.xyzh.api.ai.dto; + +import org.xyzh.common.dto.BaseDTO; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "知识库文件") +public class TbKnowledgeFile extends BaseDTO{ + private final static long serialVersionUID = 1L; + + @Schema(description = "知识库ID") + private String knowledgeId; + + @Schema(description = "文件ID") + private String fileId; + + @Schema(description = "文件根ID") + private String fileRootId; + + @Schema(description = "Dify文件ID") + private String difyFileId; + + @Schema(description = "文件版本") + private String version; + +} diff --git a/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/vo/AgentVO.java b/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/vo/AgentVO.java new file mode 100644 index 00000000..3ab8b888 --- /dev/null +++ b/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/vo/AgentVO.java @@ -0,0 +1,12 @@ +package org.xyzh.api.ai.vo; + +import org.xyzh.common.vo.BaseVO; + +import lombok.Data; +import io.swagger.v3.oas.annotations.media.Schema; + +@Data +@Schema(description = "智能体VO") +public class AgentVO extends BaseVO{ + +} diff --git a/urbanLifelineServ/apis/api-file/src/main/java/org/xyzh/api/file/dto/TbSysFileDTO.java b/urbanLifelineServ/apis/api-file/src/main/java/org/xyzh/api/file/dto/TbSysFileDTO.java index 3b3c47ce..8481bdaa 100644 --- a/urbanLifelineServ/apis/api-file/src/main/java/org/xyzh/api/file/dto/TbSysFileDTO.java +++ b/urbanLifelineServ/apis/api-file/src/main/java/org/xyzh/api/file/dto/TbSysFileDTO.java @@ -22,6 +22,12 @@ public class TbSysFileDTO extends BaseDTO { @Schema(description = "文件ID (主键)") private String fileId; + @Schema(description = "文件根ID") + private String fileRootId; + + @Schema(description = "文件版本") + private String version; + @Schema(description = "文件名") private String name; diff --git a/urbanLifelineServ/apis/pom.xml b/urbanLifelineServ/apis/pom.xml index 85987ea3..9dc297d6 100644 --- a/urbanLifelineServ/apis/pom.xml +++ b/urbanLifelineServ/apis/pom.xml @@ -21,7 +21,7 @@ api-log api-system api-crontab - api-agent + api-ai api-bidding api-platform api-workcase diff --git a/urbanLifelineServ/common/common-utils/src/main/java/org/xyzh/common/utils/json/StringArrayTypeHandler.java b/urbanLifelineServ/common/common-utils/src/main/java/org/xyzh/common/utils/json/StringArrayTypeHandler.java new file mode 100644 index 00000000..e868aaa9 --- /dev/null +++ b/urbanLifelineServ/common/common-utils/src/main/java/org/xyzh/common/utils/json/StringArrayTypeHandler.java @@ -0,0 +1,59 @@ +package org.xyzh.common.utils.json; + +import org.apache.ibatis.type.BaseTypeHandler; +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.MappedTypes; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * @description PostgreSQL VARCHAR数组类型处理器 + * @filename StringArrayTypeHandler.java + * @author yslg + * @copyright yslg + * @since 2025-12-15 + */ +@MappedTypes({List.class}) +public class StringArrayTypeHandler extends BaseTypeHandler> { + + @Override + public void setNonNullParameter(PreparedStatement ps, int i, List parameter, JdbcType jdbcType) throws SQLException { + if (parameter == null || parameter.isEmpty()) { + ps.setArray(i, null); + } else { + ps.setArray(i, ps.getConnection().createArrayOf("varchar", parameter.toArray())); + } + } + + @Override + public List getNullableResult(ResultSet rs, String columnName) throws SQLException { + java.sql.Array array = rs.getArray(columnName); + return parseArray(array); + } + + @Override + public List getNullableResult(ResultSet rs, int columnIndex) throws SQLException { + java.sql.Array array = rs.getArray(columnIndex); + return parseArray(array); + } + + @Override + public List getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { + java.sql.Array array = cs.getArray(columnIndex); + return parseArray(array); + } + + private List parseArray(java.sql.Array array) throws SQLException { + if (array == null) { + return null; + } + Object[] objects = (Object[]) array.getArray(); + return new ArrayList<>(Arrays.asList((String[]) objects)); + } +} diff --git a/urbanLifelineServ/pom.xml b/urbanLifelineServ/pom.xml index d47ed930..711b6bd8 100644 --- a/urbanLifelineServ/pom.xml +++ b/urbanLifelineServ/pom.xml @@ -17,7 +17,7 @@ file message crontab - agent + ai bidding platform workcase diff --git a/urbanLifelineServ/知识库逻辑变更.md b/urbanLifelineServ/知识库逻辑变更.md new file mode 100644 index 00000000..fe94540e --- /dev/null +++ b/urbanLifelineServ/知识库逻辑变更.md @@ -0,0 +1,2 @@ +1. 不再自己构建知识库,使用dify的知识库 +2. 服务的知识库表,只对文件进行重新上传才产生版本,对知识库内文档分段等修改不生成新的版本