mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-08 12:24:48 +08:00
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:
@@ -277,10 +277,13 @@ class Agent(LLM, ToolBase):
|
||||
return
|
||||
if delta.find("**ERROR**") >= 0:
|
||||
if self.get_exception_default_value():
|
||||
self.set_output("content", self.get_exception_default_value())
|
||||
yield self.get_exception_default_value()
|
||||
fallback = self.get_exception_default_value()
|
||||
self.set_output("content", fallback)
|
||||
yield fallback
|
||||
else:
|
||||
self.set_output("_ERROR", delta)
|
||||
self.set_output("content", delta)
|
||||
yield delta
|
||||
return
|
||||
if not need2cite or cited:
|
||||
yield delta
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -5,6 +5,7 @@ export interface IAddLlmRequestBody {
|
||||
api_base?: string; // chat|embedding|speech2text|image2text
|
||||
api_key?: string | Record<string, any>;
|
||||
max_tokens: number;
|
||||
is_tools?: boolean;
|
||||
}
|
||||
|
||||
export interface IDeleteLlmRequestBody {
|
||||
|
||||
@@ -1126,6 +1126,9 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
|
||||
Verify: 'Verify',
|
||||
keyValid: 'Your API key is valid.',
|
||||
keyInvalid: 'Your API key is invalid.',
|
||||
enableToolCall: 'Enable tool call',
|
||||
enableToolCallTip:
|
||||
'Allow this model to call tools when the selected model type supports tool calling.',
|
||||
deleteModel: 'Delete model',
|
||||
bedrockCredentialsHint:
|
||||
'Tip: Leave Access Key / Secret Key blank to use AWS IAM authentication.',
|
||||
|
||||
@@ -1036,6 +1036,8 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
||||
Verify: '验证',
|
||||
keyValid: '你的 API 密钥有效。',
|
||||
keyInvalid: '你的 API 密钥无效。',
|
||||
enableToolCall: '启用工具调用',
|
||||
enableToolCallTip: '当所选模型类型支持工具调用时,允许该模型调用工具。',
|
||||
deleteModel: '删除模型',
|
||||
modelEmptyTip: '暂无可用模型,<br>请先在右侧面板添加模型。',
|
||||
sourceEmptyTip: '暂未添加任何数据源,请从下方选择一个进行连接。',
|
||||
|
||||
@@ -228,6 +228,7 @@ export const useSubmitOllama = () => {
|
||||
api_base: detailedData.api_base || '',
|
||||
max_tokens: detailedData.max_tokens || 8192,
|
||||
api_key: '',
|
||||
is_tools: detailedData.is_tools || false,
|
||||
};
|
||||
setInitialValues(initialVals);
|
||||
} else {
|
||||
|
||||
@@ -115,6 +115,7 @@ const OllamaModal = ({
|
||||
const getOptions = (factory: string) => {
|
||||
return optionsMap[factory as LLMFactory] || optionsMap.Default;
|
||||
};
|
||||
const defaultToolCallEnabled = initialValues?.is_tools ?? false;
|
||||
|
||||
const baseFields: FormFieldConfig[] = [
|
||||
{
|
||||
@@ -177,6 +178,20 @@ const OllamaModal = ({
|
||||
},
|
||||
];
|
||||
|
||||
baseFields.push({
|
||||
name: 'is_tools',
|
||||
label: t('enableToolCall'),
|
||||
type: FormFieldType.Switch,
|
||||
required: false,
|
||||
dependencies: ['model_type'],
|
||||
shouldRender: (formValues: any) => {
|
||||
const modelType = formValues?.model_type;
|
||||
return modelType === 'chat' || modelType === 'image2text';
|
||||
},
|
||||
tooltip: t('enableToolCallTip'),
|
||||
defaultValue: defaultToolCallEnabled,
|
||||
});
|
||||
|
||||
// Add provider_order field only for OpenRouter
|
||||
if (llmFactory === 'OpenRouter') {
|
||||
baseFields.push({
|
||||
@@ -214,14 +229,18 @@ const OllamaModal = ({
|
||||
api_key: '',
|
||||
vision: initialValues.model_type === 'image2text',
|
||||
provider_order: initialValues.provider_order || '',
|
||||
is_tools: initialValues.is_tools || false,
|
||||
};
|
||||
}
|
||||
return {
|
||||
model_type:
|
||||
llmFactory in optionsMap
|
||||
? optionsMap[llmFactory as LLMFactory]?.at(0)?.value
|
||||
: 'embedding',
|
||||
llmFactory === LLMFactory.Ollama || llmFactory === LLMFactory.VLLM
|
||||
? 'chat'
|
||||
: llmFactory in optionsMap
|
||||
? optionsMap[llmFactory as LLMFactory]?.at(0)?.value
|
||||
: 'embedding',
|
||||
vision: false,
|
||||
is_tools: false,
|
||||
};
|
||||
}, [editMode, initialValues, llmFactory]);
|
||||
|
||||
@@ -232,6 +251,7 @@ const OllamaModal = ({
|
||||
values.model_type === 'chat' && values.vision
|
||||
? 'image2text'
|
||||
: values.model_type;
|
||||
const supportsToolCall = modelType === 'chat' || modelType === 'image2text';
|
||||
|
||||
const data: IAddLlmRequestBody & { provider_order?: string } = {
|
||||
llm_factory: llmFactory,
|
||||
@@ -241,6 +261,9 @@ const OllamaModal = ({
|
||||
api_key: values.api_key as string,
|
||||
max_tokens: values.max_tokens as number,
|
||||
};
|
||||
if (supportsToolCall) {
|
||||
data.is_tools = Boolean(values.is_tools);
|
||||
}
|
||||
|
||||
// Add provider_order only if it exists (for OpenRouter)
|
||||
if (values.provider_order) {
|
||||
|
||||
Reference in New Issue
Block a user