From 8c6b65db12523ce47cebca47d34b5322e0087d5b Mon Sep 17 00:00:00 2001
From: Timebomb2018 <18868801967@163.com>
Date: Thu, 16 Apr 2026 16:27:55 +0800
Subject: [PATCH 1/8] feat(llm): add json_output support for structured LLM
responses
---
api/app/controllers/chunk_controller.py | 4 +-
api/app/controllers/ontology_controller.py | 2 +-
api/app/core/agent/langchain_agent.py | 27 ++--
api/app/core/models/base.py | 125 ++++++++++++------
.../core/models/scripts/bedrock_models.yaml | 21 ++-
.../core/models/scripts/dashscope_models.yaml | 70 +++++++---
.../core/models/scripts/openai_models.yaml | 33 ++++-
.../core/models/scripts/volcano_models.yaml | 16 ++-
api/app/core/workflow/nodes/llm/config.py | 5 +
api/app/core/workflow/nodes/llm/node.py | 21 ++-
api/app/schemas/app_schema.py | 1 +
api/app/services/app_chat_service.py | 2 +
api/app/services/conversation_service.py | 2 +-
api/app/services/draft_run_service.py | 2 +
api/app/services/llm_router.py | 8 +-
api/app/services/master_agent_router.py | 2 +-
api/app/services/memory_perceptual_service.py | 2 +-
api/app/services/model_parameter_merger.py | 3 +-
api/app/services/model_service.py | 8 +-
api/app/services/multi_agent_orchestrator.py | 18 ++-
api/app/services/prompt_optimizer_service.py | 2 +-
api/app/services/shared_chat_service.py | 4 +-
api/app/version_info.json | 36 +++++
23 files changed, 304 insertions(+), 110 deletions(-)
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..90046412 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,7 +9,7 @@ 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
@@ -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,29 @@ 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
+ if self.json_output and self.deep_thinking:
+ logger.warning(
+ f"模型 {self.model_name} json_output 与 deep_thinking 互斥,已自动关闭 deep_thinking"
+ )
+ self.deep_thinking = False
+ self.thinking_budget_tokens = None
+ return self
+
class RedBearModelFactory:
"""模型工厂类"""
@@ -74,17 +98,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 +135,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 +167,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 +229,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)
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..26c3cc2c 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
+ - qwen-vl-plus-latest
is_omni: false
tags:
- 大语言模型
@@ -466,6 +480,7 @@ models:
capability:
- vision
- video
+ - qwen-vl-plus-latest
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": "炼剑",
From 7ba0726473a0443e482c506514d7ee60b65b4f1c Mon Sep 17 00:00:00 2001
From: Timebomb2018 <18868801967@163.com>
Date: Thu, 16 Apr 2026 16:36:15 +0800
Subject: [PATCH 2/8] refactor(model): remove mutual exclusion logic between
json_output and deep_thinking
---
api/app/core/models/base.py | 6 ------
1 file changed, 6 deletions(-)
diff --git a/api/app/core/models/base.py b/api/app/core/models/base.py
index 90046412..57054da1 100644
--- a/api/app/core/models/base.py
+++ b/api/app/core/models/base.py
@@ -52,12 +52,6 @@ class RedBearModelConfig(BaseModel):
f"模型 {self.model_name} 不支持 JSON 输出(capability 中无 'json_output'),已自动关闭 json_output"
)
self.json_output = False
- if self.json_output and self.deep_thinking:
- logger.warning(
- f"模型 {self.model_name} json_output 与 deep_thinking 互斥,已自动关闭 deep_thinking"
- )
- self.deep_thinking = False
- self.thinking_budget_tokens = None
return self
From ccdf7ae81da4107fbd5eb2d4686b2ebd2b62fe11 Mon Sep 17 00:00:00 2001
From: Timebomb2018 <18868801967@163.com>
Date: Thu, 16 Apr 2026 17:40:30 +0800
Subject: [PATCH 3/8] refactor(model): replace VolcanoChatOpenAI with
CompatibleChatOpenAI for unified omni model support
---
api/app/core/models/base.py | 8 ++++----
.../core/models/{volcano_chat.py => compatible_chat.py} | 4 ++--
2 files changed, 6 insertions(+), 6 deletions(-)
rename api/app/core/models/{volcano_chat.py => compatible_chat.py} (92%)
diff --git a/api/app/core/models/base.py b/api/app/core/models/base.py
index 57054da1..7b570b47 100644
--- a/api/app/core/models/base.py
+++ b/api/app/core/models/base.py
@@ -14,7 +14,7 @@ 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")
@@ -255,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)
From 62edafcebe7e268063596194b3ae71eb1b612781 Mon Sep 17 00:00:00 2001
From: Ke Sun
Date: Thu, 16 Apr 2026 17:43:23 +0800
Subject: [PATCH 4/8] ci(workflow): add PR number and merge commit SHA to
WeChat release notification
- Add PR_NUMBER environment variable to capture pull request number
- Add MERGE_SHA environment variable to capture merge commit SHA
- Extract short SHA (first 7 characters) from merge commit for display
- Update notification content to include PR number with # prefix
- Update notification content to include short commit SHA
- Improve release notification with additional metadata for better traceability
---
.github/workflows/release-notify-wechat.yml | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
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"
From e1f8ad871b4c78b29177cc9df837228ebfbc8504 Mon Sep 17 00:00:00 2001
From: Timebomb2018 <18868801967@163.com>
Date: Thu, 16 Apr 2026 17:47:47 +0800
Subject: [PATCH 5/8] refactor(model): replace qwen-vl-plus-latest with
json_output capability in dashscope_models.yaml
---
api/app/core/models/scripts/dashscope_models.yaml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/api/app/core/models/scripts/dashscope_models.yaml b/api/app/core/models/scripts/dashscope_models.yaml
index 26c3cc2c..9b45f107 100644
--- a/api/app/core/models/scripts/dashscope_models.yaml
+++ b/api/app/core/models/scripts/dashscope_models.yaml
@@ -461,7 +461,7 @@ models:
capability:
- vision
- video
- - qwen-vl-plus-latest
+ - json_output
is_omni: false
tags:
- 大语言模型
@@ -480,7 +480,7 @@ models:
capability:
- vision
- video
- - qwen-vl-plus-latest
+ - json_output
is_omni: false
tags:
- 大语言模型
From e015455fb89c2443d3685168ff3752e52bea1382 Mon Sep 17 00:00:00 2001
From: zhaoying
Date: Thu, 16 Apr 2026 19:00:58 +0800
Subject: [PATCH 6/8] feat(web): model support json
---
web/src/components/ModelSelect/index.tsx | 24 +++++++-------
web/src/i18n/en.ts | 3 ++
web/src/i18n/zh.ts | 3 ++
.../components/ModelConfigModal.tsx | 22 +++++++------
web/src/views/ApplicationConfig/types.ts | 15 +++++----
.../components/CustomModelModal.tsx | 17 +++++++---
web/src/views/ModelManagement/types.ts | 5 +--
.../Properties/ModelConfig/index.tsx | 32 ++++++++++++++++---
web/src/views/Workflow/constant.ts | 6 +++-
9 files changed, 88 insertions(+), 39 deletions(-)
diff --git a/web/src/components/ModelSelect/index.tsx b/web/src/components/ModelSelect/index.tsx
index 8f9152fb..0b4f671e 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-16 17:46:02
*/
-import { useEffect, useState, type FC } from 'react';
+import { useEffect, useState, forwardRef, useImperativeHandle } from 'react';
import { Select, Flex, Space } from 'antd';
import type { SelectProps } from 'antd/es/select';
import { useTranslation } from 'react-i18next';
@@ -14,6 +14,10 @@ import type { Query, Model } from '@/views/ModelManagement/types';
import { getListLogoUrl } from '@/views/ModelManagement/utils';
import Tag from '@/components/Tag';
+export interface ModelSelectRef {
+ options: Model[];
+}
+
/** Extends AntD SelectProps; omits filterOption since it's handled internally */
interface ModelSelectProps extends SelectProps {
/** Extra query params passed to getModelList */
@@ -24,17 +28,15 @@ interface ModelSelectProps extends SelectProps {
initialData?: Model[];
}
-const ModelSelect: FC = ({
- params,
- placeholder,
- fontClassName,
- isAutoFetch = true,
- initialData = [],
- ...props
-}) => {
+const ModelSelect = forwardRef((
+ { params, placeholder, fontClassName, isAutoFetch = true, initialData = [], ...props },
+ ref
+) => {
const { t } = useTranslation();
const [options, setOptions] = useState([]);
+ useImperativeHandle(ref, () => ({ options }), [options]);
+
// Fetch active models whenever params change; stringify for stable deep comparison
useEffect(() => {
if (!isAutoFetch) return
@@ -89,6 +91,6 @@ const ModelSelect: FC = ({
{...props}
/>
);
-};
+});
export default ModelSelect;
diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts
index 394606a3..d65e4436 100644
--- a/web/src/i18n/en.ts
+++ b/web/src/i18n/en.ts
@@ -629,6 +629,7 @@ export const en = {
video: 'Video',
thinking: 'Deep Thinking',
is_thinking: 'Deep Thinking Support',
+ json_output: 'Support JSON formatted output',
},
knowledgeBase: {
home: 'Home',
@@ -1524,6 +1525,7 @@ export const en = {
}`,
uploadCover: 'Import and Overwrite',
refresh: 'Refresh Current Page',
+ json_output: 'Support JSON formatted output',
},
userMemory: {
userMemory: 'User Memory',
@@ -2287,6 +2289,7 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
messagesPlaceholder: 'Write prompts here, type "{" to insert variables, type "insert" to insert',
vision: 'Vision',
parameterSettings: 'Parameter Settings',
+ json_output: 'Support JSON formatted output',
},
start: {
variables: 'Input Fields',
diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts
index 98676ef4..921b3554 100644
--- a/web/src/i18n/zh.ts
+++ b/web/src/i18n/zh.ts
@@ -859,6 +859,7 @@ export const zh = {
}`,
uploadCover: '导入并覆盖',
refresh: '刷新当前页',
+ json_output: '支持JSON格式化输出',
},
table: {
totalRecords: '共 {{total}} 条记录'
@@ -1307,6 +1308,7 @@ export const zh = {
video: '视频',
thinking: '深度思考',
is_thinking: '支持深度思考',
+ json_output: '支持JSON格式化输出',
},
timezones: {
'Asia/Shanghai': '中国标准时间 (UTC+8)',
@@ -2248,6 +2250,7 @@ export const zh = {
messagesPlaceholder: '在此处编写提示,输入“{”插入变量,输入“insert”插入',
vision: '视觉',
parameterSettings: '参数设置',
+ json_output: '支持JSON格式化输出',
},
start: {
variables: '输入字段',
diff --git a/web/src/views/ApplicationConfig/components/ModelConfigModal.tsx b/web/src/views/ApplicationConfig/components/ModelConfigModal.tsx
index 8e3e3257..30af7a8c 100644
--- a/web/src/views/ApplicationConfig/components/ModelConfigModal.tsx
+++ b/web/src/views/ApplicationConfig/components/ModelConfigModal.tsx
@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2026-02-03 16:28:07
* @Last Modified by: ZhaoYing
- * @Last Modified time: 2026-03-31 16:56:57
+ * @Last Modified time: 2026-04-16 18:51:01
*/
/**
* Model Configuration Modal
@@ -102,14 +102,15 @@ const ModelConfigModal = forwardRef(
}
/** Handle model selection change */
const handleChange: SelectProps['onChange'] = (_value, option) => {
- if (source === 'chat') {
- form.setFieldValue('label', (option as Model).name)
- }
-
- form.setFieldsValue({
+ const newValues: ModelConfig = {
capability: (option as Model).capability,
deep_thinking: false,
- })
+ json_output: false
+ }
+ if (source === 'chat') {
+ newValues.label = (option as Model).name
+ }
+ form.setFieldsValue(newValues)
}
/** Expose methods to parent component */
@@ -119,12 +120,10 @@ const ModelConfigModal = forwardRef(
}));
useEffect(() => {
- const { deep_thinking: _, ...rest } = data?.model_parameters || {}
+ const { deep_thinking: _, json_output: __, ...rest } = data?.model_parameters || {}
form.setFieldsValue(rest)
}, [values?.default_model_config_id])
-
- console.log('handleChange values', values)
return (
(
{t('application.deep_thinking')}
+
+ {t('application.json_output')}
+
{source === 'chat' && }
diff --git a/web/src/views/ApplicationConfig/types.ts b/web/src/views/ApplicationConfig/types.ts
index 83c11791..3b2755a9 100644
--- a/web/src/views/ApplicationConfig/types.ts
+++ b/web/src/views/ApplicationConfig/types.ts
@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2026-02-03 16:29:49
* @Last Modified by: ZhaoYing
- * @Last Modified time: 2026-04-07 15:46:19
+ * @Last Modified time: 2026-04-16 18:20:14
*/
import type { KnowledgeConfig } from './components/Knowledge/types'
import type { Variable } from './components/VariableList/types'
@@ -24,20 +24,21 @@ export interface ModelConfig {
default_model_config_id?: string;
capability?: Capability[];
/** Temperature for response randomness (0-2) */
- temperature: number;
+ temperature?: number;
/** Maximum tokens in response */
- max_tokens: number;
+ max_tokens?: number;
/** Top-p sampling parameter */
- top_p: number;
+ top_p?: number;
/** Frequency penalty */
- frequency_penalty: number;
+ frequency_penalty?: number;
/** Presence penalty */
- presence_penalty: number;
+ presence_penalty?: number;
/** Number of completions to generate */
- n: number;
+ n?: number;
/** Stop sequences */
stop?: string;
deep_thinking?: boolean;
+ json_output?: boolean;
}
/**
diff --git a/web/src/views/ModelManagement/components/CustomModelModal.tsx b/web/src/views/ModelManagement/components/CustomModelModal.tsx
index 01cc0fd6..89aaec78 100644
--- a/web/src/views/ModelManagement/components/CustomModelModal.tsx
+++ b/web/src/views/ModelManagement/components/CustomModelModal.tsx
@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2026-02-03 16:49:28
* @Last Modified by: ZhaoYing
- * @Last Modified time: 2026-03-31 13:56:18
+ * @Last Modified time: 2026-04-16 18:03:53
*/
/**
* Custom Model Modal
@@ -14,7 +14,7 @@ import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
import { Form, Input, App, Checkbox, Button, Row, Col } from 'antd';
import { useTranslation } from 'react-i18next';
-import type { CustomModelForm, ModelListItem, CustomModelModalRef, CustomModelModalProps } from '../types';
+import type { CustomModelForm, ModelListItem, CustomModelModalRef, CustomModelModalProps, Capability } from '../types';
import RbModal from '@/components/RbModal'
import CustomSelect from '@/components/CustomSelect'
import UploadImages from '@/components/Upload/UploadImages'
@@ -73,6 +73,7 @@ const CustomModelModal = forwardRef(
is_video: capability?.includes('video') || false,
is_audio: capability?.includes('audio') || false,
is_thinking: capability?.includes('thinking') || false,
+ json_output: capability?.includes('json_output') || false,
});
} else {
setIsEdit(false);
@@ -102,13 +103,13 @@ const CustomModelModal = forwardRef(
form
.validateFields()
.then((values) => {
- const { logo, type, is_vision, is_video, is_audio, is_omni, is_thinking, ...rest } = values;
+ const { logo, type, is_vision, is_video, is_audio, is_omni, is_thinking, json_output, ...rest } = values;
const formData: CustomModelForm = {
...rest,
type,
}
if (!['embedding', 'rerank'].includes(type as string)) {
- let capability = is_omni ? ["vision", "audio", 'video'] : []
+ let capability: Capability[] = is_omni ? ["vision", "audio", 'video'] : []
if (!is_omni) {
if (is_vision) {
@@ -124,6 +125,9 @@ const CustomModelModal = forwardRef(
if (is_thinking) {
capability.push('thinking')
}
+ if (json_output) {
+ capability.push('json_output')
+ }
formData.capability = capability
formData.is_omni = is_omni
@@ -269,6 +273,11 @@ const CustomModelModal = forwardRef(
{t('modelNew.is_thinking')}
+
+
+ {t('modelNew.json_output')}
+
+
}
diff --git a/web/src/views/ModelManagement/types.ts b/web/src/views/ModelManagement/types.ts
index cafac4b3..1fa6bd3d 100644
--- a/web/src/views/ModelManagement/types.ts
+++ b/web/src/views/ModelManagement/types.ts
@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2026-02-03 16:50:18
* @Last Modified by: ZhaoYing
- * @Last Modified time: 2026-03-31 15:48:02
+ * @Last Modified time: 2026-04-16 18:04:46
*/
/**
* Type definitions for Model Management
@@ -296,6 +296,7 @@ export interface CustomModelForm {
is_audio?: boolean;
is_omni?: boolean;
is_thinking?: boolean;
+ json_output?: boolean;
capability?: Capability[];
}
@@ -325,7 +326,7 @@ export interface BaseRef {
modelListDetailRefresh?: () => void;
}
-export type Capability = 'vision' | 'audio' | 'video' | 'thinking';
+export type Capability = 'vision' | 'audio' | 'video' | 'thinking' | 'json_output';
export interface Model {
name: string;
type: string;
diff --git a/web/src/views/Workflow/components/Properties/ModelConfig/index.tsx b/web/src/views/Workflow/components/Properties/ModelConfig/index.tsx
index 43476c53..18ad2521 100644
--- a/web/src/views/Workflow/components/Properties/ModelConfig/index.tsx
+++ b/web/src/views/Workflow/components/Properties/ModelConfig/index.tsx
@@ -1,15 +1,28 @@
-import { type FC } from "react";
+import { type FC, useEffect, useRef, useState } from "react";
import { useTranslation } from 'react-i18next'
-import { Form } from 'antd'
+import { Form, Switch } from 'antd'
import RbSlider from '@/components/RbSlider'
import RbCard from '@/components/RbCard/Card'
-import ModelSelect from '@/components/ModelSelect'
+import ModelSelect, { type ModelSelectRef } from '@/components/ModelSelect'
+import type { Model } from '@/views/ModelManagement/types';
const ModelConfig: FC = () => {
const { t } = useTranslation()
const form = Form.useFormInstance()
const model_id = Form.useWatch(['model_id'], form)
+ const modelSelectRef = useRef(null)
+ const [selectedModel, setSelectedModel] = useState(null)
+
+ useEffect(() => {
+ if (model_id && modelSelectRef.current?.options) {
+ const model = modelSelectRef.current?.options.find(item => item.id === model_id)
+ setSelectedModel(model || null)
+ form.setFieldValue('json_output', false)
+ } else {
+ setSelectedModel(null)
+ }
+ }, [model_id, modelSelectRef.current?.options])
console.log('ModelConfig', model_id)
return (
@@ -21,6 +34,7 @@ const ModelConfig: FC = () => {
required
>
{
{
className="rb:-mt-2!"
/>
+
+
+
)}
>
diff --git a/web/src/views/Workflow/constant.ts b/web/src/views/Workflow/constant.ts
index eed77e2c..cae20180 100644
--- a/web/src/views/Workflow/constant.ts
+++ b/web/src/views/Workflow/constant.ts
@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2026-02-03 15:06:18
* @Last Modified by: ZhaoYing
- * @Last Modified time: 2026-04-07 19:56:56
+ * @Last Modified time: 2026-04-16 17:52:30
*/
import LoopNode from './components/Nodes/LoopNode';
import NormalNode from './components/Nodes/NormalNode';
@@ -101,6 +101,10 @@ export const nodeLibrary: NodeLibrary[] = [
step: 1,
defaultValue: 2000
},
+ json_output: {
+ type: 'define',
+ defaultValue: false
+ },
context: {
type: 'variableList',
placeholder: 'workflow.config.llm.contextPlaceholder'
From c96dc535343f5960bc7a3d118d930e6a6a3a2fcd Mon Sep 17 00:00:00 2001
From: zhaoying
Date: Fri, 17 Apr 2026 10:07:45 +0800
Subject: [PATCH 7/8] fix(web): model options update
---
web/src/components/ModelSelect/index.tsx | 18 ++++++-------
.../Properties/ModelConfig/index.tsx | 25 +++++++++++++------
2 files changed, 26 insertions(+), 17 deletions(-)
diff --git a/web/src/components/ModelSelect/index.tsx b/web/src/components/ModelSelect/index.tsx
index 0b4f671e..e89b8eeb 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-04-16 17:46:02
+ * @Last Modified time: 2026-04-17 10:05:23
*/
-import { useEffect, useState, forwardRef, useImperativeHandle } 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';
@@ -26,17 +26,13 @@ interface ModelSelectProps extends SelectProps {
fontClassName?: string;
isAutoFetch?: boolean;
initialData?: Model[];
+ updateOptions?: (options: Model[]) => void;
}
-const ModelSelect = forwardRef((
- { params, placeholder, fontClassName, isAutoFetch = true, initialData = [], ...props },
- ref
-) => {
+const ModelSelect: FC = ({ params, placeholder, fontClassName, isAutoFetch = true, initialData = [], updateOptions, ...props }) => {
const { t } = useTranslation();
const [options, setOptions] = useState([]);
- useImperativeHandle(ref, () => ({ options }), [options]);
-
// Fetch active models whenever params change; stringify for stable deep comparison
useEffect(() => {
if (!isAutoFetch) return
@@ -62,6 +58,10 @@ const ModelSelect = forwardRef((
);
};
+ useEffect(() => {
+ if (updateOptions) updateOptions([...options, ...initialData]);
+ }, [options, initialData])
+
return (