Merge remote-tracking branch 'origin/dev' into authentication

This commit is contained in:
gustavoschaedler 2023-08-11 19:40:11 +01:00
commit f1b2fea20f
18 changed files with 495 additions and 315 deletions

View file

@ -0,0 +1,56 @@
from typing import List, Union
from langflow import CustomComponent
from metaphor_python import Metaphor # type: ignore
from langchain.tools import Tool
from langchain.agents import tool
from langchain.agents.agent_toolkits.base import BaseToolkit
class MetaphorToolkit(CustomComponent):
display_name: str = "Metaphor"
description: str = "Metaphor Toolkit"
documentation = (
"https://python.langchain.com/docs/integrations/tools/metaphor_search"
)
beta = True
# api key should be password = True
field_config = {
"metaphor_api_key": {"display_name": "Metaphor API Key", "password": True},
"code": {"advanced": True},
}
def build(
self,
metaphor_api_key: str,
use_autoprompt: bool = True,
search_num_results: int = 5,
similar_num_results: int = 5,
) -> Union[Tool, BaseToolkit]:
# If documents, then we need to create a Vectara instance using .from_documents
client = Metaphor(api_key=metaphor_api_key)
@tool
def search(query: str):
"""Call search engine with a query."""
return client.search(
query, use_autoprompt=use_autoprompt, num_results=search_num_results
)
@tool
def get_contents(ids: List[str]):
"""Get contents of a webpage.
The ids passed in should be a list of ids as fetched from `search`.
"""
return client.get_contents(ids)
@tool
def find_similar(url: str):
"""Get search results similar to a given URL.
The url passed in should be a URL returned from `search`
"""
return client.find_similar(url, num_results=similar_num_results)
return [search, get_contents, find_similar] # type: ignore

View file

@ -0,0 +1,50 @@
from typing import Optional, Union
from langflow import CustomComponent
from langchain.vectorstores import Vectara
from langchain.schema import Document
from langchain.vectorstores.base import VectorStore
from langchain.schema import BaseRetriever
from langchain.embeddings.base import Embeddings
class VectaraComponent(CustomComponent):
display_name: str = "Vectara"
description: str = "Implementation of Vector Store using Vectara"
documentation = (
"https://python.langchain.com/docs/integrations/vectorstores/vectara"
)
beta = True
# api key should be password = True
field_config = {
"vectara_customer_id": {"display_name": "Vectara Customer ID"},
"vectara_corpus_id": {"display_name": "Vectara Corpus ID"},
"vectara_api_key": {"display_name": "Vectara API Key", "password": True},
"code": {"show": False},
"documents": {"display_name": "Documents"},
"embedding": {"display_name": "Embedding"},
}
def build(
self,
vectara_customer_id: str,
vectara_corpus_id: str,
vectara_api_key: str,
embedding: Optional[Embeddings] = None,
documents: Optional[Document] = None,
) -> Union[VectorStore, BaseRetriever]:
# If documents, then we need to create a Vectara instance using .from_documents
if documents is not None and embedding is not None:
return Vectara.from_documents(
documents=documents, # type: ignore
vectara_customer_id=vectara_customer_id,
vectara_corpus_id=vectara_corpus_id,
vectara_api_key=vectara_api_key,
embedding=embedding,
)
return Vectara(
vectara_customer_id=vectara_customer_id,
vectara_corpus_id=vectara_corpus_id,
vectara_api_key=vectara_api_key,
)

View file

@ -66,6 +66,9 @@ class Component(BaseModel):
elif "beta" in item_name:
template_config["beta"] = ast.literal_eval(item_value)
elif "documentation" in item_name:
template_config["documentation"] = ast.literal_eval(item_value)
return template_config
def build(self, *args: Any, **kwargs: Any) -> Any:

View file

@ -50,7 +50,9 @@ class CustomComponent(Component, extra=Extra.allow):
reader = DirectoryReader("", False)
for type_hint in TYPE_HINT_LIST:
if reader.is_type_hint_used_but_not_imported(type_hint, code):
if reader._is_type_hint_used_in_args(
"Optional", code
) and not reader._is_type_hint_imported("Optional", code):
error_detail = {
"error": "Type hint Error",
"traceback": f"Type hint '{type_hint}' is used but not imported in the code.",
@ -93,9 +95,9 @@ class CustomComponent(Component, extra=Extra.allow):
return build_method["args"]
@property
def get_function_entrypoint_return_type(self) -> str:
def get_function_entrypoint_return_type(self) -> List[str]:
if not self.code:
return ""
return []
tree = self.get_code_tree(self.code)
component_classes = [
@ -104,7 +106,7 @@ class CustomComponent(Component, extra=Extra.allow):
if self.code_class_base_inheritance in cls["bases"]
]
if not component_classes:
return ""
return []
# Assume the first Component class is the one we're interested in
component_class = component_classes[0]
@ -115,11 +117,21 @@ class CustomComponent(Component, extra=Extra.allow):
]
if not build_methods:
return ""
return []
build_method = build_methods[0]
return_type = build_method["return_type"]
if not return_type:
return []
# If the return type is not a Union, then we just return it as a list
if "Union" not in return_type:
return [return_type] if return_type in self.return_type_valid_list else []
return build_method["return_type"]
# If the return type is a Union, then we need to parse it
return_type = return_type.replace("Union", "").replace("[", "").replace("]", "")
return_type = return_type.split(",")
return_type = [item.strip() for item in return_type]
return [item for item in return_type if item in self.return_type_valid_list]
@property
def get_main_class_name(self):

View file

