diff --git a/src/backend/base/langflow/load/__init__.py b/src/backend/base/langflow/load/__init__.py index 2002e8bb1..59dbdf6e0 100644 --- a/src/backend/base/langflow/load/__init__.py +++ b/src/backend/base/langflow/load/__init__.py @@ -1,3 +1,4 @@ -from .load import load_flow_from_json, run_flow_from_json # noqa: F401 +from .load import load_flow_from_json, run_flow_from_json +from .utils import upload_file, get_flow -__all__ = ["load_flow_from_json", "run_flow_from_json"] +__all__ = ["load_flow_from_json", "run_flow_from_json", "upload_file", "get_flow"] diff --git a/src/backend/base/langflow/load/utils.py b/src/backend/base/langflow/load/utils.py new file mode 100644 index 000000000..9c2918e91 --- /dev/null +++ b/src/backend/base/langflow/load/utils.py @@ -0,0 +1,89 @@ +import httpx + +from langflow.services.database.models.flow.model import FlowBase + + +def upload(file_path, host, flow_id): + """ + Upload a file to Langflow and return the file path. + + Args: + file_path (str): The path to the file to be uploaded. + host (str): The host URL of Langflow. + flow_id (UUID): The ID of the flow to which the file belongs. + + Returns: + dict: A dictionary containing the file path. + + Raises: + Exception: If an error occurs during the upload process. + """ + try: + url = f"{host}/api/v1/upload/{flow_id}" + response = httpx.post(url, files={"file": open(file_path, "rb")}) + if response.status_code == 200: + return response.json() + else: + raise Exception(f"Error uploading file: {response.status_code}") + except Exception as e: + raise Exception(f"Error uploading file: {e}") + + +def upload_file(file_path, host, flow_id, components, tweaks={}): + """ + Upload a file to Langflow and return the file path. + + Args: + file_path (str): The path to the file to be uploaded. + host (str): The host URL of Langflow. + port (int): The port number of Langflow. + flow_id (UUID): The ID of the flow to which the file belongs. + components (str): List of component IDs or names that need the file. + tweaks (dict): A dictionary of tweaks to be applied to the file. + + Returns: + dict: A dictionary containing the file path and any tweaks that were applied. + + Raises: + Exception: If an error occurs during the upload process. + """ + try: + response = upload(file_path, host, flow_id) + if response["file_path"]: + for component in components: + if isinstance(component, str): + tweaks[component] = {"file_path": response["file_path"]} + else: + raise ValueError(f"Component ID or name must be a string. Got {type(component)}") + return tweaks + else: + raise ValueError("Error uploading file") + except Exception as e: + raise ValueError(f"Error uploading file: {e}") + + +def get_flow(url: str, flow_id: str): + """Get the details of a flow from Langflow. + + Args: + url (str): The host URL of Langflow. + port (int): The port number of Langflow. + flow_id (UUID): The ID of the flow to retrieve. + + Returns: + dict: A dictionary containing the details of the flow. + + Raises: + Exception: If an error occurs during the retrieval process. + """ + try: + flow_url = f"{url}/api/v1/flows/{flow_id}" + response = httpx.get(flow_url) + if response.status_code == 200: + json_response = response.json() + flow = FlowBase(**json_response).model_dump() + return flow + else: + raise Exception(f"Error retrieving flow: {response.status_code}") + except Exception as e: + raise Exception(f"Error retrieving flow: {e}") diff --git a/src/backend/base/langflow/services/database/models/flow/model.py b/src/backend/base/langflow/services/database/models/flow/model.py index 4de1e0bc8..7727c7b86 100644 --- a/src/backend/base/langflow/services/database/models/flow/model.py +++ b/src/backend/base/langflow/services/database/models/flow/model.py @@ -29,7 +29,6 @@ class FlowBase(SQLModel): is_component: Optional[bool] = Field(default=False, nullable=True) updated_at: Optional[datetime] = Field(default_factory=lambda: datetime.now(timezone.utc), nullable=True) webhook: Optional[bool] = Field(default=False, nullable=True, description="Can be used on the webhook endpoint") - folder_id: Optional[UUID] = Field(default=None, nullable=True) endpoint_name: Optional[str] = Field(default=None, nullable=True, index=True) @field_validator("endpoint_name") diff --git a/src/frontend/src/modals/apiModal/utils/get-python-api-code.tsx b/src/frontend/src/modals/apiModal/utils/get-python-api-code.tsx index a25e993fc..e51403acb 100644 --- a/src/frontend/src/modals/apiModal/utils/get-python-api-code.tsx +++ b/src/frontend/src/modals/apiModal/utils/get-python-api-code.tsx @@ -9,19 +9,35 @@ export default function getPythonApiCode( flowId: string, isAuth: boolean, tweaksBuildedObject, + endpointName?: string ): string { const tweaksObject = tweaksBuildedObject[0]; - return `import requests + return `import argparse +import json +from argparse import RawTextHelpFormatter +import requests from typing import Optional +import warnings +try: + from langflow.load import upload_file +except ImportError: + warnings.warn("Langflow provides a function to help you upload files to the flow. Please install langflow to use it.") + upload_file = None BASE_API_URL = "${window.location.protocol}//${window.location.host}/api/v1/run" FLOW_ID = "${flowId}" +ENDPOINT = "${endpointName || ""}" ${ + endpointName + ? `# The endpoint name of the flow` + : `# You can set a specific endpoint name in the flow settings` + } + # You can tweak the flow by adding a tweaks dictionary # e.g {"OpenAI-XXXXX": {"model_name": "gpt-4"}} TWEAKS = ${JSON.stringify(tweaksObject, null, 2)} def run_flow(message: str, - flow_id: str, + endpoint: str, output_type: str = "chat", input_type: str = "chat", tweaks: Optional[dict] = None, @@ -30,11 +46,11 @@ def run_flow(message: str, Run a flow with a given message and optional tweaks. :param message: The message to send to the flow - :param flow_id: The ID of the flow to run + :param endpoint: The ID or the endpoint name of the flow :param tweaks: Optional tweaks to customize the flow :return: The JSON response from the flow """ - api_url = f"{BASE_API_URL}/{flow_id}" + api_url = f"{BASE_API_URL}/{endpoint}" payload = { "input_value": message, @@ -49,10 +65,43 @@ def run_flow(message: str, response = requests.post(api_url, json=payload, headers=headers) return response.json() -# Setup any tweaks you want to apply to the flow -message = "message" -${!isAuth ? `api_key = ""` : ""} -print(run_flow(message=message, flow_id=FLOW_ID, tweaks=TWEAKS${ - !isAuth ? `, api_key=api_key` : "" - }))`; +def main(): + parser = argparse.ArgumentParser(description="""Run a flow with a given message and optional tweaks.\nRun it like: python .py "your message here" --endpoint "your_endpoint" --tweaks '{"key": "value"}'""", + formatter_class=RawTextHelpFormatter) + parser.add_argument("message", type=str, help="The message to send to the flow") + parser.add_argument("--endpoint", type=str, default=ENDPOINT or FLOW_ID, help="The ID or the endpoint name of the flow") + parser.add_argument("--tweaks", type=str, help="JSON string representing the tweaks to customize the flow", default=json.dumps(TWEAKS)) + parser.add_argument("--api_key", type=str, help="API key for authentication", default=None) + parser.add_argument("--output_type", type=str, default="chat", help="The output type") + parser.add_argument("--input_type", type=str, default="chat", help="The input type") + parser.add_argument("--upload_file", type=str, help="Path to the file to upload", default=None) + parser.add_argument("--components", type=str, help="Components to upload the file to", default=None) + + args = parser.parse_args() + try: + tweaks = json.loads(args.tweaks) + except json.JSONDecodeError: + raise ValueError("Invalid tweaks JSON string") + + if args.upload_file: + if not upload_file: + raise ImportError("Langflow is not installed. Please install it to use the upload_file function.") + elif not args.components: + raise ValueError("You need to provide the components to upload the file to.") + tweaks = upload_file(file_path=args.upload_file, host=BASE_API_URL, flow_id=ENDPOINT, components=args.components, tweaks=tweaks) + + response = run_flow( + message=args.message, + endpoint=args.endpoint, + output_type=args.output_type, + input_type=args.input_type, + tweaks=tweaks, + api_key=args.api_key + ) + + print(json.dumps(response, indent=2)) + +if __name__ == "__main__": + main() +`; } diff --git a/src/frontend/src/modals/apiModal/views/index.tsx b/src/frontend/src/modals/apiModal/views/index.tsx index 62ee7a4e5..f7a72082c 100644 --- a/src/frontend/src/modals/apiModal/views/index.tsx +++ b/src/frontend/src/modals/apiModal/views/index.tsx @@ -35,7 +35,7 @@ const ApiModal = forwardRef( flow: FlowType; children: ReactNode; }, - ref, + ref ) => { const tweak = useTweaksStore((state) => state.tweak); const addTweaks = useTweaksStore((state) => state.setTweak); @@ -46,17 +46,22 @@ const ApiModal = forwardRef( const { autoLogin } = useContext(AuthContext); const [open, setOpen] = useState(false); const [activeTab, setActiveTab] = useState("0"); - const pythonApiCode = getPythonApiCode(flow?.id, autoLogin, tweak); + const pythonApiCode = getPythonApiCode( + flow?.id, + autoLogin, + tweak, + flow?.endpoint_name + ); const curl_run_code = getCurlRunCode( flow?.id, autoLogin, tweak, - flow?.endpoint_name, + flow?.endpoint_name ); const curl_webhook_code = getCurlWebhookCode( flow?.id, autoLogin, - flow?.endpoint_name, + flow?.endpoint_name ); const pythonCode = getPythonCode(flow?.name, tweak); const widgetCode = getWidgetCode(flow?.id, flow?.name, autoLogin); @@ -72,7 +77,7 @@ const ApiModal = forwardRef( pythonCode, ]; const [tabs, setTabs] = useState( - createTabsArray(codesArray, includeWebhook), + createTabsArray(codesArray, includeWebhook) ); const canShowTweaks = @@ -121,7 +126,7 @@ const ApiModal = forwardRef( buildTweakObject( nodeId, element.data.node.template[templateField].value, - element.data.node.template[templateField], + element.data.node.template[templateField] ); } }); @@ -138,7 +143,7 @@ const ApiModal = forwardRef( async function buildTweakObject( tw: string, changes: string | string[] | boolean | number | Object[] | Object, - template: TemplateVariableType, + template: TemplateVariableType ) { changes = getChangesType(changes, template); @@ -180,7 +185,7 @@ const ApiModal = forwardRef( flow?.id, autoLogin, cloneTweak, - flow?.endpoint_name, + flow?.endpoint_name ); const pythonCode = getPythonCode(flow?.name, cloneTweak); const widgetCode = getWidgetCode(flow?.id, flow?.name, autoLogin); @@ -224,7 +229,7 @@ const ApiModal = forwardRef( ); - }, + } ); export default ApiModal;