feat(go-agent): Ported retrieval node, added Keenable web search tool (#16396)

Ported retrieval node, added Keenable web search tool
- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
Zhichang Yu
2026-06-26 22:55:49 +08:00
committed by yzc
parent f86a0e7386
commit f58fae5fb7
91 changed files with 5920 additions and 3817 deletions

View File

@@ -574,7 +574,7 @@ async def move_files(uid: str, src_file_ids: list, dest_file_id: str = None, new
)
except Exception as storage_err:
raise RuntimeError(f"Move file failed at storage layer: {str(storage_err)}")
if not moved:
if moved is False:
raise RuntimeError("Move file failed at storage layer")
updates["parent_id"] = dest_folder_entry.id
updates["location"] = new_location

View File

@@ -178,6 +178,32 @@ def split_model_name(model_name: str):
return pure_model_name, instance_name, provider_name
def _resolve_instance_for_model(provider_obj, instance_name: str, model_name: str):
instance_obj = TenantModelInstanceService.get_by_provider_id_and_instance_name(provider_obj.id, instance_name)
if instance_obj:
return instance_obj
if instance_name != "default":
raise LookupError(f"Instance {instance_name} not found for model {model_name}.")
active_instances = [
inst for inst in TenantModelInstanceService.get_all_by_provider_id(provider_obj.id)
if inst.status == ActiveStatusEnum.ACTIVE.value
]
if len(active_instances) == 1:
logger.warning(
"Model instance fallback applied for legacy default instance name",
extra={
"provider_name": provider_obj.provider_name,
"requested_instance_name": instance_name,
"resolved_instance_name": active_instances[0].instance_name,
"model_name": model_name,
},
)
return active_instances[0]
raise LookupError(f"Instance {instance_name} not found for model {model_name}.")
def get_model_config_from_provider_instance(tenant_id, model_type: str|enum.Enum, model_name: str):
pure_model_name, instance_name, provider_name = split_model_name(model_name)
model_type_val = model_type if isinstance(model_type, str) else model_type.value
@@ -203,9 +229,7 @@ def get_model_config_from_provider_instance(tenant_id, model_type: str|enum.Enum
provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_name)
if not provider_obj:
raise LookupError(f"Provider {provider_name} not found for model {model_name}.")
instance_obj = TenantModelInstanceService.get_by_provider_id_and_instance_name(provider_obj.id, instance_name)
if not instance_obj:
raise LookupError(f"Instance {instance_name} not found for model {model_name}.")
instance_obj = _resolve_instance_for_model(provider_obj, instance_name, model_name)
model_obj = TenantModelService.get_by_provider_id_and_instance_id_and_model_type_and_model_name(provider_obj.id, instance_obj.id, model_type_val, pure_model_name)
api_key, is_tool, api_key_payload = _decode_api_key_config(instance_obj.api_key)
@@ -242,7 +266,7 @@ def get_model_config_from_provider_instance(tenant_id, model_type: str|enum.Enum
raise LookupError(f"Model provider config not found: {provider_name}")
llm_list = [llm for llm in fac_list[0]["llm"] if llm["llm_name"] == pure_model_name]
if not llm_list:
raise LookupError(f"Model config not found: {model_name}")
raise LookupError(f"Instance {instance_name} not found for model {model_name}.")
llm_info = llm_list[0]
if model_type_val not in _factory_model_types(llm_info):
raise LookupError(f"Model {model_name} is not a {model_type_val} model.")
@@ -268,9 +292,7 @@ def get_api_key(tenant_id: str, model_name: str):
provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_name)
if not provider_obj:
raise LookupError(f"Provider {provider_name} not found.")
instance_obj = TenantModelInstanceService.get_by_provider_id_and_instance_name(provider_obj.id, instance_name)
if not instance_obj:
raise LookupError(f"Instance {instance_name} not found.")
instance_obj = _resolve_instance_for_model(provider_obj, instance_name, model_name)
return instance_obj.api_key
@@ -279,9 +301,7 @@ def get_model_type_by_name(tenant_id: str, model_name: str):
provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_name)
if not provider_obj:
raise LookupError(f"Provider {provider_name} not found for model {model_name}.")
instance_obj = TenantModelInstanceService.get_by_provider_id_and_instance_name(provider_obj.id, instance_name)
if not instance_obj:
raise LookupError(f"Instance {instance_name} not found for model {model_name}.")
instance_obj = _resolve_instance_for_model(provider_obj, instance_name, model_name)
model_objs = TenantModelService.get_by_provider_id_and_instance_id_and_model_name(provider_obj.id, instance_obj.id, pure_model_name)
types_in_json = []
if not model_objs:

