From 50b5e593ed6dec5d0fc259ee99ac419e8c155e43 Mon Sep 17 00:00:00 2001 From: sladro Date: Fri, 30 Jan 2026 13:14:22 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0es=E6=95=B0=E6=8D=AE=E8=B5=84?= =?UTF-8?q?=E6=96=99=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-fastapi-backend/ES_BM25_KB_PLAN.md | 329 ++++++++++ ruoyi-fastapi-backend/config/env.py | 32 +- .../controller/ragflow_controller.py | 158 ++++- .../module_admin/service/kb_es_service.py | 197 ++++++ ruoyi-fastapi-backend/pytest.ini | 2 + ruoyi-fastapi-backend/requirements.txt | 1 + .../sub_applications/kb_es/create_index.py | 80 +++ .../sub_applications/kb_es/ingest_folder.py | 150 +++++ .../opensearch/docker-compose.yml | 28 + ruoyi-fastapi-backend/test.md | 589 ++++++++++++++++++ 10 files changed, 1554 insertions(+), 12 deletions(-) create mode 100644 ruoyi-fastapi-backend/ES_BM25_KB_PLAN.md create mode 100644 ruoyi-fastapi-backend/module_admin/service/kb_es_service.py create mode 100644 ruoyi-fastapi-backend/pytest.ini create mode 100644 ruoyi-fastapi-backend/sub_applications/kb_es/create_index.py create mode 100644 ruoyi-fastapi-backend/sub_applications/kb_es/ingest_folder.py create mode 100644 ruoyi-fastapi-backend/sub_applications/opensearch/docker-compose.yml create mode 100644 ruoyi-fastapi-backend/test.md diff --git a/ruoyi-fastapi-backend/ES_BM25_KB_PLAN.md b/ruoyi-fastapi-backend/ES_BM25_KB_PLAN.md new file mode 100644 index 0000000..d0491ac --- /dev/null +++ b/ruoyi-fastapi-backend/ES_BM25_KB_PLAN.md @@ -0,0 +1,329 @@ +## 目标 + +在保留现有 `RAGFlow` 方案的前提下,引入一套 **ES/OpenSearch(BM25)为主** 的“内部固定资料问答”链路,用配置开关在 `ragflow` 与 `es_bm25` 间切换;并将外部实时问题(天气/新闻等)继续沿用项目现有的“搜索增强”流程。 + +核心诉求: + +1. **首 token 更快**(内部资料命中时尽量做到“检索毫秒级 + LLM首字”)。 +2. **可上线稳定**:有置信度、兜底、同义词、索引治理、可观测。 +3. **可回滚**:配置切回 `RAGFlow` 即可。 + +--- + +## 现状简述(与你们当前代码对齐) + +当前对话入口:`POST /system/ragflow/converse_with_chat_assistant`(`module_admin/controller/ragflow_controller.py`) + +现有大致流程: + +1. 移除风格提示词(轻量) +2. `StaticQA` 静态FAQ(本地相似度) +3. `SearchService.classify_intent`:判断是否走外部搜索(可能有外部模型调用) +4. `SemanticCache`:Redis 问答历史缓存(目前实现会扫 keys) +5. 调用 `RAGFlow` 返回流式答案 + +对于“首 token 慢”,主要风险点在于:前置链路过长、外部意图识别网络 RTT、缓存扫描、以及 RAGFlow 调用与流式处理的阻塞/日志等。 + +--- + +## 方案 B:可上线稳定的 ES/BM25 内部知识链路 + +### 1) 总体架构(保留 RAGFlow,通过配置切换) + +抽象一个内部知识提供方(KB Provider),提供“检索 +(可选)生成”的能力: + +- `KB_PROVIDER=ragflow`:保持现状(走 RAGFlow) +- `KB_PROVIDER=es_bm25`:内部知识走 ES/OpenSearch(BM25) + +外部实时问题(天气/新闻等)继续由现有 `SearchService` 分流处理,不受 KB_PROVIDER 影响。 + +建议新增配置(示例,实际以你们 env.py 读取方式为准): + +- `KB_PROVIDER=ragflow|es_bm25`(默认 ragflow) +- `KB_ES_URL=http(s)://host:9200` 或 `KB_OS_URL=http(s)://host:9200` +- `KB_ES_INDEX=kb_chunks_v1` +- `KB_ES_USERNAME=...`(如开启安全) +- `KB_ES_PASSWORD=...` +- `KB_TOP_K=8`(检索召回) +- `KB_MIN_SCORE=...`(置信度阈值的一个维度) +- `KB_CACHE_TTL=...`(检索结果缓存 TTL) + +### 2) 请求路由(兼容你们已有流程) + +建议稳定且首 token 友好的顺序: + +1. 静态FAQ(命中直接回) +2. 内部会话缓存(命中直接回) +3. 意图识别(外部搜索 vs 内部KB) +4. 内部KB:根据 `KB_PROVIDER` 走 `es_bm25` 或 `ragflow` +5. 兜底:内部KB低置信度 →(可配置)fallback 到 ragflow 或通用对话 + +说明: + +- 把“缓存”放到“外部意图识别”前,避免命中时仍需等待外部网络调用。 + +--- + +## ES/OpenSearch 索引治理 + +### 1) 数据结构(Chunk 化) + +内部资料固定,推荐离线预处理:文档 → 分块(chunk)→ 入索引。 + +每条 chunk 建议字段: + +- `doc_id`:文档ID +- `chunk_id`:分块ID +- `title`:标题/章节名(可空) +- `content`:正文内容(核心检索字段) +- `source`:来源(文件名/系统名) +- `tags`:标签(产品/制度/流程/人名/地名等) +- `category`:大类(可选) +- `updated_at`:更新时间 +- `version`:索引版本号/数据版本 + +### 2) 中文分词与同义词 + +选择建议: + +- OpenSearch:优先使用 `analysis-icu` +(可选)`analysis-smartcn`(若可用),并结合自定义同义词。 +- Elasticsearch:常见做法是安装 IK 分词(`analysis-ik`)或 `analysis-smartcn`。 + +同义词治理建议: + +- 维护一份 `synonyms.txt`(公司/产品/缩写/别名),例如: + - `康达新材, 康达` + - `胶粘剂, 胶水` + - `厂区, 园区, 工厂` +- 分两层: + - **query-time synonyms**(更安全,不必全量重建索引) + - **index-time synonyms**(效果可能更强,但改词需 reindex) + +### 3) Mapping & Query(BM25) + +建议用多字段策略: + +- `title`:boost 高 +- `content`:主字段 +- `tags/category`:过滤与加权 + +查询 DSL 推荐组合: + +- `multi_match`(`best_fields` 或 `most_fields`) +- `match_phrase`(对关键短语加分) +- `minimum_should_match`(抑制泛匹配) +- `function_score`(用字段权重/新鲜度加权) + +--- + +## 置信度与兜底策略(上线稳定的关键) + +内部资料“固定且可控”,建议对检索结果计算一个可解释的置信度,用于: + +1. 决定是否直接回答 +2. 决定是否 fallback 到 ragflow +3. 决定是否提示“我不确定/需要更多信息” + +可落地的置信度指标(建议先从简单开始): + +- `top1_score`:ES 返回的最高分 +- `score_gap = top1_score - top2_score`:区分度 +- `topk_hit_count`:有效 chunk 数 +- `keyword_coverage`:问题关键词在 top chunks 中的覆盖率(你们已有 `MatchService.calculate_keyword_coverage`) + +决策建议: + +- 高置信:`top1_score >= S1` 且 `keyword_coverage >= C1` → 直接走“基于上下文生成”或“抽取式回答” +- 中置信:走“生成 + 引用” +- 低置信: + - 若 `KB_PROVIDER=es_bm25`,则 fallback 到 `ragflow`(可配置开关 `KB_FALLBACK=ragflow|none`) + - 或提示用户改写/补充关键词 + +--- + +## 缓存与性能策略 + +建议两级缓存(Redis): + +1. 检索缓存:`kb:es:retrieval:{hash(question_norm)}` → 存 topK chunks(TTL 10~30min) +2. 答案缓存:你们已有 `SemanticCache`(建议把 KEYS 扫描改为 SCAN/MGET 并限制扫描数量) + +首 token 优化要点: + +- 检索缓存命中时:可立即开始构造 prompt 并流式输出。 +- 生成端: + - 若你们用 DeepSeek/OpenAI 兼容接口,尽量使用 `stream=True` 并减少前置串行步骤。 + +--- + +## 观测与回归指标(上线必须) + +建议打点并输出到日志/监控: + +- `ttft_ms`:Time To First Token +- `retrieve_ms`:ES 检索耗时 +- `llm_first_token_ms`:LLM 首 token(若可获得) +- `cache_hit`:静态FAQ/语义缓存/检索缓存命中率 +- `fallback_rate`:es_bm25 → ragflow 的兜底比例 +- `no_hit_rate`:检索无结果占比 + +--- + +## 迁移/上线步骤(建议) + +1. 部署 OpenSearch/ES(见下文) +2. 设计索引与分析器(中文分词、同义词) +3. 编写离线入库脚本:固定资料 → chunk → bulk ingest +4. 新增 `es_bm25` Provider(只影响内部 KB 路径) +5. 灰度: + - `KB_PROVIDER=es_bm25` 仅在一台实例开启 + - 对比 `ttft_ms`、命中率、fallback_rate +6. 全量切换;保留随时回滚到 `KB_PROVIDER=ragflow` + +--- + +## 生产环境安装 OpenSearch(推荐)/Elasticsearch 方法 + +下面给出两种最常见可控方案:Docker(快)与 systemd(稳)。生产建议至少做到:安全(认证/TLS)、资源限制、数据盘、备份与监控。 + +### 前置:Linux 内核与系统参数(必须) + +1. 设置虚拟内存映射(Lucene 需要): + +```bash +sudo sysctl -w vm.max_map_count=262144 +echo 'vm.max_map_count=262144' | sudo tee -a /etc/sysctl.conf +``` + +2. 建议关闭 swap(或至少调低 swappiness)。 + +3. 文件句柄与进程限制(systemd/ulimit): + +- `nofile` 建议 >= 65535 +- `memlock` 尽量 unlimited(视环境) + +--- + +### 方案 1:Docker 部署 OpenSearch(最快上手) + +适合:你们已有 Docker,想快速上线验证。 + +1) 创建数据目录(挂载到数据盘): + +```bash +sudo mkdir -p /data/opensearch +sudo chown -R 1000:1000 /data/opensearch +``` + +2) 启动单节点(示例,生产请开启安全与持久化配置): + +```bash +docker run -d --name opensearch \ + -p 9200:9200 -p 9600:9600 \ + -e "discovery.type=single-node" \ + -e "OPENSEARCH_JAVA_OPTS=-Xms4g -Xmx4g" \ + -e "plugins.security.disabled=false" \ + -v /data/opensearch:/usr/share/opensearch/data \ + opensearchproject/opensearch:2 +``` + +3) 验证: + +```bash +curl -k -u admin:admin https://127.0.0.1:9200 +``` + +生产建议: + +- 用 docker-compose 管理,显式配置 admin 密码、证书、网络白名单。 +- 至少做反向代理/防火墙,仅允许后端访问 9200。 + +--- + +### 方案 2:systemd 部署 OpenSearch(更适合生产) + +适合:长期稳定运行、要配合 systemd 管理与日志。 + +1) 安装 JDK(OpenSearch 自带 JDK 的版本较多,但建议按官方要求确认): + +```bash +java -version +``` + +2) 下载并解压 OpenSearch(示例命令,按你们版本选择): + +```bash +mkdir -p /opt/opensearch +tar -xf opensearch-2.x.x-linux-x64.tar.gz -C /opt/opensearch --strip-components=1 +``` + +3) 创建专用用户与数据目录: + +```bash +sudo useradd --system --home /opt/opensearch --shell /usr/sbin/nologin opensearch +sudo mkdir -p /data/opensearch +sudo chown -R opensearch:opensearch /opt/opensearch /data/opensearch +``` + +4) 配置 `opensearch.yml`(至少设置数据路径、网络、集群名、安全): + +- `path.data: /data/opensearch` +- `network.host: 0.0.0.0`(或内网IP) +- `http.port: 9200` + +5) systemd service(示例骨架): + +`/etc/systemd/system/opensearch.service` + +```ini +[Unit] +Description=OpenSearch +After=network.target + +[Service] +Type=simple +User=opensearch +Group=opensearch +WorkingDirectory=/opt/opensearch +ExecStart=/opt/opensearch/bin/opensearch +Restart=always +LimitNOFILE=65535 +LimitNPROC=4096 +LimitMEMLOCK=infinity + +[Install] +WantedBy=multi-user.target +``` + +启停: + +```bash +sudo systemctl daemon-reload +sudo systemctl enable --now opensearch +sudo systemctl status opensearch +``` + +--- + +### Elasticsearch 安装说明(可选) + +若你们更倾向 Elasticsearch: + +- 官方更推荐包管理(rpm/deb)+ systemd +- 中文分词可用 `analysis-ik` 或 `analysis-smartcn` 插件(需要安装插件并重启) + +生产同样要保证:vm.max_map_count、nofile、数据盘、认证/TLS(Elastic 8+ 默认安全开启)。 + +--- + +## 与本项目集成点(开发落点) + +建议改动范围(保持接口不变): + +1. 新增 `module_admin/service/kb_es_service.py`(或类似命名):封装检索、置信度计算、缓存。 +2. 新增“LLM 生成器”(可复用你们已有 DeepSeek/OpenAI 客户端,如果已有);支持 SSE 流式输出。 +3. 在 `ragflow_controller` 内部 KB 分支增加: + - 若 `KB_PROVIDER=es_bm25`:走 ES → 生成 →(低置信度 fallback) + - 若 `KB_PROVIDER=ragflow`:保持现状 + +备注:`RAGFLOW_*` 配置与现有服务保持不变,作为兜底/回滚路径。 diff --git a/ruoyi-fastapi-backend/config/env.py b/ruoyi-fastapi-backend/config/env.py index e1a4f6a..6060652 100644 --- a/ruoyi-fastapi-backend/config/env.py +++ b/ruoyi-fastapi-backend/config/env.py @@ -239,6 +239,28 @@ class DeepSeekSettings: DEEPSEEK_MODEL = os.getenv("DEEPSEEK_MODEL", "deepseek-chat") +class KBSettings: + """内部知识库 Provider 配置(ragflow / es_bm25)""" + + KB_PROVIDER: Literal['ragflow', 'es_bm25'] = os.getenv('KB_PROVIDER', 'ragflow') # type: ignore[assignment] + + # ES/OpenSearch + KB_ES_URL: str = os.getenv('KB_ES_URL', os.getenv('KB_OS_URL', '')).rstrip('/') + KB_ES_INDEX: str = os.getenv('KB_ES_INDEX', 'kb_chunks_v1') + KB_ES_USERNAME: str = os.getenv('KB_ES_USERNAME', '') + KB_ES_PASSWORD: str = os.getenv('KB_ES_PASSWORD', '') + KB_ES_VERIFY_SSL: bool = os.getenv('KB_ES_VERIFY_SSL', 'true').lower() == 'true' + + # retrieval + KB_TOP_K: int = int(os.getenv('KB_TOP_K', '8')) + KB_MIN_SCORE: float = float(os.getenv('KB_MIN_SCORE', '0')) + KB_MIN_COVERAGE: float = float(os.getenv('KB_MIN_COVERAGE', '0.2')) + KB_CACHE_TTL: int = int(os.getenv('KB_CACHE_TTL', '1800')) # seconds + + # fallback + KB_FALLBACK: Literal['ragflow', 'none'] = os.getenv('KB_FALLBACK', 'ragflow') # type: ignore[assignment] + + class GetConfig: """ 获取配置 @@ -326,6 +348,11 @@ class GetConfig: """获取DeepSeek配置""" return DeepSeekSettings() + @lru_cache() + def get_kb_config(self): + """获取内部知识库配置""" + return KBSettings() + @staticmethod def parse_cli_args(): """ @@ -339,7 +366,7 @@ class GetConfig: parser = argparse.ArgumentParser(description='命令行参数') parser.add_argument('--env', type=str, default='dev', help='运行环境') # 解析命令行参数 - args = parser.parse_args() + args, _unknown = parser.parse_known_args() # 设置环境变量,如果未设置命令行参数,默认APP_ENV为dev os.environ['APP_ENV'] = args.env if args.env else 'dev' # 读取运行环境 @@ -416,3 +443,6 @@ ComprefaceConfig = get_config.get_compreface_config() SearchConfig = get_config.get_search_config() # DeepSeek配置 DeepSeekConfig = get_config.get_deepseek_config() + +# KB Provider配置 +KBConfig = get_config.get_kb_config() diff --git a/ruoyi-fastapi-backend/module_admin/controller/ragflow_controller.py b/ruoyi-fastapi-backend/module_admin/controller/ragflow_controller.py index 9ce568d..531be50 100644 --- a/ruoyi-fastapi-backend/module_admin/controller/ragflow_controller.py +++ b/ruoyi-fastapi-backend/module_admin/controller/ragflow_controller.py @@ -6,12 +6,13 @@ import asyncio import hashlib import json import time -from typing import Any, AsyncGenerator, Dict, Generator, Optional, Tuple +from typing import Any, AsyncGenerator, Dict, Generator, Tuple from fastapi import APIRouter, Depends, Request from fastapi.responses import StreamingResponse from module_admin.service.login_service import LoginService +from module_admin.service.kb_es_service import KBESService from module_admin.service.ragflow_service import RAGFlowService from module_admin.service.search_service import SearchService from module_admin.entity.vo.ragflow_vo import ( @@ -22,8 +23,10 @@ from module_admin.entity.vo.ragflow_vo import ( ) from utils.log_util import logger from utils.response_util import ResponseUtil -from utils.semantic_cache_service import get_semantic_cache_service, lookup_question, store_qa_pair, get_question_hash +from utils.semantic_cache_service import get_semantic_cache_service, store_qa_pair, get_question_hash from utils.static_qa_service import get_static_qa_service +from utils.deepseek_client import DeepSeekAPIClient +from config.env import KBConfig async def _async_store_qa(chat_id: str, question: str, answer: str, redis) -> None: @@ -118,10 +121,8 @@ async def converse_with_chat_assistant( # 获取redis实例 redis = getattr(request.app.state, 'redis', None) - cache_key = None cached_answer = None cache_similarity = 0.0 - cache_source = None # ========== 1. 静态问答匹配 ========== try: @@ -133,7 +134,6 @@ async def converse_with_chat_assistant( if static_match: cached_answer = static_match.get('answer', '') cache_similarity = static_sim - cache_source = 'static_qa' logger.info(f'[RAG_SOURCE] 命中静态FAQ | chat_id={converse_params.chat_id} | question={converse_params.question} | similarity={cache_similarity:.2f} | answer_length={len(cached_answer)}') logger.info(f'[StaticQA] 流式响应使用静态问答答案,chat_id={converse_params.chat_id}') return StreamingResponse( @@ -153,12 +153,12 @@ async def converse_with_chat_assistant( try: intent = await SearchService.classify_intent(converse_params.question) logger.info(f"[RAG_PROFILE] Intent Router ({converse_params.chat_id}): {intent}") - + if intent == 'SEARCH': return await SearchService.handle_search_chat(converse_params, redis) except Exception as e: logger.warning(f"意图分类失败,使用RAG服务: {e}") - + # ========== 3. RAG历史缓存查找 ========== logger.info(f'[SemanticCache] 准备执行RAG历史缓存查找 | redis={redis is not None} | chat_id={converse_params.chat_id}') if redis: @@ -175,7 +175,6 @@ async def converse_with_chat_assistant( logger.info(f'[SemanticCache] 查找结果 | found={cache_result is not None}') if cache_result: cached_answer, cache_similarity = cache_result - cache_source = 'rag_history' logger.info(f'[RAG_SOURCE] 命中RAG会话历史 | chat_id={converse_params.chat_id} | question={converse_params.question} | similarity={cache_similarity:.2f} | answer_length={len(cached_answer)}') logger.info(f'[SemanticCache] 流式响应使用RAG历史缓存答案,chat_id={converse_params.chat_id}') return StreamingResponse( @@ -189,12 +188,66 @@ async def converse_with_chat_assistant( } ) - # 直接使用同步RAGFlow服务 + # ========== 4. RAG服务调用(在这里根据 KB_PROVIDER 选择 ragflow / es_bm25) ========== try: # 恢复原始问题(包含语言风格提示词) if style_removed: converse_params.question = original_question logger.info(f'[StyleHint] 恢复原始问题: {original_question}') + + if KBConfig.KB_PROVIDER == 'es_bm25': + try: + chunks, metrics = await KBESService.retrieve(converse_params.question, redis=redis) + logger.info( + f"[KB_ES] retrieve_ms={metrics.retrieve_ms:.1f} | top1_score={metrics.top1_score:.3f} | score_gap={metrics.score_gap:.3f} | " + f"hit_count={metrics.hit_count} | keyword_coverage={metrics.keyword_coverage:.2f} | cache_hit={metrics.cache_hit} | " + f"chat_id={converse_params.chat_id}" + ) + + if KBESService.is_confident(metrics): + async def cache_store_func(answer: str): + if redis and answer and len(answer.strip()) >= 10: + await _async_store_qa(converse_params.chat_id, converse_params.question, answer, redis) + + return StreamingResponse( + stream_kb_es_response( + question=converse_params.question, + chunks=chunks, + chat_id=converse_params.chat_id, + start_time=start_time, + cache_store_func=cache_store_func, + retrieve_ms=metrics.retrieve_ms, + ), + media_type='text/event-stream', + headers={ + 'Cache-Control': 'no-cache', + 'Connection': 'keep-alive', + 'X-Accel-Buffering': 'no', + 'Transfer-Encoding': 'chunked' + } + ) + + logger.info( + f"[KB_ES] 低置信度,准备fallback | fallback={KBConfig.KB_FALLBACK} | chat_id={converse_params.chat_id}" + ) + if KBConfig.KB_FALLBACK == 'none': + return StreamingResponse( + stream_cached_response( + '我目前不太确定答案。请补充更多关键词或换种问法。', + converse_params.chat_id, + start_time, + cache_source='kb_es_low_confidence' + ), + media_type='text/event-stream', + headers={ + 'Cache-Control': 'no-cache', + 'Connection': 'keep-alive', + 'X-Accel-Buffering': 'no', + 'Transfer-Encoding': 'chunked' + } + ) + except Exception as e: + logger.warning(f"[KB_ES] 内部KB异常,fallback到RAGFlow: {e}") logger.info(f'[RAG_SOURCE] 调用原生RAG服务 | chat_id={converse_params.chat_id} | question={converse_params.question}') result = RAGFlowService.converse_with_chat_assistant_services(converse_params) @@ -418,7 +471,6 @@ def stream_cached_response(cached_answer: str, chat_id: str, start_time: float, chunk_size = 10 # 每个块10个字符 answer_chunks = [cached_answer[i:i+chunk_size] for i in range(0, len(cached_answer), chunk_size)] - first_token_time = time.time() for i, chunk in enumerate(answer_chunks): chunk_time = time.time() @@ -450,4 +502,88 @@ def stream_cached_response(cached_answer: str, chat_id: str, start_time: float, logger.info(f"[CACHE_STREAM {stream_end_time:.3f}] 📊 缓存答案长度: {len(cached_answer)} 字符") total_time = time.perf_counter() - start_time - logger.info(f'[CACHE_STREAM {time.time():.3f}] ⏱️ Total Cache Stream Duration ({chat_id}): {total_time:.3f}s') \ No newline at end of file + logger.info(f'[CACHE_STREAM {time.time():.3f}] ⏱️ Total Cache Stream Duration ({chat_id}): {total_time:.3f}s') + + +async def stream_kb_es_response( + *, + question: str, + chunks: list[dict], + chat_id: str, + start_time: float, + cache_store_func=None, + retrieve_ms: float | None = None, +) -> AsyncGenerator[str, None]: + import time + + server_stream_start = time.time() + logger.info(f"[KB_ES_STREAM {server_stream_start:.3f}] 开始流式响应处理,chat_id: {chat_id}") + + ping_message = 'event: ping\ndata: {"status": "connected"}\n\n' + yield ping_message + + context_parts = [] + for i, c in enumerate(chunks[: KBConfig.KB_TOP_K], 1): + title = (c.get('title') or '').strip() + source = (c.get('source') or '').strip() + content = (c.get('content') or '').strip() + header = f"[{i}]" + if title: + header += f" {title}" + if source: + header += f" ({source})" + context_parts.append(f"{header}\n{content}") + + context = "\n\n".join(context_parts) + system_prompt = ( + "你是公司内部知识库助手。请严格基于【已知资料】回答;如果资料不足以回答,直接说明不知道并提示用户补充关键词。" + "回答要简洁。" + ) + + messages = [ + {"role": "system", "content": f"{system_prompt}\n\n【已知资料】\n{context}"}, + {"role": "user", "content": question}, + ] + + full_answer = '' + first_token_sent = False + + try: + client = DeepSeekAPIClient() + stream = await client.chat_completion( + messages=messages, + temperature=0.2, + max_tokens=512, + stream=True, + ) + + async for chunk in stream: + await asyncio.sleep(0) + try: + delta = chunk.choices[0].delta.content or '' + except Exception: + delta = '' + if not delta: + continue + if not first_token_sent: + first_token_sent = True + latency = time.perf_counter() - start_time + logger.info( + f"[KB_ES_STREAM {time.time():.3f}] 首token到达,ttft={latency:.3f}s | retrieve_ms={retrieve_ms} | chat_id={chat_id}" + ) + full_answer += delta + yield format_sse({'answer': delta, 'source': 'kb_es'}) + + yield format_sse({'status': 'completed', 'source': 'kb_es'}, event='end') + + except Exception as exc: + logger.exception(f"[KB_ES_STREAM] 流式对话异常: {exc}") + yield format_sse({'message': str(exc)}, event='error') + finally: + total_time = time.perf_counter() - start_time + logger.info(f"[KB_ES_STREAM {time.time():.3f}] Total Stream Duration ({chat_id}): {total_time:.3f}s") + if cache_store_func and full_answer and len(full_answer.strip()) >= 10: + try: + await cache_store_func(full_answer) + except Exception as e: + logger.warning(f"[KB_ES_STREAM] 缓存存储失败: {e}") \ No newline at end of file diff --git a/ruoyi-fastapi-backend/module_admin/service/kb_es_service.py b/ruoyi-fastapi-backend/module_admin/service/kb_es_service.py new file mode 100644 index 0000000..bbaf94e --- /dev/null +++ b/ruoyi-fastapi-backend/module_admin/service/kb_es_service.py @@ -0,0 +1,197 @@ +import hashlib +import json +import time +from dataclasses import dataclass +from typing import Any, Dict, List, Tuple + +import httpx + +from config.env import KBConfig +from utils.log_util import logger +from utils.match_service import get_match_service + + +@dataclass +class KBRetrievalMetrics: + retrieve_ms: float + top1_score: float + score_gap: float + hit_count: int + keyword_coverage: float + cache_hit: bool + + +class KBESServiceError(Exception): + pass + + +class KBESService: + """ES/OpenSearch(BM25)检索 + 置信度 + 缓存封装""" + + CACHE_PREFIX = "kb:es:retrieval" + + @classmethod + def _cache_key(cls, question: str) -> str: + q = (question or "").strip().lower() + digest = hashlib.sha256(q.encode("utf-8")).hexdigest() + return f"{cls.CACHE_PREFIX}:{digest}" + + @classmethod + async def retrieve(cls, question: str, redis=None) -> Tuple[List[Dict[str, Any]], KBRetrievalMetrics]: + if not question: + return [], KBRetrievalMetrics(0.0, 0.0, 0.0, 0, 0.0, False) + + cache_hit = False + if redis and KBConfig.KB_CACHE_TTL > 0: + key = cls._cache_key(question) + cached = await redis.get(key) + if cached: + try: + payload = json.loads(cached) + chunks = payload.get("chunks", []) + metrics = payload.get("metrics", {}) + return ( + chunks, + KBRetrievalMetrics( + retrieve_ms=metrics.get("retrieve_ms", 0.0), + top1_score=metrics.get("top1_score", 0.0), + score_gap=metrics.get("score_gap", 0.0), + hit_count=metrics.get("hit_count", len(chunks)), + keyword_coverage=metrics.get("keyword_coverage", 0.0), + cache_hit=True, + ), + ) + except Exception: + pass + + if not KBConfig.KB_ES_URL: + raise KBESServiceError("KB_ES_URL/KB_OS_URL 未配置") + + t0 = time.perf_counter() + chunks, metrics_raw = await cls._es_search(question) + retrieve_ms = (time.perf_counter() - t0) * 1000 + + match_service = get_match_service() + combined = "\n".join([(c.get("title") or "") + "\n" + (c.get("content") or "") for c in chunks]) + coverage = match_service.calculate_keyword_coverage(question, combined) + + top1 = metrics_raw.get("top1_score", 0.0) + gap = metrics_raw.get("score_gap", 0.0) + + metrics = KBRetrievalMetrics( + retrieve_ms=retrieve_ms, + top1_score=top1, + score_gap=gap, + hit_count=len(chunks), + keyword_coverage=coverage, + cache_hit=cache_hit, + ) + + if redis and KBConfig.KB_CACHE_TTL > 0: + try: + key = cls._cache_key(question) + await redis.set( + key, + json.dumps( + { + "chunks": chunks, + "metrics": { + "retrieve_ms": metrics.retrieve_ms, + "top1_score": metrics.top1_score, + "score_gap": metrics.score_gap, + "hit_count": metrics.hit_count, + "keyword_coverage": metrics.keyword_coverage, + }, + }, + ensure_ascii=False, + ), + ex=KBConfig.KB_CACHE_TTL, + ) + except Exception as e: + logger.debug(f"[KB_ES] 写入检索缓存失败: {e}") + + return chunks, metrics + + @classmethod + def is_confident(cls, metrics: KBRetrievalMetrics) -> bool: + if metrics.hit_count <= 0: + return False + if metrics.top1_score < KBConfig.KB_MIN_SCORE: + return False + if metrics.keyword_coverage < KBConfig.KB_MIN_COVERAGE: + return False + return True + + @classmethod + async def _es_search(cls, question: str) -> Tuple[List[Dict[str, Any]], Dict[str, float]]: + index = KBConfig.KB_ES_INDEX + url = f"{KBConfig.KB_ES_URL}/{index}/_search" + + query = { + "size": KBConfig.KB_TOP_K, + "track_total_hits": False, + "_source": [ + "doc_id", + "chunk_id", + "title", + "content", + "source", + "tags", + "category", + "updated_at", + "version", + ], + "query": { + "bool": { + "should": [ + { + "multi_match": { + "query": question, + "fields": ["title^2", "content", "tags^1.2", "category^1.1"], + "type": "best_fields", + "minimum_should_match": "60%", + } + }, + {"match_phrase": {"title": {"query": question, "boost": 3}}}, + {"match_phrase": {"content": {"query": question, "boost": 2}}}, + ], + "minimum_should_match": 1, + } + }, + } + + auth = None + if KBConfig.KB_ES_USERNAME and KBConfig.KB_ES_PASSWORD: + auth = (KBConfig.KB_ES_USERNAME, KBConfig.KB_ES_PASSWORD) + + async with httpx.AsyncClient(timeout=5.0, verify=KBConfig.KB_ES_VERIFY_SSL) as client: + resp = await client.post(url, json=query, auth=auth) + if resp.status_code >= 300: + raise KBESServiceError(f"ES检索失败 HTTP {resp.status_code}: {resp.text}") + data = resp.json() + + hits = (data.get("hits") or {}).get("hits") or [] + chunks: List[Dict[str, Any]] = [] + scores: List[float] = [] + for h in hits: + src = h.get("_source") or {} + score = float(h.get("_score") or 0.0) + scores.append(score) + chunks.append( + { + "doc_id": src.get("doc_id"), + "chunk_id": src.get("chunk_id"), + "title": src.get("title"), + "content": src.get("content"), + "source": src.get("source"), + "tags": src.get("tags"), + "category": src.get("category"), + "updated_at": src.get("updated_at"), + "version": src.get("version"), + "_score": score, + } + ) + + top1 = scores[0] if scores else 0.0 + top2 = scores[1] if len(scores) > 1 else 0.0 + return chunks, {"top1_score": top1, "score_gap": top1 - top2} diff --git a/ruoyi-fastapi-backend/pytest.ini b/ruoyi-fastapi-backend/pytest.ini new file mode 100644 index 0000000..2f4c80e --- /dev/null +++ b/ruoyi-fastapi-backend/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +asyncio_mode = auto diff --git a/ruoyi-fastapi-backend/requirements.txt b/ruoyi-fastapi-backend/requirements.txt index 5cd8560..b707119 100644 --- a/ruoyi-fastapi-backend/requirements.txt +++ b/ruoyi-fastapi-backend/requirements.txt @@ -19,3 +19,4 @@ SQLAlchemy[asyncio]==2.0.38 sqlglot[rs]==26.6.0 user-agents==2.2.0 openai==1.37.1 +pytest-asyncio==0.23.8 diff --git a/ruoyi-fastapi-backend/sub_applications/kb_es/create_index.py b/ruoyi-fastapi-backend/sub_applications/kb_es/create_index.py new file mode 100644 index 0000000..bfd3d66 --- /dev/null +++ b/ruoyi-fastapi-backend/sub_applications/kb_es/create_index.py @@ -0,0 +1,80 @@ +import os +from typing import Any, Dict + +import httpx + + +def _bool_env(name: str, default: bool) -> bool: + val = os.getenv(name) + if val is None: + return default + return val.strip().lower() in {"1", "true", "yes", "y", "on"} + + +def _get_settings() -> Dict[str, Any]: + es_url = os.getenv("KB_ES_URL") or os.getenv("KB_OS_URL") + if not es_url: + raise SystemExit("Missing KB_ES_URL (or KB_OS_URL)") + + return { + "es_url": es_url.rstrip("/"), + "index": os.getenv("KB_ES_INDEX", "kb_chunks_v1"), + "username": os.getenv("KB_ES_USERNAME", ""), + "password": os.getenv("KB_ES_PASSWORD", ""), + "verify_ssl": _bool_env("KB_ES_VERIFY_SSL", True), + } + + +def _mapping() -> Dict[str, Any]: + return { + "settings": { + "analysis": { + "analyzer": { + "kb_default": { + "type": "standard", + } + } + } + }, + "mappings": { + "dynamic": "true", + "properties": { + "doc_id": {"type": "keyword"}, + "chunk_id": {"type": "keyword"}, + "title": {"type": "text", "analyzer": "kb_default"}, + "content": {"type": "text", "analyzer": "kb_default"}, + "source": {"type": "keyword"}, + "tags": {"type": "keyword"}, + "category": {"type": "keyword"}, + "updated_at": {"type": "date", "ignore_malformed": True}, + "version": {"type": "keyword"}, + }, + }, + } + + +async def main() -> None: + cfg = _get_settings() + url = f"{cfg['es_url']}/{cfg['index']}" + + auth = None + if cfg["username"] and cfg["password"]: + auth = (cfg["username"], cfg["password"]) + + async with httpx.AsyncClient(timeout=10.0, verify=cfg["verify_ssl"]) as client: + head = await client.head(url, auth=auth) + if head.status_code == 200: + print(f"Index already exists: {cfg['index']}") + return + + resp = await client.put(url, json=_mapping(), auth=auth) + if resp.status_code >= 300: + raise SystemExit(f"Create index failed HTTP {resp.status_code}: {resp.text}") + + print(f"Created index: {cfg['index']}") + + +if __name__ == "__main__": + import asyncio + + asyncio.run(main()) diff --git a/ruoyi-fastapi-backend/sub_applications/kb_es/ingest_folder.py b/ruoyi-fastapi-backend/sub_applications/kb_es/ingest_folder.py new file mode 100644 index 0000000..67284f3 --- /dev/null +++ b/ruoyi-fastapi-backend/sub_applications/kb_es/ingest_folder.py @@ -0,0 +1,150 @@ +import argparse +import hashlib +import json +import os +import re +from pathlib import Path +from typing import Any, Dict, Iterable, List, Tuple + +import httpx + + +def _bool_env(name: str, default: bool) -> bool: + val = os.getenv(name) + if val is None: + return default + return val.strip().lower() in {"1", "true", "yes", "y", "on"} + + +def _cfg() -> Dict[str, Any]: + es_url = os.getenv("KB_ES_URL") or os.getenv("KB_OS_URL") + if not es_url: + raise SystemExit("Missing KB_ES_URL (or KB_OS_URL)") + + return { + "es_url": es_url.rstrip("/"), + "index": os.getenv("KB_ES_INDEX", "kb_chunks_v1"), + "username": os.getenv("KB_ES_USERNAME", ""), + "password": os.getenv("KB_ES_PASSWORD", ""), + "verify_ssl": _bool_env("KB_ES_VERIFY_SSL", True), + "version": os.getenv("KB_ES_VERSION", "v1"), + } + + +def read_text_file(path: Path) -> str: + data = path.read_text(encoding="utf-8", errors="ignore") + return data.replace("\ufeff", "").strip() + + +def iter_text_files(folder: Path, exts: Tuple[str, ...]) -> Iterable[Path]: + for p in folder.rglob("*"): + if p.is_file() and p.suffix.lower() in exts: + yield p + + +def _slug_id(s: str) -> str: + return hashlib.sha1(s.encode("utf-8")).hexdigest()[:16] + + +def chunk_text(text: str, chunk_size: int, overlap: int) -> List[str]: + text = re.sub(r"\s+", " ", text).strip() + if not text: + return [] + if chunk_size <= 0: + return [text] + overlap = max(0, min(overlap, chunk_size - 1)) + out = [] + i = 0 + while i < len(text): + out.append(text[i : i + chunk_size]) + if i + chunk_size >= len(text): + break + i = i + chunk_size - overlap + return out + + +def build_docs(file_path: Path, root: Path, *, chunk_size: int, overlap: int) -> List[Dict[str, Any]]: + rel = str(file_path.relative_to(root)).replace("\\", "/") + content = read_text_file(file_path) + title = file_path.stem + doc_id = _slug_id(rel) + + parts = chunk_text(content, chunk_size=chunk_size, overlap=overlap) + docs = [] + for idx, part in enumerate(parts): + docs.append( + { + "doc_id": doc_id, + "chunk_id": f"{doc_id}_{idx}", + "title": title, + "content": part, + "source": rel, + } + ) + return docs + + +def to_bulk_ndjson(index: str, docs: List[Dict[str, Any]], *, version: str) -> str: + lines: List[str] = [] + for d in docs: + d = dict(d) + d["version"] = version + _id = d.get("chunk_id") + lines.append(json.dumps({"index": {"_index": index, "_id": _id}}, ensure_ascii=False)) + lines.append(json.dumps(d, ensure_ascii=False)) + return "\n".join(lines) + "\n" + + +async def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--folder", required=True, help="资料目录(建议放 txt/md)") + parser.add_argument("--ext", default=".txt,.md", help="导入的扩展名,逗号分隔") + parser.add_argument("--chunk-size", type=int, default=800) + parser.add_argument("--overlap", type=int, default=120) + parser.add_argument("--batch", type=int, default=500) + args = parser.parse_args() + + cfg = _cfg() + folder = Path(args.folder).resolve() + exts = tuple([e.strip().lower() for e in args.ext.split(",") if e.strip()]) + files = list(iter_text_files(folder, exts=exts)) + if not files: + raise SystemExit(f"No files found in {folder} for exts={exts}") + + auth = None + if cfg["username"] and cfg["password"]: + auth = (cfg["username"], cfg["password"]) + + bulk_url = f"{cfg['es_url']}/_bulk" + headers = {"Content-Type": "application/x-ndjson"} + + all_docs: List[Dict[str, Any]] = [] + for f in files: + all_docs.extend(build_docs(f, folder, chunk_size=args.chunk_size, overlap=args.overlap)) + + total = len(all_docs) + print(f"Files={len(files)} Chunks={total} Index={cfg['index']}") + + async with httpx.AsyncClient(timeout=30.0, verify=cfg["verify_ssl"]) as client: + for start in range(0, total, args.batch): + batch_docs = all_docs[start : start + args.batch] + body = to_bulk_ndjson(cfg["index"], batch_docs, version=cfg["version"]) + resp = await client.post(bulk_url, content=body.encode("utf-8"), headers=headers, auth=auth) + if resp.status_code >= 300: + raise SystemExit(f"Bulk failed HTTP {resp.status_code}: {resp.text}") + payload = resp.json() + if payload.get("errors"): + first_err = None + for item in payload.get("items", []): + v = item.get("index") or item.get("create") or item.get("update") + if v and v.get("error"): + first_err = v.get("error") + break + raise SystemExit(f"Bulk had errors: {first_err}") + print(f"Ingested {min(start + len(batch_docs), total)}/{total}") + + +if __name__ == "__main__": + import asyncio + + asyncio.run(main()) diff --git a/ruoyi-fastapi-backend/sub_applications/opensearch/docker-compose.yml b/ruoyi-fastapi-backend/sub_applications/opensearch/docker-compose.yml new file mode 100644 index 0000000..9b15197 --- /dev/null +++ b/ruoyi-fastapi-backend/sub_applications/opensearch/docker-compose.yml @@ -0,0 +1,28 @@ +services: + opensearch: + image: opensearchproject/opensearch:2 + container_name: kb-opensearch + environment: + - discovery.type=single-node + - bootstrap.memory_lock=true + - OPENSEARCH_JAVA_OPTS=-Xms1g -Xmx1g + - plugins.security.disabled=true + ulimits: + memlock: + soft: -1 + hard: -1 + nofile: + soft: 65536 + hard: 65536 + volumes: + - opensearch-data:/usr/share/opensearch/data + ports: + - "9200:9200" + healthcheck: + test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:9200 >/dev/null || exit 1"] + interval: 10s + timeout: 5s + retries: 20 + +volumes: + opensearch-data: diff --git a/ruoyi-fastapi-backend/test.md b/ruoyi-fastapi-backend/test.md new file mode 100644 index 0000000..32aa2f0 --- /dev/null +++ b/ruoyi-fastapi-backend/test.md @@ -0,0 +1,589 @@ +# 康达新材Q&A清单 + +一、公司概况类 +1.康达新材料(集团)股份有限公司成立于哪一年? +•答案:1988年 +2.康达新材料(集团)股份有限公司的上市时间是什么时候? +•答案:2012年4月 +3.康达新材料(集团)股份有限公司的股票代码是什么? +•答案:[002669.SZ](http://002669.sz/) +4.康达新材的实际控制人是谁? +•答案:唐山市人民政府国有资产监督管理委员会 +5.康达新材的股权背景链条是怎样的? +•答案:唐山市人民政府国有资产监督管理委员会→唐山控股发展集团股份有限公司→唐山工业控股集团有限公司→康达新材料(集团)股份有限公司 +6.康达新材的三大业务板块是什么? +•答案:胶粘剂与特种树脂新材料、电子信息材料和电子科技 +7.康达新材的业务范围覆盖哪些新兴产业? +•答案:装备制造、新能源、轨道交通、航空航天、电子信息及低碳环保等新兴产业 +8.截至2024年底,康达新材的总资产是多少? +•答案:69.40亿元 +9.康达新材2024年的营业收入是多少? +•答案:31亿元 +10.截至2024年底,康达新材的员工总人数有多少? +•答案:1637人 +11.康达新材的管理总部位于哪里? +•答案:浦东新区张江科学城 +12.康达新材在国内哪些地区设有子公司、生产基地或分支机构? +•答案:上海、北京、天津、深圳、大连、成都、香港、福建、唐山等地 +13.康达新材的海外布局有多少处? +•答案:3处 +14.康达新材有多少个生产基地? +•答案:7个 +15.康达新材的胶粘剂产品牌号数有多少? +•答案:500+ +16.康达新材在多个细分领域的市场地位如何? +•答案:多项细分领域市场地位第一 +17.康达新材的使命是什么? +•答案:服务国家战略,引领行业发展 +18.康达新材的愿景是什么? +•答案:成为具有全球化视野的“新材料+电子科技”集团化企业 +19.康达新材的核心价值观是什么? +•答案:融合、协同、创新、超越 +20.康达新材的企业作风是什么? +•答案:不断学习,立即行动,没有任何借口 +21.康达新材的发展战略核心是什么? +•答案:坚守“稳中求进”工作总基调,以科技创新为根本动力,以转型升级、抢占新赛道为基本路径,以高质量发展为主题,立足主业,打造“新材料+电子科技”国家级“专精特新”小巨人企业集群 +22.康达新材“1+2+3+1+N”战略布局中的“1个产业研究院”具体定位是什么? +•答案:产业发展智库,科技成果研发、转化合作平台,技术信息共享中心 +23.康达新材“1+2+3+1+N”战略布局中的“2个战略新兴产业方向”是什么? +•答案:新材料+电子科技 +24.康达新材“1+2+3+1+N”战略布局中的“3大业务板块”具体指什么? +•答案:胶粘剂与特种树脂新材料+电子信息材料+电子科技 +25.康达新材“1+2+3+1+N”战略布局中的“1+N个产业园(基地)”已在哪些地区建立? +•答案:上海、北京、成都、天津、大连、深圳、唐山、福建邵武、香港、泰国等地 +26.康达新材在新材料领域的进口替代目标涉及哪些领域? +•答案:半导体材料、显示材料、生物医用材料、新能源材料、高性能纤维、高性能膜材料、先进高分子材料及其他领域 +27.康达新材在半导体材料领域的进口依赖材料有哪些? +•答案:大尺寸硅材料、大尺寸碳化硅单晶、SOI、高饱和度光刻胶、高性能靶材、电子特种气体、湿电子化学品、氮化镓单晶/氮化镓单晶衬底、化学机械抛光(CMP)材料、封装基板、高密度陶瓷材料等 +28.康达新材在显示材料领域的进口依赖材料有哪些? +•答案:OLED发光材料、超薄玻璃、高世代线玻璃基板、精细金属掩模板(FMM)、光学膜、柔性PI膜、偏光片、高性能水汽阻隔膜、异方性导电胶膜(ACF)、特种光学聚氨酯膜(PET)、OCA光学胶、微球、抗指纹涂层(AFC)涂料等 +29.康达新材在生物医用材料领域的进口依赖材料有哪些? +•答案:医用级钛粉与镍钛合金粉、苯乙烯类热塑性弹性体、医用级聚乳酸、碲锌镉晶体、人工晶状体等 +30.康达新材在新能源材料领域的进口依赖材料有哪些? +•答案:硅碳负极材料、电解铜箔、电解液添加剂、铝塑膜、质子交换膜、氢燃料电池催化剂、气体扩散层材料等 +31.康达新材在高性能纤维领域的进口依赖材料有哪些? +•答案:高性能碳纤维及其复合材料、高性能对位芳纶纤维及其复合材料、超高分子量聚乙烯纤维等 +32.康达新材在高性能膜材料领域的进口依赖材料有哪些? +•答案:海水淡化反渗透膜、陶瓷膜、离子交换膜、中空纤维膜、高导热石墨膜等 +33.康达新材在先进高分子材料领域的进口依赖材料有哪些? +•答案:聚苯硫醚(PPS)、聚砜(PSF)、聚醚醚酮(PEEK)、聚偏氯乙烯(PVDF)、聚甲醛(POM)、有机硅等 +34.康达新材在“其他”领域的进口依赖材料有哪些? +•答案:高频覆铜板基材、液晶高分子聚合物(LCP)等 +35.康达新材及下属子公司中获得国家级“专精特新”小巨人企业认定的有哪些? +•答案:大连齐化新材料有限公司(有效期:2022年7月1日至2025年6月30日)、成都必控科技有限责任公司、上海晶材新材料科技有限公司、康达新材料(集团)股份有限公司 +36.北京力源兴达科技有限公司获得了什么级别的“专精特新”相关认定? +•答案:北京市专精特新“小巨人”及中小企业 +37.成都赛英科技有限公司获得了什么级别的“专精特新”相关认定?有效期是多久? +•答案:四川省专精特新中小企业,有效期为2022年11月1日-2025年10月31日 +38.上海理日新材料科技有限公司获得了什么级别的“专精特新”相关认定? +•答案:上海市专精特新中小企业 +39.大连齐化获得了哪些资质认证? +•答案:2020年12月被科技部火炬中心认定为国家级“高新技术企业”,被辽宁省科技厅认定为“雏鹰企业”;2023年5月,质检中心被中国石油和化学工业联合会认定为“A级”石油和化工企业质量检验机构;通过ISO9001、ISO14001、ISO45001三体系认证 +40.河北惟新科技有限公司获得了什么资质?认定编号和有效期是多少? +•答案:河北省科技型中小企业,认定编号:K23016712,有效期3年 +二、历史发展类 +1.康达新材在1988年发生了什么关键事件? +•答案:成立上海康达化工实验厂 +2.康达新材在2000年发生了什么关键事件? +•答案:成立上海康达化工技术研究所 +3.康达新材在2002年完成了哪两项重要事件? +•答案:引入战略投资者,完成股份制改革,整体变更设立为上海康达化工新材料股份有限公司;获得中国合格评定国家认可委员会(CNAS)认可的实验室资质 +4.康达新材在2008年发生了什么关键事件? +•答案:成立上海康达化工有限责任公司;被认定为国家高新技术企业 +5.康达新材在2012年发生了什么关键事件? +•答案:成功在深圳交易所上市 +6.康达新材在2016年发生了什么关键事件? +•答案:非公开发行股票融资8.5亿元,助推康达新材高质量发展 +7.2018年康达新材收购了成都赛英科技有限公司,此次收购的目的是什么? +•答案:完善电子科技领域产业链布局 +8.2018年康达新材收购了上海晶材新材料科技有限公司,此次收购的目的是什么? +•答案:布局先进陶瓷新材料领域,打造“专精特新”小巨人企业集群 +9.2018年康达新材收购了大连齐化新材料有限公司,此次收购的目的是什么? +•答案:向产业链上游战略延伸,打造环氧树脂产业闭环“生态圈” +10.2019年康达新材收购了成都必控科技有限责任公司,此次收购的意义是什么? +•答案:成功迈入电子科技领域 +11.康达新材的研发中心被认定为国家企业技术中心是在哪一年? +•答案:2019年 +12.康达新材的控股股东变更为唐山工业控股集团有限公司是在哪一年? +•答案:2019年 +13.康达新材更名为“康达新材料(集团)股份有限公司”是在什么时间? +•答案:2021年 +14.康达新材在2021年进行集团化战略布局的范围是什么? +•答案:全国区域 +15.康达新材被认定为国家级“专精特新”小巨人企业是在哪一年? +•答案:2021年 +16.2022年康达新材收购了西安彩晶光电科技股份有限公司,此次收购进入了哪些领域? +•答案:电子信息材料、新能源材料、医药领域,打造新材料“第二增长极” +17.康达新材收购河北惟新科技有限公司是在哪一年? +•答案:2023年 +18.康达新材收购河北惟新科技有限公司解决了什么“卡脖子”问题? +•答案:大尺寸ITO靶材“卡脖子”问题 +三、生产基地与研发中心类 +1.奉贤研发及生产基地(上海)的位置在哪里? +•答案:上海奉贤杭州湾经济技术开发区 +2.奉贤研发及生产基地(上海)的占地面积是多少? +•答案:205亩 +3.奉贤研发及生产基地(上海)的建设历程是怎样的? +•答案:包含一期、二期、三期项目,2009年一期建成投产,2015年二期建成投产,2019年三期建成投产 +4.奉贤研发及生产基地(上海)的定位是什么? +•答案:遵循高端化、数字化、一体化、绿色生态的发展理念,打造胶粘剂、复合材料等新材料产业链;奉贤基地二期全面启用后,形成集科研开发、生产、服务、贸易为一体的科研产业综合实体 +5.奉贤研发及生产基地(上海)的胶粘剂产品产能是多少? +•答案:环氧结构胶、环氧基体树脂、丙烯酸胶、聚氨酯胶、丁基胶等胶粘剂产品产能80000吨 +6.奉贤研发及生产基地(上海)的复合材料产能是多少? +•答案:环氧基、聚氨酯基复合材料10000吨 +7.唐山丰南生产基地(河北)的位置在哪里? +•答案:河北省唐山市丰南经济开发区化工产业园区 +8.唐山丰南生产基地(河北)的占地面积是多少? +•答案:114亩 +9.唐山丰南生产基地(河北)的主要产品有哪些? +•答案:水性聚氨酯丙烯酸酯分散体、无溶剂复膜胶、改性型溶剂聚氨酯复膜胶、改性橡胶树脂溶剂胶、高性能丙烯酸酯结构胶、硅胶、环氧树脂胶 +10.唐山丰南生产基地(河北)辐射哪些市场? +•答案:北方市场 +11.唐山丰南生产基地(河北)的产品应用于哪些领域? +•答案:风电叶片制造、包装材料、轨道交通、汽车、电子电器、机械设备、建筑等领域(含在建基地) +12.福建邵武生产基地的位置在哪里? +•答案:福建省邵武市金塘省级化工园区 +13.福建邵武生产基地的占地面积是多少? +•答案:95亩 +14.福建邵武生产基地的主要产品有哪些? +•答案:功能型复膜胶、改性弹性树脂胶黏剂、高性能结构胶、功能性聚酯及反应型热熔胶、特种树脂中间体 +15.福建邵武生产基地辐射哪些市场? +•答案:南方市场 +16.福建邵武生产基地的产品应用于哪些领域? +•答案:包装材料、电子电器、机械设备、建筑装饰及工业维修等领域 +17.福建邵武生产基地的战略意义是什么? +•答案:为核心产品提供重要原材料(含在建基地) +18.大连齐化产业园区的位置在哪里? +•答案:辽宁省大连经济技术开发区 +19.大连齐化产业园区的主要产品系列有哪些? +•答案:双酚A型环氧树脂、耐热型环氧树脂和特种环氧树脂三大系列 +20.大连齐化产业园区的具体产品包含哪些? +•答案:双酚A型液体环氧树脂、双酚A型固体环氧树脂、溴化环氧树脂、邻甲酚醛环氧树脂、苯酚酚醛环氧树脂、双酚F型环氧树脂、苯氧基树脂、环氧固化剂、环氧稀释剂等多个品种 +21.大连齐化产业园区液态环氧树脂的装置数量、产能和实际产能分别是多少? +•答案:装置数量1套,产能3.4万吨,实际产能4.3万吨 +22.大连齐化产业园区固态环氧树脂的装置数量、产能和实际产能分别是多少? +•答案:装置数量1套,产能0.5万吨,实际产能1.5万吨 +23.大连齐化产业园区邻甲酚醛环氧树脂的装置数量和产能分别是多少? +•答案:装置数量1套,产能0.5万吨 +24.大连齐化产业园区特种树脂中试装置的数量和产能分别是多少? +•答案:装置数量1套,产能1000吨 +25.大连齐化产业园区的战略定位是什么? +•答案:重点布局下游产业链,打造环氧树脂产业闭环“生态圈”,以大连齐化的低溴环氧树脂技术为依托,开展覆铜板领域的战略布局 +26.成都康达电子科技产业园的位置在哪里? +•答案:四川省成都市高新区未来科技城 +27.成都康达电子科技产业园的占地面积是多少? +•答案:64亩 +28.成都康达电子科技产业园的定位是什么? +•答案:打造电子科技领域系统级产品研发、生产科技创新园区,使之成为四川乃至西南地区具有比较优势的电子信息特色产业园,推动地方战略性新兴产业的发展,助力公司电子科技核心业务做强做精 +29.康达新材的胶粘剂研发中心拥有哪些资质平台? +•答案:国家级的企业技术中心、上海胶粘剂工程技术研究中心、上海市企业技术中心、企业博士后科研工作总站、国家认定博士后科研工作站(人力资源和社会保障部、全国博士后管委会制发)、国家认可委认可的CNAS国家实验室、德国劳氏船级社(GL)中国认可检测中心 +30.康达新材胶粘剂研发中心的技术型研发占比多少?主要内容是什么? +•答案:占比25%,主要内容是引进项目的消化、吸收 +31.康达新材胶粘剂研发中心的产品型研发占比多少?主要内容是什么? +•答案:占比50%,主要内容是现有产品的再创新;中试生产及工艺优化 +32.康达新材胶粘剂研发中心的关键技术研发占比多少?主要内容是什么? +•答案:占比20%,主要内容是关键原材料及高端技术研发,为产品持续的市场竞争力提供支持 +33.康达新材胶粘剂研发中心的技术服务型研发占比多少?主要内容是什么? +•答案:占比5%,主要内容是与目前的板块服务相对应 +四、财务数据类 +1.康达新材2021年的营业收入是多少? +•答案:27.93亿元 +2.康达新材2021年归属于上市公司股东的净利润是多少? +•答案:-2.46亿元 +3.康达新材2023年的营业收入是多少? +•答案:8.77亿元 +4.康达新材2023年归属于上市公司股东的净利润是多少? +•答案:0.06亿元 +5.康达新材2024年的营业收入是多少? +•答案:31.01亿元 +6.康达新材2024年归属于上市公司股东的净利润是多少? +•答案:0.30亿元 +7.2022年康达新材的研发人员数量是多少? +•答案:376人 +8.2023年康达新材的研发人员数量是多少? +•答案:378人 +9.2024年康达新材的研发人员数量是多少? +•答案:486人 +10.2022-2024年康达新材的研发人员数量呈现什么趋势? +•答案:呈线性增长趋势 +11.2022年康达新材的研发费用是多少? +•答案:1.26亿元 +12.2023年康达新材的研发费用是多少? +•答案:1.78亿元 +13.2024年康达新材的研发费用是多少? +•答案:2.04亿元 +14.2022-2024年康达新材的研发费用呈现什么趋势? +•答案:呈线性增长趋势 +15.2024年康达新材的研发人员数量占比是多少? +•答案:22.97% +16.2024年康达新材的非研发人员占比是多少? +•答案:77.03% +17.2024年康达新材的研发支出占营业收入总额的比例是多少? +•答案:6.56% +18.2024年康达新材的非研发支出占比是多少? +•答案:93.44% +19.康达新材2021年的总资产是多少? +•答案:37.28亿元 +20.康达新材2022年的总资产是多少? +•答案:53.33亿元 +21.康达新材2023年的总资产是多少? +•答案:69.40亿元 +22.康达新材2024年的总资产是多少? +•答案:71.23亿元 +23.康达新材2021年的净资产是多少? +•答案:22.91亿元 +24.康达新材2022年的净资产是多少? +•答案:29.06亿元 +25.康达新材2023年的净资产是多少? +•答案:31.43亿元 +26.康达新材2024年的净资产是多少? +•答案:33.09亿元 +五、业务介绍类 +(一)胶粘剂与特种树脂新材料 +1.康达新材的胶粘剂与特种树脂新材料业务国内布局包含哪些企业? +•答案:河北惟新科技、北京康达晟珊、北京力源兴达、唐山南区康达化工新材有限公司、康达国际供应链(天津)有限公司、大连齐化新材、上海康达新材料科技有限公司、上海理日新材、上海晶材科技、福建南平天宇、成都必控科技、福建康达鑫宇新材料有限公司、成都赛英科技、深圳康达电子材料研发有限公司、上海康达新材(香港)有限公司 +2.康达新材的胶粘剂与特种树脂新材料业务海外研发办事处设在哪些国家? +•答案:德国、日本 +3.康达新材的胶粘剂与特种树脂新材料业务海外营销办事处设在哪个国家? +•答案:印尼 +4.康达新材的胶粘剂与特种树脂新材料业务海外生产基地设在何处? +•答案:泰国曼谷 +5.康达新材的胶粘剂与特种树脂新材料业务海外覆盖哪些区域? +•答案:德国、丹麦、西班牙、日本、东南亚、巴西等 +6.胶粘剂与特种树脂新材料业务的产品体系包含多少大系列? +•答案:8大产品系列 +7.胶粘剂与特种树脂新材料业务的产品体系包含多少种规格型号? +•答案:500多种 +8.胶粘剂与特种树脂新材料业务的核心应用领域有哪些? +•答案:风力发电、光伏太阳能、轨道交通、航空航天、海洋船舶工程、软包装复合、橡塑制品、建筑工程、电子电器、汽摩配件、电机电梯、矿业设备、工业维修、新能源汽车、工程维护、高端制造等 +9.康达新材在风电领域的核心产品有哪些? +•答案:风电胶粘剂系列产品(结构胶、灌注树脂、胶条、喷胶) +10.2024年康达新材风电领域的销售量是多少? +•答案:近9万吨 +11.2024年康达新材风电领域的销售额(不含税)是多少? +•答案:约16亿元 +12.康达新材风电领域的客户覆盖范围是什么? +•答案:国内所有叶片厂商及其基地,同时产品向海外市场推广 +13.康达新材风电领域的核心客户有哪些? +•答案:三一重能、中国中材、中国国电、艾郎科技、中科宇能、远景能源、金风科技、时代新材、上玻院、双瑞风电、上海电气、天顺风能、LM艾尔姆、明阳电气、MENS Gamesa等 +14.康达新材在风电领域的市场地位如何? +•答案:市场占有率第一,自主产品保持品牌技术领先、行业领导地位 +15.康达新材在复膜胶领域的市场份额是多少? +•答案:25% +16.复膜胶领域中,法国波士胶的市场份额是多少? +•答案:25% +17.复膜胶领域中,德国汉高的市场份额是多少? +•答案:20% +18.复膜胶领域中,其他30余家企业的市场份额总和是多少? +•答案:30% +19.康达新材在复膜胶领域的市场地位如何? +•答案:市场占有率第一 +20.2024年康达新材复膜胶领域的国内客户数量是多少? +•答案:700家 +21.2024年康达新材复膜胶领域的销售额是多少? +•答案:约3.2亿元 +22.康达新材复膜胶领域的销售额年增长率是多少? +•答案:10-15% +23.康达新材复膜胶领域的软包装复合胶包含哪些类型? +•答案:溶剂型聚氨酯复膜胶、无溶剂聚氨酯复膜胶 +24.康达新材复膜胶领域的硬包装胶包含哪些类型? +•答案:外包装热熔胶、标签快递单压敏胶等 +25.康达新材复膜胶领域的能量固化印刷油墨包含哪些类型? +•答案:UV胶印油墨、UV柔版光油等 +26.康达新材复膜胶领域的可降解材料包含哪些类型? +•答案:酪蛋白标签胶、环保餐具涂层 +27.康达新材复膜胶领域的发展规划有哪些? +•答案:传统替代、新品攻关(UV油墨、可降解材料)、专业化服务、节能环保、对接“一带一路” +28.康达新材在轨道交通领域的核心产品有哪些? +•答案:双组份丙烯酸酯结构胶、单组份结构胶、UV胶、PUR、双组份环氧胶、单组份环氧胶、瞬干胶等系列化产品(可根据客户需求个性化调整) +29.康达新材轨道交通领域重点布局哪些行业? +•答案:消费电子产品组装行业、微型扬声器行业、安防行业、动力电池行业 +30.康达新材轨道交通领域在消费电子行业的核心客户有哪些? +•答案:联想、华硕、罗技、韶音 +31.康达新材轨道交通领域的产品在消费电子行业应用于哪些产品组装? +•答案:智能手机、笔记本/平板电脑、穿戴设备、无人机、投影仪组装 +32.康达新材轨道交通领域在微型扬声器行业的核心客户有哪些? +•答案:瑞声、歌尔、豪声、上声 +33.康达新材轨道交通领域在安防行业的核心客户有哪些? +•答案:海康威视、大华 +34.康达新材轨道交通领域针对高铁/地铁的产品有哪些? +•答案:地板胶、阻尼浆、阻尼贴片 +35.康达新材轨道交通领域针对高铁/地铁的产品取得了哪些进展? +•答案:阻尼贴片材料通过中车系统检测验证,地板胶阻尼浆在中车系统重点客户试装成功 +36.康达新材通用工业领域的销售渠道以什么为主? +•答案:经销商销售渠道 +37.康达新材通用工业领域重点覆盖哪些市场? +•答案:通用工业(工业装配维修、扬声器、电梯、电机、结构胶等各类工业应用)、流通市场(民用及家装DIY、电商) +38.康达新材的汽车胶粘剂用于哪些场景? +•答案:汽车厢体、车门的结构粘接及汽车内饰件粘接 +39.康达新材的汽车胶粘剂能为客户提供什么优化方案? +•答案:汽车轻量化和减少VOCs排放的优化方案 +40.康达新材家电用胶粘剂的既定目标客户有哪些? +•答案:格力、美的等国内知名家电企业 +41.康达新材家电用胶粘剂目前已涉及美的哪些产品应用? +•答案:美的厨热、冰箱和家用空调等产品 +42.康达新材家电用胶粘剂未来的拓展计划是什么? +•答案:拓展至中央空调、洗衣机等其他生活电器 +(二)核心子公司业务详情(胶粘剂与特种树脂新材料板块) +1.大连齐化新材料有限公司的成立时间是什么时候? +•答案:起步于2001年 +2.大连齐化新材料有限公司的企业定位是什么? +•答案:以生产销售高品质环氧树脂为主,集新材料研制、开发、生产、销售、服务、转让为一体的综合性高新技术化工企业 +3.大连齐化新材料有限公司的产品体系包含哪些系列? +•答案:双酚A型环氧树脂、耐热型环氧树脂和特种环氧树脂三大系列 +4.大连齐化新材料有限公司的具体产品有哪些? +•答案:双酚A型液体环氧树脂、双酚A型固体环氧树脂、溴化环氧树脂、邻甲酚醛环氧树脂、苯酚酚醛环氧树脂、双酚F型环氧树脂、苯氧基树脂、环氧固化剂、环氧稀释剂等 +5.大连齐化新材料有限公司的产品质量指标有什么优势? +•答案:各项指标均优于中国国家标准GB/T 13657-2011《双酚A型环氧树脂》,通过RoSH和REACH认证,双酚A型环氧树脂被辽宁省工信厅认定为“专精特新”产品并获欧洲化学品管理局(ECHA)“REACH注册证书” +6.大连齐化新材料有限公司拥有多少项实用新型专利? +•答案:21项 +7.大连齐化新材料有限公司拥有多少项软件著作权? +•答案:20项 +8.大连齐化新材料有限公司正在申请多少项发明专利? +•答案:2项 +9.大连齐化新材料有限公司通过了哪些体系认证? +•答案:ISO9001、ISO14001、ISO45001三体系认证 +10.大连齐化新材料有限公司获得国家级“高新技术企业”认定的时间是什么时候? +•答案:2020年12月 +11.大连齐化新材料有限公司获得辽宁省科技厅“雏鹰企业”认定的时间是什么时候? +•答案:2020年12月 +12.大连齐化新材料有限公司获得国家级“专精特新”小巨人企业认定的时间是什么时候? +•答案:2021年7月 +13.大连齐化新材料有限公司的销售模式是什么? +•答案:直销模式 +14.南平天宇实业被康达新材收购的时间是什么时候? +•答案:2021年4月 +15.南平天宇实业的占地面积是多少? +•答案:33000㎡ +16.南平天宇实业的年产产值是多少? +•答案:近6000万元 +17.南平天宇实业的生产工艺技术水平如何? +•答案:国际领先水平 +18.南平天宇实业的产品定位是什么? +•答案:亚洲国家“水性商标胶粘剂”生产量最大、生产品种最多、工艺技术最领先的绿色环保水基性商标胶粘剂生产企业 +19.南平天宇实业的“远洋牌”水性系列商标胶有什么特点? +•答案:质量超越国际领先水平,性能稳定 +20.南平天宇实业的“远洋牌”封箱专用热熔胶有什么特点? +•答案:可塑性粘合剂,无毒、无味,属绿色环保型产品,物理状态随温度改变(化学特性不变) +21.南平天宇实业的“远洋牌”水性纸制品胶粘剂的原材料是什么? +•答案:优质高分子材料PVA、VAC、改性淀粉共聚合成 +22.南平天宇实业的“远洋牌”水性纸制品胶粘剂有什么特点? +•答案:无毒性、无公害、无腐蚀性及氧化性,符合欧美最新标准,为食品级纸制品行业不可替代的绿色环保产品 +23.南平天宇实业的核心客户有哪些? +•答案:百威啤酒、珠江啤酒、青岛啤酒、雪花啤酒、达利食品集团等 +24.上海理日新材料的技术地位如何? +•答案:成熟的研发创新能力,技术和产品保持国内领先、国际先进水平,国内市场品牌影响和占有率仅次于德国汉高等顶级跨国公司,产品已通过多家著名跨国公司认证并供货 +25.上海理日新材料的应用领域有哪些? +•答案:汽车部件、电子电缆、衣饰、制鞋、建材、装饰、包装以及低压注塑等行业 +26.上海理日新材料服务多少家客户? +•答案:数百家 +27.上海理日新材料的环保型高性能热熔胶包含哪些类型? +•答案:聚酯、聚酰胺热熔胶 +28.上海理日新材料的环保型高性能热熔胶有什么特点? +•答案:不含化学溶剂、无污染,粘接强度高,耐油耐化学性好,粘接材料广泛 +29.上海理日新材料的环保型高性能热熔胶应用于哪些企业? +•答案:汽车滤清器生产企业、制鞋服饰企业、电子电缆生产企业 +30.上海理日新材料的环保型低压注塑封装材料的目标市场是什么? +•答案:电缆电子行业中的电池板生产企业、线束插头/微型开关生产企业、连接器/传感器生产企业和PCB线路板生产企业 +31.四氢呋喃可作为上海理日新材料产品的哪些应用领域? +•答案:合成树脂的工业溶剂、合成革的表面处理剂、合成农药等领域 +(三)高性能复合材料方向 +1.康达新材在高性能复合材料方向的战略定位是什么? +•答案:聚焦玻璃纤维、碳纤维的环氧树脂、聚氨酯类复合材料领域,计划通过2-3年深化拓展;基于自身工艺和技术积累,通过投资业务契合度企业、技术创新、加大研发投入等方式,切入先进复合材料领域,推动向高分子新材料领域战略转型 +2.康达新材落实先进复合材料转型战略的关键举措是什么? +•答案:对聚发新材进行增资 +3.拉挤树脂(Urepul)的基础材料是什么? +•答案:玻纤&碳纤 +4.拉挤树脂(Urepul)的应用领域有哪些? +•答案:桥架、绝缘梯、桥梁、高压管道、景观工程、高铁、轨道、光伏 +5.SMC/预浸料(Urepreg)的基础材料是什么? +•答案:玻纤&碳纤 +6.SMC/预浸料(Urepreg)的应用领域有哪些? +•答案:汽车、轨道交通、电子产品 +7.RTM树脂(Urethan)的基础材料是什么? +•答案:玻纤&碳纤 +8.RTM树脂(Urethan)的应用领域有哪些? +•答案:风电叶片、汽车内外饰、高铁 +9.缠绕树脂(Urecom)的基础材料是什么? +•答案:玻纤&碳纤 +10.缠绕树脂(Urecom)的应用领域有哪些? +•答案:电线杆、传动轴 +11.截至2025年8月,康达新材高性能复合材料已实现量产应用的产品有哪些? +•答案:电缆桥架、高铁设备舱及裙板、新能源车电池盒骨架 +12.截至2025年8月,康达新材高性能复合材料技术开发完成的产品有哪些? +•答案:汽车型材、托盘、高铁步道板、绝缘臂腕 +13.截至2025年8月,康达新材高性能复合材料处于开发中的产品有哪些? +•答案:太阳能边框及支架、铁路鱼尾板 +14.康达新材的高性能复合材料在性能方面有什么优势? +•答案:出色的机械性能,与玻纤完美结合,更高的粘接强度,优异的抗疲劳性能 +15.康达新材的高性能复合材料在生产效率方面有什么优势? +•答案:更快的灌注速度,更快的固化速度 +16.康达新材的高性能复合材料在综合成本方面有什么优势? +•答案:模具投入少,具有竞争力的树脂价格 +(四)电子信息材料 +1.惟新科技被康达新材收购的时间是什么时候? +•答案:2023年 +2.惟新科技的企业定位是什么? +•答案:国家高新技术企业,致力于研发和生产高端ITO(氧化铟锡)靶材及ITO粉体、ITO气氛烧结炉等ITO周边产品,解决大尺寸ITO靶材“卡脖子”问题 +3.惟新科技的核心技术有哪些? +•答案:与清华大学化工系合作,研发微反应器制备高活性纳米ITO粉体技术;自主研发适合超大尺寸靶材制备的湿法注浆成型技术,拥有自主知识产权的超大空间高温烧结炉 +4.惟新科技成功制备的大尺寸ITO靶材规格是多少? +•答案:1550x250mm +5.惟新科技制备的大尺寸ITO靶材性能指标如何? +•答案:与国际同类靶材相当,达到国际先进水平,填补国内空白 +6.惟新科技的战略意义有哪些? +•答案:打破技术壁垒,减少高端靶材进口依赖,助力中美贸易战应对;推动我国铟资源高效利用;填补我国ITO靶材在中、高端平板显示器制造领域应用空白;促进ITO靶材平板生产工艺研发与人才培养 +7.惟新科技的ITO靶材试验线设备升级改造完成时间是什么时候? +•答案:2022年 +8.惟新科技目前的产能提升目标是什么? +•答案:推进30T/年产能提升工作 +9.惟新科技的溅射靶材下游覆盖哪些领域? +•答案:平板显示器、光伏电池、记录媒体、半导体等领域 +10.平板显示器领域在惟新科技溅射靶材下游应用中占比多少? +•答案:33.8% +11.记录媒体领域在惟新科技溅射靶材下游应用中占比多少? +•答案:28.6% +12.半导体领域在惟新科技溅射靶材下游应用中占比多少? +•答案:18.5% +13.晶材科技成立于哪一年? +•答案:2016年 +14.晶材科技被康达新材收购的时间是什么时候? +•答案:2018年 +15.晶材科技的企业定位是什么? +•答案:专注于高端电子陶瓷材料国产化,集研发与规模化生产于一体,提供先进材料解决方案,代理德国瓦克化学有机硅产品 +16.晶材科技的技术路线是什么? +•答案:微晶玻璃体系、微晶玻璃/陶瓷复合体系 +17.晶材科技在行业中的地位如何? +•答案:目前市场唯一批量供货的国产生料带供应商,解决“卡脖子”难题 +18.晶材科技的自研产品有哪些? +•答案:陶瓷生料带、贵金属浆料、瓷粉、导电胶 +19.晶材科技的自研产品应用于什么技术领域? +•答案:低温共烧陶瓷技术(LTCC)的电子元器件、电路模块制造和封装 +20.晶材科技的代理产品是什么? +•答案:有机硅水胶(车载显示器光学全贴合重要原材料) +21.晶材科技的产品在军用领域有什么应用? +•答案:有源相控阵雷达 +22.晶材科技的产品在民用领域有什么应用? +•答案:高端半导体传感器、光通信器件、汽车电子电路模块、5G路由器、5G机房、数据中心等 +23.晶材科技的生料带现有产能是多少? +•答案:240万片/年 +24.晶材科技的贵金属浆料现有产能是多少? +•答案:30吨/年 +(五)电子科技——赛英科技 +1.赛英科技被康达新材收购的时间是什么时候? +•答案:2018年 +2.赛英科技的地理位置在哪里? +•答案:成都龙潭新经济产业功能区 +3.赛英科技的科研生产基地规模是多少? +•答案:8000余平方米 +4.赛英科技的核心业务是什么? +•答案:科研、生产整机雷达、微波组件、专用仪器仪表、海洋通讯产品 +5.赛英科技在安全管控领域的产品有哪些? +•答案:近程警戒雷达(地面、海面)、周界安全警戒雷达(系列产品) +6.赛英科技在交通监测领域的产品有哪些? +•答案:FOD跑道异物监测雷达(塔架、移动) +7.赛英科技在工业监测领域的产品有哪些? +•答案:高炉料面监测雷达、微位移监测雷达(应用于大型钢厂、铁路大坝等重要区域) +8.赛英科技获得了哪些企业荣誉? +•答案:成都市企业技术中心、成都市成华区2020年度“工业纳税先进单位”、四川省“专精特新”中小企业、成都市科技装备业商会“副会长单位”等 +六、康达新材日常聊天类 +1.你能给康达新材创作一首诗吗? +•答案:匠心三十载耕耘,新材科技破卡颈胶黏万物兴百业,引领制造向前行 +2.如果用三个关键词概括康达新材的核心气质,你觉得会是什么?为什么? +•答案:硬核(深耕“卡脖子”技术)、创新(研发投入持续增长)、多元(覆盖多业务板块) +3.假设你要给朋友介绍康达新材,会用一段轻松有趣的话怎么说? +•答案:这家1988年成立的国企超牛!能做粘风电叶片的“超级胶水”,还搞定高端ITO靶材,业务横跨新材料和电子科技,妥妥的科技实力派~ +4.要是给康达新材设计一句接地气的宣传语,不局限于官方口号,你会怎么写? +•答案:康达新材,粘得牢、造得硬,让中国制造更靠谱! +5.想象康达新材要举办一场员工家庭日活动,你觉得可以设计哪些有趣的互动环节? +•答案:①亲子胶粘剂组装模型;②VR参观生产基地答题;③家庭新材料元素手作赛 +6.如果你是康达新材的一名新员工,入职第一天最想了解公司的哪三个方面? +•答案:①岗位与核心业务的关联;②研发创新支持政策;③培训与晋升路径 +7.用拟人化的手法描述康达新材,你会把它比作哪种形象?理由是什么? +•答案:比作“实干工匠”,30余年坚守行业,身怀多元技能,持续突破技术难题 +8.康达新材涉及多个业务板块,要是用一种美食比喻它的业务布局,你觉得像什么?为什么? +•答案:像“什锦火锅”,胶粘剂是锅底,各业务板块是食材,协同发力丰富产业生态 +9.要是给康达新材的研发人员写一句鼓励的话,你会怎么表达? +•答案:以匠心攻技术,以创新破壁垒,为中国智造添力! +10.假设康达新材要出一款周边产品送给客户,你觉得什么类型的产品既实用又能体现公司特色? +•答案:定制多功能粘接工具套装,含迷你环保胶粘剂、ITO靶材图案书签 +11.用一句话形容康达新材从1988年成立到现在的发展历程,你会怎么说? +•答案:从化工实验厂到“新材料+电子科技”集团,30余年坚守创新,领跑细分领域 +12.如果你要在社交平台分享康达新材的一则动态,配文会怎么写才能吸引关注? +•答案:国企硬核!粘风电叶片、破ITO靶材“卡脖子”,覆盖新能源、电子科技,为中国制造打call~#康达新材 #国货之光 +13.康达新材在多个地区有生产基地,要是选一个基地推荐给朋友参观,你会选哪个?理由是什么? +•答案:选上海奉贤基地,集研发生产于一体,能直观看到产品从研发到量产的全过程 +14.要是给康达新材设计一个吉祥物,你觉得它的外形和寓意应该是什么样的? +•答案:蓝色分子造型小精灵,带翅膀(象征创新),头顶ITO靶材,寓意科技赋能 +15.想象康达新材要拍摄一支企业宣传片,你觉得开篇画面应该是什么场景? +•答案:高空俯瞰全球生产基地,快速切换研发实验室、生产线、风电应用场景,定格企业口号 +16.如果你是康达新材的客服,接到客户咨询时,第一句暖心的开场白会怎么说? +•答案:您好!康达新材客服为您服务,有任何产品或合作问题,我都会全力解答~ +17.用三个 emoji 代表康达新材的业务板块,你会选哪三个?分别对应什么业务? +•答案:🧪(胶粘剂与特种树脂)、💻(电子科技)、📱(电子信息材料) +18.康达新材有不少“专精特新”子公司,要是给这些子公司办一场表彰会,颁奖词可以怎么写? +•答案:深耕细分领域,以硬科技破壁垒、填空白,为产业生态注入强劲动力,致敬每一份坚守! +19.假设你要给康达新材的员工设计一件文化衫,正面和背面会印什么图案或文字? +•答案:正面:康达新材logo+“以新材赋能”;背面:三大业务图标+“融合 协同 创新 超越” +20.用简单的语言给小朋友解释康达新材生产的胶粘剂是做什么用的,你会怎么说? +•答案:这是“超级胶水”,能把风电叶片、汽车零件粘得牢牢的,让它们更结实哦~ +21.要是康达新材要发起一个公益活动,结合它的业务特点,你觉得可以开展什么主题的活动? +•答案:“绿色新材助环保”,用环保胶粘剂、可降解材料为乡村学校修补课桌椅 +22.如果你参加康达新材的年会,最期待看到哪个环节的表演?为什么? +•答案:业务板块创意秀,能通过表演直观了解各部门工作,还能感受团队风采 +23.用对比的方式说说康达新材和你印象中的传统化工企业有什么不同? +•答案:传统化工多单一业务、低技术,康达聚焦新兴产业,研发投入高、业务多元且绿色环保 +24.康达新材的使命是“服务国家战略,引领行业发展”,要是你是员工,会怎么理解这句话并落实到工作中? +•答案:紧跟国家需求,研发时聚焦“卡脖子”技术,生产时严把质量关,助力产业升级 +25.假设你要给康达新材的官网设计一个新栏目,你会建议增加什么内容? +•答案:“新材科普”栏目,用动画、图文讲解产品原理和应用,开设互动问答 +26.用一句幽默的话调侃康达新材的“硬核”业务(比如胶粘剂、ITO靶材),你会怎么说? +•答案:康达业务太硬核,万物皆可粘、皆可造,就没有搞不定的技术难题? +27.要是康达新材要和一所高校开展合作,你觉得可以在哪些领域合作共赢? +•答案:①联合研发“卡脖子”技术;②开设定制班培养人才;③加速科研成果转化 +28.如果你是康达新材的hr,招聘时会用什么独特的理由吸引人才加入? +•答案:参与热门领域技术攻关,国企保障+清晰晋升,与行业顶尖人才共事 +29.用比喻的手法说说康达新材的研发团队像什么?为什么? +•答案:像“产业探路者”,探索前沿技术,开辟新路径,指引产业发展方向 +30.康达新材海外有布局,要是你负责海外业务推广,会先给海外客户推荐哪款产品? +•答案:风电胶粘剂,市场占有率第一,技术领先,契合全球新能源需求 +31.假设康达新材要举办一场客户答谢会,你觉得现场布置可以融入哪些业务元素? +•答案:①产品模型展示区;②分子结构装饰;③产品拼图、技术问答互动 +32.用一句话总结康达新材对国家新材料产业的意义,你会怎么说? +•答案:打破进口依赖,树立国产标杆,为新材料产业高质量发展提供支撑 +33.要是你是康达新材的一名司机,开车运送产品时,会怎么跟别人介绍车上的货物? +•答案:这是国产硬核科技产品,有风电专用胶粘剂、电子设备核心材料,助力多个行业发展~ +34.康达新材有很多生产基地,要是把这些基地比作一串珍珠,你觉得哪颗“珍珠”最亮眼? +•答案:成都电子科技产业园,聚焦战略新兴产业,是西南地区电子信息特色产业园 +35.用简单的手势或动作比划康达新材某款产品的用途,你会怎么比划? +•答案:比划胶粘剂:双手模拟“叶片”,合拢按压示意粘贴牢固 +36.假设康达新材要出一本企业内刊,你会给内刊起什么名字?设置哪些栏目? +•答案:名字《康达新声》,栏目:总裁寄语、技术前沿、业务动态、员工风采 +37.用一句话形容康达新材员工的工作状态,你会怎么描述? +•答案:勤学实干、协同创新,在各自岗位上全力冲刺目标 +38.要是康达新材要开展员工技能比赛,你觉得可以设置哪些比赛项目? +•答案:①技术攻关挑战赛;②生产精准操作大赛;③产品推广创意赛 +39.用对比的方式说说康达新材20年前和现在的变化,你会怎么总结? +•答案:20年前业务单一,如今成长为“新材料+电子科技”集团,技术、布局全面升级 +40.康达新材的核心价值观是“融合、协同、创新、超越”,要是你用生活中的例子解释“协同”,会选什么例子? +•答案:像家庭做饭,分工不同但目标一致,相互配合才能快速做出丰盛饭菜 +41.假设你要给康达新材的会议室起名字,结合业务特色,你会起什么名字? +•答案:粘合力厅、靶材阁、科创堂、协同馆 +42.用一句鼓励的话送给正在攻克“卡脖子”技术的康达新材研发团队,你会怎么说? +•答案:坚守初心攻难关,国产技术定发光,为你们加油! +43.要是康达新材要拍一支员工纪录片,你觉得应该采访哪些岗位的员工? +•答案:研发人员、生产线工人、销售人员、子公司负责人、行政后勤人员 +44.用比喻的手法说说康达新材的产品像什么?比如胶粘剂像“工业胶水”,再具体描述一下 +•答案:①胶粘剂像“工业纽带”,连接各类部件;②ITO靶材像“电子基石”,支撑电子设备运行 +45.康达新材涉及电子科技领域,要是你给长辈解释什么是“微波组件”,会怎么说? +•答案:这是电子设备的“信号中转站”,能让雷达、通讯设备精准收发信号,保障安全和监测 +46.假设康达新材要参加一场行业展会,你觉得展位设计要突出什么亮点才能吸引观众? +•答案:突出科技感(分子结构造型、LED大屏)和互动性(产品体验、VR参观) +47.用一句话说说你对康达新材未来5年发展的期待,你会怎么说? +•答案:期待成为全球领先的“新材料+电子科技”集团,让更多国产硬核科技走向世界 +48.要是你是康达新材的食堂阿姨,会怎么用食堂菜品比喻公司的业务多元化? +•答案:食堂菜像公司业务,有“核心硬菜”(胶粘剂)、“新鲜时蔬”(电子信息材料),搭配均衡、营养丰富~ +49.用简单的画画形式表现康达新材的业务,你会画哪些元素? +•答案:画大圆圈代表公司,内部画风电叶片、手机、芯片、分子结构,用箭头连接体现协同 +50.康达新材有不少荣誉资质,要是你把这些荣誉比作公司的“勋章”,最想给别人展示哪枚“勋章”?为什么? +•答案:国家级“专精特新”小巨人企业,体现公司技术实力和细分领域领先地位 \ No newline at end of file