Merge pull request #267 from logspace-ai:better_predict
Predict endpoint now has better docs and better API
This commit is contained in:
commit
ae1ed90b35
4 changed files with 250 additions and 240 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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<TabsContextType>(
|
||||
TabsContextInitialValue
|
||||
TabsContextInitialValue
|
||||
);
|
||||
|
||||
export function TabsProvider({ children }: { children: ReactNode }) {
|
||||
const { setNoticeData } = useContext(alertContext);
|
||||
const [tabIndex, setTabIndex] = useState(0);
|
||||
const [flows, setFlows] = useState<Array<FlowType>>([]);
|
||||
const [id, setId] = useState("");
|
||||
const { templates } = useContext(typesContext);
|
||||
const { setNoticeData } = useContext(alertContext);
|
||||
const [tabIndex, setTabIndex] = useState(0);
|
||||
const [flows, setFlows] = useState<Array<FlowType>>([]);
|
||||
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 (
|
||||
<TabsContext.Provider
|
||||
value={{
|
||||
save,
|
||||
hardReset,
|
||||
tabIndex,
|
||||
setTabIndex,
|
||||
flows,
|
||||
incrementNodeId,
|
||||
removeFlow,
|
||||
addFlow,
|
||||
updateFlow,
|
||||
downloadFlow,
|
||||
uploadFlow,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</TabsContext.Provider>
|
||||
);
|
||||
return (
|
||||
<TabsContext.Provider
|
||||
value={{
|
||||
save,
|
||||
hardReset,
|
||||
tabIndex,
|
||||
setTabIndex,
|
||||
flows,
|
||||
incrementNodeId,
|
||||
removeFlow,
|
||||
addFlow,
|
||||
updateFlow,
|
||||
downloadFlow,
|
||||
uploadFlow,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</TabsContext.Provider>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue