diff --git a/src/backend/langflow/api/endpoints.py b/src/backend/langflow/api/endpoints.py index b8290e691..d4e592901 100644 --- a/src/backend/langflow/api/endpoints.py +++ b/src/backend/langflow/api/endpoints.py @@ -5,6 +5,12 @@ from fastapi import APIRouter, HTTPException from langflow.interface.run import process_graph_cached from langflow.interface.types import build_langchain_types_dict +from langflow.api.schemas import ( + ExportedFlow, + GraphData, + PredictRequest, + PredictResponse, +) # build router router = APIRouter() @@ -16,10 +22,14 @@ def get_all(): return build_langchain_types_dict() -@router.post("/predict") -def get_load(data: Dict[str, Any]): +@router.post("/predict", response_model=PredictResponse) +async def get_load(predict_request: PredictRequest): try: - return process_graph_cached(data) + exported_flow: ExportedFlow = predict_request.exported_flow + graph_data: GraphData = exported_flow.data + data = graph_data.dict() + response = process_graph_cached(data, predict_request.message) + return PredictResponse(result=response.get("result", "")) except Exception as e: # Log stack trace logger.exception(e) diff --git a/src/backend/langflow/api/schemas.py b/src/backend/langflow/api/schemas.py index dd157d85f..2d14bad50 100644 --- a/src/backend/langflow/api/schemas.py +++ b/src/backend/langflow/api/schemas.py @@ -1,8 +1,37 @@ -from typing import Any, Union +from typing import Any, Union, Dict, List from pydantic import BaseModel, validator +class GraphData(BaseModel): + """Data inside the exported flow.""" + + nodes: List[Dict[str, Any]] + edges: List[Dict[str, Any]] + + +class ExportedFlow(BaseModel): + """Exported flow from LangFlow.""" + + description: str + name: str + id: str + data: GraphData + + +class PredictRequest(BaseModel): + """Predict request schema.""" + + message: str + exported_flow: ExportedFlow + + +class PredictResponse(BaseModel): + """Predict response schema.""" + + result: str + + class ChatMessage(BaseModel): """Chat message schema.""" diff --git a/src/backend/langflow/interface/run.py b/src/backend/langflow/interface/run.py index 68639785c..4f2143e2e 100644 --- a/src/backend/langflow/interface/run.py +++ b/src/backend/langflow/interface/run.py @@ -100,13 +100,12 @@ def process_graph(data_graph: Dict[str, Any]): return {"result": str(result), "thought": thought.strip()} -def process_graph_cached(data_graph: Dict[str, Any]): +def process_graph_cached(data_graph: Dict[str, Any], message: str): """ Process graph by extracting input variables and replacing ZeroShotPrompt with PromptTemplate,then run the graph and return the result and thought. """ # Load langchain object - message = data_graph.pop("message", "") is_first_message = len(data_graph.get("chatHistory", [])) == 0 langchain_object = load_or_build_langchain_object(data_graph, is_first_message) logger.debug("Loaded langchain object") @@ -119,7 +118,7 @@ def process_graph_cached(data_graph: Dict[str, Any]): # Generate result and thought logger.debug("Generating result and thought") - result, thought = get_result_and_steps(langchain_object, message) + result, thought = get_result_and_thought(langchain_object, message) logger.debug("Generated result and thought") return {"result": str(result), "thought": thought.strip()} @@ -241,7 +240,7 @@ def get_result_and_steps(langchain_object, message: str): return result, thought -def async_get_result_and_steps(langchain_object, message: str): +def get_result_and_thought(langchain_object, message: str): """Get result and thought from extracted json""" try: if hasattr(langchain_object, "verbose"): @@ -296,34 +295,6 @@ def async_get_result_and_steps(langchain_object, message: str): return result, thought -def get_result_and_thought(extracted_json: Dict[str, Any], message: str): - """Get result and thought from extracted json""" - try: - langchain_object = loading.load_langchain_type_from_config( - config=extracted_json - ) - with io.StringIO() as output_buffer, contextlib.redirect_stdout(output_buffer): - output = langchain_object(message) - intermediate_steps = ( - output.get("intermediate_steps", []) if isinstance(output, dict) else [] - ) - result = ( - output.get(langchain_object.output_keys[0]) - if isinstance(output, dict) - else output - ) - - if intermediate_steps: - thought = format_intermediate_steps(intermediate_steps) - else: - thought = output_buffer.getvalue() - - except Exception as e: - result = f"Error: {str(e)}" - thought = "" - return result, thought - - def format_intermediate_steps(intermediate_steps): formatted_chain = "> Entering new AgentExecutor chain...\n" for step in intermediate_steps: diff --git a/src/frontend/src/contexts/tabsContext.tsx b/src/frontend/src/contexts/tabsContext.tsx index 6dfa37324..3384ab479 100644 --- a/src/frontend/src/contexts/tabsContext.tsx +++ b/src/frontend/src/contexts/tabsContext.tsx @@ -1,10 +1,10 @@ import { - createContext, - useEffect, - useState, - useRef, - ReactNode, - useContext, + createContext, + useEffect, + useState, + useRef, + ReactNode, + useContext, } from "react"; import { FlowType } from "../types/flow"; import { LangFlowState, TabsContextType } from "../types/tabs"; @@ -15,221 +15,221 @@ import { APITemplateType, TemplateVariableType } from "../types/api"; const { v4: uuidv4 } = require("uuid"); const TabsContextInitialValue: TabsContextType = { - save: () => {}, - tabIndex: 0, - setTabIndex: (index: number) => {}, - flows: [], - removeFlow: (id: string) => {}, - addFlow: (flowData?: any) => {}, - updateFlow: (newFlow: FlowType) => {}, - incrementNodeId: () => 0, - downloadFlow: (flow: FlowType) => {}, - uploadFlow: () => {}, - hardReset: () => {}, + save: () => {}, + tabIndex: 0, + setTabIndex: (index: number) => {}, + flows: [], + removeFlow: (id: string) => {}, + addFlow: (flowData?: any) => {}, + updateFlow: (newFlow: FlowType) => {}, + incrementNodeId: () => 0, + downloadFlow: (flow: FlowType) => {}, + uploadFlow: () => {}, + hardReset: () => {}, }; export const TabsContext = createContext( - TabsContextInitialValue + TabsContextInitialValue ); export function TabsProvider({ children }: { children: ReactNode }) { - const { setNoticeData } = useContext(alertContext); - const [tabIndex, setTabIndex] = useState(0); - const [flows, setFlows] = useState>([]); - const [id, setId] = useState(""); - const { templates } = useContext(typesContext); + const { setNoticeData } = useContext(alertContext); + const [tabIndex, setTabIndex] = useState(0); + const [flows, setFlows] = useState>([]); + const [id, setId] = useState(""); + const { templates } = useContext(typesContext); - const newNodeId = useRef(0); - function incrementNodeId() { - newNodeId.current = newNodeId.current + 1; - return newNodeId.current; - } - function save() { - if (flows.length !== 0) - window.localStorage.setItem( - "tabsData", - JSON.stringify({ tabIndex, flows, id, nodeId: newNodeId.current }) - ); - } - useEffect(() => { - //save tabs locally - save(); - }, [flows, id, tabIndex, newNodeId]); + const newNodeId = useRef(0); + function incrementNodeId() { + newNodeId.current = newNodeId.current + 1; + return newNodeId.current; + } + function save() { + if (flows.length !== 0) + window.localStorage.setItem( + "tabsData", + JSON.stringify({ tabIndex, flows, id, nodeId: newNodeId.current }) + ); + } + useEffect(() => { + //save tabs locally + save(); + }, [flows, id, tabIndex, newNodeId]); - useEffect(() => { - //get tabs locally saved - let cookie = window.localStorage.getItem("tabsData"); - if (cookie && Object.keys(templates).length > 0) { - let cookieObject: LangFlowState = JSON.parse(cookie); - cookieObject.flows.forEach((flow) => { - flow.data.nodes.forEach((node) => { - if (Object.keys(templates[node.data.type]["template"]).length > 0) { - node.data.node.template = updateTemplate( - templates[node.data.type][ - "template" - ] as unknown as APITemplateType, + useEffect(() => { + //get tabs locally saved + let cookie = window.localStorage.getItem("tabsData"); + if (cookie && Object.keys(templates).length > 0) { + let cookieObject: LangFlowState = JSON.parse(cookie); + cookieObject.flows.forEach((flow) => { + flow.data.nodes.forEach((node) => { + if (Object.keys(templates[node.data.type]["template"]).length > 0) { + node.data.node.template = updateTemplate( + templates[node.data.type][ + "template" + ] as unknown as APITemplateType, - node.data.node.template as APITemplateType - ); - } - }); - }); - setTabIndex(cookieObject.tabIndex); - setFlows(cookieObject.flows); - setId(cookieObject.id); - newNodeId.current = cookieObject.nodeId; - } - }, [templates]); - function hardReset() { - newNodeId.current = 0; - setTabIndex(0); - setFlows([]); - setId(""); - } + node.data.node.template as APITemplateType + ); + } + }); + }); + setTabIndex(cookieObject.tabIndex); + setFlows(cookieObject.flows); + setId(cookieObject.id); + newNodeId.current = cookieObject.nodeId; + } + }, [templates]); + function hardReset() { + newNodeId.current = 0; + setTabIndex(0); + setFlows([]); + setId(""); + } - /** - * Downloads the current flow as a JSON file - */ - function downloadFlow(flow: FlowType) { - // create a data URI with the current flow data - const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent( - JSON.stringify(flow) - )}`; + /** + * Downloads the current flow as a JSON file + */ + function downloadFlow(flow: FlowType) { + // create a data URI with the current flow data + const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent( + JSON.stringify(flow) + )}`; - // create a link element and set its properties - const link = document.createElement("a"); - link.href = jsonString; - link.download = `${normalCaseToSnakeCase(flows[tabIndex].name)}.json`; + // create a link element and set its properties + const link = document.createElement("a"); + link.href = jsonString; + link.download = `${flows[tabIndex].name}.json`; - // simulate a click on the link element to trigger the download - link.click(); - setNoticeData({ - title: "Warning: Critical data,JSON file may including API keys.", - }); - } + // simulate a click on the link element to trigger the download + link.click(); + setNoticeData({ + title: "Warning: Critical data,JSON file may including API keys.", + }); + } - /** - * Creates a file input and listens to a change event to upload a JSON flow file. - * If the file type is application/json, the file is read and parsed into a JSON object. - * The resulting JSON object is passed to the addFlow function. - */ - function uploadFlow() { - // create a file input - const input = document.createElement("input"); - input.type = "file"; - // add a change event listener to the file input - input.onchange = (e: Event) => { - // check if the file type is application/json - if ((e.target as HTMLInputElement).files[0].type === "application/json") { - // get the file from the file input - const file = (e.target as HTMLInputElement).files[0]; - // read the file as text - file.text().then((text) => { - // parse the text into a JSON object - let flow: FlowType = JSON.parse(text); + /** + * Creates a file input and listens to a change event to upload a JSON flow file. + * If the file type is application/json, the file is read and parsed into a JSON object. + * The resulting JSON object is passed to the addFlow function. + */ + function uploadFlow() { + // create a file input + const input = document.createElement("input"); + input.type = "file"; + // add a change event listener to the file input + input.onchange = (e: Event) => { + // check if the file type is application/json + if ((e.target as HTMLInputElement).files[0].type === "application/json") { + // get the file from the file input + const file = (e.target as HTMLInputElement).files[0]; + // read the file as text + file.text().then((text) => { + // parse the text into a JSON object + let flow: FlowType = JSON.parse(text); - addFlow(flow); - }); - } - }; - // trigger the file input click event to open the file dialog - input.click(); - } - /** - * Removes a flow from an array of flows based on its id. - * Updates the state of flows and tabIndex using setFlows and setTabIndex hooks. - * @param {string} id - The id of the flow to remove. - */ - function removeFlow(id: string) { - setFlows((prevState) => { - const newFlows = [...prevState]; - const index = newFlows.findIndex((flow) => flow.id === id); - if (index >= 0) { - if (index === tabIndex) { - setTabIndex(flows.length - 2); - newFlows.splice(index, 1); - } else { - let flowId = flows[tabIndex].id; - newFlows.splice(index, 1); - setTabIndex(newFlows.findIndex((flow) => flow.id === flowId)); - } - } - return newFlows; - }); - } - /** - * Add a new flow to the list of flows. - * @param flow Optional flow to add. - */ - function addFlow(flow?: FlowType) { - // Get data from the flow or set it to null if there's no flow provided. - const data = flow?.data ? flow.data : null; - const description = flow?.description ? flow.description : ""; + addFlow(flow); + }); + } + }; + // trigger the file input click event to open the file dialog + input.click(); + } + /** + * Removes a flow from an array of flows based on its id. + * Updates the state of flows and tabIndex using setFlows and setTabIndex hooks. + * @param {string} id - The id of the flow to remove. + */ + function removeFlow(id: string) { + setFlows((prevState) => { + const newFlows = [...prevState]; + const index = newFlows.findIndex((flow) => flow.id === id); + if (index >= 0) { + if (index === tabIndex) { + setTabIndex(flows.length - 2); + newFlows.splice(index, 1); + } else { + let flowId = flows[tabIndex].id; + newFlows.splice(index, 1); + setTabIndex(newFlows.findIndex((flow) => flow.id === flowId)); + } + } + return newFlows; + }); + } + /** + * Add a new flow to the list of flows. + * @param flow Optional flow to add. + */ + function addFlow(flow?: FlowType) { + // Get data from the flow or set it to null if there's no flow provided. + const data = flow?.data ? flow.data : null; + const description = flow?.description ? flow.description : ""; - if (data) { - data.nodes.forEach((node) => { - if (Object.keys(templates[node.data.type]["template"]).length > 0) { - node.data.node.template = updateTemplate( - templates[node.data.type]["template"] as unknown as APITemplateType, - node.data.node.template as APITemplateType - ); - } - }); - } - // Create a new flow with a default name if no flow is provided. - let newFlow: FlowType = { - description, - name: flow?.name ?? "New Flow", - id: id.toString(), - data, - }; + if (data) { + data.nodes.forEach((node) => { + if (Object.keys(templates[node.data.type]["template"]).length > 0) { + node.data.node.template = updateTemplate( + templates[node.data.type]["template"] as unknown as APITemplateType, + node.data.node.template as APITemplateType + ); + } + }); + } + // Create a new flow with a default name if no flow is provided. + let newFlow: FlowType = { + description, + name: flow?.name ?? "New Flow", + id: id.toString(), + data, + }; - // Increment the ID counter. - setId(uuidv4()); + // Increment the ID counter. + setId(uuidv4()); - // Add the new flow to the list of flows. - setFlows((prevState) => { - const newFlows = [...prevState, newFlow]; - return newFlows; - }); + // Add the new flow to the list of flows. + setFlows((prevState) => { + const newFlows = [...prevState, newFlow]; + return newFlows; + }); - // Set the tab index to the new flow. - setTabIndex(flows.length); - } - /** - * Updates an existing flow with new data - * @param newFlow - The new flow object containing the updated data - */ - function updateFlow(newFlow: FlowType) { - setFlows((prevState) => { - const newFlows = [...prevState]; - const index = newFlows.findIndex((flow) => flow.id === newFlow.id); - if (index !== -1) { - newFlows[index].description = newFlow.description ?? ""; - newFlows[index].data = newFlow.data; - newFlows[index].name = newFlow.name; - } - return newFlows; - }); - } + // Set the tab index to the new flow. + setTabIndex(flows.length); + } + /** + * Updates an existing flow with new data + * @param newFlow - The new flow object containing the updated data + */ + function updateFlow(newFlow: FlowType) { + setFlows((prevState) => { + const newFlows = [...prevState]; + const index = newFlows.findIndex((flow) => flow.id === newFlow.id); + if (index !== -1) { + newFlows[index].description = newFlow.description ?? ""; + newFlows[index].data = newFlow.data; + newFlows[index].name = newFlow.name; + } + return newFlows; + }); + } - return ( - - {children} - - ); + return ( + + {children} + + ); }