diff --git a/.github/workflows/release-notify-wechat.yml b/.github/workflows/release-notify-wechat.yml index bc67518b..935d84d5 100644 --- a/.github/workflows/release-notify-wechat.yml +++ b/.github/workflows/release-notify-wechat.yml @@ -121,6 +121,8 @@ jobs: AUTHOR: ${{ github.event.pull_request.user.login }} PR_TITLE: ${{ github.event.pull_request.title }} PR_URL: ${{ github.event.pull_request.html_url }} + PR_NUMBER: ${{ github.event.pull_request.number }} + MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha }} SOURCERY_FOUND: ${{ steps.sourcery.outputs.found }} SOURCERY_SUMMARY: ${{ steps.sourcery.outputs.summary }} QWEN_SUMMARY: ${{ steps.qwen.outputs.summary }} @@ -135,11 +137,16 @@ jobs: label = "AI变更摘要" summary = os.environ.get("QWEN_SUMMARY", "AI 摘要生成失败") + pr_number = os.environ.get("PR_NUMBER", "") + short_sha = os.environ.get("MERGE_SHA", "")[:7] + content = ( "## 🚀 Release 发布通知\n" - "> 📦 **分支**: " + os.environ["BRANCH"] + "\n" + "> � **分支**: " + os.environ["BRANCH"] + "\n" "> 👤 **提交人**: " + os.environ["AUTHOR"] + "\n" - "> 📝 **标题**: " + os.environ["PR_TITLE"] + "\n\n" + "> 📝 **标题**: " + os.environ["PR_TITLE"] + "\n" + "> 🔢 **PR编号**: #" + pr_number + "\n" + "> 🔖 **Commit**: " + short_sha + "\n\n" "### 🧠 " + label + "\n" + summary + "\n\n" "---\n" diff --git a/api/app/controllers/chunk_controller.py b/api/app/controllers/chunk_controller.py index b5c0a5ae..cc1f8c98 100644 --- a/api/app/controllers/chunk_controller.py +++ b/api/app/controllers/chunk_controller.py @@ -443,10 +443,10 @@ async def retrieve_chunks( match retrieve_data.retrieve_type: case chunk_schema.RetrieveType.PARTICIPLE: rs = vector_service.search_by_full_text(query=retrieve_data.query, top_k=retrieve_data.top_k, indices=indices, score_threshold=retrieve_data.similarity_threshold, file_names_filter=retrieve_data.file_names_filter) - return success(data=rs, msg="retrieval successful") + return success(data=jsonable_encoder(rs), msg="retrieval successful") case chunk_schema.RetrieveType.SEMANTIC: rs = vector_service.search_by_vector(query=retrieve_data.query, top_k=retrieve_data.top_k, indices=indices, score_threshold=retrieve_data.vector_similarity_weight, file_names_filter=retrieve_data.file_names_filter) - return success(data=rs, msg="retrieval successful") + return success(data=jsonable_encoder(rs), msg="retrieval successful") case _: rs1 = vector_service.search_by_vector(query=retrieve_data.query, top_k=retrieve_data.top_k, indices=indices, score_threshold=retrieve_data.vector_similarity_weight, file_names_filter=retrieve_data.file_names_filter) rs2 = vector_service.search_by_full_text(query=retrieve_data.query, top_k=retrieve_data.top_k, indices=indices, score_threshold=retrieve_data.similarity_threshold, file_names_filter=retrieve_data.file_names_filter) diff --git a/api/app/controllers/ontology_controller.py b/api/app/controllers/ontology_controller.py index 83f75888..602ee709 100644 --- a/api/app/controllers/ontology_controller.py +++ b/api/app/controllers/ontology_controller.py @@ -165,7 +165,7 @@ def _get_ontology_service( api_key=api_key_config.api_key, base_url=api_key_config.api_base, is_omni=api_key_config.is_omni, - support_thinking="thinking" in (api_key_config.capability or []), + capability=api_key_config.capability, max_retries=3, timeout=60.0 ) diff --git a/api/app/core/agent/langchain_agent.py b/api/app/core/agent/langchain_agent.py index ca7172e8..927eb734 100644 --- a/api/app/core/agent/langchain_agent.py +++ b/api/app/core/agent/langchain_agent.py @@ -41,6 +41,7 @@ class LangChainAgent: max_tool_consecutive_calls: int = 3, # 单个工具最大连续调用次数 deep_thinking: bool = False, # 是否启用深度思考模式 thinking_budget_tokens: Optional[int] = None, # 深度思考 token 预算 + json_output: bool = False, # 是否强制 JSON 输出 capability: Optional[List[str]] = None # 模型能力列表,用于校验是否支持深度思考 ): """初始化 LangChain Agent @@ -64,7 +65,6 @@ class LangChainAgent: self.streaming = streaming self.is_omni = is_omni self.max_tool_consecutive_calls = max_tool_consecutive_calls - self.deep_thinking = deep_thinking and ("thinking" in (capability or [])) # 工具调用计数器:记录每个工具的连续调用次数 self.tool_call_counter: Dict[str, int] = {} @@ -80,6 +80,12 @@ class LangChainAgent: self.system_prompt = system_prompt or "你是一个专业的AI助手" + # ChatTongyi 要求 messages 含 'json' 字样才能使用 response_format + # 在 system prompt 中注入 JSON 要求 + from app.models.models_model import ModelProvider + if json_output and provider.lower() == ModelProvider.DASHSCOPE and not is_omni: + self.system_prompt += "\n请以JSON格式输出。" + logger.debug( f"Agent 迭代次数配置: max_iterations={self.max_iterations}, " f"tool_count={len(self.tools)}, " @@ -87,23 +93,17 @@ class LangChainAgent: f"auto_calculated={max_iterations is None}" ) - # 根据 capability 校验是否真正支持深度思考 - actual_deep_thinking = self.deep_thinking - if deep_thinking and not actual_deep_thinking: - logger.warning( - f"模型 {model_name} 不支持深度思考(capability 中无 'thinking'),已自动关闭 deep_thinking" - ) - - # 创建 RedBearLLM(支持多提供商) + # 创建 RedBearLLM,capability 校验由 RedBearModelConfig 统一处理 model_config = RedBearModelConfig( model_name=model_name, provider=provider, api_key=api_key, base_url=api_base, is_omni=is_omni, - deep_thinking=actual_deep_thinking, - thinking_budget_tokens=thinking_budget_tokens if actual_deep_thinking else None, - support_thinking="thinking" in (capability or []), + capability=capability, + deep_thinking=deep_thinking, + thinking_budget_tokens=thinking_budget_tokens, + json_output=json_output, extra_params={ "temperature": temperature, "max_tokens": max_tokens, @@ -112,6 +112,9 @@ class LangChainAgent: ) self.llm = RedBearLLM(model_config, type=ModelType.CHAT) + # 从经过校验的 config 读取实际生效的能力开关 + self.deep_thinking = model_config.deep_thinking + self.json_output = model_config.json_output # 获取底层模型用于真正的流式调用 self._underlying_llm = self.llm._model if hasattr(self.llm, '_model') else self.llm diff --git a/api/app/core/models/base.py b/api/app/core/models/base.py index 89a7dcee..7b570b47 100644 --- a/api/app/core/models/base.py +++ b/api/app/core/models/base.py @@ -1,7 +1,7 @@ from __future__ import annotations import os -from typing import Any, Dict, Optional, TypeVar +from typing import Any, Dict, List, Optional, TypeVar from langchain_aws import ChatBedrock from langchain_community.chat_models import ChatTongyi @@ -9,12 +9,12 @@ from langchain_core.embeddings import Embeddings from langchain_core.language_models import BaseLLM from langchain_ollama import OllamaLLM from langchain_openai import ChatOpenAI, OpenAI -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, model_validator from app.core.error_codes import BizCode from app.core.exceptions import BusinessException from app.models.models_model import ModelProvider, ModelType -from app.core.models.volcano_chat import VolcanoChatOpenAI +from app.core.models.compatible_chat import CompatibleChatOpenAI T = TypeVar("T") @@ -25,10 +25,11 @@ class RedBearModelConfig(BaseModel): provider: str api_key: str base_url: Optional[str] = None + capability: List[str] = Field(default_factory=list) # 模型能力列表,驱动所有能力开关 is_omni: bool = False # 是否为 Omni 模型 deep_thinking: bool = False # 是否启用深度思考模式 thinking_budget_tokens: Optional[int] = None # 深度思考 token 预算 - support_thinking: bool = False # 模型是否支持 enable_thinking 参数(capability 含 thinking) + json_output: bool = False # 是否强制 JSON 输出 # 请求超时时间(秒)- 默认120秒以支持复杂的LLM调用,可通过环境变量 LLM_TIMEOUT 配置 timeout: float = Field(default_factory=lambda: float(os.getenv("LLM_TIMEOUT", "120.0"))) # 最大重试次数 - 默认2次以避免过长等待,可通过环境变量 LLM_MAX_RETRIES 配置 @@ -36,6 +37,23 @@ class RedBearModelConfig(BaseModel): concurrency: int = 5 # 并发限流 extra_params: Dict[str, Any] = {} + @model_validator(mode="after") + def _resolve_capabilities(self) -> "RedBearModelConfig": + from app.core.logging_config import get_business_logger + logger = get_business_logger() + if self.deep_thinking and "thinking" not in self.capability: + logger.warning( + f"模型 {self.model_name} 不支持深度思考(capability 中无 'thinking'),已自动关闭 deep_thinking" + ) + self.deep_thinking = False + self.thinking_budget_tokens = None + if self.json_output and "json_output" not in self.capability: + logger.warning( + f"模型 {self.model_name} 不支持 JSON 输出(capability 中无 'json_output'),已自动关闭 json_output" + ) + self.json_output = False + return self + class RedBearModelFactory: """模型工厂类""" @@ -74,17 +92,20 @@ class RedBearModelFactory: is_streaming = bool(config.extra_params.get("streaming")) if is_streaming: params["stream_usage"] = True - # 只有支持 thinking 的模型才传 enable_thinking - if config.support_thinking: - model_kwargs: Dict[str, Any] = config.extra_params.get("model_kwargs", {}) - if is_streaming: - model_kwargs["enable_thinking"] = config.deep_thinking - if config.deep_thinking: - model_kwargs["incremental_output"] = True - if config.thinking_budget_tokens: - model_kwargs["thinking_budget"] = config.thinking_budget_tokens - else: - model_kwargs["enable_thinking"] = False + # 支持 thinking 的模型始终传 enable_thinking,关闭时显式传 False 避免模型默认开启思考 + if "thinking" in config.capability: + extra_body = params.setdefault("extra_body", {}) + if config.deep_thinking: + extra_body["enable_thinking"] = False + if is_streaming: + extra_body["enable_thinking"] = True + if config.thinking_budget_tokens: + extra_body["thinking_budget"] = config.thinking_budget_tokens + params["extra_body"] = extra_body + # JSON 输出模式 + if config.json_output: + model_kwargs = params.setdefault("model_kwargs", {}) + model_kwargs["response_format"] = {"type": "json_object"} params["model_kwargs"] = model_kwargs return params @@ -108,27 +129,30 @@ class RedBearModelFactory: **config.extra_params } # 流式模式下启用 stream_usage 以获取 token 统计 - if config.extra_params.get("streaming"): - params["stream_usage"] = True - # 深度思考模式 is_streaming = bool(config.extra_params.get("streaming")) - if config.support_thinking: - if is_streaming and not config.is_omni: - if provider == ModelProvider.VOLCANO: - # 火山引擎深度思考仅流式调用支持,非流式时不传 thinking 参数 - thinking_config: Dict[str, Any] = { - "type": "enabled" if config.deep_thinking else "disabled" - } - if config.deep_thinking and config.thinking_budget_tokens: - thinking_config["budget_tokens"] = config.thinking_budget_tokens - params["extra_body"] = {"thinking": thinking_config} - else: - # 始终显式传递 enable_thinking,不支持该参数的模型(如 DeepSeek-R1)会直接忽略 - model_kwargs: Dict[str, Any] = config.extra_params.get("model_kwargs", {}) - model_kwargs["enable_thinking"] = config.deep_thinking - if config.deep_thinking and config.thinking_budget_tokens: - model_kwargs["thinking_budget"] = config.thinking_budget_tokens - params["model_kwargs"] = model_kwargs + if is_streaming: + params["stream_usage"] = True + # 支持 thinking 的模型始终传 enable_thinking,关闭时显式传 False 避免模型默认开启思考 + if "thinking" in config.capability: + # VOLCANO 深度思考仅流式支持 + if provider == ModelProvider.VOLCANO: + thinking_config: Dict[str, Any] = {"type": "enabled" if config.deep_thinking else "disabled"} + if config.deep_thinking and config.thinking_budget_tokens: + thinking_config["budget_tokens"] = config.thinking_budget_tokens + params["extra_body"] = {"thinking": thinking_config} + else: + extra_body = params.setdefault("extra_body", {}) + if config.deep_thinking: + extra_body["enable_thinking"] = False + if is_streaming: + extra_body["enable_thinking"] = True + if config.thinking_budget_tokens: + extra_body["thinking_budget"] = config.thinking_budget_tokens + params["extra_body"] = extra_body + # JSON 输出模式 + if config.json_output: + params.setdefault("model_kwargs", {}) + params["model_kwargs"]["response_format"] = {"type": "json_object"} return params elif provider == ModelProvider.DASHSCOPE: params = { @@ -137,18 +161,21 @@ class RedBearModelFactory: "max_retries": config.max_retries, **config.extra_params } - # 只有支持 thinking 的模型才传 enable_thinking - if config.support_thinking: + # 支持 thinking 的模型始终传 enable_thinking,关闭时显式传 False 避免模型默认开启思考 + if "thinking" in config.capability: is_streaming = bool(config.extra_params.get("streaming")) - model_kwargs: Dict[str, Any] = config.extra_params.get("model_kwargs", {}) - if is_streaming: - model_kwargs["enable_thinking"] = config.deep_thinking - if config.deep_thinking: - model_kwargs["incremental_output"] = True - if config.thinking_budget_tokens: - model_kwargs["thinking_budget"] = config.thinking_budget_tokens - else: + model_kwargs = params.setdefault("model_kwargs", {}) + if config.deep_thinking: model_kwargs["enable_thinking"] = False + if is_streaming: + model_kwargs["enable_thinking"] = True + model_kwargs["incremental_output"] = True + if config.thinking_budget_tokens: + model_kwargs["thinking_budget"] = config.thinking_budget_tokens + params["model_kwargs"] = model_kwargs + if config.json_output: + model_kwargs = params.setdefault("model_kwargs", {}) + model_kwargs["response_format"] = {"type": "json_object"} params["model_kwargs"] = model_kwargs return params elif provider == ModelProvider.BEDROCK: @@ -196,6 +223,10 @@ class RedBearModelFactory: params["additional_model_request_fields"] = { "thinking": {"type": "enabled", "budget_tokens": budget} } + # JSON 输出模式 + if config.json_output: + params.setdefault("model_kwargs", {}) + params["model_kwargs"]["response_format"] = {"type": "json_object"} return params else: raise BusinessException(f"不支持的提供商: {provider}", code=BizCode.PROVIDER_NOT_SUPPORTED) @@ -224,11 +255,11 @@ def get_provider_llm_class(config: RedBearModelConfig, type: ModelType = ModelTy """根据模型提供商获取对应的模型类""" provider = config.provider.lower() - # dashscope 的 omni 模型使用 OpenAI 兼容模式 + # dashscope的omni模型 和 volcano模型使用 if provider == ModelProvider.DASHSCOPE and config.is_omni: - return ChatOpenAI + return CompatibleChatOpenAI if provider == ModelProvider.VOLCANO: - return VolcanoChatOpenAI + return CompatibleChatOpenAI if provider in [ModelProvider.OPENAI, ModelProvider.XINFERENCE, ModelProvider.GPUSTACK]: if type == ModelType.LLM: return OpenAI diff --git a/api/app/core/models/volcano_chat.py b/api/app/core/models/compatible_chat.py similarity index 92% rename from api/app/core/models/volcano_chat.py rename to api/app/core/models/compatible_chat.py index d9a51d13..114a3567 100644 --- a/api/app/core/models/volcano_chat.py +++ b/api/app/core/models/compatible_chat.py @@ -12,8 +12,8 @@ from langchain_core.outputs import ChatGenerationChunk, ChatResult from langchain_openai import ChatOpenAI -class VolcanoChatOpenAI(ChatOpenAI): - """火山引擎 Chat 模型,支持深度思考内容(reasoning_content)的流式和非流式透传。""" +class CompatibleChatOpenAI(ChatOpenAI): + """火山和千问的omni兼容模型,支持深度思考内容(reasoning_content)的流式和非流式透传。""" def _create_chat_result(self, response: Union[dict, Any], generation_info: Optional[dict] = None) -> ChatResult: result = super()._create_chat_result(response, generation_info) diff --git a/api/app/core/models/scripts/bedrock_models.yaml b/api/app/core/models/scripts/bedrock_models.yaml index 5b3a2f64..f96dba15 100644 --- a/api/app/core/models/scripts/bedrock_models.yaml +++ b/api/app/core/models/scripts/bedrock_models.yaml @@ -6,7 +6,8 @@ models: description: AI21 Labs大语言模型,completion生成模式,256000上下文窗口 is_deprecated: false is_official: true - capability: [] + capability: + - json_output is_omni: false tags: - 大语言模型 @@ -20,6 +21,7 @@ models: is_official: true capability: - vision + - json_output is_omni: false tags: - 大语言模型 @@ -38,6 +40,7 @@ models: capability: - vision - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -54,7 +57,8 @@ models: description: Cohere大语言模型,支持智能体思考、工具调用、流式工具调用,128000上下文窗口,对话模式 is_deprecated: false is_official: true - capability: [] + capability: + - json_output is_omni: false tags: - 大语言模型 @@ -72,6 +76,7 @@ models: capability: - vision - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -87,7 +92,8 @@ models: description: Meta Llama大语言模型,支持智能体思考、工具调用,128000上下文窗口,对话模式 is_deprecated: false is_official: true - capability: [] + capability: + - json_output is_omni: false tags: - 大语言模型 @@ -101,7 +107,8 @@ models: description: Mistral AI大语言模型,支持智能体思考、工具调用,32000上下文窗口,对话模式 is_deprecated: false is_official: true - capability: [] + capability: + - json_output is_omni: false tags: - 大语言模型 @@ -115,7 +122,8 @@ models: description: OpenAI大语言模型,支持智能体思考、工具调用、流式工具调用,32768上下文窗口,对话模式 is_deprecated: false is_official: true - capability: [] + capability: + - json_output is_omni: false tags: - 大语言模型 @@ -130,7 +138,8 @@ models: description: Qwen大语言模型,支持智能体思考、工具调用、流式工具调用,32768上下文窗口,对话模式 is_deprecated: false is_official: true - capability: [] + capability: + - json_output is_omni: false tags: - 大语言模型 diff --git a/api/app/core/models/scripts/dashscope_models.yaml b/api/app/core/models/scripts/dashscope_models.yaml index d9e6a00f..9b45f107 100644 --- a/api/app/core/models/scripts/dashscope_models.yaml +++ b/api/app/core/models/scripts/dashscope_models.yaml @@ -8,6 +8,7 @@ models: is_official: true capability: - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -22,6 +23,7 @@ models: is_official: true capability: - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -36,6 +38,7 @@ models: is_official: true capability: - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -48,7 +51,8 @@ models: description: DeepSeek-V3.1大语言模型,支持智能体思考,131072超大上下文窗口,对话模式,支持丰富生成参数调节 is_deprecated: false is_official: true - capability: [] + capability: + - json_output is_omni: false tags: - 大语言模型 @@ -61,7 +65,8 @@ models: description: DeepSeek-V3.2-exp实验版大语言模型,支持智能体思考,131072超大上下文窗口,对话模式,支持丰富生成参数调节 is_deprecated: false is_official: true - capability: [] + capability: + - json_output is_omni: false tags: - 大语言模型 @@ -74,7 +79,8 @@ models: description: DeepSeek-V3.2大语言模型,支持智能体思考,131072超大上下文窗口,对话模式,支持丰富生成参数调节 is_deprecated: false is_official: true - capability: [] + capability: + - json_output is_omni: false tags: - 大语言模型 @@ -87,7 +93,8 @@ models: description: DeepSeek-V3大语言模型,支持智能体思考,64000上下文窗口,对话模式,支持文本与JSON格式输出 is_deprecated: false is_official: true - capability: [] + capability: + - json_output is_omni: false tags: - 大语言模型 @@ -100,7 +107,8 @@ models: description: farui-plus大语言模型,支持多工具调用、智能体思考、流式工具调用,12288上下文窗口,对话模式 is_deprecated: false is_official: true - capability: [] + capability: + - json_output is_omni: false tags: - 大语言模型 @@ -115,7 +123,8 @@ models: description: GLM-4.7大语言模型,支持多工具调用、智能体思考、流式工具调用,202752超大上下文窗口,对话模式 is_deprecated: false is_official: true - capability: [] + capability: + - json_output is_omni: false tags: - 大语言模型 @@ -133,6 +142,7 @@ models: capability: - vision - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -150,6 +160,7 @@ models: capability: - vision - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -180,6 +191,7 @@ models: is_official: true capability: - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -210,7 +222,7 @@ models: is_deprecated: false is_official: true capability: - - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -376,6 +388,7 @@ models: capability: - vision - video + - json_output is_omni: false tags: - 大语言模型 @@ -448,6 +461,7 @@ models: capability: - vision - video + - json_output is_omni: false tags: - 大语言模型 @@ -466,6 +480,7 @@ models: capability: - vision - video + - json_output is_omni: false tags: - 大语言模型 @@ -481,7 +496,8 @@ models: description: qwen2.5-0.5b-instruct大语言模型,支持多工具调用、智能体思考、流式工具调用,32768上下文窗口,对话模式,未废弃 is_deprecated: false is_official: true - capability: [] + capability: + - json_output is_omni: false tags: - 大语言模型 @@ -498,6 +514,7 @@ models: is_official: true capability: - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -513,7 +530,7 @@ models: is_deprecated: false is_official: true capability: - - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -530,6 +547,7 @@ models: is_official: true capability: - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -546,6 +564,7 @@ models: is_official: true capability: - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -561,7 +580,7 @@ models: is_deprecated: false is_official: true capability: - - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -578,6 +597,7 @@ models: is_official: true capability: - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -594,6 +614,7 @@ models: is_official: true capability: - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -610,6 +631,7 @@ models: is_official: true capability: - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -626,6 +648,7 @@ models: is_official: true capability: - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -641,7 +664,7 @@ models: is_deprecated: false is_official: true capability: - - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -656,7 +679,7 @@ models: is_deprecated: false is_official: true capability: - - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -672,6 +695,7 @@ models: is_official: true capability: - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -687,6 +711,7 @@ models: is_official: true capability: - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -702,6 +727,7 @@ models: is_official: true capability: - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -719,6 +745,7 @@ models: is_official: true capability: - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -736,6 +763,7 @@ models: is_official: true capability: - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -752,6 +780,7 @@ models: is_official: true capability: - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -768,7 +797,7 @@ models: is_deprecated: false is_official: true capability: - - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -785,6 +814,7 @@ models: is_official: true capability: - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -803,6 +833,8 @@ models: - vision - video - audio + - thinking + - json_output is_omni: true tags: - 大语言模型 @@ -822,7 +854,7 @@ models: capability: - vision - video - - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -844,6 +876,7 @@ models: - vision - video - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -864,7 +897,7 @@ models: capability: - vision - video - - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -886,6 +919,7 @@ models: - vision - video - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -907,6 +941,7 @@ models: - vision - video - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -928,6 +963,7 @@ models: - vision - video - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -947,6 +983,7 @@ models: - vision - video - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -964,6 +1001,7 @@ models: is_official: true capability: - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -979,6 +1017,7 @@ models: is_official: true capability: - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -994,6 +1033,7 @@ models: is_official: true capability: - thinking + - json_output is_omni: false tags: - 大语言模型 diff --git a/api/app/core/models/scripts/openai_models.yaml b/api/app/core/models/scripts/openai_models.yaml index 08b81008..1c0a0b2d 100644 --- a/api/app/core/models/scripts/openai_models.yaml +++ b/api/app/core/models/scripts/openai_models.yaml @@ -10,6 +10,7 @@ models: - vision - audio - video + - json_output is_omni: true tags: - 大语言模型 @@ -27,7 +28,8 @@ models: description: gpt-3.5-turbo-0125大语言模型,支持多工具调用、智能体思考、流式工具调用,16385上下文窗口,对话模式 is_deprecated: false is_official: true - capability: [] + capability: + - json_output is_omni: false tags: - 大语言模型 @@ -42,7 +44,8 @@ models: description: gpt-3.5-turbo-1106大语言模型,支持多工具调用、智能体思考、流式工具调用,16385上下文窗口,对话模式 is_deprecated: false is_official: true - capability: [] + capability: + - json_output is_omni: false tags: - 大语言模型 @@ -57,7 +60,8 @@ models: description: gpt-3.5-turbo-16k大语言模型,支持多工具调用、智能体思考、流式工具调用,16385上下文窗口,对话模式 is_deprecated: false is_official: true - capability: [] + capability: + - json_output is_omni: false tags: - 大语言模型 @@ -84,7 +88,8 @@ models: description: gpt-3.5-turbo大语言模型,支持多工具调用、智能体思考、流式工具调用,16385上下文窗口,对话模式 is_deprecated: false is_official: true - capability: [] + capability: + - json_output is_omni: false tags: - 大语言模型 @@ -99,7 +104,8 @@ models: description: gpt-4-0125-preview大语言模型,支持多工具调用、智能体思考、流式工具调用,128000上下文窗口,对话模式 is_deprecated: false is_official: true - capability: [] + capability: + - json_output is_omni: false tags: - 大语言模型 @@ -114,7 +120,8 @@ models: description: gpt-4-1106-preview大语言模型,支持多工具调用、智能体思考、流式工具调用,128000上下文窗口,对话模式 is_deprecated: false is_official: true - capability: [] + capability: + - json_output is_omni: false tags: - 大语言模型 @@ -131,6 +138,7 @@ models: is_official: true capability: - vision + - json_output is_omni: false tags: - 大语言模型 @@ -146,7 +154,8 @@ models: description: gpt-4-turbo-preview大语言模型,支持多工具调用、智能体思考、流式工具调用,128000上下文窗口,对话模式 is_deprecated: false is_official: true - capability: [] + capability: + - json_output is_omni: false tags: - 大语言模型 @@ -163,6 +172,7 @@ models: is_official: true capability: - vision + - json_output is_omni: false tags: - 大语言模型 @@ -194,6 +204,7 @@ models: capability: - vision - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -213,6 +224,7 @@ models: capability: - vision - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -231,6 +243,7 @@ models: is_official: true capability: - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -248,6 +261,7 @@ models: is_official: true capability: - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -266,6 +280,7 @@ models: capability: - vision - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -284,6 +299,7 @@ models: capability: - vision - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -302,6 +318,7 @@ models: capability: - vision - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -321,6 +338,7 @@ models: capability: - vision - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -340,6 +358,7 @@ models: capability: - vision - thinking + - json_output is_omni: false tags: - 大语言模型 diff --git a/api/app/core/models/scripts/volcano_models.yaml b/api/app/core/models/scripts/volcano_models.yaml index c86d41ac..6658c2f9 100644 --- a/api/app/core/models/scripts/volcano_models.yaml +++ b/api/app/core/models/scripts/volcano_models.yaml @@ -11,6 +11,7 @@ models: - vision - video - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -26,6 +27,7 @@ models: - vision - video - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -41,6 +43,7 @@ models: - vision - video - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -56,6 +59,7 @@ models: - vision - video - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -72,6 +76,7 @@ models: capability: - vision - video + - json_output is_omni: false tags: - 大语言模型 @@ -87,6 +92,7 @@ models: - vision - video - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -102,6 +108,7 @@ models: - vision - video - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -117,6 +124,7 @@ models: - vision - video - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -132,6 +140,7 @@ models: - vision - video - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -148,6 +157,7 @@ models: - vision - video - thinking + - json_output is_omni: false tags: - 大语言模型 @@ -175,7 +185,8 @@ models: description: 全新一代主力模型,性能全面升级,在知识、代码、推理等方面表现卓越。最大支持 128k 上下文窗口,输出长度支持最大 12k tokens。 is_deprecated: false is_official: true - capability: [] + capability: + - json_output is_omni: false tags: - 大语言模型 @@ -187,7 +198,8 @@ models: description: 全新一代轻量版模型,极致响应速度,效果与时延均达到全球一流水平。支持 32k 上下文窗口,输出长度支持最大 12k tokens。 is_deprecated: false is_official: true - capability: [] + capability: + - json_output is_omni: false tags: - 大语言模型 diff --git a/api/app/core/workflow/nodes/llm/config.py b/api/app/core/workflow/nodes/llm/config.py index 771262c1..b815c80f 100644 --- a/api/app/core/workflow/nodes/llm/config.py +++ b/api/app/core/workflow/nodes/llm/config.py @@ -116,6 +116,11 @@ class LLMNodeConfig(BaseNodeConfig): description="Top-p 采样参数" ) + json_output: bool = Field( + default=False, + description="是否以 JSON 格式输出" + ) + frequency_penalty: float | None = Field( default=None, ge=-2.0, diff --git a/api/app/core/workflow/nodes/llm/node.py b/api/app/core/workflow/nodes/llm/node.py index bb87c845..664a28fa 100644 --- a/api/app/core/workflow/nodes/llm/node.py +++ b/api/app/core/workflow/nodes/llm/node.py @@ -22,6 +22,7 @@ from app.db import get_db_context from app.models import ModelType from app.schemas.model_schema import ModelInfo from app.services.model_service import ModelConfigService +from app.models.models_model import ModelProvider logger = logging.getLogger(__name__) @@ -126,7 +127,11 @@ class LLMNode(BaseNode): # 4. 创建 LLM 实例(使用已提取的数据) # 注意:对于流式输出,需要在模型初始化时设置 streaming=True - extra_params = {"streaming": stream} if stream else {} + extra_params: dict[str, Any] = {"streaming": stream} if stream else {} + if self.typed_config.temperature is not None: + extra_params["temperature"] = self.typed_config.temperature + if self.typed_config.max_tokens is not None: + extra_params["max_tokens"] = self.typed_config.max_tokens llm = RedBearLLM( RedBearModelConfig( @@ -135,7 +140,9 @@ class LLMNode(BaseNode): api_key=model_info.api_key, base_url=model_info.api_base, extra_params=extra_params, - is_omni=model_info.is_omni + is_omni=model_info.is_omni, + capability=model_info.capability, + json_output=self.typed_config.json_output, ), type=model_info.model_type ) @@ -218,6 +225,16 @@ class LLMNode(BaseNode): rendered = self._render_template(prompt_template, variable_pool) self.messages = [{"role": "user", "content": rendered}] + # ChatTongyi 要求 messages 含 'json' 字样才能使用 response_format,在 system prompt 中注入 + if (self.typed_config.json_output + and model_info.provider.lower() == ModelProvider.DASHSCOPE + and not model_info.is_omni): + system_msg = next((m for m in self.messages if m["role"] == "system"), None) + if system_msg: + system_msg["content"] += "\n请以JSON格式输出。" + else: + self.messages.insert(0, {"role": "system", "content": "请以JSON格式输出。"}) + return llm async def execute(self, state: WorkflowState, variable_pool: VariablePool) -> AIMessage: diff --git a/api/app/schemas/app_schema.py b/api/app/schemas/app_schema.py index 1ed98f68..e93c513d 100644 --- a/api/app/schemas/app_schema.py +++ b/api/app/schemas/app_schema.py @@ -245,6 +245,7 @@ class ModelParameters(BaseModel): stop: Optional[List[str]] = Field(default=None, description="停止序列") deep_thinking: bool = Field(default=False, description="是否启用深度思考模式(需模型支持,如 DeepSeek-R1、QwQ 等)") thinking_budget_tokens: Optional[int] = Field(default=None, ge=1024, le=131072, description="深度思考 token 预算(仅部分模型支持)") + json_output: bool = Field(default=False, description="是否强制 JSON 格式输出(需模型支持 json_output 能力)") class VariableDefinition(BaseModel): diff --git a/api/app/services/app_chat_service.py b/api/app/services/app_chat_service.py index 2d10ed44..56e25713 100644 --- a/api/app/services/app_chat_service.py +++ b/api/app/services/app_chat_service.py @@ -120,6 +120,7 @@ class AppChatService: tools=tools, deep_thinking=model_parameters.get("deep_thinking", False), thinking_budget_tokens=model_parameters.get("thinking_budget_tokens"), + json_output=model_parameters.get("json_output", False), capability=api_key_obj.capability or [], ) @@ -392,6 +393,7 @@ class AppChatService: streaming=True, deep_thinking=model_parameters.get("deep_thinking", False), thinking_budget_tokens=model_parameters.get("thinking_budget_tokens"), + json_output=model_parameters.get("json_output", False), capability=api_key_obj.capability or [], ) diff --git a/api/app/services/conversation_service.py b/api/app/services/conversation_service.py index 6e9f3544..61744ec7 100644 --- a/api/app/services/conversation_service.py +++ b/api/app/services/conversation_service.py @@ -544,7 +544,7 @@ class ConversationService: api_key=api_key, base_url=api_base, is_omni=is_omni, - support_thinking="thinking" in (capability or []), + capability=capability, ), type=ModelType(model_type) ) diff --git a/api/app/services/draft_run_service.py b/api/app/services/draft_run_service.py index b47bd4cd..81457a08 100644 --- a/api/app/services/draft_run_service.py +++ b/api/app/services/draft_run_service.py @@ -597,6 +597,7 @@ class AgentRunService: tools=tools, deep_thinking=effective_params.get("deep_thinking", False), thinking_budget_tokens=effective_params.get("thinking_budget_tokens"), + json_output=effective_params.get("json_output", False), capability=api_key_config.get("capability", []), ) @@ -853,6 +854,7 @@ class AgentRunService: streaming=True, deep_thinking=effective_params.get("deep_thinking", False), thinking_budget_tokens=effective_params.get("thinking_budget_tokens"), + json_output=effective_params.get("json_output", False), capability=api_key_config.get("capability", []), ) diff --git a/api/app/services/llm_router.py b/api/app/services/llm_router.py index 7087415e..bd90eee9 100644 --- a/api/app/services/llm_router.py +++ b/api/app/services/llm_router.py @@ -415,9 +415,11 @@ class LLMRouter: api_key=api_key_config.api_key, base_url=api_key_config.api_base, is_omni=api_key_config.is_omni, - support_thinking="thinking" in (api_key_config.capability or []), - temperature=0.3, - max_tokens=500 + capability=api_key_config.capability, + extra_params={ + "temperature": 0.3, + "max_tokens": 500 + } ) logger.debug(f"创建 LLM 实例 - Provider: {api_key_config.provider}, Model: {api_key_config.model_name}") diff --git a/api/app/services/master_agent_router.py b/api/app/services/master_agent_router.py index 206443bd..dfb3c2da 100644 --- a/api/app/services/master_agent_router.py +++ b/api/app/services/master_agent_router.py @@ -393,7 +393,7 @@ class MasterAgentRouter: api_key=api_key_config.api_key, base_url=api_key_config.api_base, is_omni=api_key_config.is_omni, - support_thinking="thinking" in (api_key_config.capability or []), + capability=api_key_config.capability, extra_params = extra_params ) diff --git a/api/app/services/memory_perceptual_service.py b/api/app/services/memory_perceptual_service.py index 7d6d1092..8fa9c9bf 100644 --- a/api/app/services/memory_perceptual_service.py +++ b/api/app/services/memory_perceptual_service.py @@ -233,7 +233,7 @@ class MemoryPerceptualService: api_key=model_config.api_key, base_url=model_config.api_base, is_omni=model_config.is_omni, - support_thinking="thinking" in (model_config.capability or []), + capability=model_config.capability, ) ) return llm, model_config diff --git a/api/app/services/model_parameter_merger.py b/api/app/services/model_parameter_merger.py index 4be83851..6911a9d5 100644 --- a/api/app/services/model_parameter_merger.py +++ b/api/app/services/model_parameter_merger.py @@ -47,7 +47,8 @@ class ModelParameterMerger: "n": 1, "stop": None, "deep_thinking": False, - "thinking_budget_tokens": None + "thinking_budget_tokens": None, + "json_output": False } # 合并参数:默认值 -> 模型配置 -> Agent 配置 diff --git a/api/app/services/model_service.py b/api/app/services/model_service.py index d202b83a..8807020b 100644 --- a/api/app/services/model_service.py +++ b/api/app/services/model_service.py @@ -125,9 +125,11 @@ class ModelConfigService: api_key=api_key, base_url=api_base, is_omni=is_omni, - support_thinking="thinking" in (capability or []), - temperature=0.7, - max_tokens=100 + capability=capability, + extra_params={ + "temperature": 0.7, + "max_tokens": 100 + } ) # 根据模型类型选择不同的验证方式 diff --git a/api/app/services/multi_agent_orchestrator.py b/api/app/services/multi_agent_orchestrator.py index 216aeb6e..d30dc822 100644 --- a/api/app/services/multi_agent_orchestrator.py +++ b/api/app/services/multi_agent_orchestrator.py @@ -2616,9 +2616,11 @@ class MultiAgentOrchestrator: api_key=api_key_config.api_key, base_url=api_key_config.api_base, is_omni=api_key_config.is_omni, - support_thinking="thinking" in (api_key_config.capability or []), - temperature=0.7, # 整合任务使用中等温度 - max_tokens=2000 + capability=api_key_config.capability, + extra_params={ + "temperature": 0.7, # 整合任务使用中等温度 + "max_tokens": 2000 + } ) # 创建 LLM 实例 @@ -2795,10 +2797,12 @@ class MultiAgentOrchestrator: api_key=api_key_config.api_key, base_url=api_key_config.api_base, is_omni=api_key_config.is_omni, - support_thinking="thinking" in (api_key_config.capability or []), - temperature=0.7, - max_tokens=2000, - extra_params={"streaming": True} # 启用流式输出 + capability=api_key_config.capability, + extra_params={ + "temperature": 0.7, + "max_tokens": 2000, + "streaming": True # 启用流式输出 + } ) # 创建 LLM 实例 diff --git a/api/app/services/prompt_optimizer_service.py b/api/app/services/prompt_optimizer_service.py index 30901111..1686a164 100644 --- a/api/app/services/prompt_optimizer_service.py +++ b/api/app/services/prompt_optimizer_service.py @@ -186,7 +186,7 @@ class PromptOptimizerService: api_key=api_config.api_key, base_url=api_config.api_base, is_omni=api_config.is_omni, - support_thinking="thinking" in (api_config.capability or []), + capability=api_config.capability, ), type=ModelType(model_config.type)) try: prompt_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'prompt') diff --git a/api/app/services/shared_chat_service.py b/api/app/services/shared_chat_service.py index b1e40a2d..37956d77 100644 --- a/api/app/services/shared_chat_service.py +++ b/api/app/services/shared_chat_service.py @@ -250,7 +250,8 @@ class SharedChatService: tools=tools, deep_thinking=model_parameters.get("deep_thinking", False), thinking_budget_tokens=model_parameters.get("thinking_budget_tokens"), - capability=api_key_obj.capability or [], + json_output=model_parameters.get("json_output", False), + capability=api_key_obj.capability, ) # 加载历史消息 @@ -455,6 +456,7 @@ class SharedChatService: streaming=True, deep_thinking=model_parameters.get("deep_thinking", False), thinking_budget_tokens=model_parameters.get("thinking_budget_tokens"), + json_output=model_parameters.get("json_output", False), capability=api_key_obj.capability or [], ) diff --git a/api/app/version_info.json b/api/app/version_info.json index d07035e2..a094b64c 100644 --- a/api/app/version_info.json +++ b/api/app/version_info.json @@ -1,4 +1,40 @@ { + "v0.3.0": { + "introduction": { + "codeName": "破晓", + "releaseDate": "2026-4-15", + "upgradePosition": "🐻 全面升级应用工作流、记忆智能与系统稳健性,引入版本化API、多模态记忆感知及大量工作流增强,打造更可靠、精准的 MemoryBear", + "coreUpgrades": [ + "1. 应用与API增强
* 版本化API调用支持:对外服务API支持指定版本调用
* 工作流检查清单:新增结构化验证步骤
* 深度思考参数精准控制:仅向支持深度推理的模型发送思考参数
* 提示器模型返回优化:优化提示器模型响应处理", + "2. 记忆智能 🧠
* 多模态记忆感知Agent:支持多模态记忆读取与写入
* OpenClaw内置工具:新增内置工具扩展Agent工具集", + "3. 用户体验 🎨
* 流式渲染稳定性优化:解决LLM流式输出页面抖动问题
* 记忆中枢更名:「记忆相关」更名为「记忆中枢」", + "4. 工作流改进 ⚙️
* 三级变量模板转换:支持三级变量解析
* VL模型Token统计:修复模型组合中VL模型Token未统计问题
* 导入工作流功能特性同步:正确同步开场白、引用等属性
* 会话变量名称唯一性校验:防止变量名冲突
* 文件类型提取修复:正确提取file.type信息
* 条件分支显示修复:值为0或会话变量时正确渲染
* Object/Array校验规则:防止JSON序列化错误
* HTTP请求Body字段修正:body字段从name改为key", + "5. 知识库 📚
* Embedding Token截断安全边界:统一添加8000 token截断,优化Excel独立chunk处理", + "6. 稳健性与缺陷修复 🔧
* 原子性更新与批量访问失败修复
* 对话别名提取错误修复
* 工作流别名提取修正(区分用户和AI回复)
* RAG记忆分页数据修复
* 隐式记忆详情显示修复
* 向量查询驱动关闭异常修复
* 用户管理启停异常修复
* 模型列表筛选不一致修复", + "
", + "v0.3.0 标志着 MemoryBear 向生产成熟度迈出坚实一步。后续版本将持续深化工作流表达力、记忆检索精度和跨模态理解能力,强化复杂Agent编排支持,稳固大规模生产部署基础。", + "
", + "MemoryBear — 破晓 🐻✨" + ] + }, + "introduction_en": { + "codeName": "PoXiao", + "releaseDate": "2026-4-15", + "upgradePosition": "🐻 Comprehensive upgrades across application workflows, memory intelligence, and system robustness — introducing versioned APIs, multimodal memory perception, and extensive workflow enhancements for a more reliable MemoryBear", + "coreUpgrades": [ + "1. Application & API Enhancements
* Versioned API Support: External APIs now support version-specific calls
* Workflow Checklist: Structured validation steps before deployment
* Deep Thinking Parameter Control: Only send thinking params to supported models
* Prompt Optimizer Return Optimization: Improved prompt optimizer response handling", + "2. Memory Intelligence 🧠
* Multimodal Memory Perception Agent: Read/write multimodal memory
* OpenClaw Built-in Tool: New built-in tool for agent operations", + "3. User Experience 🎨
* Streaming Render Stabilization: Eliminated page jitter during LLM output
* Memory Hub Renaming: Renamed to better reflect central memory role", + "4. Workflow Improvements ⚙️
* Three-Level Variable Template Conversion: Support for three-level variable resolution
* VL Model Token Tracking: Fixed token tracking for VL models in model groups
* Imported Workflow Feature Sync: Properly sync opening messages, citations, etc.
* Session Variable Name Uniqueness: Prevent variable name conflicts
* File Type Extraction Fix: Correctly extract file.type information
* Condition Branch Display Fix: Correct rendering for value 0 or session variables
* Object/Array Validation Rules: Prevent JSON serialization save errors
* HTTP Request Body Key Fix: Body field uses key instead of name", + "5. Knowledge Base 📚
* Embedding Token Truncation Safety: Unified 8000-token boundary, optimized Excel chunk processing", + "6. Robustness & Bug Fixes 🔧
* Atomic update & batch access failure fixes
* Conversation alias extraction fix
* Workflow alias extraction correction (user vs AI distinction)
* RAG memory pagination fix
* Implicit memory detail display fix
* Vector query driver closed exception fix
* User management enable/disable fix
* Model list filter inconsistency fix", + "
", + "v0.3.0 marks a meaningful step toward production maturity for MemoryBear. Upcoming releases will deepen workflow expressiveness, memory retrieval precision, and cross-modal understanding while strengthening complex agent orchestration and large-scale deployment foundations.", + "
", + "MemoryBear — Daybreak 🐻✨" + ] + } + }, "v0.2.10": { "introduction": { "codeName": "炼剑", diff --git a/web/src/components/ModelSelect/index.tsx b/web/src/components/ModelSelect/index.tsx index 8f9152fb..85977376 100644 --- a/web/src/components/ModelSelect/index.tsx +++ b/web/src/components/ModelSelect/index.tsx @@ -2,9 +2,9 @@ * @Author: ZhaoYing * @Date: 2026-03-07 16:49:59 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-25 11:21:59 + * @Last Modified time: 2026-04-17 10:11:54 */ -import { useEffect, useState, type FC } from 'react'; +import { type FC, useEffect, useState } from 'react'; import { Select, Flex, Space } from 'antd'; import type { SelectProps } from 'antd/es/select'; import { useTranslation } from 'react-i18next'; @@ -22,16 +22,10 @@ interface ModelSelectProps extends SelectProps { fontClassName?: string; isAutoFetch?: boolean; initialData?: Model[]; + updateOptions?: (options: Model[]) => void; } -const ModelSelect: FC = ({ - params, - placeholder, - fontClassName, - isAutoFetch = true, - initialData = [], - ...props -}) => { +const ModelSelect: FC = ({ params, placeholder, fontClassName, isAutoFetch = true, initialData = [], updateOptions, ...props }) => { const { t } = useTranslation(); const [options, setOptions] = useState([]); @@ -60,6 +54,10 @@ const ModelSelect: FC = ({ ); }; + useEffect(() => { + if (updateOptions) updateOptions([...options, ...initialData]); + }, [options, initialData]) + return (