Fix: support tool call config (#14616)

### What problem does this PR solve?
support tool call config

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
buua436
2026-05-07 15:54:57 +08:00
committed by GitHub
parent 5b162a0c46
commit 0501134820
9 changed files with 137 additions and 15 deletions

View File

@@ -29,6 +29,23 @@ from rag.utils.base64_image import test_image
from rag.llm import EmbeddingModel, ChatModel, RerankModel, CvModel, TTSModel, OcrModel, Seq2txtModel
def _resolve_my_llm_is_tools(o_dict: dict) -> bool:
decode_api_key_config = getattr(TenantLLMService, "_decode_api_key_config", None)
if callable(decode_api_key_config):
_, is_tools, _ = decode_api_key_config(o_dict.get("api_key", ""))
if is_tools is not None:
return bool(is_tools)
try:
base_name, fid = TenantLLMService.split_model_name_and_factory(o_dict["llm_name"])
llm_cfg = LLMService.query(llm_name=base_name, fid=fid) if fid else LLMService.query(llm_name=base_name)
if not llm_cfg and fid:
llm_cfg = LLMService.query(llm_name=base_name)
return bool(llm_cfg[0].is_tools) if llm_cfg else False
except Exception:
return False
@manager.route("/factories", methods=["GET"]) # noqa: F821
@login_required
def factories():
@@ -229,6 +246,19 @@ async def add_llm():
elif factory == "OpenDataLoader":
api_key = apikey_json(["api_key", "provider_order"])
existing_llm = None
existing_api_key = None
if req.get("api_key") is None:
existing_llms = TenantLLMService.query(tenant_id=current_user.id, llm_factory=factory, llm_name=llm_name)
if existing_llms:
existing_llm = existing_llms[0]
existing_api_key, _, existing_api_key_payload = TenantLLMService._decode_api_key_config(existing_llm.api_key)
if existing_api_key_payload is not None:
existing_api_key = existing_api_key_payload
if req.get("api_key") is None:
api_key = existing_api_key if existing_api_key is not None else "x"
llm = {
"tenant_id": current_user.id,
"llm_factory": factory,
@@ -353,6 +383,9 @@ async def add_llm():
if msg:
return get_data_error_result(message=msg)
if "is_tools" in req:
llm["api_key"] = TenantLLMService._encode_api_key_config(llm["api_key"], bool(req["is_tools"]))
if not TenantLLMService.filter_update([TenantLLM.tenant_id == current_user.id, TenantLLM.llm_factory == factory, TenantLLM.llm_name == llm["llm_name"]], llm):
TenantLLMService.save(**llm)
@@ -421,6 +454,7 @@ def my_llms():
"api_base": o_dict["api_base"] or "",
"max_tokens": o_dict["max_tokens"] or 8192,
"status": o_dict["status"] or "1",
"is_tools": _resolve_my_llm_is_tools(o_dict),
}
)
else:

View File

