Merge pull request #267 from logspace-ai:better_predict

Predict endpoint now has better docs and better API
This commit is contained in:
Gabriel Luiz Freitas Almeida 2023-05-09 17:09:45 +00:00 • committed by GitHub
commit ae1ed90b35
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 250 additions and 240 deletions

View file

@ -5,6 +5,12 @@ from fastapi import APIRouter, HTTPException
from langflow.interface.run import process_graph_cached from langflow.interface.run import process_graph_cached
from langflow.interface.types import build_langchain_types_dict from langflow.interface.types import build_langchain_types_dict
from langflow.api.schemas import (
ExportedFlow,
GraphData,
PredictRequest,
PredictResponse,
)
# build router # build router
router = APIRouter() router = APIRouter()
@ -16,10 +22,14 @@ def get_all():
return build_langchain_types_dict() return build_langchain_types_dict()
@router.post("/predict") @router.post("/predict", response_model=PredictResponse)
def get_load(data: Dict[str, Any]): async def get_load(predict_request: PredictRequest):
try: 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: except Exception as e:
# Log stack trace # Log stack trace
logger.exception(e) logger.exception(e)

View file

@ -1,8 +1,37 @@
from typing import Any, Union from typing import Any, Union, Dict, List
from pydantic import BaseModel, validator 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): class ChatMessage(BaseModel):
"""Chat message schema.""" """Chat message schema."""

View file