View File

@@ -279,16 +279,20 @@ def normalize_str(v: Any) -> Any:
def validate_uuid1_hex(v: Any) -> str:
"""
Validates and converts input to a UUID version 1 hexadecimal string.
Validates and converts input to a UUID hexadecimal string.
This function performs strict validation and normalization:
The function name is retained for backward compatibility; only UUID
*format* is enforced (any version is accepted), because some IDs in the
system originate from external imports and use non-v1 UUIDs.
This function performs validation and normalization:
1. Accepts either UUID objects or UUID-formatted strings
2. Verifies the UUID is version 1 (time-based)
3. Returns the 32-character hexadecimal representation
2. Returns the 32-character hexadecimal representation
3. Rejects anything that is not a valid UUID
Args:
v (Any): Input value to validate. Can be:
- UUID object (must be version 1)
- UUID object (any version)
- String in UUID format (e.g. "550e8400-e29b-41d4-a716-446655440000")
Returns:
@@ -296,9 +300,8 @@ def validate_uuid1_hex(v: Any) -> str:
Example: "550e8400e29b41d4a716446655440000"
Raises:
PydanticCustomError: With code "invalid_UUID1_format" when:
PydanticCustomError: With code "invalid_uuid_format" when:
- Input is not a UUID object or valid UUID string
- UUID version is not 1
- String doesn't match UUID format
Examples:
@@ -311,20 +314,22 @@ def validate_uuid1_hex(v: Any) -> str:
Invalid cases:
>>> validate_uuid1_hex("not-a-uuid") # raises PydanticCustomError
>>> validate_uuid1_hex(12345) # raises PydanticCustomError
>>> validate_uuid1_hex(UUID(int=0)) # v4, raises PydanticCustomError
Notes:
- Uses Python's built-in UUID parser for format validation
- Version check prevents accidental use of other UUID versions
- UUID version is no longer enforced (v1, v4, v7, etc. all accepted)
- Hyphens in input strings are automatically removed in output
"""
try:
uuid_obj = UUID(v) if isinstance(v, str) else v
if uuid_obj.version != 1:
raise PydanticCustomError("invalid_UUID1_format", "Must be a UUID1 format")
if isinstance(v, UUID):
uuid_obj = v
elif isinstance(v, str):
uuid_obj = UUID(v)
else:
raise TypeError
return uuid_obj.hex
except (AttributeError, ValueError, TypeError):
raise PydanticCustomError("invalid_UUID1_format", "Invalid UUID1 format")
raise PydanticCustomError("invalid_uuid_format", "Invalid UUID format")
class Base(BaseModel):
@@ -801,7 +806,7 @@ class DeleteReq(Base):
This post-processing validator performs:
1. None input handling (pass-through)
2. UUID version 1 validation for each list item
2. UUID format validation for each list item (any version accepted)
3. Duplicate value detection
4. Returns normalized UUID hex strings or None
@@ -814,18 +819,18 @@ class DeleteReq(Base):
- None if input was None
- List of normalized UUID hex strings otherwise:
* 32-character lowercase
* Valid UUID version 1
* Valid UUID format (any version)
* Unique within list
Raises:
PydanticCustomError: With structured error details when:
- "invalid_UUID1_format": Any string fails UUIDv1 validation
- "invalid_uuid_format": Any string fails UUID format validation
- "duplicate_uuids": If duplicate IDs are detected
Validation Rules:
- None input returns None
- Empty list returns empty list
- All non-None items must be valid UUIDv1
- All non-None items must be valid UUIDs (any version)
- No duplicates permitted
- Original order preserved
@@ -840,12 +845,12 @@ class DeleteReq(Base):
Invalid cases:
>>> validate_ids(["invalid"])
# raises PydanticCustomError(invalid_UUID1_format)
# raises PydanticCustomError(invalid_uuid_format)
>>> validate_ids(["550e...", "550e..."])
# raises PydanticCustomError(duplicate_uuids)
Security Notes:
- Validates UUID version to prevent version spoofing
- Validates UUID format (any version)
- Duplicate check prevents data injection
- None handling maintains pipeline integrity
"""