diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index b5424b367..f63920703 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -30,6 +30,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up QEMU uses: docker/setup-qemu-action@v3 + id: qemu - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub @@ -52,6 +53,7 @@ jobs: with: context: . push: true + platforms: "linux/amd64,linux/arm64/v8" file: ${{ env.DOCKERFILE }} tags: ${{ env.TAGS }} - name: Wait for Docker Hub to propagate @@ -62,6 +64,7 @@ jobs: with: context: . push: true + platforms: "linux/amd64,linux/arm64/v8" file: ./docker/build_and_push_backend.Dockerfile build-args: | LANGFLOW_IMAGE=langflowai/langflow:${{ inputs.version }} @@ -75,6 +78,7 @@ jobs: context: . push: true file: ./docker/frontend/build_and_push_frontend.Dockerfile + platforms: "linux/amd64,linux/arm64/v8" tags: | langflowai/langflow-frontend:${{ inputs.version }} langflowai/langflow-frontend:1.0-alpha diff --git a/.github/workflows/docker_test.yml b/.github/workflows/docker_test.yml index f46010358..9be7beb00 100644 --- a/.github/workflows/docker_test.yml +++ b/.github/workflows/docker_test.yml @@ -8,6 +8,7 @@ on: - "poetry.lock" - "pyproject.toml" - "src/backend/**" + - ".github/workflows/docker_test.yml" pull_request: branches: [dev] paths: @@ -15,12 +16,13 @@ on: - "poetry.lock" - "pyproject.toml" - "src/**" + - ".github/workflows/docker_test.yml" env: POETRY_VERSION: "1.8.2" jobs: - build: + test-docker: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -59,3 +61,20 @@ jobs: docker build -t langflowai/langflow-frontend:latest-dev \ -f docker/frontend/build_and_push_frontend.Dockerfile \ . + test-multi-arch-build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + id: qemu + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + push: false + file: ./docker/build_and_push.Dockerfile + platforms: "linux/amd64,linux/arm64/v8" + tags: langflowai/langflow:latest-dev diff --git a/.github/workflows/pre-release-base.yml b/.github/workflows/pre-release-base.yml index 6045038be..6f1e4fe8e 100644 --- a/.github/workflows/pre-release-base.yml +++ b/.github/workflows/pre-release-base.yml @@ -60,6 +60,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up QEMU uses: docker/setup-qemu-action@v3 + id: qemu - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub @@ -73,5 +74,6 @@ jobs: context: . push: true file: ./docker/build_and_push_base.Dockerfile + platforms: "linux/amd64,linux/arm64/v8" tags: | langflowai/langflow:base-${{ needs.release.outputs.version }} diff --git a/.github/workflows/pre-release-langflow.yml b/.github/workflows/pre-release-langflow.yml index f3909f7b1..79c1d6af5 100644 --- a/.github/workflows/pre-release-langflow.yml +++ b/.github/workflows/pre-release-langflow.yml @@ -66,6 +66,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up QEMU uses: docker/setup-qemu-action@v3 + id: qemu - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub @@ -79,6 +80,7 @@ jobs: context: . push: true file: ./docker/build_and_push.Dockerfile + platforms: "linux/amd64,linux/arm64/v8" tags: | langflowai/langflow:${{ needs.release.outputs.version }} langflowai/langflow:1.0-alpha @@ -88,6 +90,7 @@ jobs: context: . push: true file: ./docker/frontend/build_and_push_frontend.Dockerfile + platforms: "linux/amd64,linux/arm64/v8" tags: | langflowai/langflow-frontend:${{ needs.release.outputs.version }} langflowai/langflow-frontend:1.0-alpha @@ -99,6 +102,7 @@ jobs: context: . push: true file: ./docker/build_and_push_backend.Dockerfile + platforms: "linux/amd64,linux/arm64/v8" build-args: | LANGFLOW_IMAGE=langflowai/langflow:${{ needs.release.outputs.version }} tags: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 851f06424..364289b90 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,6 +38,7 @@ jobs: poetry publish - name: Set up QEMU uses: docker/setup-qemu-action@v3 + id: qemu - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub @@ -51,6 +52,7 @@ jobs: context: . push: true file: ./docker/build_and_push.Dockerfile + platforms: "linux/amd64,linux/arm64/v8" tags: | langflowai/langflow:${{ steps.check-version.outputs.version }} langflowai/langflow:latest @@ -62,6 +64,7 @@ jobs: context: . push: true file: ./docker/build_and_push_backend.Dockerfile + platforms: "linux/amd64,linux/arm64/v8" build-args: | LANGFLOW_IMAGE=langflowai/langflow:${{ steps.check-version.outputs.version }} tags: | @@ -73,6 +76,7 @@ jobs: context: . push: true file: ./docker/frontend/build_and_push_frontend.Dockerfile + platforms: "linux/amd64,linux/arm64/v8" tags: | langflowai/langflow-frontend:${{ steps.check-version.outputs.version }} langflowai/langflow-frontend:latest diff --git a/docker/build_and_push.Dockerfile b/docker/build_and_push.Dockerfile index e93477d2a..43d9a0272 100644 --- a/docker/build_and_push.Dockerfile +++ b/docker/build_and_push.Dockerfile @@ -6,7 +6,9 @@ # BUILDER-BASE # Used to build deps + create our virtual environment ################################ -FROM python:3.12-slim as builder-base + +# force platform to the current architecture to increase build speed time on multi-platform builds +FROM --platform=$BUILDPLATFORM python:3.12-slim as builder-base ENV PYTHONDONTWRITEBYTECODE=1 \ \ diff --git a/docker/frontend/build_and_push_frontend.Dockerfile b/docker/frontend/build_and_push_frontend.Dockerfile index e954a801e..46c5ffdeb 100644 --- a/docker/frontend/build_and_push_frontend.Dockerfile +++ b/docker/frontend/build_and_push_frontend.Dockerfile @@ -4,7 +4,9 @@ ################################ # BUILDER-BASE ################################ -FROM node:lts-bookworm-slim as builder-base + +# force platform to the current architecture to increase build speed time on multi-platform builds +FROM --platform=$BUILDPLATFORM node:lts-bookworm-slim as builder-base COPY src/frontend /frontend RUN cd /frontend && npm install && npm run build diff --git a/poetry.lock b/poetry.lock index 9fbe55107..bee5cb67c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -4409,7 +4409,7 @@ six = "*" [[package]] name = "langflow-base" -version = "0.0.60" +version = "0.0.61" description = "A Python package with a built-in web application" optional = false python-versions = ">=3.10,<3.13" @@ -4490,13 +4490,13 @@ openai = ["openai (>=0.27.8)"] [[package]] name = "langsmith" -version = "0.1.75" +version = "0.1.76" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." optional = false python-versions = "<4.0,>=3.8.1" files = [ - {file = "langsmith-0.1.75-py3-none-any.whl", hash = "sha256:d08b08dd6b3fa4da170377f95123d77122ef4c52999d10fff4ae08ff70d07aed"}, - {file = "langsmith-0.1.75.tar.gz", hash = "sha256:61274e144ea94c297dd78ce03e6dfae18459fe9bd8ab5094d61a0c4816561279"}, + {file = "langsmith-0.1.76-py3-none-any.whl", hash = "sha256:4b8cb14f2233d9673ce9e6e3d545359946d9690a2c1457ab01e7459ec97b964e"}, + {file = "langsmith-0.1.76.tar.gz", hash = "sha256:5829f997495c0f9a39f91fe0a57e0cb702e8642e6948945f5bb9f46337db7732"}, ] [package.dependencies] @@ -4506,13 +4506,13 @@ requests = ">=2,<3" [[package]] name = "litellm" -version = "1.40.7" +version = "1.40.8" description = "Library to easily interface with LLM API providers" optional = false python-versions = "!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*,>=3.8" files = [ - {file = "litellm-1.40.7-py3-none-any.whl", hash = "sha256:c98dd8733e632aba16f14bf82e56f7159222097a6d085b242a3140b5d3e7baa4"}, - {file = "litellm-1.40.7.tar.gz", hash = "sha256:557bb19e8e484d0dfe8e4eaa9ccefc888617852988a46d6e7adc41585a2c0600"}, + {file = "litellm-1.40.8-py3-none-any.whl", hash = "sha256:cd0c313423dad49224696c45ac02c574abcaed6666c597543c2318b3521f4320"}, + {file = "litellm-1.40.8.tar.gz", hash = "sha256:8878d2437ac50bcc6f39ded1729e2113eb5fee645fcebcd32fc241c529a21c00"}, ] [package.dependencies] diff --git a/pyproject.toml b/pyproject.toml index 2a651fb76..bf25d3ba5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langflow" -version = "1.0.0a49" +version = "1.0.0a50" description = "A Python package with a built-in web application" authors = ["Langflow "] maintainers = [ diff --git a/src/backend/base/langflow/base/data/utils.py b/src/backend/base/langflow/base/data/utils.py index 3779e8065..9bad1dabf 100644 --- a/src/backend/base/langflow/base/data/utils.py +++ b/src/backend/base/langflow/base/data/utils.py @@ -107,7 +107,7 @@ def read_text_file(file_path: str) -> str: result = chardet.detect(raw_data) encoding = result["encoding"] - if encoding in ["Windows-1252", "Windows-1254"]: + if encoding in ["Windows-1252", "Windows-1254", "MacRoman"]: encoding = "utf-8" with open(file_path, "r", encoding=encoding) as f: diff --git a/src/backend/base/langflow/components/tools/PythonCodeStructuredTool.py b/src/backend/base/langflow/components/tools/PythonCodeStructuredTool.py new file mode 100644 index 000000000..7de988d24 --- /dev/null +++ b/src/backend/base/langflow/components/tools/PythonCodeStructuredTool.py @@ -0,0 +1,91 @@ +from typing import Any, Dict, List, Callable +import ast +from langchain.agents import Tool +from langchain.tools import StructuredTool +from langflow.interface.custom.custom_component import CustomComponent +from langflow.schema.dotdict import dotdict +from langchain.pydantic_v1 import BaseModel, Field + + +class PythonCodeStructuredTool(CustomComponent): + display_name = "PythonCodeTool" + description = "structuredtool dataclass code to tool" + documentation = "https://python.langchain.com/docs/modules/tools/custom_tools/#structuredtool-dataclass" + icon = "🐍" + field_order = ["name", "description", "tool_code", + "return_direct", "tool_function", "tool_class"] + + def build_config(self) -> Dict[str, Any]: + return { + "tool_code": { + "display_name": "Tool Code", + "info": "Enter the dataclass code.", + "placeholder": "def my_function(args):\n pass", + "multiline": True, + "refresh_button": True, + }, + "name": { + "display_name": "Tool Name", + "info": "Enter the name of the tool.", + }, + "description": { + "display_name": "Description", + "info": "Provide a brief description of what the tool does.", + }, + "return_direct": { + "display_name": "Return Directly", + "info": "Should the tool return the function output directly?", + }, + "tool_function": { + "display_name": "Tool Function", + "info": "Select the function for additional expressions.", + "options": [], + "refresh_button": True, + }, + "tool_class": { + "display_name": "Tool Class", + "info": "Select the class for additional expressions.", + "options": [], + "refresh_button": True, + "required": False, + }, + } + + def parse_source_name(self, code: str) -> Dict: + parsed_code = ast.parse(code) + class_names = [ + node.name for node in parsed_code.body if isinstance(node, ast.ClassDef)] + function_names = [ + node.name for node in parsed_code.body if isinstance(node, ast.FunctionDef)] + return {"class": class_names, "function": function_names} + + def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None) -> dotdict: + if field_name == "tool_code" or field_name == "tool_function" or field_name == "tool_class": + try: + names = self.parse_source_name(build_config.tool_code.value) + build_config.tool_class.options = names["class"] + build_config.tool_function.options = names["function"] + except Exception as e: + self.status = f"Failed to extract class names: {str(e)}" + build_config.tool_class.options = ["Failed to parse", str(e)] + build_config.tool_function.options = [] + return build_config + + async def build(self, tool_code: Code, name: str, description: str, tool_function: List[str], return_direct: bool, tool_class: List[str] = None) -> Tool: + local_namespace = {} + exec(tool_code, globals(), local_namespace) + + func = local_namespace[tool_function] + _class = None + + if tool_class: + _class = local_namespace[tool_class] + + tool = StructuredTool.from_function( + func=func, + args_schema=_class, + name=name, + description=description, + return_direct=return_direct + ) + return tool diff --git a/src/backend/base/langflow/components/vectorstores/Couchbase.py b/src/backend/base/langflow/components/vectorstores/Couchbase.py index 81fa0727a..ffc17f1b6 100644 --- a/src/backend/base/langflow/components/vectorstores/Couchbase.py +++ b/src/backend/base/langflow/components/vectorstores/Couchbase.py @@ -1,10 +1,6 @@ from datetime import timedelta from typing import List, Optional, Union -from couchbase.auth import PasswordAuthenticator # type: ignore -from couchbase.cluster import Cluster # type: ignore -from couchbase.options import ClusterOptions # type: ignore -from langchain_community.vectorstores import CouchbaseVectorStore from langchain_core.retrievers import BaseRetriever from langflow.custom import CustomComponent @@ -52,6 +48,16 @@ class CouchbaseComponent(CustomComponent): couchbase_username: str = "", couchbase_password: str = "", ) -> Union[VectorStore, BaseRetriever]: + try: + from couchbase.auth import PasswordAuthenticator # type: ignore + from couchbase.cluster import Cluster # type: ignore + from couchbase.options import ClusterOptions # type: ignore + from langchain_community.vectorstores import CouchbaseVectorStore + except ImportError as e: + raise ImportError( + "Failed to import Couchbase dependencies. Install it using `pip install langflow[couchbase] --pre`" + ) from e + try: auth = PasswordAuthenticator(couchbase_username, couchbase_password) options = ClusterOptions(auth) diff --git a/src/backend/base/poetry.lock b/src/backend/base/poetry.lock index 5f66f0d1a..a3e029b17 100644 --- a/src/backend/base/poetry.lock +++ b/src/backend/base/poetry.lock @@ -1275,13 +1275,13 @@ types-requests = ">=2.31.0.2,<3.0.0.0" [[package]] name = "langsmith" -version = "0.1.75" +version = "0.1.76" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." optional = false python-versions = "<4.0,>=3.8.1" files = [ - {file = "langsmith-0.1.75-py3-none-any.whl", hash = "sha256:d08b08dd6b3fa4da170377f95123d77122ef4c52999d10fff4ae08ff70d07aed"}, - {file = "langsmith-0.1.75.tar.gz", hash = "sha256:61274e144ea94c297dd78ce03e6dfae18459fe9bd8ab5094d61a0c4816561279"}, + {file = "langsmith-0.1.76-py3-none-any.whl", hash = "sha256:4b8cb14f2233d9673ce9e6e3d545359946d9690a2c1457ab01e7459ec97b964e"}, + {file = "langsmith-0.1.76.tar.gz", hash = "sha256:5829f997495c0f9a39f91fe0a57e0cb702e8642e6948945f5bb9f46337db7732"}, ] [package.dependencies] diff --git a/src/backend/base/pyproject.toml b/src/backend/base/pyproject.toml index b175fbcf5..291a8133a 100644 --- a/src/backend/base/pyproject.toml +++ b/src/backend/base/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langflow-base" -version = "0.0.60" +version = "0.0.61" description = "A Python package with a built-in web application" authors = ["Langflow "] maintainers = [ 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 e7999b69b..9aa946746 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 @@ -2,19 +2,27 @@ * Function to get the python code for the API * @param {string} flowId - The id of the flow * @param {boolean} isAuth - If the API is authenticated - * @param {any[]} tweak - The tweaks + * @param {any[]} tweaksBuildedObject - The tweaks + * @param {string} [endpointName] - The optional endpoint name * @returns {string} - The python code */ export default function getPythonApiCode( flowId: string, isAuth: boolean, - tweaksBuildedObject, - endpointName?: string + tweaksBuildedObject: any[], + endpointName?: string, ): string { - const tweaksObject = tweaksBuildedObject[0]; - const tweaksString = JSON.stringify(tweaksObject, null, 2) - .replace(/true/g, "True") - .replace(/false/g, "False"); + let tweaksString = "{}"; + if (tweaksBuildedObject && tweaksBuildedObject.length > 0) { + const tweaksObject = tweaksBuildedObject[0]; + if (!tweaksObject) { + throw new Error("expected tweaks"); + } + tweaksString = JSON.stringify(tweaksObject, null, 2) + .replace(/true/g, "True") + .replace(/false/g, "False"); + } + return `import argparse import json from argparse import RawTextHelpFormatter diff --git a/src/frontend/src/modals/apiModal/utils/get-python-code.tsx b/src/frontend/src/modals/apiModal/utils/get-python-code.tsx index 9fd8048f2..734e16bbe 100644 --- a/src/frontend/src/modals/apiModal/utils/get-python-code.tsx +++ b/src/frontend/src/modals/apiModal/utils/get-python-code.tsx @@ -1,17 +1,26 @@ /** * Function to get the python code for the API * @param {string} flow - The current flow - * @param {any[]} tweak - The tweaks + * @param {any[]} tweaksBuildedObject - The tweaks * @returns {string} - The python code */ export default function getPythonCode( flowName: string, - tweaksBuildedObject + tweaksBuildedObject: any[], ): string { - const tweaksObject = tweaksBuildedObject[0]; + let tweaksString = "{}"; + if (tweaksBuildedObject && tweaksBuildedObject.length > 0) { + const tweaksObject = tweaksBuildedObject[0]; + if (!tweaksObject) { + throw new Error("expected tweaks"); + } + tweaksString = JSON.stringify(tweaksObject, null, 2) + .replace(/true/g, "True") + .replace(/false/g, "False"); + } return `from langflow.load import run_flow_from_json -TWEAKS = ${JSON.stringify(tweaksObject, null, 2)} +TWEAKS = ${tweaksString} result = run_flow_from_json(flow="${flowName}.json", input_value="message",