@ -152,15 +152,19 @@ class DirectoryReader:
Check if a specific type hint is used in the
function definitions within the given code.
"""
module = ast.parse(code)
try:
module = ast.parse(code)
for node in ast.walk(module):
if isinstance(node, ast.FunctionDef):
for arg in node.args.args:
if self._is_type_hint_in_arg_annotation(
arg.annotation, type_hint_name
):
return True
for node in ast.walk(module):
if isinstance(node, ast.FunctionDef):
for arg in node.args.args:
if self._is_type_hint_in_arg_annotation(
arg.annotation, type_hint_name
):
return True
except SyntaxError:
# Returns False if the code is not valid Python
return False
return False
def _is_type_hint_in_arg_annotation(self, annotation, type_hint_name: str) -> bool:
@ -204,8 +208,13 @@ class DirectoryReader:
return False, "Syntax error"
elif not self.validate_build(file_content):
return False, "Missing build function"
elif self.is_type_hint_used_but_not_imported("Optional", file_content):
return False, "Type hint 'Optional' is used but not imported in the code."
elif self._is_type_hint_used_in_args(
"Optional", file_content
) and not self._is_type_hint_imported("Optional", file_content):
return (
False,
"Type hint 'Optional' is used but not imported in the code.",
)
else:
if self.compress_code_field:
file_content = str(StringCompressor(file_content).compress_string())

View file

@ -116,9 +116,12 @@ def instantiate_based_on_type(class_object, base_type, node_type, params):
def instantiate_custom_component(node_type, class_object, params):
class_object = get_function_custom(params.pop("code"))
# we need to make a copy of the params because we will be
# modifying it
params_copy = params.copy()
class_object = get_function_custom(params_copy.pop("code"))
custom_component = class_object()
built_object = custom_component.build(**params)
built_object = custom_component.build(**params_copy)
return built_object, {"repr": custom_component.custom_repr()}

View file

@ -130,8 +130,8 @@ def initialize_pinecone(class_object: Type[Pinecone], params: dict):
import pinecone # type: ignore
pinecone_api_key = params.get("pinecone_api_key")
pinecone_env = params.get("pinecone_env")
pinecone_api_key = params.pop("pinecone_api_key")
pinecone_env = params.pop("pinecone_env")
if pinecone_api_key is None or pinecone_env is None:
if os.getenv("PINECONE_API_KEY") is not None:

View file

@ -1,6 +1,6 @@
import ast
import contextlib
from typing import Any
from typing import Any, List
from langflow.api.utils import merge_nested_dicts_with_renaming
from langflow.interface.agents.base import agent_creator
from langflow.interface.chains.base import chain_creator
@ -199,6 +199,9 @@ def update_attributes(frontend_node, template_config):
if "beta" in template_config:
frontend_node["beta"] = template_config["beta"]
if "documentation" in template_config:
frontend_node["documentation"] = template_config["documentation"]
def build_field_config(custom_component: CustomComponent):
"""Build the field configuration for a custom component"""
@ -257,26 +260,27 @@ def get_field_properties(extra_field):
return field_name, field_type, field_value, field_required
def add_base_classes(frontend_node, return_type):
def add_base_classes(frontend_node, return_types: List[str]):
"""Add base classes to the frontend node"""
if return_type not in CUSTOM_COMPONENT_SUPPORTED_TYPES or return_type is None:
raise HTTPException(
status_code=400,
detail={
"error": (
"Invalid return type should be one of: "
f"{list(CUSTOM_COMPONENT_SUPPORTED_TYPES.keys())}"
),
"traceback": traceback.format_exc(),
},
)
for return_type in return_types:
if return_type not in CUSTOM_COMPONENT_SUPPORTED_TYPES or return_type is None:
raise HTTPException(
status_code=400,
detail={
"error": (
"Invalid return type should be one of: "
f"{list(CUSTOM_COMPONENT_SUPPORTED_TYPES.keys())}"
),
"traceback": traceback.format_exc(),
},
)
return_type_instance = CUSTOM_COMPONENT_SUPPORTED_TYPES.get(return_type)
base_classes = get_base_classes(return_type_instance)
return_type_instance = CUSTOM_COMPONENT_SUPPORTED_TYPES.get(return_type)
base_classes = get_base_classes(return_type_instance)
for base_class in base_classes:
if base_class not in CLASSES_TO_REMOVE:
frontend_node.get("base_classes").append(base_class)
for base_class in base_classes:
if base_class not in CLASSES_TO_REMOVE:
frontend_node.get("base_classes").append(base_class)
def build_langchain_template_custom_component(custom_component: CustomComponent):

View file

@ -31,11 +31,27 @@ export default function Header() {
return (
<div className="header-arrangement">
<div className="header-start-display">
<Link to="/">
<span className="ml-4 text-2xl"></span>
</Link>
{flows.findIndex((flow) => tabId === flow.id) !== -1 &&
tabId !== "" && <MenuBar flows={flows} tabId={tabId} />}
{tabId === "" || !tabId ? (
<div className="ml-2">
<a
href="https://www.langflow.org/"
target="_blank"
rel="noreferrer"
className="header-waitlist-link-box"
>
<span className="pr-1 text-2xl"></span>
<span>Join The Waitlist</span>
</a>
</div>
) : (
<Link to="/">
<span className="ml-4 text-2xl"></span>
</Link>
)}
{flows.findIndex((f) => tabId === f.id) !== -1 && tabId !== "" && (
<MenuBar flows={flows} tabId={tabId} />
)}
</div>
<div className="round-button-div">
<Link to="/">

View file

@ -495,6 +495,12 @@
.header-github-link-box {
@apply inline-flex h-9 items-center justify-center rounded-md border border-input px-3 pr-0 shadow-sm;
}
.header-waitlist-link-box {
@apply inline-flex h-9 items-center justify-center rounded-md border border-input px-2 shadow-sm text-sm font-medium text-muted-foreground ring-offset-background disabled:pointer-events-none disabled:opacity-50 whitespace-nowrap;
}
.header-waitlist-link-box:hover {
@apply hover:bg-accent hover:text-accent-foreground;
}
.header-github-link {
@apply header-github-link-box text-sm font-medium text-muted-foreground ring-offset-background disabled:pointer-events-none disabled:opacity-50;
}