@ -100,13 +100,12 @@ def process_graph(data_graph: Dict[str, Any]):
return {"result": str(result), "thought": thought.strip()} 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 Process graph by extracting input variables and replacing ZeroShotPrompt
with PromptTemplate,then run the graph and return the result and thought. with PromptTemplate,then run the graph and return the result and thought.
""" """
# Load langchain object # Load langchain object
message = data_graph.pop("message", "")
is_first_message = len(data_graph.get("chatHistory", [])) == 0 is_first_message = len(data_graph.get("chatHistory", [])) == 0
langchain_object = load_or_build_langchain_object(data_graph, is_first_message) langchain_object = load_or_build_langchain_object(data_graph, is_first_message)
logger.debug("Loaded langchain object") logger.debug("Loaded langchain object")
@ -119,7 +118,7 @@ def process_graph_cached(data_graph: Dict[str, Any]):
# Generate result and thought # Generate result and thought
logger.debug("Generating 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") logger.debug("Generated result and thought")
return {"result": str(result), "thought": thought.strip()} return {"result": str(result), "thought": thought.strip()}
@ -241,7 +240,7 @@ def get_result_and_steps(langchain_object, message: str):
return result, thought 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""" """Get result and thought from extracted json"""
try: try:
if hasattr(langchain_object, "verbose"): if hasattr(langchain_object, "verbose"):
@ -296,34 +295,6 @@ def async_get_result_and_steps(langchain_object, message: str):
return result, thought 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): def format_intermediate_steps(intermediate_steps):
formatted_chain = "> Entering new AgentExecutor chain...\n" formatted_chain = "> Entering new AgentExecutor chain...\n"
for step in intermediate_steps: for step in intermediate_steps:

View file

@ -1,10 +1,10 @@
import { import {
createContext, createContext,
useEffect, useEffect,
useState, useState,
useRef, useRef,
ReactNode, ReactNode,
useContext, useContext,
} from "react"; } from "react";
import { FlowType } from "../types/flow"; import { FlowType } from "../types/flow";
import { LangFlowState, TabsContextType } from "../types/tabs"; import { LangFlowState, TabsContextType } from "../types/tabs";
@ -15,221 +15,221 @@ import { APITemplateType, TemplateVariableType } from "../types/api";
const { v4: uuidv4 } = require("uuid"); const { v4: uuidv4 } = require("uuid");
const TabsContextInitialValue: TabsContextType = { const TabsContextInitialValue: TabsContextType = {
save: () => {}, save: () => {},
tabIndex: 0, tabIndex: 0,
setTabIndex: (index: number) => {}, setTabIndex: (index: number) => {},
flows: [], flows: [],
removeFlow: (id: string) => {}, removeFlow: (id: string) => {},
addFlow: (flowData?: any) => {}, addFlow: (flowData?: any) => {},
updateFlow: (newFlow: FlowType) => {}, updateFlow: (newFlow: FlowType) => {},
incrementNodeId: () => 0, incrementNodeId: () => 0,
downloadFlow: (flow: FlowType) => {}, downloadFlow: (flow: FlowType) => {},
uploadFlow: () => {}, uploadFlow: () => {},
hardReset: () => {}, hardReset: () => {},
}; };
export const TabsContext = createContext<TabsContextType>( export const TabsContext = createContext<TabsContextType>(
TabsContextInitialValue TabsContextInitialValue
); );
export function TabsProvider({ children }: { children: ReactNode }) { export function TabsProvider({ children }: { children: ReactNode }) {
const { setNoticeData } = useContext(alertContext); const { setNoticeData } = useContext(alertContext);
const [tabIndex, setTabIndex] = useState(0); const [tabIndex, setTabIndex] = useState(0);
const [flows, setFlows] = useState<Array<FlowType>>([]); const [flows, setFlows] = useState<Array<FlowType>>([]);
const [id, setId] = useState(""); const [id, setId] = useState("");
const { templates } = useContext(typesContext); const { templates } = useContext(typesContext);
const newNodeId = useRef(0); const newNodeId = useRef(0);
function incrementNodeId() { function incrementNodeId() {
newNodeId.current = newNodeId.current + 1; newNodeId.current = newNodeId.current + 1;
return newNodeId.current; return newNodeId.current;
} }
function save() { function save() {
if (flows.length !== 0) if (flows.length !== 0)
window.localStorage.setItem( window.localStorage.setItem(
"tabsData", "tabsData",
JSON.stringify({ tabIndex, flows, id, nodeId: newNodeId.current }) JSON.stringify({ tabIndex, flows, id, nodeId: newNodeId.current })
); );
} }
useEffect(() => { useEffect(() => {
//save tabs locally //save tabs locally
save(); save();
}, [flows, id, tabIndex, newNodeId]); }, [flows, id, tabIndex, newNodeId]);
useEffect(() => { useEffect(() => {
//get tabs locally saved //get tabs locally saved
let cookie = window.localStorage.getItem("tabsData"); let cookie = window.localStorage.getItem("tabsData");
if (cookie && Object.keys(templates).length > 0) { if (cookie && Object.keys(templates).length > 0) {
let cookieObject: LangFlowState = JSON.parse(cookie); let cookieObject: LangFlowState = JSON.parse(cookie);
cookieObject.flows.forEach((flow) => { cookieObject.flows.forEach((flow) => {
flow.data.nodes.forEach((node) => { flow.data.nodes.forEach((node) => {
if (Object.keys(templates[node.data.type]["template"]).length > 0) { if (Object.keys(templates[node.data.type]["template"]).length > 0) {
node.data.node.template = updateTemplate( node.data.node.template = updateTemplate(
templates[node.data.type][ templates[node.data.type][
"template" "template"
] as unknown as APITemplateType, ] as unknown as APITemplateType,
node.data.node.template as APITemplateType node.data.node.template as APITemplateType
); );
} }
}); });
}); });
setTabIndex(cookieObject.tabIndex); setTabIndex(cookieObject.tabIndex);
setFlows(cookieObject.flows); setFlows(cookieObject.flows);
setId(cookieObject.id); setId(cookieObject.id);
newNodeId.current = cookieObject.nodeId; newNodeId.current = cookieObject.nodeId;
} }
}, [templates]); }, [templates]);
function hardReset() { function hardReset() {
newNodeId.current = 0; newNodeId.current = 0;
setTabIndex(0); setTabIndex(0);
setFlows([]); setFlows([]);
setId(""); setId("");
} }
/** /**
* Downloads the current flow as a JSON file * Downloads the current flow as a JSON file
*/ */
function downloadFlow(flow: FlowType) { function downloadFlow(flow: FlowType) {
// create a data URI with the current flow data // create a data URI with the current flow data
const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent( const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent(
JSON.stringify(flow) JSON.stringify(flow)
)}`; )}`;
// create a link element and set its properties // create a link element and set its properties
const link = document.createElement("a"); const link = document.createElement("a");
link.href = jsonString; link.href = jsonString;
link.download = `${normalCaseToSnakeCase(flows[tabIndex].name)}.json`; link.download = `${flows[tabIndex].name}.json`;
// simulate a click on the link element to trigger the download // simulate a click on the link element to trigger the download
link.click(); link.click();
setNoticeData({ setNoticeData({
title: "Warning: Critical data,JSON file may including API keys.", 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. * 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. * 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. * The resulting JSON object is passed to the addFlow function.
*/ */
function uploadFlow() { function uploadFlow() {
// create a file input // create a file input
const input = document.createElement("input"); const input = document.createElement("input");
input.type = "file"; input.type = "file";
// add a change event listener to the file input // add a change event listener to the file input
input.onchange = (e: Event) => { input.onchange = (e: Event) => {
// check if the file type is application/json // check if the file type is application/json
if ((e.target as HTMLInputElement).files[0].type === "application/json") { if ((e.target as HTMLInputElement).files[0].type === "application/json") {
// get the file from the file input // get the file from the file input
const file = (e.target as HTMLInputElement).files[0]; const file = (e.target as HTMLInputElement).files[0];
// read the file as text // read the file as text
file.text().then((text) => { file.text().then((text) => {
// parse the text into a JSON object // parse the text into a JSON object
let flow: FlowType = JSON.parse(text); let flow: FlowType = JSON.parse(text);
addFlow(flow); addFlow(flow);
}); });
} }
}; };
// trigger the file input click event to open the file dialog // trigger the file input click event to open the file dialog
input.click(); input.click();
} }
/** /**
* Removes a flow from an array of flows based on its id. * Removes a flow from an array of flows based on its id.
* Updates the state of flows and tabIndex using setFlows and setTabIndex hooks. * Updates the state of flows and tabIndex using setFlows and setTabIndex hooks.
* @param {string} id - The id of the flow to remove. * @param {string} id - The id of the flow to remove.
*/ */
function removeFlow(id: string) { function removeFlow(id: string) {
setFlows((prevState) => { setFlows((prevState) => {
const newFlows = [...prevState]; const newFlows = [...prevState];
const index = newFlows.findIndex((flow) => flow.id === id); const index = newFlows.findIndex((flow) => flow.id === id);
if (index >= 0) { if (index >= 0) {
if (index === tabIndex) { if (index === tabIndex) {
setTabIndex(flows.length - 2); setTabIndex(flows.length - 2);
newFlows.splice(index, 1); newFlows.splice(index, 1);
} else { } else {
let flowId = flows[tabIndex].id; let flowId = flows[tabIndex].id;
newFlows.splice(index, 1); newFlows.splice(index, 1);
setTabIndex(newFlows.findIndex((flow) => flow.id === flowId)); setTabIndex(newFlows.findIndex((flow) => flow.id === flowId));
} }
} }
return newFlows; return newFlows;
}); });
} }
/** /**
* Add a new flow to the list of flows. * Add a new flow to the list of flows.
* @param flow Optional flow to add. * @param flow Optional flow to add.
*/ */
function addFlow(flow?: FlowType) { function addFlow(flow?: FlowType) {
// Get data from the flow or set it to null if there's no flow provided. // Get data from the flow or set it to null if there's no flow provided.
const data = flow?.data ? flow.data : null; const data = flow?.data ? flow.data : null;
const description = flow?.description ? flow.description : ""; const description = flow?.description ? flow.description : "";
if (data) { if (data) {
data.nodes.forEach((node) => { data.nodes.forEach((node) => {
if (Object.keys(templates[node.data.type]["template"]).length > 0) { if (Object.keys(templates[node.data.type]["template"]).length > 0) {
node.data.node.template = updateTemplate( node.data.node.template = updateTemplate(
templates[node.data.type]["template"] as unknown as APITemplateType, templates[node.data.type]["template"] as unknown as APITemplateType,
node.data.node.template as APITemplateType node.data.node.template as APITemplateType
); );
} }
}); });
} }
// Create a new flow with a default name if no flow is provided. // Create a new flow with a default name if no flow is provided.
let newFlow: FlowType = { let newFlow: FlowType = {
description, description,
name: flow?.name ?? "New Flow", name: flow?.name ?? "New Flow",
id: id.toString(), id: id.toString(),
data, data,
}; };
// Increment the ID counter. // Increment the ID counter.
setId(uuidv4()); setId(uuidv4());
// Add the new flow to the list of flows. // Add the new flow to the list of flows.
setFlows((prevState) => { setFlows((prevState) => {
const newFlows = [...prevState, newFlow]; const newFlows = [...prevState, newFlow];
return newFlows; return newFlows;
}); });
// Set the tab index to the new flow. // Set the tab index to the new flow.
setTabIndex(flows.length); setTabIndex(flows.length);
} }
/** /**
* Updates an existing flow with new data * Updates an existing flow with new data
* @param newFlow - The new flow object containing the updated data * @param newFlow - The new flow object containing the updated data
*/ */
function updateFlow(newFlow: FlowType) { function updateFlow(newFlow: FlowType) {
setFlows((prevState) => { setFlows((prevState) => {
const newFlows = [...prevState]; const newFlows = [...prevState];
const index = newFlows.findIndex((flow) => flow.id === newFlow.id); const index = newFlows.findIndex((flow) => flow.id === newFlow.id);
if (index !== -1) { if (index !== -1) {
newFlows[index].description = newFlow.description ?? ""; newFlows[index].description = newFlow.description ?? "";
newFlows[index].data = newFlow.data; newFlows[index].data = newFlow.data;
newFlows[index].name = newFlow.name; newFlows[index].name = newFlow.name;
} }
return newFlows; return newFlows;
}); });
} }
return ( return (
<TabsContext.Provider <TabsContext.Provider
value={{ value={{
save, save,
hardReset, hardReset,
tabIndex, tabIndex,
setTabIndex, setTabIndex,
flows, flows,
incrementNodeId, incrementNodeId,
removeFlow, removeFlow,
addFlow, addFlow,
updateFlow, updateFlow,
downloadFlow, downloadFlow,
uploadFlow, uploadFlow,
}} }}
> >
{children} {children}
</TabsContext.Provider> </TabsContext.Provider>
); );
} }