@@ -26,8 +26,14 @@ def get_model_config_by_id(tenant_model_id: int) -> dict:
if not found:
raise LookupError(f"Tenant Model with id {tenant_model_id} not found")
config_dict = model_config.to_dict()
api_key, is_tools, api_key_payload = TenantLLMService._decode_api_key_config(config_dict.get("api_key", ""))
config_dict["api_key"] = api_key
if api_key_payload is not None:
config_dict["api_key_payload"] = api_key_payload
if is_tools is not None:
config_dict["is_tools"] = is_tools
llm = LLMService.query(llm_name=config_dict["llm_name"])
if llm:
if "is_tools" not in config_dict and llm:
config_dict["is_tools"] = llm[0].is_tools
return config_dict
@@ -73,6 +79,12 @@ def get_model_config_by_type_and_name(tenant_id: str, model_type: str, model_nam
else:
# model_name without @factory
config_dict = model_config.to_dict()
api_key, is_tools, api_key_payload = TenantLLMService._decode_api_key_config(config_dict.get("api_key", ""))
config_dict["api_key"] = api_key
if api_key_payload is not None:
config_dict["api_key_payload"] = api_key_payload
if is_tools is not None:
config_dict["is_tools"] = is_tools
config_model_type = config_dict.get("model_type")
config_model_type = config_model_type.value if hasattr(config_model_type, "value") else config_model_type
if config_model_type != model_type_val and not (
@@ -83,7 +95,7 @@ def get_model_config_by_type_and_name(tenant_id: str, model_type: str, model_nam
f"Tenant Model with name {model_name} has type {config_model_type}, expected {model_type_val}"
)
llm = LLMService.query(llm_name=config_dict["llm_name"])
if llm:
if "is_tools" not in config_dict and llm:
config_dict["is_tools"] = llm[0].is_tools
return config_dict

View File

@@ -34,6 +34,42 @@ class LLMFactoriesService(CommonService):
class TenantLLMService(CommonService):
model = TenantLLM
@staticmethod
def _decode_api_key_config(raw_api_key: str) -> tuple[str, bool | None, str | None]:
if not raw_api_key:
return raw_api_key, None, None
try:
parsed = json.loads(raw_api_key)
except Exception:
return raw_api_key, None, None
if not isinstance(parsed, dict):
return raw_api_key, None, None
is_tools = bool(parsed["is_tools"]) if "is_tools" in parsed else None
if set(parsed.keys()) <= {"api_key", "is_tools"}:
return parsed.get("api_key", ""), is_tools, None
return parsed.get("api_key", raw_api_key), is_tools, raw_api_key
@staticmethod
def _encode_api_key_config(raw_api_key: str, is_tools: bool | None) -> str:
if is_tools is None:
return raw_api_key
try:
parsed = json.loads(raw_api_key or "{}")
except Exception:
parsed = None
if isinstance(parsed, dict):
payload = dict(parsed)
payload["is_tools"] = bool(is_tools)
return json.dumps(payload)
return json.dumps({"api_key": raw_api_key or "", "is_tools": bool(is_tools)})
@classmethod
@DB.connection_context()
def get_api_key(cls, tenant_id, model_name, model_type=None):
@@ -123,6 +159,12 @@ class TenantLLMService(CommonService):
model_config = cls.get_api_key(tenant_id, mdlnm, llm_type)
if model_config:
model_config = model_config.to_dict()
api_key, is_tools, api_key_payload = cls._decode_api_key_config(model_config.get("api_key", ""))
model_config["api_key"] = api_key
if api_key_payload is not None:
model_config["api_key_payload"] = api_key_payload
if is_tools is not None:
model_config["is_tools"] = is_tools
elif llm_type == LLMType.EMBEDDING and fid == "Builtin" and "tei-" in os.getenv("COMPOSE_PROFILES", "") and mdlnm == os.getenv("TEI_MODEL", ""):
embedding_cfg = settings.EMBEDDING_CFG
model_config = {"llm_factory": "Builtin", "api_key": embedding_cfg["api_key"], "llm_name": mdlnm, "api_base": embedding_cfg["base_url"]}
@@ -132,7 +174,7 @@ class TenantLLMService(CommonService):
llm = LLMService.query(llm_name=mdlnm) if not fid else LLMService.query(llm_name=mdlnm, fid=fid)
if not llm and fid: # for some cases seems fid mismatch
llm = LLMService.query(llm_name=mdlnm)
if llm:
if "is_tools" not in model_config and llm:
model_config["is_tools"] = llm[0].is_tools
return model_config
@@ -142,35 +184,36 @@ class TenantLLMService(CommonService):
if not model_config:
raise LookupError("Model config is required")
kwargs.update({"provider": model_config["llm_factory"]})
api_key = model_config.get("api_key_payload", model_config["api_key"])
if model_config["model_type"] == LLMType.EMBEDDING.value:
if model_config["llm_factory"] not in EmbeddingModel:
return None
return EmbeddingModel[model_config["llm_factory"]](model_config["api_key"], model_config["llm_name"], base_url=model_config["api_base"])
return EmbeddingModel[model_config["llm_factory"]](api_key, model_config["llm_name"], base_url=model_config["api_base"])
elif model_config["model_type"] == LLMType.RERANK:
if model_config["llm_factory"] not in RerankModel:
return None
return RerankModel[model_config["llm_factory"]](model_config["api_key"], model_config["llm_name"], base_url=model_config["api_base"])
return RerankModel[model_config["llm_factory"]](api_key, model_config["llm_name"], base_url=model_config["api_base"])
elif model_config["model_type"] == LLMType.IMAGE2TEXT.value:
if model_config["llm_factory"] not in CvModel:
return None
return CvModel[model_config["llm_factory"]](model_config["api_key"], model_config["llm_name"], lang, base_url=model_config["api_base"], **kwargs)
return CvModel[model_config["llm_factory"]](api_key, model_config["llm_name"], lang, base_url=model_config["api_base"], **kwargs)
elif model_config["model_type"] == LLMType.CHAT.value:
if model_config["llm_factory"] not in ChatModel:
return None
return ChatModel[model_config["llm_factory"]](model_config["api_key"], model_config["llm_name"], base_url=model_config["api_base"], **kwargs)
return ChatModel[model_config["llm_factory"]](api_key, model_config["llm_name"], base_url=model_config["api_base"], **kwargs)
elif model_config["model_type"] == LLMType.SPEECH2TEXT:
if model_config["llm_factory"] not in Seq2txtModel:
return None
return Seq2txtModel[model_config["llm_factory"]](key=model_config["api_key"], model_name=model_config["llm_name"], lang=lang, base_url=model_config["api_base"])
return Seq2txtModel[model_config["llm_factory"]](key=api_key, model_name=model_config["llm_name"], lang=lang, base_url=model_config["api_base"])
elif model_config["model_type"] == LLMType.TTS:
if model_config["llm_factory"] not in TTSModel:
return None
return TTSModel[model_config["llm_factory"]](
model_config["api_key"],
api_key,
model_config["llm_name"],
base_url=model_config["api_base"],
)
@@ -179,7 +222,7 @@ class TenantLLMService(CommonService):
if model_config["llm_factory"] not in OcrModel:
return None
return OcrModel[model_config["llm_factory"]](
key=model_config["api_key"],
key=api_key,
model_name=model_config["llm_name"],
base_url=model_config.get("api_base", ""),
**kwargs,