Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

code_exec.py 7.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. #
  2. # Copyright 2025 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. import base64
  17. import logging
  18. import os
  19. from abc import ABC
  20. from strenum import StrEnum
  21. from typing import Optional
  22. from pydantic import BaseModel, Field, field_validator
  23. from agent.tools.base import ToolParamBase, ToolBase, ToolMeta
  24. from api import settings
  25. from api.utils.api_utils import timeout
  26. class Language(StrEnum):
  27. PYTHON = "python"
  28. NODEJS = "nodejs"
  29. class CodeExecutionRequest(BaseModel):
  30. code_b64: str = Field(..., description="Base64 encoded code string")
  31. language: str = Field(default=Language.PYTHON.value, description="Programming language")
  32. arguments: Optional[dict] = Field(default={}, description="Arguments")
  33. @field_validator("code_b64")
  34. @classmethod
  35. def validate_base64(cls, v: str) -> str:
  36. try:
  37. base64.b64decode(v, validate=True)
  38. return v
  39. except Exception as e:
  40. raise ValueError(f"Invalid base64 encoding: {str(e)}")
  41. @field_validator("language", mode="before")
  42. @classmethod
  43. def normalize_language(cls, v) -> str:
  44. if isinstance(v, str):
  45. low = v.lower()
  46. if low in ("python", "python3"):
  47. return "python"
  48. elif low in ("javascript", "nodejs"):
  49. return "nodejs"
  50. raise ValueError(f"Unsupported language: {v}")
  51. class CodeExecParam(ToolParamBase):
  52. """
  53. Define the code sandbox component parameters.
  54. """
  55. def __init__(self):
  56. self.meta:ToolMeta = {
  57. "name": "execute_code",
  58. "description": """
  59. This tool has a sandbox that can execute code written in 'Python'/'Javascript'. It recieves a piece of code and return a Json string.
  60. Here's a code example for Python(`main` function MUST be included):
  61. def main() -> dict:
  62. \"\"\"
  63. Generate Fibonacci numbers within 100.
  64. \"\"\"
  65. def fibonacci_recursive(n):
  66. if n <= 1:
  67. return n
  68. else:
  69. return fibonacci_recursive(n-1) + fibonacci_recursive(n-2)
  70. return {
  71. "result": fibonacci_recursive(100),
  72. }
  73. Here's a code example for Javascript(`main` function MUST be included and exported):
  74. const axios = require('axios');
  75. async function main(args) {
  76. try {
  77. const response = await axios.get('https://github.com/infiniflow/ragflow');
  78. console.log('Body:', response.data);
  79. } catch (error) {
  80. console.error('Error:', error.message);
  81. }
  82. }
  83. module.exports = { main };
  84. """,
  85. "parameters": {
  86. "lang": {
  87. "type": "string",
  88. "description": "The programming language of this piece of code.",
  89. "enum": ["python", "javascript"],
  90. "required": True,
  91. },
  92. "script": {
  93. "type": "string",
  94. "description": "A piece of code in right format. There MUST be main function.",
  95. "required": True
  96. }
  97. }
  98. }
  99. super().__init__()
  100. self.lang = Language.PYTHON.value
  101. self.script = "def main(arg1: str, arg2: str) -> dict: return {\"result\": arg1 + arg2}"
  102. self.arguments = {}
  103. self.outputs = {"result": {"value": "", "type": "string"}}
  104. def check(self):
  105. self.check_valid_value(self.lang, "Support languages", ["python", "python3", "nodejs", "javascript"])
  106. self.check_empty(self.script, "Script")
  107. def get_input_form(self) -> dict[str, dict]:
  108. res = {}
  109. for k, v in self.arguments.items():
  110. res[k] = {
  111. "type": "line",
  112. "name": k
  113. }
  114. return res
  115. class CodeExec(ToolBase, ABC):
  116. component_name = "CodeExec"
  117. @timeout(os.environ.get("COMPONENT_EXEC_TIMEOUT", 10*60))
  118. def _invoke(self, **kwargs):
  119. lang = kwargs.get("lang", self._param.lang)
  120. script = kwargs.get("script", self._param.script)
  121. arguments = {}
  122. for k, v in self._param.arguments.items():
  123. if kwargs.get(k):
  124. arguments[k] = kwargs[k]
  125. continue
  126. arguments[k] = self._canvas.get_variable_value(v) if v else None
  127. self._execute_code(
  128. language=lang,
  129. code=script,
  130. arguments=arguments
  131. )
  132. def _execute_code(self, language: str, code: str, arguments: dict):
  133. import requests
  134. try:
  135. code_b64 = self._encode_code(code)
  136. code_req = CodeExecutionRequest(code_b64=code_b64, language=language, arguments=arguments).model_dump()
  137. except Exception as e:
  138. self.set_output("_ERROR", "construct code request error: " + str(e))
  139. try:
  140. resp = requests.post(url=f"http://{settings.SANDBOX_HOST}:9385/run", json=code_req, timeout=os.environ.get("COMPONENT_EXEC_TIMEOUT", 10*60))
  141. logging.info(f"http://{settings.SANDBOX_HOST}:9385/run", code_req, resp.status_code)
  142. if resp.status_code != 200:
  143. resp.raise_for_status()
  144. body = resp.json()
  145. if body:
  146. stderr = body.get("stderr")
  147. if stderr:
  148. self.set_output("_ERROR", stderr)
  149. return
  150. try:
  151. rt = eval(body.get("stdout", ""))
  152. except Exception:
  153. rt = body.get("stdout", "")
  154. logging.info(f"http://{settings.SANDBOX_HOST}:9385/run -> {rt}")
  155. if isinstance(rt, tuple):
  156. for i, (k, o) in enumerate(self._param.outputs.items()):
  157. if k.find("_") == 0:
  158. continue
  159. o["value"] = rt[i]
  160. elif isinstance(rt, dict):
  161. for i, (k, o) in enumerate(self._param.outputs.items()):
  162. if k not in rt or k.find("_") == 0:
  163. continue
  164. o["value"] = rt[k]
  165. else:
  166. for i, (k, o) in enumerate(self._param.outputs.items()):
  167. if k.find("_") == 0:
  168. continue
  169. o["value"] = rt
  170. else:
  171. self.set_output("_ERROR", "There is no response from sandbox")
  172. except Exception as e:
  173. self.set_output("_ERROR", "Exception executing code: " + str(e))
  174. return self.output()
  175. def _encode_code(self, code: str) -> str:
  176. return base64.b64encode(code.encode("utf-8")).decode("utf-8")
  177. def thoughts(self) -> str:
  178. return "Running a short script to process data."