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

@@ -14,7 +14,6 @@
# limitations under the License.
#
from agent.component.fillup import UserFillUpParam, UserFillUp
from api.db.services.file_service import FileService
class BeginParam(UserFillUpParam):
@@ -42,20 +41,11 @@ class Begin(UserFillUp):
return
layout_recognize = self._param.layout_recognize or None
for k, v in kwargs.get("inputs", {}).items():
merged_inputs = self._merge_runtime_inputs(kwargs.get("inputs", {}))
for k, v in merged_inputs.items():
if self.check_if_canceled("Begin processing"):
return
if isinstance(v, dict) and v.get("type", "").lower().find("file") >= 0:
if v.get("optional") and v.get("value", None) is None:
v = None
else:
file_value = v["value"]
# Support both single file (backward compatibility) and multiple files
files = file_value if isinstance(file_value, list) else [file_value]
v = FileService.get_files(files, layout_recognize=layout_recognize)
else:
v = v.get("value")
v = self._resolve_input_value(v, layout_recognize)
self.set_output(k, v)
self.set_input_value(k, v)

View File

@@ -21,6 +21,9 @@ from agent.component.base import ComponentParamBase, ComponentBase
from api.db.services.file_service import FileService
_INITIAL_USER_INPUT_CONSUMED_KEY = "sys.__initial_user_input_consumed__"
class UserFillUpParam(ComponentParamBase):
def __init__(self):
@@ -36,6 +39,52 @@ class UserFillUpParam(ComponentParamBase):
class UserFillUp(ComponentBase):
component_name = "UserFillUp"
def _merge_runtime_inputs(self, runtime_inputs):
if runtime_inputs:
return runtime_inputs
fields = self.get_input_elements()
if not fields:
return {}
if self._canvas.globals.get(_INITIAL_USER_INPUT_CONSUMED_KEY):
return {}
query = self._canvas.globals.get("sys.query")
if query is None or query == "":
return {}
if isinstance(query, dict):
matched = {
key: value if isinstance(value, dict) else {"value": value}
for key, value in query.items()
if key in fields
}
if matched:
self._canvas.globals[_INITIAL_USER_INPUT_CONSUMED_KEY] = True
return matched
if len(fields) == 1:
field_name = next(iter(fields))
self._canvas.globals[_INITIAL_USER_INPUT_CONSUMED_KEY] = True
return {field_name: {"value": query}}
return {}
def _resolve_input_value(self, value, layout_recognize):
if isinstance(value, dict) and value.get("type", "").lower().find("file") >= 0:
if value.get("optional") and value.get("value", None) is None:
return None
file_value = value["value"]
files = file_value if isinstance(file_value, list) else [file_value]
return FileService.get_files(files, layout_recognize=layout_recognize)
if isinstance(value, dict):
return value.get("value")
return value
def _invoke(self, **kwargs):
if self.check_if_canceled("UserFillUp processing"):
return
@@ -63,20 +112,13 @@ class UserFillUp(ComponentBase):
self.set_output("tips", content)
layout_recognize = self._param.layout_recognize or None
for k, v in kwargs.get("inputs", {}).items():
merged_inputs = self._merge_runtime_inputs(kwargs.get("inputs", {}))
for k, v in merged_inputs.items():
if self.check_if_canceled("UserFillUp processing"):
return
if isinstance(v, dict) and v.get("type", "").lower().find("file") >= 0:
if v.get("optional") and v.get("value", None) is None:
v = None
else:
file_value = v["value"]
# Support both single file (backward compatibility) and multiple files
files = file_value if isinstance(file_value, list) else [file_value]
v = FileService.get_files(files, layout_recognize=layout_recognize)
else:
v = v.get("value")
self.set_output(k, v)
resolved = self._resolve_input_value(v, layout_recognize)
self.set_output(k, resolved)
self.set_input_value(k, resolved)
def thoughts(self) -> str:
return "Waiting for your input..."

View File

@@ -38,9 +38,17 @@ class ListOperationsParam(ComponentParamBase):
"type": "?"
}
}
@staticmethod
def _normalize_operation_name(operation):
op = "" if operation is None else str(operation).strip()
if op.lower() == "topn":
return "head"
return op or "nth"
def check(self):
self.check_empty(self.query, "query")
self.operations = self._normalize_operation_name(self.operations)
self.check_valid_value(
self.operations,
"Support operations",