Add iteration for agent. (#4258)

### What problem does this PR solve?

#4242
### Type of change

- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
Kevin Hu 2024-12-27 11:38:33 +08:00 committed by GitHub
parent a1a825c830
commit c3e3f0fbb4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 150 additions and 12 deletions

View File

@ -18,6 +18,9 @@ import json
from abc import ABC from abc import ABC
from copy import deepcopy from copy import deepcopy
from functools import partial from functools import partial
import pandas as pd
from agent.component import component_class from agent.component import component_class
from agent.component.base import ComponentBase from agent.component.base import ComponentBase
@ -83,7 +86,8 @@ class Canvas(ABC):
} }
}, },
"downstream": [], "downstream": [],
"upstream": [] "upstream": [],
"parent_id": ""
} }
}, },
"history": [], "history": [],
@ -207,6 +211,14 @@ class Canvas(ABC):
waiting.append(c) waiting.append(c)
continue continue
yield "*'{}'* is running...🕞".format(self.get_compnent_name(c)) yield "*'{}'* is running...🕞".format(self.get_compnent_name(c))
if cpn.component_name.lower() == "iteration":
st_cpn = cpn.get_start()
assert st_cpn, "Start component not found for Iteration."
if not st_cpn["obj"].end():
cpn = st_cpn["obj"]
c = cpn._id
try: try:
ans = cpn.run(self.history, **kwargs) ans = cpn.run(self.history, **kwargs)
except Exception as e: except Exception as e:
@ -215,16 +227,26 @@ class Canvas(ABC):
ran += 1 ran += 1
raise e raise e
self.path[-1].append(c) self.path[-1].append(c)
ran += 1 ran += 1
for m in prepare2run(self.components[self.path[-2][-1]]["downstream"]): downstream = self.components[self.path[-2][-1]]["downstream"]
if not downstream and self.components[self.path[-2][-1]].get("parent_id"):
cid = self.path[-2][-1]
pid = self.components[cid]["parent_id"]
o, _ = self.components[cid]["obj"].output(allow_partial=False)
oo, _ = self.components[pid]["obj"].output(allow_partial=False)
self.components[pid]["obj"].set(pd.concat([oo, o], ignore_index=True))
downstream = [pid]
for m in prepare2run(downstream):
yield {"content": m, "running_status": True} yield {"content": m, "running_status": True}
while 0 <= ran < len(self.path[-1]): while 0 <= ran < len(self.path[-1]):
logging.debug(f"Canvas.run: {ran} {self.path}") logging.debug(f"Canvas.run: {ran} {self.path}")
cpn_id = self.path[-1][ran] cpn_id = self.path[-1][ran]
cpn = self.get_component(cpn_id) cpn = self.get_component(cpn_id)
if not cpn["downstream"]: if not any([cpn["downstream"], cpn.get("parent_id"), waiting]):
break break
loop = self._find_loop() loop = self._find_loop()
@ -239,7 +261,15 @@ class Canvas(ABC):
yield {"content": m, "running_status": True} yield {"content": m, "running_status": True}
continue continue
for m in prepare2run(cpn["downstream"]): downstream = cpn["downstream"]
if not downstream and cpn.get("parent_id"):
pid = cpn["parent_id"]
_, o = cpn["obj"].output(allow_partial=False)
_, oo = self.components[pid]["obj"].output(allow_partial=False)
self.components[pid]["obj"].set_output(pd.concat([oo.dropna(axis=1), o.dropna(axis=1)], ignore_index=True))
downstream = [pid]
for m in prepare2run(downstream):
yield {"content": m, "running_status": True} yield {"content": m, "running_status": True}
if ran >= len(self.path[-1]) and waiting: if ran >= len(self.path[-1]) and waiting:
@ -247,6 +277,7 @@ class Canvas(ABC):
waiting = [] waiting = []
for m in prepare2run(without_dependent_checking): for m in prepare2run(without_dependent_checking):
yield {"content": m, "running_status": True} yield {"content": m, "running_status": True}
without_dependent_checking = []
ran -= 1 ran -= 1
if self.answer: if self.answer:
@ -294,7 +325,7 @@ class Canvas(ABC):
return False return False
for i in range(len(path)): for i in range(len(path)):
if path[i].lower().find("answer") >= 0: if path[i].lower().find("answer") == 0 or path[i].lower().find("iterationitem") == 0:
path = path[:i] path = path[:i]
break break

View File

@ -32,6 +32,8 @@ from .crawler import Crawler, CrawlerParam
from .invoke import Invoke, InvokeParam from .invoke import Invoke, InvokeParam
from .template import Template, TemplateParam from .template import Template, TemplateParam
from .email import Email, EmailParam from .email import Email, EmailParam
from .iteration import Iteration, IterationParam
from .iterationitem import IterationItem, IterationItemParam
@ -103,6 +105,10 @@ __all__ = [
"CrawlerParam", "CrawlerParam",
"Invoke", "Invoke",
"InvokeParam", "InvokeParam",
"Iteration",
"IterationParam",
"IterationItem",
"IterationItemParam",
"Template", "Template",
"TemplateParam", "TemplateParam",
"Email", "Email",

View File

@ -44,7 +44,7 @@ class Baidu(ComponentBase, ABC):
return Baidu.be_output("") return Baidu.be_output("")
try: try:
url = 'https://www.baidu.com/s?wd=' + ans + '&rn=' + str(self._param.top_n) url = 'http://www.baidu.com/s?wd=' + ans + '&rn=' + str(self._param.top_n)
headers = { headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Safari/537.36'} 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Safari/537.36'}
response = requests.get(url=url, headers=headers) response = requests.get(url=url, headers=headers)

View File

@ -426,10 +426,14 @@ class ComponentBase(ABC):
def output(self, allow_partial=True) -> Tuple[str, Union[pd.DataFrame, partial]]: def output(self, allow_partial=True) -> Tuple[str, Union[pd.DataFrame, partial]]:
o = getattr(self._param, self._param.output_var_name) o = getattr(self._param, self._param.output_var_name)
if not isinstance(o, partial) and not isinstance(o, pd.DataFrame): if not isinstance(o, partial):
if not isinstance(o, list): if not isinstance(o, pd.DataFrame):
o = [o] if isinstance(o, list):
o = pd.DataFrame(o) return self._param.output_var_name, pd.DataFrame(o)
if o is None:
return self._param.output_var_name, pd.DataFrame()
return self._param.output_var_name, pd.DataFrame([{"content": str(o)}])
return self._param.output_var_name, o
if allow_partial or not isinstance(o, partial): if allow_partial or not isinstance(o, partial):
if not isinstance(o, partial) and not isinstance(o, pd.DataFrame): if not isinstance(o, partial) and not isinstance(o, pd.DataFrame):
@ -574,4 +578,8 @@ class ComponentBase(ABC):
return self._canvas.get_component(cpn_id)["obj"].component_name.lower() return self._canvas.get_component(cpn_id)["obj"].component_name.lower()
def debug(self, **kwargs): def debug(self, **kwargs):
return self._run([], **kwargs) return self._run([], **kwargs)
def get_parent(self):
pid = self._canvas.get_component(self._id)["parent_id"]
return self._canvas.get_component(pid)["obj"]

View File

@ -0,0 +1,45 @@
#
# Copyright 2024 The InfiniFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from abc import ABC
from agent.component.base import ComponentBase, ComponentParamBase
class IterationParam(ComponentParamBase):
"""
Define the Iteration component parameters.
"""
def __init__(self):
super().__init__()
self.delimiter = ","
def check(self):
self.check_empty(self.delimiter, "Delimiter")
class Iteration(ComponentBase, ABC):
component_name = "Iteration"
def get_start(self):
for cid in self._canvas.components.keys():
if self._canvas.get_component(cid)["obj"].component_name.lower() != "iterationitem":
continue
if self._canvas.get_component(cid)["parent_id"] == self._id:
return self._canvas.get_component(cid)
def _run(self, history, **kwargs):
return self.output(allow_partial=False)[1]

View File

@ -0,0 +1,49 @@
#
# Copyright 2024 The InfiniFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from abc import ABC
import pandas as pd
from agent.component.base import ComponentBase, ComponentParamBase
class IterationItemParam(ComponentParamBase):
"""
Define the IterationItem component parameters.
"""
def check(self):
return True
class IterationItem(ComponentBase, ABC):
component_name = "IterationItem"
def __init__(self, canvas, id, param: ComponentParamBase):
super().__init__(canvas, id, param)
self._idx = 0
def _run(self, history, **kwargs):
parent = self.get_parent()
ans = parent.get_input()
ans = parent._param.delimiter.join(ans["content"]) if "content" in ans else ""
ans = [a.strip() for a in ans.split(parent._param.delimiter)]
df = pd.DataFrame([{"content": ans[self._idx]}])
self._idx += 1
if self._idx >= len(ans):
self._idx = -1
return df
def end(self):
return self._idx == -1

View File

@ -53,7 +53,6 @@ class API4ConversationService(CommonService):
sessions = sessions.order_by(cls.model.getter_by(orderby).desc()) sessions = sessions.order_by(cls.model.getter_by(orderby).desc())
else: else:
sessions = sessions.order_by(cls.model.getter_by(orderby).asc()) sessions = sessions.order_by(cls.model.getter_by(orderby).asc())
sessions = sessions.where(cls.model.user_id == tenant_id)
sessions = sessions.paginate(page_number, items_per_page) sessions = sessions.paginate(page_number, items_per_page)
return list(sessions.dicts()) return list(sessions.dicts())