From 3e5878ddc282557315f532605357ff782af13d64 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 6 Jun 2023 10:01:18 -0300 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=89=20feat(langflow):=20add=20new=20fi?= =?UTF-8?q?les=20base.py=20and=20callback.py=20The=20base.py=20file=20cont?= =?UTF-8?q?ains=20the=20following=20classes=20and=20functions:=20-=20Cache?= =?UTF-8?q?Response:=20a=20pydantic=20BaseModel=20that=20represents=20a=20?= =?UTF-8?q?response=20containing=20a=20dictionary=20of=20data=20-=20Code:?= =?UTF-8?q?=20a=20pydantic=20BaseModel=20that=20represents=20a=20code=20st?= =?UTF-8?q?ring=20-=20Prompt:=20a=20pydantic=20BaseModel=20that=20represen?= =?UTF-8?q?ts=20a=20prompt=20template=20string=20-=20CodeValidationRespons?= =?UTF-8?q?e:=20a=20pydantic=20BaseModel=20that=20represents=20a=20respons?= =?UTF-8?q?e=20containing=20the=20validation=20results=20of=20code=20-=20P?= =?UTF-8?q?romptValidationResponse:=20a=20pydantic=20BaseModel=20that=20re?= =?UTF-8?q?presents=20a=20response=20containing=20the=20validation=20resul?= =?UTF-8?q?ts=20of=20a=20prompt=20-=20validate=5Fprompt:=20a=20function=20?= =?UTF-8?q?that=20validates=20a=20prompt=20template=20string=20and=20retur?= =?UTF-8?q?ns=20a=20PromptValidationResponse=20object=20-=20check=5Finput?= =?UTF-8?q?=5Fvariables:=20a=20function=20that=20checks=20if=20input=20var?= =?UTF-8?q?iables=20contain=20invalid=20characters=20and=20returns=20a=20l?= =?UTF-8?q?ist=20of=20fixed=20input=20variables?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The callback.py file contains the following classes: - AsyncStreamingLLMCallbackHandler: an AsyncCallbackHandler that handles streaming LLM responses asynchronously - StreamingLLMCallbackHandler: a BaseCallbackHandler that handles streaming LLM responses These files were added to provide support for Langflow's backend API. --- src/backend/langflow/api/v1/base.py | 84 +++++++++++++++++++++++++ src/backend/langflow/api/v1/callback.py | 32 ++++++++++ 2 files changed, 116 insertions(+) create mode 100644 src/backend/langflow/api/v1/base.py create mode 100644 src/backend/langflow/api/v1/callback.py diff --git a/src/backend/langflow/api/v1/base.py b/src/backend/langflow/api/v1/base.py new file mode 100644 index 000000000..6941bedf3 --- /dev/null +++ b/src/backend/langflow/api/v1/base.py @@ -0,0 +1,84 @@ +from pydantic import BaseModel, validator + +from langflow.interface.utils import extract_input_variables_from_prompt + + +class CacheResponse(BaseModel): + data: dict + + +class Code(BaseModel): + code: str + + +class Prompt(BaseModel): + template: str + + +# Build ValidationResponse class for {"imports": {"errors": []}, "function": {"errors": []}} +class CodeValidationResponse(BaseModel): + imports: dict + function: dict + + @validator("imports") + def validate_imports(cls, v): + return v or {"errors": []} + + @validator("function") + def validate_function(cls, v): + return v or {"errors": []} + + +class PromptValidationResponse(BaseModel): + input_variables: list + + +INVALID_CHARACTERS = { + " ", + ",", + ".", + ":", + ";", + "!", + "?", + "/", + "\\", + "(", + ")", + "[", + "]", + "{", + "}", +} + + +def validate_prompt(template: str): + input_variables = extract_input_variables_from_prompt(template) + + # Check if there are invalid characters in the input_variables + input_variables = check_input_variables(input_variables) + + return PromptValidationResponse(input_variables=input_variables) + + +def check_input_variables(input_variables: list): + invalid_chars = [] + fixed_variables = [] + for variable in input_variables: + new_var = variable + for char in INVALID_CHARACTERS: + if char in variable: + invalid_chars.append(char) + new_var = new_var.replace(char, "") + fixed_variables.append(new_var) + if new_var != variable: + input_variables.remove(variable) + input_variables.append(new_var) + # If any of the input_variables is not in the fixed_variables, then it means that + # there are invalid characters in the input_variables + if any(var not in fixed_variables for var in input_variables): + raise ValueError( + f"Invalid input variables: {input_variables}. Please, use something like {fixed_variables} instead." + ) + + return input_variables diff --git a/src/backend/langflow/api/v1/callback.py b/src/backend/langflow/api/v1/callback.py new file mode 100644 index 000000000..b58393d7b --- /dev/null +++ b/src/backend/langflow/api/v1/callback.py @@ -0,0 +1,32 @@ +import asyncio +from typing import Any + +from langchain.callbacks.base import AsyncCallbackHandler, BaseCallbackHandler + +from langflow.api.v1.schemas import ChatResponse + + +# https://github.com/hwchase17/chat-langchain/blob/master/callback.py +class AsyncStreamingLLMCallbackHandler(AsyncCallbackHandler): + """Callback handler for streaming LLM responses.""" + + def __init__(self, websocket): + self.websocket = websocket + + async def on_llm_new_token(self, token: str, **kwargs: Any) -> None: + resp = ChatResponse(message=token, type="stream", intermediate_steps="") + await self.websocket.send_json(resp.dict()) + + +class StreamingLLMCallbackHandler(BaseCallbackHandler): + """Callback handler for streaming LLM responses.""" + + def __init__(self, websocket): + self.websocket = websocket + + def on_llm_new_token(self, token: str, **kwargs: Any) -> None: + resp = ChatResponse(message=token, type="stream", intermediate_steps="") + + loop = asyncio.get_event_loop() + coroutine = self.websocket.send_json(resp.dict()) + asyncio.run_coroutine_threadsafe(coroutine, loop)