Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. #
  2. # Copyright 2024 The InfiniFlow Authors. All Rights Reserved.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. #
  16. from abc import ABC
  17. import builtins
  18. import json
  19. import os
  20. import logging
  21. from functools import partial
  22. from typing import Tuple, Union
  23. import pandas as pd
  24. from agent import settings
  25. _FEEDED_DEPRECATED_PARAMS = "_feeded_deprecated_params"
  26. _DEPRECATED_PARAMS = "_deprecated_params"
  27. _USER_FEEDED_PARAMS = "_user_feeded_params"
  28. _IS_RAW_CONF = "_is_raw_conf"
  29. class ComponentParamBase(ABC):
  30. def __init__(self):
  31. self.output_var_name = "output"
  32. self.message_history_window_size = 22
  33. self.query = []
  34. self.inputs = []
  35. self.debug_inputs = []
  36. def set_name(self, name: str):
  37. self._name = name
  38. return self
  39. def check(self):
  40. raise NotImplementedError("Parameter Object should be checked.")
  41. @classmethod
  42. def _get_or_init_deprecated_params_set(cls):
  43. if not hasattr(cls, _DEPRECATED_PARAMS):
  44. setattr(cls, _DEPRECATED_PARAMS, set())
  45. return getattr(cls, _DEPRECATED_PARAMS)
  46. def _get_or_init_feeded_deprecated_params_set(self, conf=None):
  47. if not hasattr(self, _FEEDED_DEPRECATED_PARAMS):
  48. if conf is None:
  49. setattr(self, _FEEDED_DEPRECATED_PARAMS, set())
  50. else:
  51. setattr(
  52. self,
  53. _FEEDED_DEPRECATED_PARAMS,
  54. set(conf[_FEEDED_DEPRECATED_PARAMS]),
  55. )
  56. return getattr(self, _FEEDED_DEPRECATED_PARAMS)
  57. def _get_or_init_user_feeded_params_set(self, conf=None):
  58. if not hasattr(self, _USER_FEEDED_PARAMS):
  59. if conf is None:
  60. setattr(self, _USER_FEEDED_PARAMS, set())
  61. else:
  62. setattr(self, _USER_FEEDED_PARAMS, set(conf[_USER_FEEDED_PARAMS]))
  63. return getattr(self, _USER_FEEDED_PARAMS)
  64. def get_user_feeded(self):
  65. return self._get_or_init_user_feeded_params_set()
  66. def get_feeded_deprecated_params(self):
  67. return self._get_or_init_feeded_deprecated_params_set()
  68. @property
  69. def _deprecated_params_set(self):
  70. return {name: True for name in self.get_feeded_deprecated_params()}
  71. def __str__(self):
  72. return json.dumps(self.as_dict(), ensure_ascii=False)
  73. def as_dict(self):
  74. def _recursive_convert_obj_to_dict(obj):
  75. ret_dict = {}
  76. for attr_name in list(obj.__dict__):
  77. if attr_name in [_FEEDED_DEPRECATED_PARAMS, _DEPRECATED_PARAMS, _USER_FEEDED_PARAMS, _IS_RAW_CONF]:
  78. continue
  79. # get attr
  80. attr = getattr(obj, attr_name)
  81. if isinstance(attr, pd.DataFrame):
  82. ret_dict[attr_name] = attr.to_dict()
  83. continue
  84. if attr and type(attr).__name__ not in dir(builtins):
  85. ret_dict[attr_name] = _recursive_convert_obj_to_dict(attr)
  86. else:
  87. ret_dict[attr_name] = attr
  88. return ret_dict
  89. return _recursive_convert_obj_to_dict(self)
  90. def update(self, conf, allow_redundant=False):
  91. update_from_raw_conf = conf.get(_IS_RAW_CONF, True)
  92. if update_from_raw_conf:
  93. deprecated_params_set = self._get_or_init_deprecated_params_set()
  94. feeded_deprecated_params_set = (
  95. self._get_or_init_feeded_deprecated_params_set()
  96. )
  97. user_feeded_params_set = self._get_or_init_user_feeded_params_set()
  98. setattr(self, _IS_RAW_CONF, False)
  99. else:
  100. feeded_deprecated_params_set = (
  101. self._get_or_init_feeded_deprecated_params_set(conf)
  102. )
  103. user_feeded_params_set = self._get_or_init_user_feeded_params_set(conf)
  104. def _recursive_update_param(param, config, depth, prefix):
  105. if depth > settings.PARAM_MAXDEPTH:
  106. raise ValueError("Param define nesting too deep!!!, can not parse it")
  107. inst_variables = param.__dict__
  108. redundant_attrs = []
  109. for config_key, config_value in config.items():
  110. # redundant attr
  111. if config_key not in inst_variables:
  112. if not update_from_raw_conf and config_key.startswith("_"):
  113. setattr(param, config_key, config_value)
  114. else:
  115. setattr(param, config_key, config_value)
  116. # redundant_attrs.append(config_key)
  117. continue
  118. full_config_key = f"{prefix}{config_key}"
  119. if update_from_raw_conf:
  120. # add user feeded params
  121. user_feeded_params_set.add(full_config_key)
  122. # update user feeded deprecated param set
  123. if full_config_key in deprecated_params_set:
  124. feeded_deprecated_params_set.add(full_config_key)
  125. # supported attr
  126. attr = getattr(param, config_key)
  127. if type(attr).__name__ in dir(builtins) or attr is None:
  128. setattr(param, config_key, config_value)
  129. else:
  130. # recursive set obj attr
  131. sub_params = _recursive_update_param(
  132. attr, config_value, depth + 1, prefix=f"{prefix}{config_key}."
  133. )
  134. setattr(param, config_key, sub_params)
  135. if not allow_redundant and redundant_attrs:
  136. raise ValueError(
  137. f"cpn `{getattr(self, '_name', type(self))}` has redundant parameters: `{[redundant_attrs]}`"
  138. )
  139. return param
  140. return _recursive_update_param(param=self, config=conf, depth=0, prefix="")
  141. def extract_not_builtin(self):
  142. def _get_not_builtin_types(obj):
  143. ret_dict = {}
  144. for variable in obj.__dict__:
  145. attr = getattr(obj, variable)
  146. if attr and type(attr).__name__ not in dir(builtins):
  147. ret_dict[variable] = _get_not_builtin_types(attr)
  148. return ret_dict
  149. return _get_not_builtin_types(self)
  150. def validate(self):
  151. self.builtin_types = dir(builtins)
  152. self.func = {
  153. "ge": self._greater_equal_than,
  154. "le": self._less_equal_than,
  155. "in": self._in,
  156. "not_in": self._not_in,
  157. "range": self._range,
  158. }
  159. home_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
  160. param_validation_path_prefix = home_dir + "/param_validation/"
  161. param_name = type(self).__name__
  162. param_validation_path = "/".join(
  163. [param_validation_path_prefix, param_name + ".json"]
  164. )
  165. validation_json = None
  166. try:
  167. with open(param_validation_path, "r") as fin:
  168. validation_json = json.loads(fin.read())
  169. except BaseException:
  170. return
  171. self._validate_param(self, validation_json)
  172. def _validate_param(self, param_obj, validation_json):
  173. default_section = type(param_obj).__name__
  174. var_list = param_obj.__dict__
  175. for variable in var_list:
  176. attr = getattr(param_obj, variable)
  177. if type(attr).__name__ in self.builtin_types or attr is None:
  178. if variable not in validation_json:
  179. continue
  180. validation_dict = validation_json[default_section][variable]
  181. value = getattr(param_obj, variable)
  182. value_legal = False
  183. for op_type in validation_dict:
  184. if self.func[op_type](value, validation_dict[op_type]):
  185. value_legal = True
  186. break
  187. if not value_legal:
  188. raise ValueError(
  189. "Plase check runtime conf, {} = {} does not match user-parameter restriction".format(
  190. variable, value
  191. )
  192. )
  193. elif variable in validation_json:
  194. self._validate_param(attr, validation_json)
  195. @staticmethod
  196. def check_string(param, descr):
  197. if type(param).__name__ not in ["str"]:
  198. raise ValueError(
  199. descr + " {} not supported, should be string type".format(param)
  200. )
  201. @staticmethod
  202. def check_empty(param, descr):
  203. if not param:
  204. raise ValueError(
  205. descr + " does not support empty value."
  206. )
  207. @staticmethod
  208. def check_positive_integer(param, descr):
  209. if type(param).__name__ not in ["int", "long"] or param <= 0:
  210. raise ValueError(
  211. descr + " {} not supported, should be positive integer".format(param)
  212. )
  213. @staticmethod
  214. def check_positive_number(param, descr):
  215. if type(param).__name__ not in ["float", "int", "long"] or param <= 0:
  216. raise ValueError(
  217. descr + " {} not supported, should be positive numeric".format(param)
  218. )
  219. @staticmethod
  220. def check_nonnegative_number(param, descr):
  221. if type(param).__name__ not in ["float", "int", "long"] or param < 0:
  222. raise ValueError(
  223. descr
  224. + " {} not supported, should be non-negative numeric".format(param)
  225. )
  226. @staticmethod
  227. def check_decimal_float(param, descr):
  228. if type(param).__name__ not in ["float", "int"] or param < 0 or param > 1:
  229. raise ValueError(
  230. descr
  231. + " {} not supported, should be a float number in range [0, 1]".format(
  232. param
  233. )
  234. )
  235. @staticmethod
  236. def check_boolean(param, descr):
  237. if type(param).__name__ != "bool":
  238. raise ValueError(
  239. descr + " {} not supported, should be bool type".format(param)
  240. )
  241. @staticmethod
  242. def check_open_unit_interval(param, descr):
  243. if type(param).__name__ not in ["float"] or param <= 0 or param >= 1:
  244. raise ValueError(
  245. descr + " should be a numeric number between 0 and 1 exclusively"
  246. )
  247. @staticmethod
  248. def check_valid_value(param, descr, valid_values):
  249. if param not in valid_values:
  250. raise ValueError(
  251. descr
  252. + " {} is not supported, it should be in {}".format(param, valid_values)
  253. )
  254. @staticmethod
  255. def check_defined_type(param, descr, types):
  256. if type(param).__name__ not in types:
  257. raise ValueError(
  258. descr + " {} not supported, should be one of {}".format(param, types)
  259. )
  260. @staticmethod
  261. def check_and_change_lower(param, valid_list, descr=""):
  262. if type(param).__name__ != "str":
  263. raise ValueError(
  264. descr
  265. + " {} not supported, should be one of {}".format(param, valid_list)
  266. )
  267. lower_param = param.lower()
  268. if lower_param in valid_list:
  269. return lower_param
  270. else:
  271. raise ValueError(
  272. descr
  273. + " {} not supported, should be one of {}".format(param, valid_list)
  274. )
  275. @staticmethod
  276. def _greater_equal_than(value, limit):
  277. return value >= limit - settings.FLOAT_ZERO
  278. @staticmethod
  279. def _less_equal_than(value, limit):
  280. return value <= limit + settings.FLOAT_ZERO
  281. @staticmethod
  282. def _range(value, ranges):
  283. in_range = False
  284. for left_limit, right_limit in ranges:
  285. if (
  286. left_limit - settings.FLOAT_ZERO
  287. <= value
  288. <= right_limit + settings.FLOAT_ZERO
  289. ):
  290. in_range = True
  291. break
  292. return in_range
  293. @staticmethod
  294. def _in(value, right_value_list):
  295. return value in right_value_list
  296. @staticmethod
  297. def _not_in(value, wrong_value_list):
  298. return value not in wrong_value_list
  299. def _warn_deprecated_param(self, param_name, descr):
  300. if self._deprecated_params_set.get(param_name):
  301. logging.warning(
  302. f"{descr} {param_name} is deprecated and ignored in this version."
  303. )
  304. def _warn_to_deprecate_param(self, param_name, descr, new_param):
  305. if self._deprecated_params_set.get(param_name):
  306. logging.warning(
  307. f"{descr} {param_name} will be deprecated in future release; "
  308. f"please use {new_param} instead."
  309. )
  310. return True
  311. return False
  312. class ComponentBase(ABC):
  313. component_name: str
  314. def __str__(self):
  315. """
  316. {
  317. "component_name": "Begin",
  318. "params": {}
  319. }
  320. """
  321. return """{{
  322. "component_name": "{}",
  323. "params": {},
  324. "output": {},
  325. "inputs": {}
  326. }}""".format(self.component_name,
  327. self._param,
  328. json.dumps(json.loads(str(self._param)).get("output", {}), ensure_ascii=False),
  329. json.dumps(json.loads(str(self._param)).get("inputs", []), ensure_ascii=False)
  330. )
  331. def __init__(self, canvas, id, param: ComponentParamBase):
  332. self._canvas = canvas
  333. self._id = id
  334. self._param = param
  335. self._param.check()
  336. def get_dependent_components(self):
  337. cpnts = set([para["component_id"].split("@")[0] for para in self._param.query \
  338. if para.get("component_id") \
  339. and para["component_id"].lower().find("answer") < 0 \
  340. and para["component_id"].lower().find("begin") < 0])
  341. return list(cpnts)
  342. def run(self, history, **kwargs):
  343. logging.debug("{}, history: {}, kwargs: {}".format(self, json.dumps(history, ensure_ascii=False),
  344. json.dumps(kwargs, ensure_ascii=False)))
  345. self._param.debug_inputs = []
  346. try:
  347. res = self._run(history, **kwargs)
  348. self.set_output(res)
  349. except Exception as e:
  350. self.set_output(pd.DataFrame([{"content": str(e)}]))
  351. raise e
  352. return res
  353. def _run(self, history, **kwargs):
  354. raise NotImplementedError()
  355. def output(self, allow_partial=True) -> Tuple[str, Union[pd.DataFrame, partial]]:
  356. o = getattr(self._param, self._param.output_var_name)
  357. if not isinstance(o, partial) and not isinstance(o, pd.DataFrame):
  358. if not isinstance(o, list):
  359. o = [o]
  360. o = pd.DataFrame(o)
  361. if allow_partial or not isinstance(o, partial):
  362. if not isinstance(o, partial) and not isinstance(o, pd.DataFrame):
  363. return pd.DataFrame(o if isinstance(o, list) else [o])
  364. return self._param.output_var_name, o
  365. outs = None
  366. for oo in o():
  367. if not isinstance(oo, pd.DataFrame):
  368. outs = pd.DataFrame(oo if isinstance(oo, list) else [oo])
  369. else:
  370. outs = oo
  371. return self._param.output_var_name, outs
  372. def reset(self):
  373. setattr(self._param, self._param.output_var_name, None)
  374. self._param.inputs = []
  375. def set_output(self, v):
  376. setattr(self._param, self._param.output_var_name, v)
  377. def get_input(self):
  378. if self._param.debug_inputs:
  379. return pd.DataFrame([{"content": v["value"]} for v in self._param.debug_inputs])
  380. reversed_cpnts = []
  381. if len(self._canvas.path) > 1:
  382. reversed_cpnts.extend(self._canvas.path[-2])
  383. reversed_cpnts.extend(self._canvas.path[-1])
  384. if self._param.query:
  385. self._param.inputs = []
  386. outs = []
  387. for q in self._param.query:
  388. if q.get("component_id"):
  389. if q["component_id"].split("@")[0].lower().find("begin") >= 0:
  390. cpn_id, key = q["component_id"].split("@")
  391. for p in self._canvas.get_component(cpn_id)["obj"]._param.query:
  392. if p["key"] == key:
  393. outs.append(pd.DataFrame([{"content": p.get("value", "")}]))
  394. self._param.inputs.append({"component_id": q["component_id"],
  395. "content": p.get("value", "")})
  396. break
  397. else:
  398. assert False, f"Can't find parameter '{key}' for {cpn_id}"
  399. continue
  400. if q["component_id"].lower().find("answer") == 0:
  401. for r, c in self._canvas.history[::-1]:
  402. if r == "user":
  403. self._param.inputs.append(pd.DataFrame([{"content": c, "component_id": q["component_id"]}]))
  404. break
  405. continue
  406. outs.append(self._canvas.get_component(q["component_id"])["obj"].output(allow_partial=False)[1])
  407. self._param.inputs.append({"component_id": q["component_id"],
  408. "content": "\n".join(
  409. [str(d["content"]) for d in outs[-1].to_dict('records')])})
  410. elif q.get("value"):
  411. self._param.inputs.append({"component_id": None, "content": q["value"]})
  412. outs.append(pd.DataFrame([{"content": q["value"]}]))
  413. if outs:
  414. df = pd.concat(outs, ignore_index=True)
  415. if "content" in df:
  416. df = df.drop_duplicates(subset=['content']).reset_index(drop=True)
  417. return df
  418. upstream_outs = []
  419. for u in reversed_cpnts[::-1]:
  420. if self.get_component_name(u) in ["switch", "concentrator"]:
  421. continue
  422. if self.component_name.lower() == "generate" and self.get_component_name(u) == "retrieval":
  423. o = self._canvas.get_component(u)["obj"].output(allow_partial=False)[1]
  424. if o is not None:
  425. o["component_id"] = u
  426. upstream_outs.append(o)
  427. continue
  428. #if self.component_name.lower()!="answer" and u not in self._canvas.get_component(self._id)["upstream"]: continue
  429. if self.component_name.lower().find("switch") < 0 \
  430. and self.get_component_name(u) in ["relevant", "categorize"]:
  431. continue
  432. if u.lower().find("answer") >= 0:
  433. for r, c in self._canvas.history[::-1]:
  434. if r == "user":
  435. upstream_outs.append(pd.DataFrame([{"content": c, "component_id": u}]))
  436. break
  437. break
  438. if self.component_name.lower().find("answer") >= 0 and self.get_component_name(u) in ["relevant"]:
  439. continue
  440. o = self._canvas.get_component(u)["obj"].output(allow_partial=False)[1]
  441. if o is not None:
  442. o["component_id"] = u
  443. upstream_outs.append(o)
  444. break
  445. assert upstream_outs, "Can't inference the where the component input is. Please identify whose output is this component's input."
  446. df = pd.concat(upstream_outs, ignore_index=True)
  447. if "content" in df:
  448. df = df.drop_duplicates(subset=['content']).reset_index(drop=True)
  449. self._param.inputs = []
  450. for _, r in df.iterrows():
  451. self._param.inputs.append({"component_id": r["component_id"], "content": r["content"]})
  452. return df
  453. def get_input_elements(self):
  454. assert self._param.query, "Please identify input parameters firstly."
  455. eles = []
  456. for q in self._param.query:
  457. if q.get("component_id"):
  458. cpn_id = q["component_id"]
  459. if cpn_id.split("@")[0].lower().find("begin") >= 0:
  460. cpn_id, key = cpn_id.split("@")
  461. eles.extend(self._canvas.get_component(cpn_id)["obj"]._param.query)
  462. continue
  463. eles.append({"name": self._canvas.get_compnent_name(cpn_id), "key": cpn_id})
  464. else:
  465. eles.append({"key": q["value"], "name": q["value"], "value": q["value"]})
  466. return eles
  467. def get_stream_input(self):
  468. reversed_cpnts = []
  469. if len(self._canvas.path) > 1:
  470. reversed_cpnts.extend(self._canvas.path[-2])
  471. reversed_cpnts.extend(self._canvas.path[-1])
  472. for u in reversed_cpnts[::-1]:
  473. if self.get_component_name(u) in ["switch", "answer"]:
  474. continue
  475. return self._canvas.get_component(u)["obj"].output()[1]
  476. @staticmethod
  477. def be_output(v):
  478. return pd.DataFrame([{"content": v}])
  479. def get_component_name(self, cpn_id):
  480. return self._canvas.get_component(cpn_id)["obj"].component_name.lower()
  481. def debug(self, **kwargs):
  482. return self._run([], **kwargs)