Remove console.log
This commit is contained in:
commit
25d3b96600
88 changed files with 1621 additions and 1797 deletions
8
.github/workflows/lint.yml
vendored
8
.github/workflows/lint.yml
vendored
|
|
@ -3,7 +3,15 @@ name: lint
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
|
paths:
|
||||||
|
- "poetry.lock"
|
||||||
|
- "pyproject.toml"
|
||||||
|
- "src/backend/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- "poetry.lock"
|
||||||
|
- "pyproject.toml"
|
||||||
|
- "src/backend/**"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
POETRY_VERSION: "1.7.0"
|
POETRY_VERSION: "1.7.0"
|
||||||
|
|
|
||||||
8
.github/workflows/test.yml
vendored
8
.github/workflows/test.yml
vendored
|
|
@ -3,8 +3,16 @@ name: test
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
|
paths:
|
||||||
|
- "poetry.lock"
|
||||||
|
- "pyproject.toml"
|
||||||
|
- "src/backend/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [dev]
|
branches: [dev]
|
||||||
|
paths:
|
||||||
|
- "poetry.lock"
|
||||||
|
- "pyproject.toml"
|
||||||
|
- "src/backend/**"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
POETRY_VERSION: "1.5.0"
|
POETRY_VERSION: "1.5.0"
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Annotated, Optional
|
||||||
|
|
||||||
from fastapi import (
|
from fastapi import (
|
||||||
APIRouter,
|
APIRouter,
|
||||||
BackgroundTasks,
|
BackgroundTasks,
|
||||||
|
Body,
|
||||||
Depends,
|
Depends,
|
||||||
HTTPException,
|
HTTPException,
|
||||||
WebSocket,
|
WebSocket,
|
||||||
|
|
@ -21,6 +22,7 @@ from langflow.api.utils import (
|
||||||
format_exception_message,
|
format_exception_message,
|
||||||
)
|
)
|
||||||
from langflow.api.v1.schemas import (
|
from langflow.api.v1.schemas import (
|
||||||
|
InputValueRequest,
|
||||||
ResultDataResponse,
|
ResultDataResponse,
|
||||||
StreamData,
|
StreamData,
|
||||||
VertexBuildResponse,
|
VertexBuildResponse,
|
||||||
|
|
@ -32,8 +34,9 @@ from langflow.services.auth.utils import (
|
||||||
get_current_user_for_websocket,
|
get_current_user_for_websocket,
|
||||||
)
|
)
|
||||||
from langflow.services.chat.service import ChatService
|
from langflow.services.chat.service import ChatService
|
||||||
from langflow.services.deps import get_chat_service, get_session
|
from langflow.services.deps import get_chat_service, get_session, get_session_service
|
||||||
from langflow.services.monitor.utils import log_vertex_build
|
from langflow.services.monitor.utils import log_vertex_build
|
||||||
|
from langflow.services.session.service import SessionService
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from langflow.graph.vertex.types import ChatVertex
|
from langflow.graph.vertex.types import ChatVertex
|
||||||
|
|
@ -138,10 +141,12 @@ async def build_vertex(
|
||||||
flow_id: str,
|
flow_id: str,
|
||||||
vertex_id: str,
|
vertex_id: str,
|
||||||
background_tasks: BackgroundTasks,
|
background_tasks: BackgroundTasks,
|
||||||
|
inputs: Annotated[InputValueRequest, Body(embed=True)] = None,
|
||||||
chat_service: "ChatService" = Depends(get_chat_service),
|
chat_service: "ChatService" = Depends(get_chat_service),
|
||||||
current_user=Depends(get_current_active_user),
|
current_user=Depends(get_current_active_user),
|
||||||
):
|
):
|
||||||
"""Build a vertex instead of the entire graph."""
|
"""Build a vertex instead of the entire graph."""
|
||||||
|
{"inputs": {"input_value": "some value"}}
|
||||||
start_time = time.perf_counter()
|
start_time = time.perf_counter()
|
||||||
try:
|
try:
|
||||||
start_time = time.perf_counter()
|
start_time = time.perf_counter()
|
||||||
|
|
@ -162,7 +167,8 @@ async def build_vertex(
|
||||||
vertex = graph.get_vertex(vertex_id)
|
vertex = graph.get_vertex(vertex_id)
|
||||||
try:
|
try:
|
||||||
if not vertex.pinned or not vertex._built:
|
if not vertex.pinned or not vertex._built:
|
||||||
await vertex.build(user_id=current_user.id)
|
inputs_dict = inputs.model_dump() if inputs else {}
|
||||||
|
await vertex.build(user_id=current_user.id, inputs=inputs_dict)
|
||||||
|
|
||||||
if vertex.result is not None:
|
if vertex.result is not None:
|
||||||
params = vertex._built_object_repr()
|
params = vertex._built_object_repr()
|
||||||
|
|
@ -175,7 +181,7 @@ async def build_vertex(
|
||||||
result_data_response = ResultDataResponse(**result_dict.model_dump())
|
result_data_response = ResultDataResponse(**result_dict.model_dump())
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error(f"Error building vertex: {exc}")
|
logger.exception(f"Error building vertex: {exc}")
|
||||||
params = format_exception_message(exc)
|
params = format_exception_message(exc)
|
||||||
valid = False
|
valid = False
|
||||||
result_data_response = ResultDataResponse(results={})
|
result_data_response = ResultDataResponse(results={})
|
||||||
|
|
@ -185,15 +191,16 @@ async def build_vertex(
|
||||||
chat_service.clear_cache(flow_id)
|
chat_service.clear_cache(flow_id)
|
||||||
|
|
||||||
# Log the vertex build
|
# Log the vertex build
|
||||||
background_tasks.add_task(
|
if not vertex.will_stream:
|
||||||
log_vertex_build,
|
background_tasks.add_task(
|
||||||
flow_id=flow_id,
|
log_vertex_build,
|
||||||
vertex_id=vertex_id,
|
flow_id=flow_id,
|
||||||
valid=valid,
|
vertex_id=vertex_id,
|
||||||
params=params,
|
valid=valid,
|
||||||
data=result_data_response,
|
params=params,
|
||||||
artifacts=artifacts,
|
data=result_data_response,
|
||||||
)
|
artifacts=artifacts,
|
||||||
|
)
|
||||||
|
|
||||||
timedelta = time.perf_counter() - start_time
|
timedelta = time.perf_counter() - start_time
|
||||||
duration = format_elapsed_time(timedelta)
|
duration = format_elapsed_time(timedelta)
|
||||||
|
|
@ -226,39 +233,56 @@ async def build_vertex(
|
||||||
async def build_vertex_stream(
|
async def build_vertex_stream(
|
||||||
flow_id: str,
|
flow_id: str,
|
||||||
vertex_id: str,
|
vertex_id: str,
|
||||||
|
session_id: Optional[str] = None,
|
||||||
chat_service: "ChatService" = Depends(get_chat_service),
|
chat_service: "ChatService" = Depends(get_chat_service),
|
||||||
|
session_service: "SessionService" = Depends(get_session_service),
|
||||||
):
|
):
|
||||||
"""Build a vertex instead of the entire graph."""
|
"""Build a vertex instead of the entire graph."""
|
||||||
try:
|
try:
|
||||||
|
|
||||||
async def stream_vertex():
|
async def stream_vertex():
|
||||||
try:
|
try:
|
||||||
cache = chat_service.get_cache(flow_id)
|
if not session_id:
|
||||||
if not cache:
|
cache = chat_service.get_cache(flow_id)
|
||||||
# If there's no cache
|
if not cache:
|
||||||
raise ValueError(f"No cache found for {flow_id}.")
|
# If there's no cache
|
||||||
|
raise ValueError(f"No cache found for {flow_id}.")
|
||||||
|
else:
|
||||||
|
graph = cache.get("result")
|
||||||
else:
|
else:
|
||||||
graph = cache.get("result")
|
session_data = await session_service.load_session(session_id)
|
||||||
|
graph, artifacts = session_data if session_data else (None, None)
|
||||||
|
if not graph:
|
||||||
|
raise ValueError(f"No graph found for {flow_id}.")
|
||||||
|
|
||||||
vertex: "ChatVertex" = graph.get_vertex(vertex_id)
|
vertex: "ChatVertex" = graph.get_vertex(vertex_id)
|
||||||
if not hasattr(vertex, "stream"):
|
if not hasattr(vertex, "stream"):
|
||||||
raise ValueError(f"Vertex {vertex_id} does not support streaming")
|
raise ValueError(f"Vertex {vertex_id} does not support streaming")
|
||||||
if not vertex.pinned or not vertex._built:
|
if isinstance(vertex._built_result, str) and vertex._built_result:
|
||||||
|
stream_data = StreamData(
|
||||||
|
event="message",
|
||||||
|
data={"message": f"Streaming vertex {vertex_id}"},
|
||||||
|
)
|
||||||
|
yield str(stream_data)
|
||||||
|
stream_data = StreamData(
|
||||||
|
event="message",
|
||||||
|
data={"chunk": vertex._built_result},
|
||||||
|
)
|
||||||
|
yield str(stream_data)
|
||||||
|
|
||||||
|
elif not vertex.pinned or not vertex._built:
|
||||||
logger.debug(f"Streaming vertex {vertex_id}")
|
logger.debug(f"Streaming vertex {vertex_id}")
|
||||||
stream_data = StreamData(
|
stream_data = StreamData(
|
||||||
event="message",
|
event="message",
|
||||||
data={"message": f"Streaming vertex {vertex_id}"},
|
data={"message": f"Streaming vertex {vertex_id}"},
|
||||||
)
|
)
|
||||||
yield str(stream_data)
|
yield str(stream_data)
|
||||||
number_of_chunks = 0
|
|
||||||
async for chunk in vertex.stream():
|
async for chunk in vertex.stream():
|
||||||
stream_data = StreamData(
|
stream_data = StreamData(
|
||||||
event="message",
|
event="message",
|
||||||
data={"chunk": chunk},
|
data={"chunk": chunk},
|
||||||
)
|
)
|
||||||
number_of_chunks += 1
|
|
||||||
yield str(stream_data)
|
yield str(stream_data)
|
||||||
logger.debug(f"Number of chunks: {number_of_chunks}")
|
|
||||||
elif vertex.result is not None:
|
elif vertex.result is not None:
|
||||||
stream_data = StreamData(
|
stream_data = StreamData(
|
||||||
event="message",
|
event="message",
|
||||||
|
|
|
||||||
|
|
@ -228,6 +228,7 @@ async def run_flow_with_caching(
|
||||||
flow_id: str,
|
flow_id: str,
|
||||||
inputs: Optional[Union[List[dict], dict]] = None,
|
inputs: Optional[Union[List[dict], dict]] = None,
|
||||||
tweaks: Optional[dict] = None,
|
tweaks: Optional[dict] = None,
|
||||||
|
stream: Annotated[bool, Body(embed=True)] = False, # noqa: F821
|
||||||
session_id: Annotated[Union[None, str], Body(embed=True)] = None, # noqa: F821
|
session_id: Annotated[Union[None, str], Body(embed=True)] = None, # noqa: F821
|
||||||
api_key_user: User = Depends(api_key_security),
|
api_key_user: User = Depends(api_key_security),
|
||||||
session_service: SessionService = Depends(get_session_service),
|
session_service: SessionService = Depends(get_session_service),
|
||||||
|
|
@ -239,13 +240,14 @@ async def run_flow_with_caching(
|
||||||
task_result: Any = None
|
task_result: Any = None
|
||||||
if not graph:
|
if not graph:
|
||||||
raise ValueError("Graph not found in the session")
|
raise ValueError("Graph not found in the session")
|
||||||
task_result = await run_graph(
|
task_result, session_id = await run_graph(
|
||||||
graph=graph,
|
graph=graph,
|
||||||
flow_id=flow_id,
|
flow_id=flow_id,
|
||||||
session_id=session_id,
|
session_id=session_id,
|
||||||
inputs=inputs,
|
inputs=inputs,
|
||||||
artifacts=artifacts,
|
artifacts=artifacts,
|
||||||
session_service=session_service,
|
session_service=session_service,
|
||||||
|
stream=stream,
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
@ -263,13 +265,14 @@ async def run_flow_with_caching(
|
||||||
raise ValueError(f"Flow {flow_id} has no data")
|
raise ValueError(f"Flow {flow_id} has no data")
|
||||||
graph_data = flow.data
|
graph_data = flow.data
|
||||||
graph_data = process_tweaks(graph_data, tweaks)
|
graph_data = process_tweaks(graph_data, tweaks)
|
||||||
task_result = await run_graph(
|
task_result, session_id = await run_graph(
|
||||||
graph=graph_data,
|
graph=graph_data,
|
||||||
flow_id=flow_id,
|
flow_id=flow_id,
|
||||||
session_id=session_id,
|
session_id=session_id,
|
||||||
inputs=inputs,
|
inputs=inputs,
|
||||||
artifacts={},
|
artifacts={},
|
||||||
session_service=session_service,
|
session_service=session_service,
|
||||||
|
stream=stream,
|
||||||
)
|
)
|
||||||
|
|
||||||
return RunResponse(outputs=task_result, session_id=session_id)
|
return RunResponse(outputs=task_result, session_id=session_id)
|
||||||
|
|
|
||||||
|
|
@ -261,3 +261,7 @@ class VertexBuildResponse(BaseModel):
|
||||||
|
|
||||||
class VerticesBuiltResponse(BaseModel):
|
class VerticesBuiltResponse(BaseModel):
|
||||||
vertices: List[VertexBuildResponse]
|
vertices: List[VertexBuildResponse]
|
||||||
|
|
||||||
|
|
||||||
|
class InputValueRequest(BaseModel):
|
||||||
|
input_value: str
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,8 @@ from langflow.field_typing.range_spec import RangeSpec
|
||||||
class ConversationalAgent(CustomComponent):
|
class ConversationalAgent(CustomComponent):
|
||||||
display_name: str = "OpenAI Conversational Agent"
|
display_name: str = "OpenAI Conversational Agent"
|
||||||
description: str = "Conversational Agent that can use OpenAI's function calling API"
|
description: str = "Conversational Agent that can use OpenAI's function calling API"
|
||||||
|
icon = "OpenAI"
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
openai_function_models = [
|
openai_function_models = [
|
||||||
"gpt-4-turbo-preview",
|
"gpt-4-turbo-preview",
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,10 @@ class RetrievalQAComponent(CustomComponent):
|
||||||
"input_key": {"display_name": "Input Key", "advanced": True},
|
"input_key": {"display_name": "Input Key", "advanced": True},
|
||||||
"output_key": {"display_name": "Output Key", "advanced": True},
|
"output_key": {"display_name": "Output Key", "advanced": True},
|
||||||
"return_source_documents": {"display_name": "Return Source Documents"},
|
"return_source_documents": {"display_name": "Return Source Documents"},
|
||||||
"inputs": {"display_name": "Input", "input_types": ["Text", "Document"]},
|
"input_value": {
|
||||||
|
"display_name": "Input",
|
||||||
|
"input_types": ["Text", "Document"],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def build(
|
def build(
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
from concurrent import futures
|
from concurrent import futures
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
from langflow import CustomComponent
|
from langflow import CustomComponent
|
||||||
from langflow.schema import Record
|
from langflow.schema import Record
|
||||||
|
|
@ -12,21 +12,30 @@ class GatherRecordsComponent(CustomComponent):
|
||||||
|
|
||||||
def build_config(self) -> Dict[str, Any]:
|
def build_config(self) -> Dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
|
"path": {"display_name": "Path"},
|
||||||
|
"types": {
|
||||||
|
"display_name": "Types",
|
||||||
|
"info": "File types to load. Leave empty to load all types.",
|
||||||
|
},
|
||||||
|
"depth": {"display_name": "Depth", "info": "Depth to search for files."},
|
||||||
|
"max_concurrency": {"display_name": "Max Concurrency", "advanced": True},
|
||||||
"load_hidden": {
|
"load_hidden": {
|
||||||
"display_name": "Load Hidden Files",
|
"display_name": "Load Hidden",
|
||||||
"value": False,
|
|
||||||
"advanced": True,
|
"advanced": True,
|
||||||
|
"info": "If true, hidden files will be loaded.",
|
||||||
},
|
},
|
||||||
"max_concurrency": {
|
"recursive": {
|
||||||
"display_name": "Max Concurrency",
|
"display_name": "Recursive",
|
||||||
"value": 10,
|
|
||||||
"advanced": True,
|
"advanced": True,
|
||||||
|
"info": "If true, the search will be recursive.",
|
||||||
|
},
|
||||||
|
"silent_errors": {
|
||||||
|
"display_name": "Silent Errors",
|
||||||
|
"advanced": True,
|
||||||
|
"info": "If true, errors will not raise an exception.",
|
||||||
},
|
},
|
||||||
"path": {"display_name": "Local Directory"},
|
|
||||||
"recursive": {"display_name": "Recursive", "value": True, "advanced": True},
|
|
||||||
"use_multithreading": {
|
"use_multithreading": {
|
||||||
"display_name": "Use Multithreading",
|
"display_name": "Use Multithreading",
|
||||||
"value": True,
|
|
||||||
"advanced": True,
|
"advanced": True,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -61,7 +70,9 @@ class GatherRecordsComponent(CustomComponent):
|
||||||
|
|
||||||
glob = "**/*" if recursive else "*"
|
glob = "**/*" if recursive else "*"
|
||||||
paths = walk_level(path_obj, depth) if depth else path_obj.glob(glob)
|
paths = walk_level(path_obj, depth) if depth else path_obj.glob(glob)
|
||||||
file_paths = [str(p) for p in paths if p.is_file() and match_types(p) and is_not_hidden(p)]
|
file_paths = [
|
||||||
|
str(p) for p in paths if p.is_file() and match_types(p) and is_not_hidden(p)
|
||||||
|
]
|
||||||
|
|
||||||
return file_paths
|
return file_paths
|
||||||
|
|
||||||
|
|
@ -91,13 +102,20 @@ class GatherRecordsComponent(CustomComponent):
|
||||||
use_multithreading: bool,
|
use_multithreading: bool,
|
||||||
) -> List[Record]:
|
) -> List[Record]:
|
||||||
if use_multithreading:
|
if use_multithreading:
|
||||||
records = self.parallel_load_records(file_paths, silent_errors, max_concurrency)
|
records = self.parallel_load_records(
|
||||||
|
file_paths, silent_errors, max_concurrency
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
records = [self.parse_file_to_record(file_path, silent_errors) for file_path in file_paths]
|
records = [
|
||||||
|
self.parse_file_to_record(file_path, silent_errors)
|
||||||
|
for file_path in file_paths
|
||||||
|
]
|
||||||
records = list(filter(None, records))
|
records = list(filter(None, records))
|
||||||
return records
|
return records
|
||||||
|
|
||||||
def parallel_load_records(self, file_paths: List[str], silent_errors: bool, max_concurrency: int) -> List[Record]:
|
def parallel_load_records(
|
||||||
|
self, file_paths: List[str], silent_errors: bool, max_concurrency: int
|
||||||
|
) -> List[Record]:
|
||||||
with futures.ThreadPoolExecutor(max_workers=max_concurrency) as executor:
|
with futures.ThreadPoolExecutor(max_workers=max_concurrency) as executor:
|
||||||
loaded_files = executor.map(
|
loaded_files = executor.map(
|
||||||
lambda file_path: self.parse_file_to_record(file_path, silent_errors),
|
lambda file_path: self.parse_file_to_record(file_path, silent_errors),
|
||||||
|
|
@ -108,7 +126,7 @@ class GatherRecordsComponent(CustomComponent):
|
||||||
def build(
|
def build(
|
||||||
self,
|
self,
|
||||||
path: str,
|
path: str,
|
||||||
types: List[str] = None,
|
types: Optional[List[str]] = None,
|
||||||
depth: int = 0,
|
depth: int = 0,
|
||||||
max_concurrency: int = 2,
|
max_concurrency: int = 2,
|
||||||
load_hidden: bool = False,
|
load_hidden: bool = False,
|
||||||
|
|
@ -116,14 +134,23 @@ class GatherRecordsComponent(CustomComponent):
|
||||||
silent_errors: bool = False,
|
silent_errors: bool = False,
|
||||||
use_multithreading: bool = True,
|
use_multithreading: bool = True,
|
||||||
) -> List[Record]:
|
) -> List[Record]:
|
||||||
|
if types is None:
|
||||||
|
types = []
|
||||||
resolved_path = self.resolve_path(path)
|
resolved_path = self.resolve_path(path)
|
||||||
file_paths = self.retrieve_file_paths(resolved_path, types, load_hidden, recursive, depth)
|
file_paths = self.retrieve_file_paths(
|
||||||
|
resolved_path, types, load_hidden, recursive, depth
|
||||||
|
)
|
||||||
loaded_records = []
|
loaded_records = []
|
||||||
|
|
||||||
if use_multithreading:
|
if use_multithreading:
|
||||||
loaded_records = self.parallel_load_records(file_paths, silent_errors, max_concurrency)
|
loaded_records = self.parallel_load_records(
|
||||||
|
file_paths, silent_errors, max_concurrency
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
loaded_records = [self.parse_file_to_record(file_path, silent_errors) for file_path in file_paths]
|
loaded_records = [
|
||||||
|
self.parse_file_to_record(file_path, silent_errors)
|
||||||
|
for file_path in file_paths
|
||||||
|
]
|
||||||
loaded_records = list(filter(None, loaded_records))
|
loaded_records = list(filter(None, loaded_records))
|
||||||
self.status = loaded_records
|
self.status = loaded_records
|
||||||
return loaded_records
|
return loaded_records
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ class HuggingFaceEmbeddingsComponent(CustomComponent):
|
||||||
documentation = (
|
documentation = (
|
||||||
"https://python.langchain.com/docs/modules/data_connection/text_embedding/integrations/sentence_transformers"
|
"https://python.langchain.com/docs/modules/data_connection/text_embedding/integrations/sentence_transformers"
|
||||||
)
|
)
|
||||||
|
icon="HuggingFace"
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ class HuggingFaceInferenceAPIEmbeddingsComponent(CustomComponent):
|
||||||
display_name = "HuggingFaceInferenceAPIEmbeddings"
|
display_name = "HuggingFaceInferenceAPIEmbeddings"
|
||||||
description = "HuggingFace sentence_transformers embedding models, API version."
|
description = "HuggingFace sentence_transformers embedding models, API version."
|
||||||
documentation = "https://github.com/huggingface/text-embeddings-inference"
|
documentation = "https://github.com/huggingface/text-embeddings-inference"
|
||||||
|
icon="HuggingFace"
|
||||||
|
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from typing import Optional, Union
|
from typing import Optional, Union
|
||||||
|
|
||||||
|
from langflow.components.io.base.chat import ChatComponent
|
||||||
from langflow.field_typing import Text
|
from langflow.field_typing import Text
|
||||||
from langflow.io.schema import ChatComponent
|
|
||||||
from langflow.schema import Record
|
from langflow.schema import Record
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from typing import Optional, Union
|
from typing import Optional, Union
|
||||||
|
|
||||||
|
from langflow.components.io.base.chat import ChatComponent
|
||||||
from langflow.field_typing import Text
|
from langflow.field_typing import Text
|
||||||
from langflow.io.schema import ChatComponent
|
|
||||||
from langflow.schema import Record
|
from langflow.schema import Record
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ class MessageHistoryComponent(CustomComponent):
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
return {
|
return {
|
||||||
"sender": {
|
"sender": {
|
||||||
"options": ["Machine", "User"],
|
"options": ["Machine", "User", "Machine and User"],
|
||||||
"display_name": "Sender Type",
|
"display_name": "Sender Type",
|
||||||
},
|
},
|
||||||
"sender_name": {"display_name": "Sender Name"},
|
"sender_name": {"display_name": "Sender Name"},
|
||||||
|
|
@ -38,6 +38,8 @@ class MessageHistoryComponent(CustomComponent):
|
||||||
session_id: Optional[str] = None,
|
session_id: Optional[str] = None,
|
||||||
n_messages: int = 5,
|
n_messages: int = 5,
|
||||||
) -> List[Record]:
|
) -> List[Record]:
|
||||||
|
if sender == "Machine and User":
|
||||||
|
sender = None
|
||||||
messages = get_messages(
|
messages = get_messages(
|
||||||
sender=sender,
|
sender=sender,
|
||||||
sender_name=sender_name,
|
sender_name=sender_name,
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,11 @@ class TextInput(CustomComponent):
|
||||||
description = "Used to pass text input to the next component."
|
description = "Used to pass text input to the next component."
|
||||||
|
|
||||||
field_config = {
|
field_config = {
|
||||||
"value": {"display_name": "Value", "multiline": True},
|
"input_value": {"display_name": "Value", "multiline": True},
|
||||||
}
|
}
|
||||||
|
|
||||||
def build(self, value: Optional[str] = "") -> Text:
|
def build(self, input_value: Optional[str] = "") -> Text:
|
||||||
self.status = value
|
self.status = input_value
|
||||||
if not value:
|
if not input_value:
|
||||||
value = ""
|
input_value = ""
|
||||||
return value
|
return input_value
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ from langflow import CustomComponent
|
||||||
class AmazonBedrockComponent(CustomComponent):
|
class AmazonBedrockComponent(CustomComponent):
|
||||||
display_name: str = "Amazon Bedrock"
|
display_name: str = "Amazon Bedrock"
|
||||||
description: str = "LLM model from Amazon Bedrock."
|
description: str = "LLM model from Amazon Bedrock."
|
||||||
|
icon = "Amazon"
|
||||||
|
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ from langflow import CustomComponent
|
||||||
class AnthropicLLM(CustomComponent):
|
class AnthropicLLM(CustomComponent):
|
||||||
display_name: str = "AnthropicLLM"
|
display_name: str = "AnthropicLLM"
|
||||||
description: str = "Anthropic Chat&Completion large language models."
|
description: str = "Anthropic Chat&Completion large language models."
|
||||||
|
icon ="Anthropic"
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ from langflow.field_typing import BaseLanguageModel, NestedDict
|
||||||
class AnthropicComponent(CustomComponent):
|
class AnthropicComponent(CustomComponent):
|
||||||
display_name = "Anthropic"
|
display_name = "Anthropic"
|
||||||
description = "Anthropic large language models."
|
description = "Anthropic large language models."
|
||||||
|
icon ="Anthropic"
|
||||||
|
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ class ChatAnthropicComponent(CustomComponent):
|
||||||
display_name = "ChatAnthropic"
|
display_name = "ChatAnthropic"
|
||||||
description = "`Anthropic` chat large language models."
|
description = "`Anthropic` chat large language models."
|
||||||
documentation = "https://python.langchain.com/docs/modules/model_io/models/chat/integrations/anthropic"
|
documentation = "https://python.langchain.com/docs/modules/model_io/models/chat/integrations/anthropic"
|
||||||
|
icon ="Anthropic"
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ from langflow.field_typing import BaseLanguageModel, NestedDict
|
||||||
class ChatOpenAIComponent(CustomComponent):
|
class ChatOpenAIComponent(CustomComponent):
|
||||||
display_name = "ChatOpenAI"
|
display_name = "ChatOpenAI"
|
||||||
description = "`OpenAI` Chat large language models API."
|
description = "`OpenAI` Chat large language models API."
|
||||||
|
icon = "OpenAI"
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ from langflow.field_typing import BaseLanguageModel
|
||||||
class ChatVertexAIComponent(CustomComponent):
|
class ChatVertexAIComponent(CustomComponent):
|
||||||
display_name = "ChatVertexAI"
|
display_name = "ChatVertexAI"
|
||||||
description = "`Vertex AI` Chat large language models API."
|
description = "`Vertex AI` Chat large language models API."
|
||||||
|
icon="VertexAI"
|
||||||
|
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ class CohereComponent(CustomComponent):
|
||||||
display_name = "Cohere"
|
display_name = "Cohere"
|
||||||
description = "Cohere large language models."
|
description = "Cohere large language models."
|
||||||
documentation = "https://python.langchain.com/docs/modules/model_io/models/llms/integrations/cohere"
|
documentation = "https://python.langchain.com/docs/modules/model_io/models/llms/integrations/cohere"
|
||||||
|
icon = "Cohere"
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ class GoogleGenerativeAIComponent(CustomComponent):
|
||||||
display_name: str = "Google Generative AI"
|
display_name: str = "Google Generative AI"
|
||||||
description: str = "A component that uses Google Generative AI to generate text."
|
description: str = "A component that uses Google Generative AI to generate text."
|
||||||
documentation: str = "http://docs.langflow.org/components/custom"
|
documentation: str = "http://docs.langflow.org/components/custom"
|
||||||
|
icon = "Google"
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ from langflow import CustomComponent
|
||||||
class HuggingFaceEndpointsComponent(CustomComponent):
|
class HuggingFaceEndpointsComponent(CustomComponent):
|
||||||
display_name: str = "Hugging Face Inference API"
|
display_name: str = "Hugging Face Inference API"
|
||||||
description: str = "LLM model from Hugging Face Inference API."
|
description: str = "LLM model from Hugging Face Inference API."
|
||||||
|
icon="HuggingFace"
|
||||||
|
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ from langchain_community.llms.vertexai import VertexAI
|
||||||
class VertexAIComponent(CustomComponent):
|
class VertexAIComponent(CustomComponent):
|
||||||
display_name = "VertexAI"
|
display_name = "VertexAI"
|
||||||
description = "Google Vertex AI large language models"
|
description = "Google Vertex AI large language models"
|
||||||
|
icon="VertexAI"
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,14 @@ from typing import Optional
|
||||||
|
|
||||||
from langchain_community.chat_models.bedrock import BedrockChat
|
from langchain_community.chat_models.bedrock import BedrockChat
|
||||||
|
|
||||||
from langflow import CustomComponent
|
from langflow.components.models.base.model import LCModelComponent
|
||||||
from langflow.field_typing import Text
|
from langflow.field_typing import Text
|
||||||
|
|
||||||
|
|
||||||
class AmazonBedrockComponent(CustomComponent):
|
class AmazonBedrockComponent(LCModelComponent):
|
||||||
display_name: str = "Amazon Bedrock Model"
|
display_name: str = "Amazon Bedrock Model"
|
||||||
description: str = "Generate text using LLM model from Amazon Bedrock."
|
description: str = "Generate text using LLM model from Amazon Bedrock."
|
||||||
|
icon = "Amazon"
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
return {
|
return {
|
||||||
|
|
@ -34,7 +35,11 @@ class AmazonBedrockComponent(CustomComponent):
|
||||||
"model_kwargs": {"display_name": "Model Kwargs"},
|
"model_kwargs": {"display_name": "Model Kwargs"},
|
||||||
"cache": {"display_name": "Cache"},
|
"cache": {"display_name": "Cache"},
|
||||||
"code": {"advanced": True},
|
"code": {"advanced": True},
|
||||||
"inputs": {"display_name": "Input"},
|
"input_value": {"display_name": "Input"},
|
||||||
|
"stream": {
|
||||||
|
"display_name": "Stream",
|
||||||
|
"info": "Stream the response from the model.",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def build(
|
def build(
|
||||||
|
|
@ -47,6 +52,7 @@ class AmazonBedrockComponent(CustomComponent):
|
||||||
endpoint_url: Optional[str] = None,
|
endpoint_url: Optional[str] = None,
|
||||||
streaming: bool = False,
|
streaming: bool = False,
|
||||||
cache: Optional[bool] = None,
|
cache: Optional[bool] = None,
|
||||||
|
stream: bool = False,
|
||||||
) -> Text:
|
) -> Text:
|
||||||
try:
|
try:
|
||||||
output = BedrockChat(
|
output = BedrockChat(
|
||||||
|
|
@ -60,7 +66,5 @@ class AmazonBedrockComponent(CustomComponent):
|
||||||
) # type: ignore
|
) # type: ignore
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError("Could not connect to AmazonBedrock API.") from e
|
raise ValueError("Could not connect to AmazonBedrock API.") from e
|
||||||
message = output.invoke(input_value)
|
|
||||||
result = message.content if hasattr(message, "content") else message
|
return self.get_result(output=output, stream=stream, input_value=input_value)
|
||||||
self.status = result
|
|
||||||
return result
|
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,16 @@ from typing import Optional
|
||||||
from langchain_community.chat_models.anthropic import ChatAnthropic
|
from langchain_community.chat_models.anthropic import ChatAnthropic
|
||||||
from pydantic.v1 import SecretStr
|
from pydantic.v1 import SecretStr
|
||||||
|
|
||||||
from langflow import CustomComponent
|
from langflow.components.models.base.model import LCModelComponent
|
||||||
from langflow.field_typing import Text
|
from langflow.field_typing import Text
|
||||||
|
|
||||||
|
|
||||||
class AnthropicLLM(CustomComponent):
|
class AnthropicLLM(LCModelComponent):
|
||||||
display_name: str = "AnthropicModel"
|
display_name: str = "AnthropicModel"
|
||||||
description: str = (
|
description: str = (
|
||||||
"Generate text using Anthropic Chat&Completion large language models."
|
"Generate text using Anthropic Chat&Completion large language models."
|
||||||
)
|
)
|
||||||
|
icon = "Anthropic"
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
return {
|
return {
|
||||||
|
|
@ -49,7 +50,11 @@ class AnthropicLLM(CustomComponent):
|
||||||
"info": "Endpoint of the Anthropic API. Defaults to 'https://api.anthropic.com' if not specified.",
|
"info": "Endpoint of the Anthropic API. Defaults to 'https://api.anthropic.com' if not specified.",
|
||||||
},
|
},
|
||||||
"code": {"show": False},
|
"code": {"show": False},
|
||||||
"inputs": {"display_name": "Input"},
|
"input_value": {"display_name": "Input"},
|
||||||
|
"stream": {
|
||||||
|
"display_name": "Stream",
|
||||||
|
"info": "Stream the response from the model.",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def build(
|
def build(
|
||||||
|
|
@ -60,6 +65,7 @@ class AnthropicLLM(CustomComponent):
|
||||||
max_tokens: Optional[int] = None,
|
max_tokens: Optional[int] = None,
|
||||||
temperature: Optional[float] = None,
|
temperature: Optional[float] = None,
|
||||||
api_endpoint: Optional[str] = None,
|
api_endpoint: Optional[str] = None,
|
||||||
|
stream: bool = False,
|
||||||
) -> Text:
|
) -> Text:
|
||||||
# Set default API endpoint if not provided
|
# Set default API endpoint if not provided
|
||||||
if not api_endpoint:
|
if not api_endpoint:
|
||||||
|
|
@ -77,7 +83,5 @@ class AnthropicLLM(CustomComponent):
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError("Could not connect to Anthropic API.") from e
|
raise ValueError("Could not connect to Anthropic API.") from e
|
||||||
message = output.invoke(input_value)
|
|
||||||
result = message.content if hasattr(message, "content") else message
|
return self.get_result(output=output, stream=stream, input_value=input_value)
|
||||||
self.status = result
|
|
||||||
return result
|
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,17 @@ from typing import Optional
|
||||||
from langchain.llms.base import BaseLanguageModel
|
from langchain.llms.base import BaseLanguageModel
|
||||||
from langchain_openai import AzureChatOpenAI
|
from langchain_openai import AzureChatOpenAI
|
||||||
|
|
||||||
from langflow import CustomComponent
|
from langflow.components.models.base.model import LCModelComponent
|
||||||
|
|
||||||
|
|
||||||
class AzureChatOpenAIComponent(CustomComponent):
|
class AzureChatOpenAIComponent(LCModelComponent):
|
||||||
display_name: str = "AzureOpenAI Model"
|
display_name: str = "AzureOpenAI Model"
|
||||||
description: str = "Generate text using LLM model from Azure OpenAI."
|
description: str = "Generate text using LLM model from Azure OpenAI."
|
||||||
documentation: str = (
|
documentation: str = (
|
||||||
"https://python.langchain.com/docs/integrations/llms/azure_openai"
|
"https://python.langchain.com/docs/integrations/llms/azure_openai"
|
||||||
)
|
)
|
||||||
beta = False
|
beta = False
|
||||||
|
icon = "Azure"
|
||||||
|
|
||||||
AZURE_OPENAI_MODELS = [
|
AZURE_OPENAI_MODELS = [
|
||||||
"gpt-35-turbo",
|
"gpt-35-turbo",
|
||||||
|
|
@ -73,7 +74,11 @@ class AzureChatOpenAIComponent(CustomComponent):
|
||||||
"info": "Maximum number of tokens to generate.",
|
"info": "Maximum number of tokens to generate.",
|
||||||
},
|
},
|
||||||
"code": {"show": False},
|
"code": {"show": False},
|
||||||
"inputs": {"display_name": "Input"},
|
"input_value": {"display_name": "Input"},
|
||||||
|
"stream": {
|
||||||
|
"display_name": "Stream",
|
||||||
|
"info": "Stream the response from the model.",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def build(
|
def build(
|
||||||
|
|
@ -86,6 +91,7 @@ class AzureChatOpenAIComponent(CustomComponent):
|
||||||
api_version: str,
|
api_version: str,
|
||||||
temperature: float = 0.7,
|
temperature: float = 0.7,
|
||||||
max_tokens: Optional[int] = 1000,
|
max_tokens: Optional[int] = 1000,
|
||||||
|
stream: bool = False,
|
||||||
) -> BaseLanguageModel:
|
) -> BaseLanguageModel:
|
||||||
try:
|
try:
|
||||||
output = AzureChatOpenAI(
|
output = AzureChatOpenAI(
|
||||||
|
|
@ -99,7 +105,5 @@ class AzureChatOpenAIComponent(CustomComponent):
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError("Could not connect to AzureOpenAI API.") from e
|
raise ValueError("Could not connect to AzureOpenAI API.") from e
|
||||||
message = output.invoke(input_value)
|
|
||||||
result = message.content if hasattr(message, "content") else message
|
return self.get_result(output=output, stream=stream, input_value=input_value)
|
||||||
self.status = result
|
|
||||||
return result
|
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,17 @@ from typing import Optional
|
||||||
from langchain_community.chat_models.baidu_qianfan_endpoint import QianfanChatEndpoint
|
from langchain_community.chat_models.baidu_qianfan_endpoint import QianfanChatEndpoint
|
||||||
from pydantic.v1 import SecretStr
|
from pydantic.v1 import SecretStr
|
||||||
|
|
||||||
from langflow import CustomComponent
|
from langflow.components.models.base.model import LCModelComponent
|
||||||
from langflow.field_typing import Text
|
from langflow.field_typing import Text
|
||||||
|
|
||||||
|
|
||||||
class QianfanChatEndpointComponent(CustomComponent):
|
class QianfanChatEndpointComponent(LCModelComponent):
|
||||||
display_name: str = "QianfanChat Model"
|
display_name: str = "QianfanChat Model"
|
||||||
description: str = (
|
description: str = (
|
||||||
"Generate text using Baidu Qianfan chat models. Get more detail from "
|
"Generate text using Baidu Qianfan chat models. Get more detail from "
|
||||||
"https://python.langchain.com/docs/integrations/chat/baidu_qianfan_endpoint."
|
"https://python.langchain.com/docs/integrations/chat/baidu_qianfan_endpoint."
|
||||||
)
|
)
|
||||||
|
icon = "BaiduQianfan"
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
return {
|
return {
|
||||||
|
|
@ -68,7 +69,11 @@ class QianfanChatEndpointComponent(CustomComponent):
|
||||||
"info": "Endpoint of the Qianfan LLM, required if custom model used.",
|
"info": "Endpoint of the Qianfan LLM, required if custom model used.",
|
||||||
},
|
},
|
||||||
"code": {"show": False},
|
"code": {"show": False},
|
||||||
"inputs": {"display_name": "Input"},
|
"input_value": {"display_name": "Input"},
|
||||||
|
"stream": {
|
||||||
|
"display_name": "Stream",
|
||||||
|
"info": "Stream the response from the model.",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def build(
|
def build(
|
||||||
|
|
@ -81,6 +86,7 @@ class QianfanChatEndpointComponent(CustomComponent):
|
||||||
temperature: Optional[float] = None,
|
temperature: Optional[float] = None,
|
||||||
penalty_score: Optional[float] = None,
|
penalty_score: Optional[float] = None,
|
||||||
endpoint: Optional[str] = None,
|
endpoint: Optional[str] = None,
|
||||||
|
stream: bool = False,
|
||||||
) -> Text:
|
) -> Text:
|
||||||
try:
|
try:
|
||||||
output = QianfanChatEndpoint( # type: ignore
|
output = QianfanChatEndpoint( # type: ignore
|
||||||
|
|
@ -94,7 +100,5 @@ class QianfanChatEndpointComponent(CustomComponent):
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError("Could not connect to Baidu Qianfan API.") from e
|
raise ValueError("Could not connect to Baidu Qianfan API.") from e
|
||||||
message = output.invoke(input_value)
|
|
||||||
result = message.content if hasattr(message, "content") else message
|
return self.get_result(output=output, stream=stream, input_value=input_value)
|
||||||
self.status = result
|
|
||||||
return result
|
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@ from typing import Dict, Optional
|
||||||
|
|
||||||
from langchain_community.llms.ctransformers import CTransformers
|
from langchain_community.llms.ctransformers import CTransformers
|
||||||
|
|
||||||
from langflow import CustomComponent
|
from langflow.components.models.base.model import LCModelComponent
|
||||||
from langflow.field_typing import Text
|
from langflow.field_typing import Text
|
||||||
|
|
||||||
|
|
||||||
class CTransformersComponent(CustomComponent):
|
class CTransformersComponent(LCModelComponent):
|
||||||
display_name = "CTransformersModel"
|
display_name = "CTransformersModel"
|
||||||
description = "Generate text using CTransformers LLM models"
|
description = "Generate text using CTransformers LLM models"
|
||||||
documentation = "https://python.langchain.com/docs/modules/model_io/models/llms/integrations/ctransformers"
|
documentation = "https://python.langchain.com/docs/modules/model_io/models/llms/integrations/ctransformers"
|
||||||
|
|
@ -28,7 +28,11 @@ class CTransformersComponent(CustomComponent):
|
||||||
"field_type": "dict",
|
"field_type": "dict",
|
||||||
"value": '{"top_k":40,"top_p":0.95,"temperature":0.8,"repetition_penalty":1.1,"last_n_tokens":64,"seed":-1,"max_new_tokens":256,"stop":"","stream":"False","reset":"True","batch_size":8,"threads":-1,"context_length":-1,"gpu_layers":0}',
|
"value": '{"top_k":40,"top_p":0.95,"temperature":0.8,"repetition_penalty":1.1,"last_n_tokens":64,"seed":-1,"max_new_tokens":256,"stop":"","stream":"False","reset":"True","batch_size":8,"threads":-1,"context_length":-1,"gpu_layers":0}',
|
||||||
},
|
},
|
||||||
"inputs": {"display_name": "Input"},
|
"input_value": {"display_name": "Input"},
|
||||||
|
"stream": {
|
||||||
|
"display_name": "Stream",
|
||||||
|
"info": "Stream the response from the model.",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def build(
|
def build(
|
||||||
|
|
@ -38,11 +42,10 @@ class CTransformersComponent(CustomComponent):
|
||||||
input_value: str,
|
input_value: str,
|
||||||
model_type: str,
|
model_type: str,
|
||||||
config: Optional[Dict] = None,
|
config: Optional[Dict] = None,
|
||||||
|
stream: Optional[bool] = False,
|
||||||
) -> Text:
|
) -> Text:
|
||||||
output = CTransformers(
|
output = CTransformers(
|
||||||
model=model, model_file=model_file, model_type=model_type, config=config
|
model=model, model_file=model_file, model_type=model_type, config=config
|
||||||
)
|
)
|
||||||
message = output.invoke(input_value)
|
|
||||||
result = message.content if hasattr(message, "content") else message
|
return self.get_result(output=output, stream=stream, input_value=input_value)
|
||||||
self.status = result
|
|
||||||
return result
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,16 @@
|
||||||
from langchain_community.chat_models.cohere import ChatCohere
|
from langchain_community.chat_models.cohere import ChatCohere
|
||||||
|
|
||||||
from langflow import CustomComponent
|
from langflow.components.models.base.model import LCModelComponent
|
||||||
from langflow.field_typing import Text
|
from langflow.field_typing import Text
|
||||||
|
|
||||||
|
|
||||||
class CohereComponent(CustomComponent):
|
class CohereComponent(LCModelComponent):
|
||||||
display_name = "CohereModel"
|
display_name = "CohereModel"
|
||||||
description = "Generate text using Cohere large language models."
|
description = "Generate text using Cohere large language models."
|
||||||
documentation = "https://python.langchain.com/docs/modules/model_io/models/llms/integrations/cohere"
|
documentation = "https://python.langchain.com/docs/modules/model_io/models/llms/integrations/cohere"
|
||||||
|
|
||||||
|
icon = "Cohere"
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
return {
|
return {
|
||||||
"cohere_api_key": {
|
"cohere_api_key": {
|
||||||
|
|
@ -28,7 +30,11 @@ class CohereComponent(CustomComponent):
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"show": True,
|
"show": True,
|
||||||
},
|
},
|
||||||
"inputs": {"display_name": "Input"},
|
"input_value": {"display_name": "Input"},
|
||||||
|
"stream": {
|
||||||
|
"display_name": "Stream",
|
||||||
|
"info": "Stream the response from the model.",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def build(
|
def build(
|
||||||
|
|
@ -37,14 +43,11 @@ class CohereComponent(CustomComponent):
|
||||||
input_value: str,
|
input_value: str,
|
||||||
max_tokens: int = 256,
|
max_tokens: int = 256,
|
||||||
temperature: float = 0.75,
|
temperature: float = 0.75,
|
||||||
|
stream: bool = False,
|
||||||
) -> Text:
|
) -> Text:
|
||||||
output = ChatCohere(
|
output = ChatCohere(
|
||||||
cohere_api_key=cohere_api_key,
|
cohere_api_key=cohere_api_key,
|
||||||
max_tokens=max_tokens,
|
max_tokens=max_tokens,
|
||||||
temperature=temperature,
|
temperature=temperature,
|
||||||
)
|
)
|
||||||
message = output.invoke(input_value)
|
return self.get_result(output=output, stream=stream, input_value=input_value)
|
||||||
result = message.content if hasattr(message, "content") else message
|
|
||||||
self.status = result
|
|
||||||
return result
|
|
||||||
return result
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,17 @@
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from langchain_google_genai import ChatGoogleGenerativeAI # type: ignore
|
from langchain_google_genai import ChatGoogleGenerativeAI
|
||||||
from pydantic.v1.types import SecretStr
|
from pydantic.v1 import SecretStr
|
||||||
|
|
||||||
from langflow import CustomComponent
|
from langflow.components.models.base.model import LCModelComponent
|
||||||
from langflow.field_typing import RangeSpec, Text
|
from langflow.field_typing import RangeSpec, Text
|
||||||
|
|
||||||
|
|
||||||
class GoogleGenerativeAIComponent(CustomComponent):
|
class GoogleGenerativeAIComponent(LCModelComponent):
|
||||||
display_name: str = "Google Generative AIModel"
|
display_name: str = "Google Generative AIModel"
|
||||||
description: str = "Generate text using Google Generative AI to generate text."
|
description: str = "Generate text using Google Generative AI to generate text."
|
||||||
documentation: str = "http://docs.langflow.org/components/custom"
|
icon = "GoogleGenerativeAI"
|
||||||
|
icon = "Google"
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
return {
|
return {
|
||||||
|
|
@ -50,7 +51,11 @@ class GoogleGenerativeAIComponent(CustomComponent):
|
||||||
"code": {
|
"code": {
|
||||||
"advanced": True,
|
"advanced": True,
|
||||||
},
|
},
|
||||||
"inputs": {"display_name": "Input"},
|
"input_value": {"display_name": "Input", "info": "The input to the model."},
|
||||||
|
"stream": {
|
||||||
|
"display_name": "Stream",
|
||||||
|
"info": "Stream the response from the model.",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def build(
|
def build(
|
||||||
|
|
@ -63,6 +68,7 @@ class GoogleGenerativeAIComponent(CustomComponent):
|
||||||
top_k: Optional[int] = None,
|
top_k: Optional[int] = None,
|
||||||
top_p: Optional[float] = None,
|
top_p: Optional[float] = None,
|
||||||
n: Optional[int] = 1,
|
n: Optional[int] = 1,
|
||||||
|
stream: bool = False,
|
||||||
) -> Text:
|
) -> Text:
|
||||||
output = ChatGoogleGenerativeAI(
|
output = ChatGoogleGenerativeAI(
|
||||||
model=model,
|
model=model,
|
||||||
|
|
@ -73,7 +79,4 @@ class GoogleGenerativeAIComponent(CustomComponent):
|
||||||
n=n or 1,
|
n=n or 1,
|
||||||
google_api_key=SecretStr(google_api_key),
|
google_api_key=SecretStr(google_api_key),
|
||||||
)
|
)
|
||||||
message = output.invoke(input_value)
|
return self.get_result(output=output, stream=stream, input_value=input_value)
|
||||||
result = message.content if hasattr(message, "content") else message
|
|
||||||
self.status = result
|
|
||||||
return result
|
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,14 @@ from typing import Optional
|
||||||
from langchain_community.chat_models.huggingface import ChatHuggingFace
|
from langchain_community.chat_models.huggingface import ChatHuggingFace
|
||||||
from langchain_community.llms.huggingface_endpoint import HuggingFaceEndpoint
|
from langchain_community.llms.huggingface_endpoint import HuggingFaceEndpoint
|
||||||
|
|
||||||
from langflow import CustomComponent
|
from langflow.components.models.base.model import LCModelComponent
|
||||||
from langflow.field_typing import Text
|
from langflow.field_typing import Text
|
||||||
|
|
||||||
|
|
||||||
class HuggingFaceEndpointsComponent(CustomComponent):
|
class HuggingFaceEndpointsComponent(LCModelComponent):
|
||||||
display_name: str = "Hugging Face Inference API models"
|
display_name: str = "Hugging Face Inference API models"
|
||||||
description: str = "Generate text using LLM model from Hugging Face Inference API."
|
description: str = "Generate text using LLM model from Hugging Face Inference API."
|
||||||
|
icon = "HuggingFace"
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
return {
|
return {
|
||||||
|
|
@ -24,7 +25,11 @@ class HuggingFaceEndpointsComponent(CustomComponent):
|
||||||
"field_type": "code",
|
"field_type": "code",
|
||||||
},
|
},
|
||||||
"code": {"show": False},
|
"code": {"show": False},
|
||||||
"inputs": {"display_name": "Input"},
|
"input_value": {"display_name": "Input"},
|
||||||
|
"stream": {
|
||||||
|
"display_name": "Stream",
|
||||||
|
"info": "Stream the response from the model.",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def build(
|
def build(
|
||||||
|
|
@ -34,6 +39,7 @@ class HuggingFaceEndpointsComponent(CustomComponent):
|
||||||
task: str = "text2text-generation",
|
task: str = "text2text-generation",
|
||||||
huggingfacehub_api_token: Optional[str] = None,
|
huggingfacehub_api_token: Optional[str] = None,
|
||||||
model_kwargs: Optional[dict] = None,
|
model_kwargs: Optional[dict] = None,
|
||||||
|
stream: bool = False,
|
||||||
) -> Text:
|
) -> Text:
|
||||||
try:
|
try:
|
||||||
llm = HuggingFaceEndpoint(
|
llm = HuggingFaceEndpoint(
|
||||||
|
|
@ -45,7 +51,4 @@ class HuggingFaceEndpointsComponent(CustomComponent):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError("Could not connect to HuggingFace Endpoints API.") from e
|
raise ValueError("Could not connect to HuggingFace Endpoints API.") from e
|
||||||
output = ChatHuggingFace(llm=llm)
|
output = ChatHuggingFace(llm=llm)
|
||||||
message = output.invoke(input_value)alue)
|
return self.get_result(output=output, stream=stream, input_value=input_value)
|
||||||
result = message.content if hasattr(message, "content") else message
|
|
||||||
self.status = result
|
|
||||||
return result
|
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@ from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
from langchain_community.llms.llamacpp import LlamaCpp
|
from langchain_community.llms.llamacpp import LlamaCpp
|
||||||
|
|
||||||
from langflow import CustomComponent
|
from langflow.components.models.base.model import LCModelComponent
|
||||||
from langflow.field_typing import Text
|
from langflow.field_typing import Text
|
||||||
|
|
||||||
|
|
||||||
class LlamaCppComponent(CustomComponent):
|
class LlamaCppComponent(LCModelComponent):
|
||||||
display_name = "LlamaCppModel"
|
display_name = "LlamaCppModel"
|
||||||
description = "Generate text using llama.cpp model."
|
description = "Generate text using llama.cpp model."
|
||||||
documentation = "https://python.langchain.com/docs/modules/model_io/models/llms/integrations/llamacpp"
|
documentation = "https://python.langchain.com/docs/modules/model_io/models/llms/integrations/llamacpp"
|
||||||
|
|
@ -56,7 +56,11 @@ class LlamaCppComponent(CustomComponent):
|
||||||
"use_mmap": {"display_name": "Use Mmap", "advanced": True},
|
"use_mmap": {"display_name": "Use Mmap", "advanced": True},
|
||||||
"verbose": {"display_name": "Verbose", "advanced": True},
|
"verbose": {"display_name": "Verbose", "advanced": True},
|
||||||
"vocab_only": {"display_name": "Vocab Only", "advanced": True},
|
"vocab_only": {"display_name": "Vocab Only", "advanced": True},
|
||||||
"inputs": {"display_name": "Input"},
|
"input_value": {"display_name": "Input"},
|
||||||
|
"stream": {
|
||||||
|
"display_name": "Stream",
|
||||||
|
"info": "Stream the response from the model.",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def build(
|
def build(
|
||||||
|
|
@ -97,6 +101,7 @@ class LlamaCppComponent(CustomComponent):
|
||||||
use_mmap: Optional[bool] = True,
|
use_mmap: Optional[bool] = True,
|
||||||
verbose: bool = True,
|
verbose: bool = True,
|
||||||
vocab_only: bool = False,
|
vocab_only: bool = False,
|
||||||
|
stream: bool = False,
|
||||||
) -> Text:
|
) -> Text:
|
||||||
output = LlamaCpp(
|
output = LlamaCpp(
|
||||||
model_path=model_path,
|
model_path=model_path,
|
||||||
|
|
@ -135,9 +140,5 @@ class LlamaCppComponent(CustomComponent):
|
||||||
verbose=verbose,
|
verbose=verbose,
|
||||||
vocab_only=vocab_only,
|
vocab_only=vocab_only,
|
||||||
)
|
)
|
||||||
message = output.invoke(input_value)
|
|
||||||
result = message.content if hasattr(message, "content") else message
|
return self.get_result(output=output, stream=stream, input_value=input_value)
|
||||||
self.status = result
|
|
||||||
return result
|
|
||||||
self.status = result
|
|
||||||
return result
|
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,19 @@ from typing import Any, Dict, List, Optional
|
||||||
# from langchain_community.chat_models import ChatOllama
|
# from langchain_community.chat_models import ChatOllama
|
||||||
from langchain_community.chat_models import ChatOllama
|
from langchain_community.chat_models import ChatOllama
|
||||||
|
|
||||||
|
from langflow.components.models.base.model import LCModelComponent
|
||||||
|
|
||||||
# from langchain.chat_models import ChatOllama
|
# from langchain.chat_models import ChatOllama
|
||||||
from langflow import CustomComponent
|
|
||||||
from langflow.field_typing import Text
|
from langflow.field_typing import Text
|
||||||
|
|
||||||
# whe When a callback component is added to Langflow, the comment must be uncommented.
|
# whe When a callback component is added to Langflow, the comment must be uncommented.
|
||||||
# from langchain.callbacks.manager import CallbackManager
|
# from langchain.callbacks.manager import CallbackManager
|
||||||
|
|
||||||
|
|
||||||
class ChatOllamaComponent(CustomComponent):
|
class ChatOllamaComponent(LCModelComponent):
|
||||||
display_name = "ChatOllamaModel"
|
display_name = "ChatOllamaModel"
|
||||||
description = "Generate text using Local LLM for chat with Ollama."
|
description = "Generate text using Local LLM for chat with Ollama."
|
||||||
|
icon = "Ollama"
|
||||||
|
|
||||||
def build_config(self) -> dict:
|
def build_config(self) -> dict:
|
||||||
return {
|
return {
|
||||||
|
|
@ -164,7 +166,11 @@ class ChatOllamaComponent(CustomComponent):
|
||||||
"info": "Template to use for generating text.",
|
"info": "Template to use for generating text.",
|
||||||
"advanced": True,
|
"advanced": True,
|
||||||
},
|
},
|
||||||
"inputs": {"display_name": "Input"},
|
"input_value": {"display_name": "Input"},
|
||||||
|
"stream": {
|
||||||
|
"display_name": "Stream",
|
||||||
|
"info": "Stream the response from the model.",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def build(
|
def build(
|
||||||
|
|
@ -197,6 +203,7 @@ class ChatOllamaComponent(CustomComponent):
|
||||||
timeout: Optional[int] = None,
|
timeout: Optional[int] = None,
|
||||||
top_k: Optional[int] = None,
|
top_k: Optional[int] = None,
|
||||||
top_p: Optional[int] = None,
|
top_p: Optional[int] = None,
|
||||||
|
stream: Optional[bool] = False,
|
||||||
) -> Text:
|
) -> Text:
|
||||||
if not base_url:
|
if not base_url:
|
||||||
base_url = "http://localhost:11434"
|
base_url = "http://localhost:11434"
|
||||||
|
|
@ -250,7 +257,5 @@ class ChatOllamaComponent(CustomComponent):
|
||||||
output = ChatOllama(**llm_params) # type: ignore
|
output = ChatOllama(**llm_params) # type: ignore
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError("Could not initialize Ollama LLM.") from e
|
raise ValueError("Could not initialize Ollama LLM.") from e
|
||||||
message = output.invoke(input_value)
|
|
||||||
result = message.content if hasattr(message, "content") else message
|
return self.get_result(output=output, stream=stream, input_value=input_value)
|
||||||
self.status = result
|
|
||||||
return result
|
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,18 @@ from typing import Optional
|
||||||
|
|
||||||
from langchain_openai import ChatOpenAI
|
from langchain_openai import ChatOpenAI
|
||||||
|
|
||||||
from langflow import CustomComponent
|
from langflow.components.models.base.model import LCModelComponent
|
||||||
from langflow.field_typing import NestedDict, Text
|
from langflow.field_typing import NestedDict, Text
|
||||||
|
|
||||||
|
|
||||||
class OpenAIModelComponent(CustomComponent):
|
class OpenAIModelComponent(LCModelComponent):
|
||||||
display_name = "OpenAI Model"
|
display_name = "OpenAI Model"
|
||||||
description = "Generates text using OpenAI's models."
|
description = "Generates text using OpenAI's models."
|
||||||
|
icon = "OpenAI"
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
return {
|
return {
|
||||||
"inputs": {"display_name": "Input"},
|
"input_value": {"display_name": "Input"},
|
||||||
"max_tokens": {
|
"max_tokens": {
|
||||||
"display_name": "Max Tokens",
|
"display_name": "Max Tokens",
|
||||||
"advanced": False,
|
"advanced": False,
|
||||||
|
|
@ -57,6 +58,10 @@ class OpenAIModelComponent(CustomComponent):
|
||||||
"required": False,
|
"required": False,
|
||||||
"value": 0.7,
|
"value": 0.7,
|
||||||
},
|
},
|
||||||
|
"stream": {
|
||||||
|
"display_name": "Stream",
|
||||||
|
"info": "Stream the response from the model.",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def build(
|
def build(
|
||||||
|
|
@ -68,10 +73,11 @@ class OpenAIModelComponent(CustomComponent):
|
||||||
openai_api_base: Optional[str] = None,
|
openai_api_base: Optional[str] = None,
|
||||||
openai_api_key: Optional[str] = None,
|
openai_api_key: Optional[str] = None,
|
||||||
temperature: float = 0.7,
|
temperature: float = 0.7,
|
||||||
|
stream: Optional[bool] = False,
|
||||||
) -> Text:
|
) -> Text:
|
||||||
if not openai_api_base:
|
if not openai_api_base:
|
||||||
openai_api_base = "https://api.openai.com/v1"
|
openai_api_base = "https://api.openai.com/v1"
|
||||||
model = ChatOpenAI(
|
output = ChatOpenAI(
|
||||||
max_tokens=max_tokens,
|
max_tokens=max_tokens,
|
||||||
model_kwargs=model_kwargs,
|
model_kwargs=model_kwargs,
|
||||||
model=model_name,
|
model=model_name,
|
||||||
|
|
@ -80,7 +86,4 @@ class OpenAIModelComponent(CustomComponent):
|
||||||
temperature=temperature,
|
temperature=temperature,
|
||||||
)
|
)
|
||||||
|
|
||||||
message = model.invoke(input_value)
|
return self.get_result(output=output, stream=stream, input_value=input_value)
|
||||||
result = message.content if hasattr(message, "content") else message
|
|
||||||
self.status = result
|
|
||||||
return result
|
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,15 @@ from typing import List, Optional
|
||||||
|
|
||||||
from langchain_core.messages.base import BaseMessage
|
from langchain_core.messages.base import BaseMessage
|
||||||
|
|
||||||
from langflow import CustomComponent
|
from langflow.components.models.base.model import LCModelComponent
|
||||||
from langflow.field_typing import Text
|
from langflow.field_typing import Text
|
||||||
|
|
||||||
|
|
||||||
class ChatVertexAIComponent(CustomComponent):
|
class ChatVertexAIComponent(LCModelComponent):
|
||||||
display_name = "ChatVertexAIModel"
|
display_name = "ChatVertexAIModel"
|
||||||
description = "Generate text using Vertex AI Chat large language models API."
|
description = "Generate text using Vertex AI Chat large language models API."
|
||||||
|
icon="VertexAI"
|
||||||
|
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
return {
|
return {
|
||||||
|
|
@ -57,7 +59,11 @@ class ChatVertexAIComponent(CustomComponent):
|
||||||
"value": False,
|
"value": False,
|
||||||
"advanced": True,
|
"advanced": True,
|
||||||
},
|
},
|
||||||
"inputs": {"display_name": "Input"},
|
"input_value": {"display_name": "Input"},
|
||||||
|
"stream": {
|
||||||
|
"display_name": "Stream",
|
||||||
|
"info": "Stream the response from the model.",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def build(
|
def build(
|
||||||
|
|
@ -73,6 +79,7 @@ class ChatVertexAIComponent(CustomComponent):
|
||||||
top_k: int = 40,
|
top_k: int = 40,
|
||||||
top_p: float = 0.95,
|
top_p: float = 0.95,
|
||||||
verbose: bool = False,
|
verbose: bool = False,
|
||||||
|
stream: bool = False,
|
||||||
) -> Text:
|
) -> Text:
|
||||||
try:
|
try:
|
||||||
from langchain_google_vertexai import ChatVertexAI
|
from langchain_google_vertexai import ChatVertexAI
|
||||||
|
|
@ -92,7 +99,5 @@ class ChatVertexAIComponent(CustomComponent):
|
||||||
top_p=top_p,
|
top_p=top_p,
|
||||||
verbose=verbose,
|
verbose=verbose,
|
||||||
)
|
)
|
||||||
message = output.invoke(input_value)
|
|
||||||
result = message.content if hasattr(message, "content") else message
|
return self.get_result(output=output, stream=stream, input_value=input_value)
|
||||||
self.status = result
|
|
||||||
return result
|
|
||||||
|
|
|
||||||
0
src/backend/langflow/components/models/base/__init__.py
Normal file
0
src/backend/langflow/components/models/base/__init__.py
Normal file
28
src/backend/langflow/components/models/base/model.py
Normal file
28
src/backend/langflow/components/models/base/model.py
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
from langchain_core.runnables import Runnable
|
||||||
|
|
||||||
|
from langflow import CustomComponent
|
||||||
|
|
||||||
|
|
||||||
|
class LCModelComponent(CustomComponent):
|
||||||
|
display_name: str = "Model Name"
|
||||||
|
description: str = "Model Description"
|
||||||
|
|
||||||
|
def get_result(self, output: Runnable, stream: bool, input_value: str):
|
||||||
|
"""
|
||||||
|
Retrieves the result from the output of a Runnable object.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
output (Runnable): The output object to retrieve the result from.
|
||||||
|
stream (bool): Indicates whether to use streaming or invocation mode.
|
||||||
|
input_value (str): The input value to pass to the output object.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The result obtained from the output object.
|
||||||
|
"""
|
||||||
|
if stream:
|
||||||
|
result = output.stream(input_value)
|
||||||
|
else:
|
||||||
|
message = output.invoke(input_value)
|
||||||
|
result = message.content if hasattr(message, "content") else message
|
||||||
|
self.status = result
|
||||||
|
return result
|
||||||
|
|
@ -9,6 +9,7 @@ from langflow import CustomComponent
|
||||||
class AmazonKendraRetrieverComponent(CustomComponent):
|
class AmazonKendraRetrieverComponent(CustomComponent):
|
||||||
display_name: str = "Amazon Kendra Retriever"
|
display_name: str = "Amazon Kendra Retriever"
|
||||||
description: str = "Retriever that uses the Amazon Kendra API."
|
description: str = "Retriever that uses the Amazon Kendra API."
|
||||||
|
icon = "Amazon"
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ class VectaraSelfQueryRetriverComponent(CustomComponent):
|
||||||
description: str = "Implementation of Vectara Self Query Retriever"
|
description: str = "Implementation of Vectara Self Query Retriever"
|
||||||
documentation = "https://python.langchain.com/docs/integrations/retrievers/self_query/vectara_self_query"
|
documentation = "https://python.langchain.com/docs/integrations/retrievers/self_query/vectara_self_query"
|
||||||
beta = True
|
beta = True
|
||||||
|
icon="Vectara"
|
||||||
|
|
||||||
|
|
||||||
field_config = {
|
field_config = {
|
||||||
"code": {"show": True},
|
"code": {"show": True},
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ class RunnableExecComponent(CustomComponent):
|
||||||
"display_name": "Input Key",
|
"display_name": "Input Key",
|
||||||
"info": "The key to use for the input.",
|
"info": "The key to use for the input.",
|
||||||
},
|
},
|
||||||
"inputs": {
|
"input_value": {
|
||||||
"display_name": "Inputs",
|
"display_name": "Inputs",
|
||||||
"info": "The inputs to pass to the runnable.",
|
"info": "The inputs to pass to the runnable.",
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,12 @@ from typing import List, Optional
|
||||||
import chromadb # type: ignore
|
import chromadb # type: ignore
|
||||||
from langchain_community.vectorstores.chroma import Chroma
|
from langchain_community.vectorstores.chroma import Chroma
|
||||||
|
|
||||||
from langflow import CustomComponent
|
from langflow.components.vectorstores.base.model import LCVectorStoreComponent
|
||||||
from langflow.field_typing import Embeddings, Text
|
from langflow.field_typing import Embeddings, Text
|
||||||
from langflow.schema import Record, docs_to_records
|
from langflow.schema import Record
|
||||||
|
|
||||||
|
|
||||||
class ChromaSearchComponent(CustomComponent):
|
class ChromaSearchComponent(LCVectorStoreComponent):
|
||||||
"""
|
"""
|
||||||
A custom component for implementing a Vector Store using Chroma.
|
A custom component for implementing a Vector Store using Chroma.
|
||||||
"""
|
"""
|
||||||
|
|
@ -26,7 +26,7 @@ class ChromaSearchComponent(CustomComponent):
|
||||||
- dict: A dictionary containing the configuration options for the component.
|
- dict: A dictionary containing the configuration options for the component.
|
||||||
"""
|
"""
|
||||||
return {
|
return {
|
||||||
"inputs": {"display_name": "Input"},
|
"input_value": {"display_name": "Input"},
|
||||||
"search_type": {
|
"search_type": {
|
||||||
"display_name": "Search Type",
|
"display_name": "Search Type",
|
||||||
"options": ["Similarity", "MMR"],
|
"options": ["Similarity", "MMR"],
|
||||||
|
|
@ -101,17 +101,11 @@ class ChromaSearchComponent(CustomComponent):
|
||||||
chroma_server_ssl_enabled=chroma_server_ssl_enabled,
|
chroma_server_ssl_enabled=chroma_server_ssl_enabled,
|
||||||
)
|
)
|
||||||
index_directory = self.resolve_path(index_directory)
|
index_directory = self.resolve_path(index_directory)
|
||||||
chroma = Chroma(
|
vector_store = Chroma(
|
||||||
embedding_function=embedding,
|
embedding_function=embedding,
|
||||||
collection_name=collection_name,
|
collection_name=collection_name,
|
||||||
persist_directory=index_directory,
|
persist_directory=index_directory,
|
||||||
client_settings=chroma_settings,
|
client_settings=chroma_settings,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Validate the inputs
|
return self.search_with_vector_store(input_value, search_type, vector_store)
|
||||||
docs = []
|
|
||||||
if inputs and isinstance(inputs, str):
|
|
||||||
docs = chroma.search(query=inputs, search_type=search_type.lower())
|
|
||||||
else:
|
|
||||||
raise ValueError("Invalid inputs provided.")
|
|
||||||
return docs_to_records(docs)
|
|
||||||
|
|
|
||||||
|
|
@ -3,24 +3,36 @@ from typing import List, Union
|
||||||
from langchain.schema import BaseRetriever
|
from langchain.schema import BaseRetriever
|
||||||
from langchain_community.vectorstores import VectorStore
|
from langchain_community.vectorstores import VectorStore
|
||||||
from langchain_community.vectorstores.faiss import FAISS
|
from langchain_community.vectorstores.faiss import FAISS
|
||||||
|
|
||||||
from langflow import CustomComponent
|
from langflow import CustomComponent
|
||||||
from langflow.field_typing import Document, Embeddings
|
from langflow.field_typing import Document, Embeddings
|
||||||
|
|
||||||
|
|
||||||
class FAISSComponent(CustomComponent):
|
class FAISSComponent(CustomComponent):
|
||||||
display_name = "FAISS"
|
display_name = "FAISS"
|
||||||
description = "Construct FAISS wrapper from raw documents."
|
description = "Ingest documents into FAISS Vector Store."
|
||||||
documentation = "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/faiss"
|
documentation = "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/faiss"
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
return {
|
return {
|
||||||
"documents": {"display_name": "Documents"},
|
"documents": {"display_name": "Documents"},
|
||||||
"embedding": {"display_name": "Embedding"},
|
"embedding": {"display_name": "Embedding"},
|
||||||
|
"folder_path": {
|
||||||
|
"display_name": "Folder Path",
|
||||||
|
"info": "Path to save the FAISS index. It will be relative to where Langflow is running.",
|
||||||
|
},
|
||||||
|
"index_name": {"display_name": "Index Name"},
|
||||||
}
|
}
|
||||||
|
|
||||||
def build(
|
def build(
|
||||||
self,
|
self,
|
||||||
embedding: Embeddings,
|
embedding: Embeddings,
|
||||||
documents: List[Document],
|
documents: List[Document],
|
||||||
|
folder_path: str,
|
||||||
|
index_name: str = "langflow_index",
|
||||||
) -> Union[VectorStore, FAISS, BaseRetriever]:
|
) -> Union[VectorStore, FAISS, BaseRetriever]:
|
||||||
return FAISS.from_documents(documents=documents, embedding=embedding)
|
vector_store = FAISS.from_documents(documents=documents, embedding=embedding)
|
||||||
|
if not folder_path:
|
||||||
|
raise ValueError("Folder path is required to save the FAISS index.")
|
||||||
|
path = self.resolve_path(folder_path)
|
||||||
|
vector_store.save_local(str(path), index_name)
|
||||||
|
|
|
||||||
45
src/backend/langflow/components/vectorstores/FAISSSearch.py
Normal file
45
src/backend/langflow/components/vectorstores/FAISSSearch.py
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from langchain_community.vectorstores.faiss import FAISS
|
||||||
|
|
||||||
|
from langflow.components.vectorstores.base.model import LCVectorStoreComponent
|
||||||
|
from langflow.field_typing import Embeddings
|
||||||
|
from langflow.schema import Record
|
||||||
|
|
||||||
|
|
||||||
|
class FAISSSearchComponent(LCVectorStoreComponent):
|
||||||
|
display_name = "FAISS Search"
|
||||||
|
description = "Search a FAISS Vector Store for similar documents."
|
||||||
|
documentation = "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/faiss"
|
||||||
|
|
||||||
|
def build_config(self):
|
||||||
|
return {
|
||||||
|
"documents": {"display_name": "Documents"},
|
||||||
|
"embedding": {"display_name": "Embedding"},
|
||||||
|
"folder_path": {
|
||||||
|
"display_name": "Folder Path",
|
||||||
|
"info": "Path to save the FAISS index. It will be relative to where Langflow is running.",
|
||||||
|
},
|
||||||
|
"input_value": {"display_name": "Input"},
|
||||||
|
"index_name": {"display_name": "Index Name"},
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(
|
||||||
|
self,
|
||||||
|
input_value: str,
|
||||||
|
embedding: Embeddings,
|
||||||
|
folder_path: str,
|
||||||
|
index_name: str = "langflow_index",
|
||||||
|
) -> List[Record]:
|
||||||
|
if not folder_path:
|
||||||
|
raise ValueError("Folder path is required to save the FAISS index.")
|
||||||
|
path = self.resolve_path(folder_path)
|
||||||
|
vector_store = FAISS.load_local(
|
||||||
|
folder_path=str(path), embeddings=embedding, index_name=index_name
|
||||||
|
)
|
||||||
|
if not vector_store:
|
||||||
|
raise ValueError("Failed to load the FAISS index.")
|
||||||
|
|
||||||
|
return self.search_with_vector_store(
|
||||||
|
vector_store=vector_store, input_value=input_value, search_type="similarity"
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from langchain_community.vectorstores.mongodb_atlas import MongoDBAtlasVectorSearch
|
||||||
|
|
||||||
|
from langflow import CustomComponent
|
||||||
|
from langflow.field_typing import Document, Embeddings, NestedDict
|
||||||
|
|
||||||
|
|
||||||
|
class MongoDBAtlasComponent(CustomComponent):
|
||||||
|
display_name = "MongoDB Atlas"
|
||||||
|
description = (
|
||||||
|
"Construct a `MongoDB Atlas Vector Search` vector store from raw documents."
|
||||||
|
)
|
||||||
|
icon="MongoDB"
|
||||||
|
|
||||||
|
def build_config(self):
|
||||||
|
return {
|
||||||
|
"documents": {"display_name": "Documents"},
|
||||||
|
"embedding": {"display_name": "Embedding"},
|
||||||
|
"collection_name": {"display_name": "Collection Name"},
|
||||||
|
"db_name": {"display_name": "Database Name"},
|
||||||
|
"index_name": {"display_name": "Index Name"},
|
||||||
|
"mongodb_atlas_cluster_uri": {"display_name": "MongoDB Atlas Cluster URI"},
|
||||||
|
"search_kwargs": {"display_name": "Search Kwargs", "advanced": True},
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(
|
||||||
|
self,
|
||||||
|
embedding: Embeddings,
|
||||||
|
documents: List[Document] = None,
|
||||||
|
collection_name: str = "",
|
||||||
|
db_name: str = "",
|
||||||
|
index_name: str = "",
|
||||||
|
mongodb_atlas_cluster_uri: str = "",
|
||||||
|
search_kwargs: Optional[NestedDict] = None,
|
||||||
|
) -> MongoDBAtlasVectorSearch:
|
||||||
|
search_kwargs = search_kwargs or {}
|
||||||
|
if documents:
|
||||||
|
vector_store = MongoDBAtlasVectorSearch.from_documents(
|
||||||
|
documents=documents,
|
||||||
|
embedding=embedding,
|
||||||
|
collection_name=collection_name,
|
||||||
|
db_name=db_name,
|
||||||
|
index_name=index_name,
|
||||||
|
mongodb_atlas_cluster_uri=mongodb_atlas_cluster_uri,
|
||||||
|
search_kwargs=search_kwargs,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
vector_store = MongoDBAtlasVectorSearch(
|
||||||
|
embedding=embedding,
|
||||||
|
collection_name=collection_name,
|
||||||
|
db_name=db_name,
|
||||||
|
index_name=index_name,
|
||||||
|
mongodb_atlas_cluster_uri=mongodb_atlas_cluster_uri,
|
||||||
|
search_kwargs=search_kwargs,
|
||||||
|
)
|
||||||
|
return vector_store
|
||||||
|
|
@ -1,22 +1,22 @@
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from langchain_community.vectorstores import MongoDBAtlasVectorSearch
|
from langflow.components.vectorstores.base.model import LCVectorStoreComponent
|
||||||
|
from langflow.components.vectorstores.MongoDBAtlasVector import MongoDBAtlasComponent
|
||||||
from langflow import CustomComponent
|
from langflow.field_typing import Embeddings, NestedDict
|
||||||
from langflow.field_typing import (
|
from langflow.schema import Record
|
||||||
Document,
|
|
||||||
Embeddings,
|
|
||||||
NestedDict,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class MongoDBAtlasComponent(CustomComponent):
|
class MongoDBAtlasSearchComponent(MongoDBAtlasComponent, LCVectorStoreComponent):
|
||||||
display_name = "MongoDB Atlas"
|
display_name = "MongoDB Atlas Search"
|
||||||
description = "Construct a `MongoDB Atlas Vector Search` vector store from raw documents."
|
description = "Search a MongoDB Atlas Vector Store for similar documents."
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
return {
|
return {
|
||||||
"documents": {"display_name": "Documents"},
|
"search_type": {
|
||||||
|
"display_name": "Search Type",
|
||||||
|
"options": ["Similarity", "MMR"],
|
||||||
|
},
|
||||||
|
"input_value": {"display_name": "Input"},
|
||||||
"embedding": {"display_name": "Embedding"},
|
"embedding": {"display_name": "Embedding"},
|
||||||
"collection_name": {"display_name": "Collection Name"},
|
"collection_name": {"display_name": "Collection Name"},
|
||||||
"db_name": {"display_name": "Database Name"},
|
"db_name": {"display_name": "Database Name"},
|
||||||
|
|
@ -27,17 +27,16 @@ class MongoDBAtlasComponent(CustomComponent):
|
||||||
|
|
||||||
def build(
|
def build(
|
||||||
self,
|
self,
|
||||||
documents: List[Document],
|
input_value: str,
|
||||||
|
search_type: str,
|
||||||
embedding: Embeddings,
|
embedding: Embeddings,
|
||||||
collection_name: str = "",
|
collection_name: str = "",
|
||||||
db_name: str = "",
|
db_name: str = "",
|
||||||
index_name: str = "",
|
index_name: str = "",
|
||||||
mongodb_atlas_cluster_uri: str = "",
|
mongodb_atlas_cluster_uri: str = "",
|
||||||
search_kwargs: Optional[NestedDict] = None,
|
search_kwargs: Optional[NestedDict] = None,
|
||||||
) -> MongoDBAtlasVectorSearch:
|
) -> List[Record]:
|
||||||
search_kwargs = search_kwargs or {}
|
vector_store = super().build(
|
||||||
return MongoDBAtlasVectorSearch(
|
|
||||||
documents=documents,
|
|
||||||
embedding=embedding,
|
embedding=embedding,
|
||||||
collection_name=collection_name,
|
collection_name=collection_name,
|
||||||
db_name=db_name,
|
db_name=db_name,
|
||||||
|
|
@ -45,3 +44,8 @@ class MongoDBAtlasComponent(CustomComponent):
|
||||||
mongodb_atlas_cluster_uri=mongodb_atlas_cluster_uri,
|
mongodb_atlas_cluster_uri=mongodb_atlas_cluster_uri,
|
||||||
search_kwargs=search_kwargs,
|
search_kwargs=search_kwargs,
|
||||||
)
|
)
|
||||||
|
if not vector_store:
|
||||||
|
raise ValueError("Failed to create MongoDB Atlas Vector Store")
|
||||||
|
return self.search_with_vector_store(
|
||||||
|
vector_store=vector_store, input_value=input_value, search_type=search_type
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import pinecone # type: ignore
|
||||||
from langchain.schema import BaseRetriever
|
from langchain.schema import BaseRetriever
|
||||||
from langchain_community.vectorstores import VectorStore
|
from langchain_community.vectorstores import VectorStore
|
||||||
from langchain_community.vectorstores.pinecone import Pinecone
|
from langchain_community.vectorstores.pinecone import Pinecone
|
||||||
|
|
||||||
from langflow import CustomComponent
|
from langflow import CustomComponent
|
||||||
from langflow.field_typing import Document, Embeddings
|
from langflow.field_typing import Document, Embeddings
|
||||||
|
|
||||||
|
|
@ -12,6 +13,7 @@ from langflow.field_typing import Document, Embeddings
|
||||||
class PineconeComponent(CustomComponent):
|
class PineconeComponent(CustomComponent):
|
||||||
display_name = "Pinecone"
|
display_name = "Pinecone"
|
||||||
description = "Construct Pinecone wrapper from raw documents."
|
description = "Construct Pinecone wrapper from raw documents."
|
||||||
|
icon = "Pinecone"
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
return {
|
return {
|
||||||
|
|
@ -19,10 +21,23 @@ class PineconeComponent(CustomComponent):
|
||||||
"embedding": {"display_name": "Embedding"},
|
"embedding": {"display_name": "Embedding"},
|
||||||
"index_name": {"display_name": "Index Name"},
|
"index_name": {"display_name": "Index Name"},
|
||||||
"namespace": {"display_name": "Namespace"},
|
"namespace": {"display_name": "Namespace"},
|
||||||
"pinecone_api_key": {"display_name": "Pinecone API Key", "default": "", "password": True, "required": True},
|
"pinecone_api_key": {
|
||||||
"pinecone_env": {"display_name": "Pinecone Environment", "default": "", "required": True},
|
"display_name": "Pinecone API Key",
|
||||||
|
"default": "",
|
||||||
|
"password": True,
|
||||||
|
"required": True,
|
||||||
|
},
|
||||||
|
"pinecone_env": {
|
||||||
|
"display_name": "Pinecone Environment",
|
||||||
|
"default": "",
|
||||||
|
"required": True,
|
||||||
|
},
|
||||||
"search_kwargs": {"display_name": "Search Kwargs", "default": "{}"},
|
"search_kwargs": {"display_name": "Search Kwargs", "default": "{}"},
|
||||||
"pool_threads": {"display_name": "Pool Threads", "default": 1, "advanced": True},
|
"pool_threads": {
|
||||||
|
"display_name": "Pool Threads",
|
||||||
|
"default": 1,
|
||||||
|
"advanced": True,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def build(
|
def build(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from langflow.components.vectorstores.base.model import LCVectorStoreComponent
|
||||||
|
from langflow.components.vectorstores.Pinecone import PineconeComponent
|
||||||
|
from langflow.field_typing import Embeddings
|
||||||
|
from langflow.schema import Record
|
||||||
|
|
||||||
|
|
||||||
|
class PineconeSearchComponent(PineconeComponent, LCVectorStoreComponent):
|
||||||
|
display_name = "Pinecone Search"
|
||||||
|
description = "Search a Pinecone Vector Store for similar documents."
|
||||||
|
icon = "Pinecone"
|
||||||
|
|
||||||
|
def build_config(self):
|
||||||
|
return {
|
||||||
|
"search_type": {
|
||||||
|
"display_name": "Search Type",
|
||||||
|
"options": ["Similarity", "MMR"],
|
||||||
|
},
|
||||||
|
"input_value": {"display_name": "Input"},
|
||||||
|
"embedding": {"display_name": "Embedding"},
|
||||||
|
"index_name": {"display_name": "Index Name"},
|
||||||
|
"namespace": {"display_name": "Namespace"},
|
||||||
|
"pinecone_api_key": {
|
||||||
|
"display_name": "Pinecone API Key",
|
||||||
|
"default": "",
|
||||||
|
"password": True,
|
||||||
|
"required": True,
|
||||||
|
},
|
||||||
|
"pinecone_env": {
|
||||||
|
"display_name": "Pinecone Environment",
|
||||||
|
"default": "",
|
||||||
|
"required": True,
|
||||||
|
},
|
||||||
|
"search_kwargs": {"display_name": "Search Kwargs", "default": "{}"},
|
||||||
|
"pool_threads": {
|
||||||
|
"display_name": "Pool Threads",
|
||||||
|
"default": 1,
|
||||||
|
"advanced": True,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(
|
||||||
|
self,
|
||||||
|
input_value: str,
|
||||||
|
embedding: Embeddings,
|
||||||
|
pinecone_env: str,
|
||||||
|
text_key: str = "text",
|
||||||
|
pool_threads: int = 4,
|
||||||
|
index_name: Optional[str] = None,
|
||||||
|
pinecone_api_key: Optional[str] = None,
|
||||||
|
namespace: Optional[str] = "default",
|
||||||
|
search_type: str = "similarity",
|
||||||
|
) -> List[Record]:
|
||||||
|
vector_store = super().build(
|
||||||
|
embedding=embedding,
|
||||||
|
pinecone_env=pinecone_env,
|
||||||
|
documents=[],
|
||||||
|
text_key=text_key,
|
||||||
|
pool_threads=pool_threads,
|
||||||
|
index_name=index_name,
|
||||||
|
pinecone_api_key=pinecone_api_key,
|
||||||
|
namespace=namespace,
|
||||||
|
)
|
||||||
|
if not vector_store:
|
||||||
|
raise ValueError("Failed to load the Pinecone index.")
|
||||||
|
|
||||||
|
return self.search_with_vector_store(
|
||||||
|
vector_store=vector_store, input_value=input_value, search_type=search_type
|
||||||
|
)
|
||||||
|
|
@ -10,6 +10,7 @@ from langflow.field_typing import Document, Embeddings, NestedDict
|
||||||
class QdrantComponent(CustomComponent):
|
class QdrantComponent(CustomComponent):
|
||||||
display_name = "Qdrant"
|
display_name = "Qdrant"
|
||||||
description = "Construct Qdrant wrapper from a list of texts."
|
description = "Construct Qdrant wrapper from a list of texts."
|
||||||
|
icon="Qdrant"
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
93
src/backend/langflow/components/vectorstores/QdrantSearch.py
Normal file
93
src/backend/langflow/components/vectorstores/QdrantSearch.py
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from langflow.components.vectorstores.base.model import LCVectorStoreComponent
|
||||||
|
from langflow.components.vectorstores.Qdrant import QdrantComponent
|
||||||
|
from langflow.field_typing import Embeddings, NestedDict
|
||||||
|
from langflow.schema import Record
|
||||||
|
|
||||||
|
|
||||||
|
class QdrantSearchComponent(QdrantComponent, LCVectorStoreComponent):
|
||||||
|
display_name = "Qdrant"
|
||||||
|
description = "Construct Qdrant wrapper from a list of texts."
|
||||||
|
icon="Qdrant"
|
||||||
|
|
||||||
|
|
||||||
|
def build_config(self):
|
||||||
|
return {
|
||||||
|
"search_type": {
|
||||||
|
"display_name": "Search Type",
|
||||||
|
"options": ["Similarity", "MMR"],
|
||||||
|
},
|
||||||
|
"input_value": {"display_name": "Input"},
|
||||||
|
"embedding": {"display_name": "Embedding"},
|
||||||
|
"api_key": {"display_name": "API Key", "password": True, "advanced": True},
|
||||||
|
"collection_name": {"display_name": "Collection Name"},
|
||||||
|
"content_payload_key": {
|
||||||
|
"display_name": "Content Payload Key",
|
||||||
|
"advanced": True,
|
||||||
|
},
|
||||||
|
"distance_func": {"display_name": "Distance Function", "advanced": True},
|
||||||
|
"grpc_port": {"display_name": "gRPC Port", "advanced": True},
|
||||||
|
"host": {"display_name": "Host", "advanced": True},
|
||||||
|
"https": {"display_name": "HTTPS", "advanced": True},
|
||||||
|
"location": {"display_name": "Location", "advanced": True},
|
||||||
|
"metadata_payload_key": {
|
||||||
|
"display_name": "Metadata Payload Key",
|
||||||
|
"advanced": True,
|
||||||
|
},
|
||||||
|
"path": {"display_name": "Path", "advanced": True},
|
||||||
|
"port": {"display_name": "Port", "advanced": True},
|
||||||
|
"prefer_grpc": {"display_name": "Prefer gRPC", "advanced": True},
|
||||||
|
"prefix": {"display_name": "Prefix", "advanced": True},
|
||||||
|
"search_kwargs": {"display_name": "Search Kwargs", "advanced": True},
|
||||||
|
"timeout": {"display_name": "Timeout", "advanced": True},
|
||||||
|
"url": {"display_name": "URL", "advanced": True},
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(
|
||||||
|
self,
|
||||||
|
input_value: str,
|
||||||
|
embedding: Embeddings,
|
||||||
|
collection_name: str,
|
||||||
|
search_type: str = "similarity",
|
||||||
|
api_key: Optional[str] = None,
|
||||||
|
content_payload_key: str = "page_content",
|
||||||
|
distance_func: str = "Cosine",
|
||||||
|
grpc_port: int = 6334,
|
||||||
|
https: bool = False,
|
||||||
|
host: Optional[str] = None,
|
||||||
|
location: Optional[str] = None,
|
||||||
|
metadata_payload_key: str = "metadata",
|
||||||
|
path: Optional[str] = None,
|
||||||
|
port: Optional[int] = 6333,
|
||||||
|
prefer_grpc: bool = False,
|
||||||
|
prefix: Optional[str] = None,
|
||||||
|
search_kwargs: Optional[NestedDict] = None,
|
||||||
|
timeout: Optional[int] = None,
|
||||||
|
url: Optional[str] = None,
|
||||||
|
) -> List[Record]:
|
||||||
|
vector_store = super().build(
|
||||||
|
embedding=embedding,
|
||||||
|
collection_name=collection_name,
|
||||||
|
api_key=api_key,
|
||||||
|
content_payload_key=content_payload_key,
|
||||||
|
distance_func=distance_func,
|
||||||
|
grpc_port=grpc_port,
|
||||||
|
https=https,
|
||||||
|
host=host,
|
||||||
|
location=location,
|
||||||
|
metadata_payload_key=metadata_payload_key,
|
||||||
|
path=path,
|
||||||
|
port=port,
|
||||||
|
prefer_grpc=prefer_grpc,
|
||||||
|
prefix=prefix,
|
||||||
|
search_kwargs=search_kwargs,
|
||||||
|
timeout=timeout,
|
||||||
|
url=url,
|
||||||
|
)
|
||||||
|
if not vector_store:
|
||||||
|
raise ValueError("Failed to load the Qdrant index.")
|
||||||
|
|
||||||
|
return self.search_with_vector_store(
|
||||||
|
vector_store=vector_store, input_value=input_value, search_type=search_type
|
||||||
|
)
|
||||||
77
src/backend/langflow/components/vectorstores/RedisSearch.py
Normal file
77
src/backend/langflow/components/vectorstores/RedisSearch.py
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from langchain.embeddings.base import Embeddings
|
||||||
|
|
||||||
|
from langflow.components.vectorstores.base.model import LCVectorStoreComponent
|
||||||
|
from langflow.components.vectorstores.Redis import RedisComponent
|
||||||
|
from langflow.schema import Record
|
||||||
|
|
||||||
|
|
||||||
|
class RedisSearchComponent(RedisComponent, LCVectorStoreComponent):
|
||||||
|
"""
|
||||||
|
A custom component for implementing a Vector Store using Redis.
|
||||||
|
"""
|
||||||
|
|
||||||
|
display_name: str = "Redis Search"
|
||||||
|
description: str = "Search a Redis Vector Store for similar documents."
|
||||||
|
documentation = "https://python.langchain.com/docs/integrations/vectorstores/redis"
|
||||||
|
beta = True
|
||||||
|
|
||||||
|
def build_config(self):
|
||||||
|
"""
|
||||||
|
Builds the configuration for the component.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- dict: A dictionary containing the configuration options for the component.
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
"search_type": {
|
||||||
|
"display_name": "Search Type",
|
||||||
|
"options": ["Similarity", "MMR"],
|
||||||
|
},
|
||||||
|
"input_value": {"display_name": "Input"},
|
||||||
|
"index_name": {"display_name": "Index Name", "value": "your_index"},
|
||||||
|
"code": {"show": False, "display_name": "Code"},
|
||||||
|
"documents": {"display_name": "Documents", "is_list": True},
|
||||||
|
"embedding": {"display_name": "Embedding"},
|
||||||
|
"schema": {"display_name": "Schema", "file_types": [".yaml"]},
|
||||||
|
"redis_server_url": {
|
||||||
|
"display_name": "Redis Server Connection String",
|
||||||
|
"advanced": False,
|
||||||
|
},
|
||||||
|
"redis_index_name": {"display_name": "Redis Index", "advanced": False},
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(
|
||||||
|
self,
|
||||||
|
input_value: str,
|
||||||
|
search_type: str,
|
||||||
|
embedding: Embeddings,
|
||||||
|
redis_server_url: str,
|
||||||
|
redis_index_name: str,
|
||||||
|
schema: Optional[str] = None,
|
||||||
|
) -> List[Record]:
|
||||||
|
"""
|
||||||
|
Builds the Vector Store or BaseRetriever object.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- embedding (Embeddings): The embeddings to use for the Vector Store.
|
||||||
|
- documents (Optional[Document]): The documents to use for the Vector Store.
|
||||||
|
- redis_index_name (str): The name of the Redis index.
|
||||||
|
- redis_server_url (str): The URL for the Redis server.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- VectorStore: The Vector Store object.
|
||||||
|
"""
|
||||||
|
vector_store = super().build(
|
||||||
|
embedding=embedding,
|
||||||
|
redis_server_url=redis_server_url,
|
||||||
|
redis_index_name=redis_index_name,
|
||||||
|
schema=schema,
|
||||||
|
)
|
||||||
|
if not vector_store:
|
||||||
|
raise ValueError("Failed to load the Redis index.")
|
||||||
|
|
||||||
|
return self.search_with_vector_store(
|
||||||
|
input_value=input_value, search_type=search_type, vector_store=vector_store
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from langchain_community.vectorstores.supabase import SupabaseVectorStore
|
||||||
|
from supabase.client import Client, create_client
|
||||||
|
|
||||||
|
from langflow.components.vectorstores.base.model import LCVectorStoreComponent
|
||||||
|
from langflow.field_typing import Embeddings
|
||||||
|
from langflow.schema import Record
|
||||||
|
|
||||||
|
|
||||||
|
class SupabaseSearchComponent(LCVectorStoreComponent):
|
||||||
|
display_name = "Supabase Search"
|
||||||
|
description = "Search a Supabase Vector Store for similar documents."
|
||||||
|
icon="Supabase"
|
||||||
|
|
||||||
|
def build_config(self):
|
||||||
|
return {
|
||||||
|
"search_type": {
|
||||||
|
"display_name": "Search Type",
|
||||||
|
"options": ["Similarity", "MMR"],
|
||||||
|
},
|
||||||
|
"input_value": {"display_name": "Input"},
|
||||||
|
"embedding": {"display_name": "Embedding"},
|
||||||
|
"query_name": {"display_name": "Query Name"},
|
||||||
|
"search_kwargs": {"display_name": "Search Kwargs", "advanced": True},
|
||||||
|
"supabase_service_key": {"display_name": "Supabase Service Key"},
|
||||||
|
"supabase_url": {"display_name": "Supabase URL"},
|
||||||
|
"table_name": {"display_name": "Table Name", "advanced": True},
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(
|
||||||
|
self,
|
||||||
|
input_value: str,
|
||||||
|
search_type: str,
|
||||||
|
embedding: Embeddings,
|
||||||
|
query_name: str = "",
|
||||||
|
supabase_service_key: str = "",
|
||||||
|
supabase_url: str = "",
|
||||||
|
table_name: str = "",
|
||||||
|
) -> List[Record]:
|
||||||
|
supabase: Client = create_client(
|
||||||
|
supabase_url, supabase_key=supabase_service_key
|
||||||
|
)
|
||||||
|
vector_store = SupabaseVectorStore(
|
||||||
|
client=supabase,
|
||||||
|
embedding=embedding,
|
||||||
|
table_name=table_name,
|
||||||
|
query_name=query_name,
|
||||||
|
)
|
||||||
|
return self.search_with_vector_store(input_value, search_type, vector_store)
|
||||||
|
|
@ -8,13 +8,17 @@ from langchain_community.vectorstores.vectara import Vectara
|
||||||
from langchain_core.vectorstores import VectorStore
|
from langchain_core.vectorstores import VectorStore
|
||||||
from langflow import CustomComponent
|
from langflow import CustomComponent
|
||||||
from langflow.field_typing import BaseRetriever, Document
|
from langflow.field_typing import BaseRetriever, Document
|
||||||
|
from langchain_community.vectorstores.vectara import Vectara
|
||||||
|
|
||||||
|
|
||||||
class VectaraComponent(CustomComponent):
|
class VectaraComponent(CustomComponent):
|
||||||
display_name: str = "Vectara"
|
display_name: str = "Vectara"
|
||||||
description: str = "Implementation of Vector Store using Vectara"
|
description: str = "Implementation of Vector Store using Vectara"
|
||||||
documentation = "https://python.langchain.com/docs/integrations/vectorstores/vectara"
|
documentation = (
|
||||||
|
"https://python.langchain.com/docs/integrations/vectorstores/vectara"
|
||||||
|
)
|
||||||
beta = True
|
beta = True
|
||||||
|
icon="Vectara"
|
||||||
field_config = {
|
field_config = {
|
||||||
"vectara_customer_id": {
|
"vectara_customer_id": {
|
||||||
"display_name": "Vectara Customer ID",
|
"display_name": "Vectara Customer ID",
|
||||||
|
|
@ -26,7 +30,10 @@ class VectaraComponent(CustomComponent):
|
||||||
"display_name": "Vectara API Key",
|
"display_name": "Vectara API Key",
|
||||||
"password": True,
|
"password": True,
|
||||||
},
|
},
|
||||||
"documents": {"display_name": "Documents", "info": "If provided, will be upserted to corpus (optional)"},
|
"documents": {
|
||||||
|
"display_name": "Documents",
|
||||||
|
"info": "If provided, will be upserted to corpus (optional)",
|
||||||
|
},
|
||||||
"files_url": {
|
"files_url": {
|
||||||
"display_name": "Files Url",
|
"display_name": "Files Url",
|
||||||
"info": "Make vectara object using url of files (optional)",
|
"info": "Make vectara object using url of files (optional)",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from langchain_community.vectorstores.vectara import Vectara
|
||||||
|
|
||||||
|
from langflow.components.vectorstores.base.model import LCVectorStoreComponent
|
||||||
|
from langflow.components.vectorstores.Vectara import VectaraComponent
|
||||||
|
from langflow.schema import Record
|
||||||
|
|
||||||
|
|
||||||
|
class VectaraSearchComponent(VectaraComponent, LCVectorStoreComponent):
|
||||||
|
display_name: str = "Vectara Search"
|
||||||
|
description: str = "Search a Vectara Vector Store for similar documents."
|
||||||
|
documentation = (
|
||||||
|
"https://python.langchain.com/docs/integrations/vectorstores/vectara"
|
||||||
|
)
|
||||||
|
beta = True
|
||||||
|
icon="Vectara"
|
||||||
|
|
||||||
|
field_config = {
|
||||||
|
"search_type": {
|
||||||
|
"display_name": "Search Type",
|
||||||
|
"options": ["Similarity", "MMR"],
|
||||||
|
},
|
||||||
|
"input_value": {"display_name": "Input"},
|
||||||
|
"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,
|
||||||
|
},
|
||||||
|
"documents": {
|
||||||
|
"display_name": "Documents",
|
||||||
|
"info": "If provided, will be upserted to corpus (optional)",
|
||||||
|
},
|
||||||
|
"files_url": {
|
||||||
|
"display_name": "Files Url",
|
||||||
|
"info": "Make vectara object using url of files (optional)",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(
|
||||||
|
self,
|
||||||
|
input_value: str,
|
||||||
|
search_type: str,
|
||||||
|
vectara_customer_id: str,
|
||||||
|
vectara_corpus_id: str,
|
||||||
|
vectara_api_key: str,
|
||||||
|
) -> List[Record]:
|
||||||
|
source = "Langflow"
|
||||||
|
vector_store = Vectara(
|
||||||
|
vectara_customer_id=vectara_customer_id,
|
||||||
|
vectara_corpus_id=vectara_corpus_id,
|
||||||
|
vectara_api_key=vectara_api_key,
|
||||||
|
source=source,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not vector_store:
|
||||||
|
raise ValueError("Failed to create Vectara Vector Store")
|
||||||
|
|
||||||
|
return self.search_with_vector_store(
|
||||||
|
vector_store=vector_store, input_value=input_value, search_type=search_type
|
||||||
|
)
|
||||||
|
|
@ -8,10 +8,12 @@ from langchain_community.vectorstores import VectorStore, Weaviate
|
||||||
from langflow import CustomComponent
|
from langflow import CustomComponent
|
||||||
|
|
||||||
|
|
||||||
class WeaviateVectorStore(CustomComponent):
|
class WeaviateVectorStoreComponent(CustomComponent):
|
||||||
display_name: str = "Weaviate"
|
display_name: str = "Weaviate"
|
||||||
description: str = "Implementation of Vector Store using Weaviate"
|
description: str = "Implementation of Vector Store using Weaviate"
|
||||||
documentation = "https://python.langchain.com/docs/integrations/vectorstores/weaviate"
|
documentation = (
|
||||||
|
"https://python.langchain.com/docs/integrations/vectorstores/weaviate"
|
||||||
|
)
|
||||||
beta = True
|
beta = True
|
||||||
field_config = {
|
field_config = {
|
||||||
"url": {"display_name": "Weaviate URL", "value": "http://localhost:8080"},
|
"url": {"display_name": "Weaviate URL", "value": "http://localhost:8080"},
|
||||||
|
|
@ -24,7 +26,12 @@ class WeaviateVectorStore(CustomComponent):
|
||||||
"display_name": "Index name",
|
"display_name": "Index name",
|
||||||
"required": False,
|
"required": False,
|
||||||
},
|
},
|
||||||
"text_key": {"display_name": "Text Key", "required": False, "advanced": True, "value": "text"},
|
"text_key": {
|
||||||
|
"display_name": "Text Key",
|
||||||
|
"required": False,
|
||||||
|
"advanced": True,
|
||||||
|
"value": "text",
|
||||||
|
},
|
||||||
"documents": {"display_name": "Documents", "is_list": True},
|
"documents": {"display_name": "Documents", "is_list": True},
|
||||||
"embedding": {"display_name": "Embedding"},
|
"embedding": {"display_name": "Embedding"},
|
||||||
"attributes": {
|
"attributes": {
|
||||||
|
|
@ -34,7 +41,11 @@ class WeaviateVectorStore(CustomComponent):
|
||||||
"field_type": "str",
|
"field_type": "str",
|
||||||
"advanced": True,
|
"advanced": True,
|
||||||
},
|
},
|
||||||
"search_by_text": {"display_name": "Search By Text", "field_type": "bool", "advanced": True},
|
"search_by_text": {
|
||||||
|
"display_name": "Search By Text",
|
||||||
|
"field_type": "bool",
|
||||||
|
"advanced": True,
|
||||||
|
},
|
||||||
"code": {"show": False},
|
"code": {"show": False},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from langchain.embeddings.base import Embeddings
|
||||||
|
|
||||||
|
from langflow.components.vectorstores.base.model import LCVectorStoreComponent
|
||||||
|
from langflow.components.vectorstores.Weaviate import WeaviateVectorStoreComponent
|
||||||
|
from langflow.schema import Record
|
||||||
|
|
||||||
|
|
||||||
|
class WeaviateSearchVectorStore(WeaviateVectorStoreComponent, LCVectorStoreComponent):
|
||||||
|
display_name: str = "Weaviate Search"
|
||||||
|
description: str = "Search a Weaviate Vector Store for similar documents."
|
||||||
|
documentation = (
|
||||||
|
"https://python.langchain.com/docs/integrations/vectorstores/weaviate"
|
||||||
|
)
|
||||||
|
beta = True
|
||||||
|
icon="Weaviate"
|
||||||
|
|
||||||
|
field_config = {
|
||||||
|
"search_type": {
|
||||||
|
"display_name": "Search Type",
|
||||||
|
"options": ["Similarity", "MMR"],
|
||||||
|
},
|
||||||
|
"input_value": {"display_name": "Input"},
|
||||||
|
"url": {"display_name": "Weaviate URL", "value": "http://localhost:8080"},
|
||||||
|
"api_key": {
|
||||||
|
"display_name": "API Key",
|
||||||
|
"password": True,
|
||||||
|
"required": False,
|
||||||
|
},
|
||||||
|
"index_name": {
|
||||||
|
"display_name": "Index name",
|
||||||
|
"required": False,
|
||||||
|
},
|
||||||
|
"text_key": {
|
||||||
|
"display_name": "Text Key",
|
||||||
|
"required": False,
|
||||||
|
"advanced": True,
|
||||||
|
"value": "text",
|
||||||
|
},
|
||||||
|
"documents": {"display_name": "Documents", "is_list": True},
|
||||||
|
"embedding": {"display_name": "Embedding"},
|
||||||
|
"attributes": {
|
||||||
|
"display_name": "Attributes",
|
||||||
|
"required": False,
|
||||||
|
"is_list": True,
|
||||||
|
"field_type": "str",
|
||||||
|
"advanced": True,
|
||||||
|
},
|
||||||
|
"search_by_text": {
|
||||||
|
"display_name": "Search By Text",
|
||||||
|
"field_type": "bool",
|
||||||
|
"advanced": True,
|
||||||
|
},
|
||||||
|
"code": {"show": False},
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(
|
||||||
|
self,
|
||||||
|
input_value: str,
|
||||||
|
search_type: str,
|
||||||
|
url: str,
|
||||||
|
search_by_text: bool = False,
|
||||||
|
api_key: Optional[str] = None,
|
||||||
|
index_name: Optional[str] = None,
|
||||||
|
text_key: str = "text",
|
||||||
|
embedding: Optional[Embeddings] = None,
|
||||||
|
attributes: Optional[list] = None,
|
||||||
|
) -> List[Record]:
|
||||||
|
vector_store = super().build(
|
||||||
|
url=url,
|
||||||
|
api_key=api_key,
|
||||||
|
index_name=index_name,
|
||||||
|
text_key=text_key,
|
||||||
|
embedding=embedding,
|
||||||
|
attributes=attributes,
|
||||||
|
search_by_text=search_by_text,
|
||||||
|
)
|
||||||
|
if not vector_store:
|
||||||
|
raise ValueError("Failed to load the Weaviate index.")
|
||||||
|
|
||||||
|
return self.search_with_vector_store(
|
||||||
|
vector_store=vector_store, input_value=input_value, search_type=search_type
|
||||||
|
)
|
||||||
41
src/backend/langflow/components/vectorstores/base/model.py
Normal file
41
src/backend/langflow/components/vectorstores/base/model.py
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from langchain_core.vectorstores import VectorStore
|
||||||
|
|
||||||
|
from langflow import CustomComponent
|
||||||
|
from langflow.field_typing import Text
|
||||||
|
from langflow.schema import Record, docs_to_records
|
||||||
|
|
||||||
|
|
||||||
|
class LCVectorStoreComponent(CustomComponent):
|
||||||
|
|
||||||
|
display_name: str = "LC Vector Store"
|
||||||
|
description: str = "Search a LC Vector Store for similar documents."
|
||||||
|
beta: bool = True
|
||||||
|
|
||||||
|
def search_with_vector_store(
|
||||||
|
self, input_value: Text, search_type: str, vector_store: VectorStore
|
||||||
|
) -> List[Record]:
|
||||||
|
"""
|
||||||
|
Search for records in the vector store based on the input value and search type.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_value (Text): The input value to search for.
|
||||||
|
search_type (str): The type of search to perform.
|
||||||
|
vector_store (VectorStore): The vector store to search in.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Record]: A list of records matching the search criteria.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If invalid inputs are provided.
|
||||||
|
"""
|
||||||
|
|
||||||
|
docs = []
|
||||||
|
if input_value and isinstance(input_value, str):
|
||||||
|
docs = vector_store.search(
|
||||||
|
query=input_value, search_type=search_type.lower()
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise ValueError("Invalid inputs provided.")
|
||||||
|
return docs_to_records(docs)
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from langchain.embeddings.base import Embeddings
|
||||||
|
|
||||||
|
from langflow.components.vectorstores.base.model import LCVectorStoreComponent
|
||||||
|
from langflow.components.vectorstores.pgvector import PGVectorComponent
|
||||||
|
from langflow.schema import Record
|
||||||
|
|
||||||
|
|
||||||
|
class PGVectorSearchComponent(PGVectorComponent, LCVectorStoreComponent):
|
||||||
|
"""
|
||||||
|
A custom component for implementing a Vector Store using PostgreSQL.
|
||||||
|
"""
|
||||||
|
|
||||||
|
display_name: str = "PGVector Search"
|
||||||
|
description: str = "Search a PGVector Store for similar documents."
|
||||||
|
documentation = (
|
||||||
|
"https://python.langchain.com/docs/integrations/vectorstores/pgvector"
|
||||||
|
)
|
||||||
|
|
||||||
|
def build_config(self):
|
||||||
|
"""
|
||||||
|
Builds the configuration for the component.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- dict: A dictionary containing the configuration options for the component.
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
"code": {"show": False},
|
||||||
|
"embedding": {"display_name": "Embedding"},
|
||||||
|
"search_type": {
|
||||||
|
"display_name": "Search Type",
|
||||||
|
"options": ["Similarity", "MMR"],
|
||||||
|
},
|
||||||
|
"pg_server_url": {
|
||||||
|
"display_name": "PostgreSQL Server Connection String",
|
||||||
|
"advanced": False,
|
||||||
|
},
|
||||||
|
"collection_name": {"display_name": "Table", "advanced": False},
|
||||||
|
"input_value": {"display_name": "Input"},
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(
|
||||||
|
self,
|
||||||
|
input_value: str,
|
||||||
|
embedding: Embeddings,
|
||||||
|
pg_server_url: str,
|
||||||
|
collection_name: str,
|
||||||
|
search_type: Optional[str] = None,
|
||||||
|
) -> List[Record]:
|
||||||
|
"""
|
||||||
|
Builds the Vector Store or BaseRetriever object.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- input_value (str): The input value to search for.
|
||||||
|
- embedding (Embeddings): The embeddings to use for the Vector Store.
|
||||||
|
- collection_name (str): The name of the PG table.
|
||||||
|
- pg_server_url (str): The URL for the PG server.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- VectorStore: The Vector Store object.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
vector_store = super().build(
|
||||||
|
embedding=embedding,
|
||||||
|
pg_server_url=pg_server_url,
|
||||||
|
collection_name=collection_name,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
raise RuntimeError(f"Failed to build PGVector: {e}")
|
||||||
|
return self.search_with_vector_store(
|
||||||
|
input_value=input_value, search_type=search_type, vector_store=vector_store
|
||||||
|
)
|
||||||
|
|
@ -218,24 +218,7 @@ retrievers:
|
||||||
# https://github.com/supabase-community/supabase-py/issues/482
|
# https://github.com/supabase-community/supabase-py/issues/482
|
||||||
# ZepRetriever:
|
# ZepRetriever:
|
||||||
# documentation: "https://python.langchain.com/docs/modules/data_connection/retrievers/integrations/zep_memorystore"
|
# documentation: "https://python.langchain.com/docs/modules/data_connection/retrievers/integrations/zep_memorystore"
|
||||||
vectorstores:
|
|
||||||
# Chroma:
|
|
||||||
# documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/chroma"
|
|
||||||
Qdrant:
|
|
||||||
documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/qdrant"
|
|
||||||
FAISS:
|
|
||||||
documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/faiss"
|
|
||||||
Pinecone:
|
|
||||||
documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/pinecone"
|
|
||||||
ElasticsearchStore:
|
|
||||||
documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/elasticsearch"
|
|
||||||
SupabaseVectorStore:
|
|
||||||
documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/supabase"
|
|
||||||
MongoDBAtlasVectorSearch:
|
|
||||||
documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/mongodb_atlas"
|
|
||||||
# Requires docarray >=0.32.0 but langchain-serve requires jina 3.15.2 which doesn't support docarray >=0.32.0
|
|
||||||
# DocArrayInMemorySearch:
|
|
||||||
# documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/docarray_in_memory"
|
|
||||||
wrappers:
|
wrappers:
|
||||||
RequestsWrapper:
|
RequestsWrapper:
|
||||||
documentation: ""
|
documentation: ""
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ class Graph:
|
||||||
if getattr(vertex, attribute):
|
if getattr(vertex, attribute):
|
||||||
getattr(self, f"_{attribute}_vertices").append(vertex.id)
|
getattr(self, f"_{attribute}_vertices").append(vertex.id)
|
||||||
|
|
||||||
async def _run(self, inputs: Dict[str, str]) -> List["ResultData"]:
|
async def _run(self, inputs: Dict[str, str], stream: bool) -> List["ResultData"]:
|
||||||
"""Runs the graph with the given inputs."""
|
"""Runs the graph with the given inputs."""
|
||||||
for vertex_id in self._is_input_vertices:
|
for vertex_id in self._is_input_vertices:
|
||||||
vertex = self.get_vertex(vertex_id)
|
vertex = self.get_vertex(vertex_id)
|
||||||
|
|
@ -91,10 +91,14 @@ class Graph:
|
||||||
vertex = self.get_vertex(vertex_id)
|
vertex = self.get_vertex(vertex_id)
|
||||||
if vertex is None:
|
if vertex is None:
|
||||||
raise ValueError(f"Vertex {vertex_id} not found")
|
raise ValueError(f"Vertex {vertex_id} not found")
|
||||||
|
if not stream and hasattr(vertex, "consume_async_generator"):
|
||||||
|
await vertex.consume_async_generator()
|
||||||
outputs.append(vertex.result)
|
outputs.append(vertex.result)
|
||||||
return outputs
|
return outputs
|
||||||
|
|
||||||
async def run(self, inputs: Dict[str, Union[str, list[str]]]) -> List["ResultData"]:
|
async def run(
|
||||||
|
self, inputs: Dict[str, Union[str, list[str]]], stream: bool
|
||||||
|
) -> List["ResultData"]:
|
||||||
"""Runs the graph with the given inputs."""
|
"""Runs the graph with the given inputs."""
|
||||||
|
|
||||||
# inputs is {"message": "Hello, world!"}
|
# inputs is {"message": "Hello, world!"}
|
||||||
|
|
@ -106,7 +110,9 @@ class Graph:
|
||||||
if not isinstance(inputs_values, list):
|
if not isinstance(inputs_values, list):
|
||||||
inputs_values = [inputs_values]
|
inputs_values = [inputs_values]
|
||||||
for input_value in inputs_values:
|
for input_value in inputs_values:
|
||||||
run_outputs = await self._run({INPUT_FIELD_NAME: input_value})
|
run_outputs = await self._run(
|
||||||
|
{INPUT_FIELD_NAME: input_value}, stream=stream
|
||||||
|
)
|
||||||
logger.debug(f"Run outputs: {run_outputs}")
|
logger.debug(f"Run outputs: {run_outputs}")
|
||||||
outputs.extend(run_outputs)
|
outputs.extend(run_outputs)
|
||||||
return outputs
|
return outputs
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ class Vertex:
|
||||||
) -> None:
|
) -> None:
|
||||||
# is_external means that the Vertex send or receives data from
|
# is_external means that the Vertex send or receives data from
|
||||||
# an external source (e.g the chat)
|
# an external source (e.g the chat)
|
||||||
|
self.will_stream = False
|
||||||
self.updated_raw_params = False
|
self.updated_raw_params = False
|
||||||
self.id: str = data["id"]
|
self.id: str = data["id"]
|
||||||
self.is_input = any(
|
self.is_input = any(
|
||||||
|
|
@ -391,6 +392,8 @@ class Vertex:
|
||||||
ValueError: If any key in new_params is not found in self._raw_params.
|
ValueError: If any key in new_params is not found in self._raw_params.
|
||||||
"""
|
"""
|
||||||
# First check if the input_value in _raw_params is not a vertex
|
# First check if the input_value in _raw_params is not a vertex
|
||||||
|
if not new_params:
|
||||||
|
return
|
||||||
if any(isinstance(self._raw_params.get(key), Vertex) for key in new_params):
|
if any(isinstance(self._raw_params.get(key), Vertex) for key in new_params):
|
||||||
return
|
return
|
||||||
self._raw_params.update(new_params)
|
self._raw_params.update(new_params)
|
||||||
|
|
@ -456,7 +459,7 @@ class Vertex:
|
||||||
await self._build_node_and_update_params(key, value, user_id)
|
await self._build_node_and_update_params(key, value, user_id)
|
||||||
elif isinstance(value, list) and self._is_list_of_nodes(value):
|
elif isinstance(value, list) and self._is_list_of_nodes(value):
|
||||||
await self._build_list_of_nodes_and_update_params(key, value, user_id)
|
await self._build_list_of_nodes_and_update_params(key, value, user_id)
|
||||||
elif key not in self.params:
|
elif key not in self.params or self.updated_raw_params:
|
||||||
self.params[key] = value
|
self.params[key] = value
|
||||||
|
|
||||||
def _is_node(self, value):
|
def _is_node(self, value):
|
||||||
|
|
@ -610,6 +613,7 @@ class Vertex:
|
||||||
async def build(
|
async def build(
|
||||||
self,
|
self,
|
||||||
user_id=None,
|
user_id=None,
|
||||||
|
inputs: Optional[Dict[str, Any]] = None,
|
||||||
requester: Optional["Vertex"] = None,
|
requester: Optional["Vertex"] = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
|
|
@ -622,6 +626,9 @@ class Vertex:
|
||||||
return self.get_requester_result(requester)
|
return self.get_requester_result(requester)
|
||||||
self._reset()
|
self._reset()
|
||||||
|
|
||||||
|
if self.is_input:
|
||||||
|
self.update_raw_params(inputs)
|
||||||
|
|
||||||
# Run steps
|
# Run steps
|
||||||
for step in self.steps:
|
for step in self.steps:
|
||||||
if step not in self.steps_ran:
|
if step not in self.steps_ran:
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ from langflow.graph.utils import UnbuiltObject, flatten_list
|
||||||
from langflow.graph.vertex.base import StatefulVertex, StatelessVertex
|
from langflow.graph.vertex.base import StatefulVertex, StatelessVertex
|
||||||
from langflow.interface.utils import extract_input_variables_from_prompt
|
from langflow.interface.utils import extract_input_variables_from_prompt
|
||||||
from langflow.schema import Record
|
from langflow.schema import Record
|
||||||
from langflow.services.monitor.utils import log_message
|
from langflow.services.monitor.utils import log_vertex_build
|
||||||
from langflow.utils.schemas import ChatOutputResponse
|
from langflow.utils.schemas import ChatOutputResponse
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -394,6 +394,8 @@ class ChatVertex(StatelessVertex):
|
||||||
sender_name=sender_name,
|
sender_name=sender_name,
|
||||||
stream_url=stream_url,
|
stream_url=stream_url,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.will_stream = stream_url is not None
|
||||||
if artifacts:
|
if artifacts:
|
||||||
self.artifacts = artifacts.model_dump()
|
self.artifacts = artifacts.model_dump()
|
||||||
if isinstance(self._built_object, (AsyncIterator, Iterator)):
|
if isinstance(self._built_object, (AsyncIterator, Iterator)):
|
||||||
|
|
@ -434,19 +436,25 @@ class ChatVertex(StatelessVertex):
|
||||||
self._built_result = complete_message
|
self._built_result = complete_message
|
||||||
# Update artifacts with the message
|
# Update artifacts with the message
|
||||||
# and remove the stream_url
|
# and remove the stream_url
|
||||||
|
self._finalize_build()
|
||||||
logger.debug(f"Streamed message: {complete_message}")
|
logger.debug(f"Streamed message: {complete_message}")
|
||||||
|
|
||||||
await log_message(
|
await log_vertex_build(
|
||||||
sender=self.params.get("sender", ""),
|
flow_id=self.graph.flow_id,
|
||||||
sender_name=self.params.get("sender_name", ""),
|
vertex_id=self.id,
|
||||||
message=complete_message,
|
valid=True,
|
||||||
session_id=self.params.get("session_id", ""),
|
params=self._built_object_repr(),
|
||||||
|
data=self.result,
|
||||||
artifacts=self.artifacts,
|
artifacts=self.artifacts,
|
||||||
)
|
)
|
||||||
|
|
||||||
self._validate_built_object()
|
self._validate_built_object()
|
||||||
self._built = True
|
self._built = True
|
||||||
|
|
||||||
|
async def consume_async_generator(self):
|
||||||
|
async for _ in self.stream():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class RoutingVertex(StatelessVertex):
|
class RoutingVertex(StatelessVertex):
|
||||||
def __init__(self, data: Dict, graph):
|
def __init__(self, data: Dict, graph):
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import ast
|
import ast
|
||||||
import os
|
import os
|
||||||
import zlib
|
import zlib
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
|
|
@ -79,9 +80,13 @@ class DirectoryReader:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error while loading component: {e}")
|
logger.error(f"Error while loading component: {e}")
|
||||||
continue
|
continue
|
||||||
items.append({"name": menu["name"], "path": menu["path"], "components": components})
|
items.append(
|
||||||
|
{"name": menu["name"], "path": menu["path"], "components": components}
|
||||||
|
)
|
||||||
filtered = [menu for menu in items if menu["components"]]
|
filtered = [menu for menu in items if menu["components"]]
|
||||||
logger.debug(f'Filtered components {"with errors" if with_errors else ""}: {len(filtered)}')
|
logger.debug(
|
||||||
|
f'Filtered components {"with errors" if with_errors else ""}: {len(filtered)}'
|
||||||
|
)
|
||||||
return {"menu": filtered}
|
return {"menu": filtered}
|
||||||
|
|
||||||
def validate_code(self, file_content):
|
def validate_code(self, file_content):
|
||||||
|
|
@ -114,15 +119,24 @@ class DirectoryReader:
|
||||||
Walk through the directory path and return a list of all .py files.
|
Walk through the directory path and return a list of all .py files.
|
||||||
"""
|
"""
|
||||||
if not (safe_path := self.get_safe_path()):
|
if not (safe_path := self.get_safe_path()):
|
||||||
raise CustomComponentPathValueError(f"The path needs to start with '{self.base_path}'.")
|
raise CustomComponentPathValueError(
|
||||||
|
f"The path needs to start with '{self.base_path}'."
|
||||||
|
)
|
||||||
|
|
||||||
file_list = []
|
file_list = []
|
||||||
for root, _, files in os.walk(safe_path):
|
safe_path_obj = Path(safe_path)
|
||||||
file_list.extend(
|
for file_path in safe_path_obj.rglob("*.py"):
|
||||||
os.path.join(root, filename)
|
# The other condtion is that it should be
|
||||||
for filename in files
|
# in the safe_path/[folder]/[file].py format
|
||||||
if filename.endswith(".py") and not filename.startswith("__")
|
# any folders below [folder] will be ignored
|
||||||
)
|
# basically the parent folder of the file should be a
|
||||||
|
# folder in the safe_path
|
||||||
|
if (
|
||||||
|
file_path.is_file()
|
||||||
|
and file_path.parent.parent == safe_path_obj
|
||||||
|
and not file_path.name.startswith("__")
|
||||||
|
):
|
||||||
|
file_list.append(str(file_path))
|
||||||
return file_list
|
return file_list
|
||||||
|
|
||||||
def find_menu(self, response, menu_name):
|
def find_menu(self, response, menu_name):
|
||||||
|
|
@ -159,7 +173,9 @@ class DirectoryReader:
|
||||||
for node in ast.walk(module):
|
for node in ast.walk(module):
|
||||||
if isinstance(node, ast.FunctionDef):
|
if isinstance(node, ast.FunctionDef):
|
||||||
for arg in node.args.args:
|
for arg in node.args.args:
|
||||||
if self._is_type_hint_in_arg_annotation(arg.annotation, type_hint_name):
|
if self._is_type_hint_in_arg_annotation(
|
||||||
|
arg.annotation, type_hint_name
|
||||||
|
):
|
||||||
return True
|
return True
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
# Returns False if the code is not valid Python
|
# Returns False if the code is not valid Python
|
||||||
|
|
@ -177,14 +193,16 @@ class DirectoryReader:
|
||||||
and annotation.value.id == type_hint_name
|
and annotation.value.id == type_hint_name
|
||||||
)
|
)
|
||||||
|
|
||||||
def is_type_hint_used_but_not_imported(self, type_hint_name: str, code: str) -> bool:
|
def is_type_hint_used_but_not_imported(
|
||||||
|
self, type_hint_name: str, code: str
|
||||||
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Check if a type hint is used but not imported in the given code.
|
Check if a type hint is used but not imported in the given code.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return self._is_type_hint_used_in_args(type_hint_name, code) and not self._is_type_hint_imported(
|
return self._is_type_hint_used_in_args(
|
||||||
type_hint_name, code
|
type_hint_name, code
|
||||||
)
|
) and not self._is_type_hint_imported(type_hint_name, code)
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
# Returns True if there's something wrong with the code
|
# Returns True if there's something wrong with the code
|
||||||
# TODO : Find a better way to handle this
|
# TODO : Find a better way to handle this
|
||||||
|
|
@ -205,9 +223,9 @@ class DirectoryReader:
|
||||||
return False, "Syntax error"
|
return False, "Syntax error"
|
||||||
elif not self.validate_build(file_content):
|
elif not self.validate_build(file_content):
|
||||||
return False, "Missing build function"
|
return False, "Missing build function"
|
||||||
elif self._is_type_hint_used_in_args("Optional", file_content) and not self._is_type_hint_imported(
|
elif self._is_type_hint_used_in_args(
|
||||||
"Optional", file_content
|
"Optional", file_content
|
||||||
):
|
) and not self._is_type_hint_imported("Optional", file_content):
|
||||||
return (
|
return (
|
||||||
False,
|
False,
|
||||||
"Type hint 'Optional' is used but not imported in the code.",
|
"Type hint 'Optional' is used but not imported in the code.",
|
||||||
|
|
@ -223,7 +241,9 @@ class DirectoryReader:
|
||||||
from the .py files in the directory.
|
from the .py files in the directory.
|
||||||
"""
|
"""
|
||||||
response = {"menu": []}
|
response = {"menu": []}
|
||||||
logger.debug("-------------------- Building component menu list --------------------")
|
logger.debug(
|
||||||
|
"-------------------- Building component menu list --------------------"
|
||||||
|
)
|
||||||
|
|
||||||
for file_path in file_paths:
|
for file_path in file_paths:
|
||||||
menu_name = os.path.basename(os.path.dirname(file_path))
|
menu_name = os.path.basename(os.path.dirname(file_path))
|
||||||
|
|
@ -243,7 +263,9 @@ class DirectoryReader:
|
||||||
|
|
||||||
# first check if it's already CamelCase
|
# first check if it's already CamelCase
|
||||||
if "_" in component_name:
|
if "_" in component_name:
|
||||||
component_name_camelcase = " ".join(word.title() for word in component_name.split("_"))
|
component_name_camelcase = " ".join(
|
||||||
|
word.title() for word in component_name.split("_")
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
component_name_camelcase = component_name
|
component_name_camelcase = component_name
|
||||||
|
|
||||||
|
|
@ -251,7 +273,9 @@ class DirectoryReader:
|
||||||
try:
|
try:
|
||||||
output_types = self.get_output_types_from_code(result_content)
|
output_types = self.get_output_types_from_code(result_content)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception(f"Error while getting output types from code: {str(exc)}")
|
logger.exception(
|
||||||
|
f"Error while getting output types from code: {str(exc)}"
|
||||||
|
)
|
||||||
output_types = [component_name_camelcase]
|
output_types = [component_name_camelcase]
|
||||||
else:
|
else:
|
||||||
output_types = [component_name_camelcase]
|
output_types = [component_name_camelcase]
|
||||||
|
|
@ -267,7 +291,9 @@ class DirectoryReader:
|
||||||
|
|
||||||
if menu_result not in response["menu"]:
|
if menu_result not in response["menu"]:
|
||||||
response["menu"].append(menu_result)
|
response["menu"].append(menu_result)
|
||||||
logger.debug("-------------------- Component menu list built --------------------")
|
logger.debug(
|
||||||
|
"-------------------- Component menu list built --------------------"
|
||||||
|
)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,18 @@
|
||||||
from langflow.interface.custom.directory_reader import DirectoryReader
|
|
||||||
from langflow.template.frontend_node.custom_components import CustomComponentFrontendNode
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
|
from langflow.interface.custom.directory_reader import DirectoryReader
|
||||||
|
from langflow.template.frontend_node.custom_components import (
|
||||||
|
CustomComponentFrontendNode,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def merge_nested_dicts_with_renaming(dict1, dict2):
|
def merge_nested_dicts_with_renaming(dict1, dict2):
|
||||||
for key, value in dict2.items():
|
for key, value in dict2.items():
|
||||||
if key in dict1 and isinstance(value, dict) and isinstance(dict1.get(key), dict):
|
if (
|
||||||
|
key in dict1
|
||||||
|
and isinstance(value, dict)
|
||||||
|
and isinstance(dict1.get(key), dict)
|
||||||
|
):
|
||||||
for sub_key, sub_value in value.items():
|
for sub_key, sub_value in value.items():
|
||||||
# if sub_key in dict1[key]:
|
# if sub_key in dict1[key]:
|
||||||
# new_key = get_new_key(dict1[key], sub_key)
|
# new_key = get_new_key(dict1[key], sub_key)
|
||||||
|
|
@ -62,7 +69,9 @@ def build_custom_component_list_from_path(path: str):
|
||||||
file_list = load_files_from_path(path)
|
file_list = load_files_from_path(path)
|
||||||
reader = DirectoryReader(path, False)
|
reader = DirectoryReader(path, False)
|
||||||
|
|
||||||
valid_components, invalid_components = build_and_validate_all_files(reader, file_list)
|
valid_components, invalid_components = build_and_validate_all_files(
|
||||||
|
reader, file_list
|
||||||
|
)
|
||||||
|
|
||||||
valid_menu = build_valid_menu(valid_components)
|
valid_menu = build_valid_menu(valid_components)
|
||||||
invalid_menu = build_invalid_menu(invalid_components)
|
invalid_menu = build_invalid_menu(invalid_components)
|
||||||
|
|
@ -109,7 +118,9 @@ def build_invalid_menu_items(menu_item):
|
||||||
menu_items[component_name] = component_template
|
menu_items[component_name] = component_template
|
||||||
logger.debug(f"Added {component_name} to invalid menu.")
|
logger.debug(f"Added {component_name} to invalid menu.")
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception(f"Error while creating custom component [{component_name}]: {str(exc)}")
|
logger.exception(
|
||||||
|
f"Error while creating custom component [{component_name}]: {str(exc)}"
|
||||||
|
)
|
||||||
return menu_items
|
return menu_items
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -136,12 +147,14 @@ def determine_component_name(component):
|
||||||
def build_menu_items(menu_item):
|
def build_menu_items(menu_item):
|
||||||
"""Build menu items for a given menu."""
|
"""Build menu items for a given menu."""
|
||||||
menu_items = {}
|
menu_items = {}
|
||||||
|
logger.debug(f"Building menu items for {menu_item['name']}")
|
||||||
|
logger.debug(f"Loading {len(menu_item['components'])} components")
|
||||||
for component_name, component_template, component in menu_item["components"]:
|
for component_name, component_template, component in menu_item["components"]:
|
||||||
try:
|
try:
|
||||||
menu_items[component_name] = component_template
|
menu_items[component_name] = component_template
|
||||||
logger.debug(f"Added {component_name} to valid menu.")
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error(f"Error loading Component: {component['output_types']}")
|
logger.error(f"Error loading Component: {component['output_types']}")
|
||||||
logger.exception(f"Error while building custom component {component['output_types']}: {exc}")
|
logger.exception(
|
||||||
return menu_items
|
f"Error while building custom component {component['output_types']}: {exc}"
|
||||||
|
)
|
||||||
return menu_items
|
return menu_items
|
||||||
|
|
|
||||||
|
|
@ -271,18 +271,26 @@ async def run_graph(
|
||||||
graph: Union["Graph", dict],
|
graph: Union["Graph", dict],
|
||||||
flow_id: str,
|
flow_id: str,
|
||||||
session_id: str,
|
session_id: str,
|
||||||
|
stream: bool,
|
||||||
inputs: Optional[Union[dict, List[dict]]] = None,
|
inputs: Optional[Union[dict, List[dict]]] = None,
|
||||||
artifacts: Optional[Dict[str, Any]] = None,
|
artifacts: Optional[Dict[str, Any]] = None,
|
||||||
session_service: Optional[SessionService] = None,
|
session_service: Optional[SessionService] = None,
|
||||||
):
|
):
|
||||||
"""Run the graph and generate the result"""
|
"""Run the graph and generate the result"""
|
||||||
if isinstance(graph, dict):
|
if isinstance(graph, dict):
|
||||||
|
graph_data = graph
|
||||||
graph = Graph.from_payload(graph, flow_id=flow_id)
|
graph = Graph.from_payload(graph, flow_id=flow_id)
|
||||||
|
else:
|
||||||
|
graph_data = graph._graph_data
|
||||||
|
if not session_id:
|
||||||
|
session_id = session_service.generate_key(
|
||||||
|
session_id=flow_id, data_graph=graph_data
|
||||||
|
)
|
||||||
|
|
||||||
outputs = await graph.run(inputs)
|
outputs = await graph.run(inputs, stream=stream)
|
||||||
if session_id and session_service:
|
if session_id and session_service:
|
||||||
session_service.update_session(session_id, (graph, artifacts))
|
session_service.update_session(session_id, (graph, artifacts))
|
||||||
return outputs
|
return outputs, session_id
|
||||||
|
|
||||||
|
|
||||||
def validate_input(
|
def validate_input(
|
||||||
|
|
|
||||||
|
|
@ -5,16 +5,17 @@ from typing import TYPE_CHECKING
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from alembic import command, util
|
from alembic import command, util
|
||||||
from alembic.config import Config
|
from alembic.config import Config
|
||||||
|
from loguru import logger
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
from sqlalchemy.exc import OperationalError
|
||||||
|
from sqlmodel import Session, SQLModel, create_engine, select, text
|
||||||
|
|
||||||
from langflow.services.base import Service
|
from langflow.services.base import Service
|
||||||
from langflow.services.database import models # noqa
|
from langflow.services.database import models # noqa
|
||||||
from langflow.services.database.models.user.crud import get_user_by_username
|
from langflow.services.database.models.user.crud import get_user_by_username
|
||||||
from langflow.services.database.utils import Result, TableResults
|
from langflow.services.database.utils import Result, TableResults
|
||||||
from langflow.services.deps import get_settings_service
|
from langflow.services.deps import get_settings_service
|
||||||
from langflow.services.utils import teardown_superuser
|
from langflow.services.utils import teardown_superuser
|
||||||
from loguru import logger
|
|
||||||
from sqlalchemy import inspect
|
|
||||||
from sqlalchemy.exc import OperationalError
|
|
||||||
from sqlmodel import Session, SQLModel, create_engine, select, text
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from sqlalchemy.engine import Engine
|
from sqlalchemy.engine import Engine
|
||||||
|
|
@ -39,7 +40,7 @@ class DatabaseService(Service):
|
||||||
connect_args = {"check_same_thread": False}
|
connect_args = {"check_same_thread": False}
|
||||||
else:
|
else:
|
||||||
connect_args = {}
|
connect_args = {}
|
||||||
return create_engine(self.database_url, connect_args=connect_args, max_overflow=-1)
|
return create_engine(self.database_url, connect_args=connect_args)
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
self._session = Session(self.engine)
|
self._session = Session(self.engine)
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,11 @@ import Checkmark from "../../components/ui/checkmark";
|
||||||
import Loading from "../../components/ui/loading";
|
import Loading from "../../components/ui/loading";
|
||||||
import { Textarea } from "../../components/ui/textarea";
|
import { Textarea } from "../../components/ui/textarea";
|
||||||
import Xmark from "../../components/ui/xmark";
|
import Xmark from "../../components/ui/xmark";
|
||||||
import { priorityFields, statusBuild, statusBuilding } from "../../constants/constants";
|
import {
|
||||||
|
priorityFields,
|
||||||
|
statusBuild,
|
||||||
|
statusBuilding,
|
||||||
|
} from "../../constants/constants";
|
||||||
import { BuildStatus } from "../../constants/enums";
|
import { BuildStatus } from "../../constants/enums";
|
||||||
import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent";
|
import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent";
|
||||||
import { useDarkStore } from "../../stores/darkStore";
|
import { useDarkStore } from "../../stores/darkStore";
|
||||||
|
|
@ -211,9 +215,7 @@ export default function GenericNode({
|
||||||
return "inactive-status";
|
return "inactive-status";
|
||||||
}
|
}
|
||||||
if (buildStatus === BuildStatus.BUILT && isInvalid) {
|
if (buildStatus === BuildStatus.BUILT && isInvalid) {
|
||||||
return isDark
|
return isDark ? "built-invalid-status-dark" : "built-invalid-status";
|
||||||
? "built-invalid-status-dark"
|
|
||||||
: "built-invalid-status";
|
|
||||||
} else if (buildStatus === BuildStatus.BUILDING) {
|
} else if (buildStatus === BuildStatus.BUILDING) {
|
||||||
return "building-status";
|
return "building-status";
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -296,7 +298,7 @@ export default function GenericNode({
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
"generic-node-title-arrangement rounded-full" +
|
"generic-node-title-arrangement rounded-full" +
|
||||||
(!showNode && " justify-center")
|
(!showNode && " justify-center ")
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{iconNodeRender()}
|
{iconNodeRender()}
|
||||||
|
|
@ -332,21 +334,20 @@ export default function GenericNode({
|
||||||
) : (
|
) : (
|
||||||
<ShadTooltip content={data.node?.display_name}>
|
<ShadTooltip content={data.node?.display_name}>
|
||||||
<div className="group flex items-center gap-2.5">
|
<div className="group flex items-center gap-2.5">
|
||||||
|
<div
|
||||||
<div
|
onDoubleClick={(event) => {
|
||||||
onDoubleClick={(event) => {
|
if (nameEditable) {
|
||||||
if (nameEditable) {
|
setInputName(true);
|
||||||
setInputName(true);
|
}
|
||||||
}
|
takeSnapshot();
|
||||||
takeSnapshot();
|
event.stopPropagation();
|
||||||
event.stopPropagation();
|
event.preventDefault();
|
||||||
event.preventDefault();
|
}}
|
||||||
}}
|
data-testid={"title-" + data.node?.display_name}
|
||||||
data-testid={"title-" + data.node?.display_name}
|
className="generic-node-tooltip-div text-primary"
|
||||||
className="generic-node-tooltip-div text-primary"
|
>
|
||||||
>
|
{data.node?.display_name}
|
||||||
{data.node?.display_name}
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{nameEditable && (
|
{nameEditable && (
|
||||||
<div
|
<div
|
||||||
|
|
@ -465,7 +466,7 @@ export default function GenericNode({
|
||||||
if (buildStatus === BuildStatus.BUILDING || isBuilding)
|
if (buildStatus === BuildStatus.BUILDING || isBuilding)
|
||||||
return;
|
return;
|
||||||
setValidationStatus(null);
|
setValidationStatus(null);
|
||||||
buildFlow(data.id);
|
buildFlow({nodeId: data.id});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -478,11 +479,11 @@ export default function GenericNode({
|
||||||
) : (
|
) : (
|
||||||
<div className="max-h-96 overflow-auto">
|
<div className="max-h-96 overflow-auto">
|
||||||
{typeof validationStatus.params === "string"
|
{typeof validationStatus.params === "string"
|
||||||
? (`${durationString}\n${validationStatus.params}`
|
? `${durationString}\n${validationStatus.params}`
|
||||||
.split("\n")
|
.split("\n")
|
||||||
.map((line, index) => (
|
.map((line, index) => (
|
||||||
<div key={index}>{line}</div>
|
<div key={index}>{line}</div>
|
||||||
)))
|
))
|
||||||
: durationString}
|
: durationString}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { Cross2Icon } from "@radix-ui/react-icons";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import IconComponent from "../../components/genericIconComponent";
|
import IconComponent from "../../components/genericIconComponent";
|
||||||
import {
|
import {
|
||||||
|
|
@ -46,15 +47,15 @@ export default function AlertDropdown({
|
||||||
setTimeout(clearNotificationList, 100);
|
setTimeout(clearNotificationList, 100);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconComponent name="Trash2" className="h-[1.1rem] w-[1.1rem]" />
|
<IconComponent name="Trash2" className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="text-foreground hover:text-status-red"
|
className="text-foreground opacity-70 hover:opacity-100"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconComponent name="X" className="h-5 w-5" />
|
<Cross2Icon className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,12 @@ export default function IOInputField({
|
||||||
<Textarea
|
<Textarea
|
||||||
className="w-full"
|
className="w-full"
|
||||||
placeholder={"Enter text..."}
|
placeholder={"Enter text..."}
|
||||||
value={node.data.node!.template["value"].value}
|
value={node.data.node!.template["input_value"].value}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
e.target.value;
|
e.target.value;
|
||||||
if (node) {
|
if (node) {
|
||||||
let newNode = cloneDeep(node);
|
let newNode = cloneDeep(node);
|
||||||
newNode.data.node!.template["value"].value = e.target.value;
|
newNode.data.node!.template["input_value"].value = e.target.value;
|
||||||
setNode(node.id, newNode);
|
setNode(node.id, newNode);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
@ -49,12 +49,12 @@ export default function IOInputField({
|
||||||
<Textarea
|
<Textarea
|
||||||
className="w-full custom-scroll"
|
className="w-full custom-scroll"
|
||||||
placeholder={"Enter text..."}
|
placeholder={"Enter text..."}
|
||||||
value={node.data.node!.template["value"]}
|
value={node.data.node!.template["input_value"]}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
e.target.value;
|
e.target.value;
|
||||||
if (node) {
|
if (node) {
|
||||||
let newNode = cloneDeep(node);
|
let newNode = cloneDeep(node);
|
||||||
newNode.data.node!.template["value"].value = e.target.value;
|
newNode.data.node!.template["input_value"].value = e.target.value;
|
||||||
setNode(node.id, newNode);
|
setNode(node.id, newNode);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -30,12 +30,12 @@ export default function IOOutputView({
|
||||||
<Textarea
|
<Textarea
|
||||||
className="w-full custom-scroll"
|
className="w-full custom-scroll"
|
||||||
placeholder={"Enter text..."}
|
placeholder={"Enter text..."}
|
||||||
value={node.data.node!.template["value"]}
|
value={node.data.node!.template["input_value"]}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
e.target.value;
|
e.target.value;
|
||||||
if (node) {
|
if (node) {
|
||||||
let newNode = cloneDeep(node);
|
let newNode = cloneDeep(node);
|
||||||
newNode.data.node!.template["value"].value = e.target.value;
|
newNode.data.node!.template["input_value"].value = e.target.value;
|
||||||
setNode(node.id, newNode);
|
setNode(node.id, newNode);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
import { cloneDeep } from "lodash";
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { CHAT_FORM_DIALOG_SUBTITLE, outputsModalTitle, textInputModalTitle } from "../../constants/constants";
|
import {
|
||||||
|
CHAT_FORM_DIALOG_SUBTITLE,
|
||||||
|
outputsModalTitle,
|
||||||
|
textInputModalTitle,
|
||||||
|
} from "../../constants/constants";
|
||||||
import BaseModal from "../../modals/baseModal";
|
import BaseModal from "../../modals/baseModal";
|
||||||
import useAlertStore from "../../stores/alertStore";
|
|
||||||
import useFlowStore from "../../stores/flowStore";
|
import useFlowStore from "../../stores/flowStore";
|
||||||
|
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||||
|
import { updateVerticesOrder } from "../../utils/buildUtils";
|
||||||
import { cn } from "../../utils/utils";
|
import { cn } from "../../utils/utils";
|
||||||
import AccordionComponent from "../AccordionComponent";
|
import AccordionComponent from "../AccordionComponent";
|
||||||
import IOInputField from "../IOInputField";
|
import IOInputField from "../IOInputField";
|
||||||
|
|
@ -40,27 +44,30 @@ export default function IOView({ children, open, setOpen }): JSX.Element {
|
||||||
{ type: string; id: string } | undefined
|
{ type: string; id: string } | undefined
|
||||||
>(undefined);
|
>(undefined);
|
||||||
|
|
||||||
const { getNode, setNode, buildFlow, getFlow } = useFlowStore();
|
const buildFlow = useFlowStore((state) => state.buildFlow);
|
||||||
const { setErrorData } = useAlertStore();
|
|
||||||
const setIsBuilding = useFlowStore((state) => state.setIsBuilding);
|
const setIsBuilding = useFlowStore((state) => state.setIsBuilding);
|
||||||
const [lockChat, setLockChat] = useState(false);
|
const [lockChat, setLockChat] = useState(false);
|
||||||
const [chatValue, setChatValue] = useState("");
|
const [chatValue, setChatValue] = useState("");
|
||||||
const isBuilding = useFlowStore((state) => state.isBuilding);
|
const isBuilding = useFlowStore((state) => state.isBuilding);
|
||||||
|
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
||||||
|
|
||||||
|
async function updateVertices() {
|
||||||
|
return updateVerticesOrder(currentFlow!.id, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
updateVertices();
|
||||||
|
}
|
||||||
|
}, [open, currentFlow]);
|
||||||
|
|
||||||
async function sendMessage(count = 1): Promise<void> {
|
async function sendMessage(count = 1): Promise<void> {
|
||||||
if (isBuilding) return;
|
if (isBuilding) return;
|
||||||
const { nodes, edges } = getFlow();
|
|
||||||
setIsBuilding(true);
|
setIsBuilding(true);
|
||||||
setLockChat(true);
|
setLockChat(true);
|
||||||
setChatValue("");
|
setChatValue("");
|
||||||
const chatInputNode = nodes.find((node) => node.id === chatInput?.id);
|
|
||||||
if (chatInputNode) {
|
|
||||||
let newNode = cloneDeep(chatInputNode);
|
|
||||||
newNode.data.node!.template["message"].value = chatValue;
|
|
||||||
setNode(chatInput!.id, newNode);
|
|
||||||
}
|
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
await buildFlow().catch((err) => {
|
await buildFlow({ input_value: chatValue }).catch((err) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
setLockChat(false);
|
setLockChat(false);
|
||||||
});
|
});
|
||||||
|
|
@ -104,7 +111,7 @@ export default function IOView({ children, open, setOpen }): JSX.Element {
|
||||||
<Tabs
|
<Tabs
|
||||||
value={selectedTab.toString()}
|
value={selectedTab.toString()}
|
||||||
className={
|
className={
|
||||||
"flex h-full flex-col overflow-y-auto custom-scroll rounded-md border bg-muted text-center"
|
"flex h-full flex-col overflow-y-auto rounded-md border bg-muted text-center custom-scroll"
|
||||||
}
|
}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
setSelectedTab(Number(value));
|
setSelectedTab(Number(value));
|
||||||
|
|
@ -266,24 +273,27 @@ export default function IOView({ children, open, setOpen }): JSX.Element {
|
||||||
{selectedViewField.type}
|
{selectedViewField.type}
|
||||||
</div>
|
</div>
|
||||||
<div className="h-full">
|
<div className="h-full">
|
||||||
{inputs.some(
|
{inputs.some(
|
||||||
(input) => input.id === selectedViewField.id
|
(input) => input.id === selectedViewField.id
|
||||||
) ? (
|
) ? (
|
||||||
<IOInputField
|
<IOInputField
|
||||||
inputType={selectedViewField.type!}
|
inputType={selectedViewField.type!}
|
||||||
inputId={selectedViewField.id!}
|
inputId={selectedViewField.id!}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<IOOutputView
|
<IOOutputView
|
||||||
outputType={selectedViewField.type!}
|
outputType={selectedViewField.type!}
|
||||||
outputId={selectedViewField.id!}
|
outputId={selectedViewField.id!}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
className={cn("flex w-full h-full",selectedViewField ? "hidden" : "")}
|
className={cn(
|
||||||
|
"flex h-full w-full",
|
||||||
|
selectedViewField ? "hidden" : ""
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<NewChatView
|
<NewChatView
|
||||||
sendMessage={sendMessage}
|
sendMessage={sendMessage}
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,6 @@ export default function BuildTrigger({
|
||||||
const nodes = useFlowStore((state) => state.nodes);
|
const nodes = useFlowStore((state) => state.nodes);
|
||||||
const edges = useFlowStore((state) => state.edges);
|
const edges = useFlowStore((state) => state.edges);
|
||||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
|
||||||
const setFlowState = useFlowStore((state) => state.setFlowState);
|
|
||||||
|
|
||||||
const eventClick = isBuilding ? "pointer-events-none" : "";
|
const eventClick = isBuilding ? "pointer-events-none" : "";
|
||||||
const [progress, setProgress] = useState(0);
|
const [progress, setProgress] = useState(0);
|
||||||
|
|
@ -47,7 +45,7 @@ export default function BuildTrigger({
|
||||||
setIsBuilding(true);
|
setIsBuilding(true);
|
||||||
|
|
||||||
await enforceMinimumLoadingTime(startTime, minimumLoadingTime);
|
await enforceMinimumLoadingTime(startTime, minimumLoadingTime);
|
||||||
await buildFlow();
|
await buildFlow({});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error:", error);
|
console.error("Error:", error);
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { Textarea } from "../../../components/ui/textarea";
|
||||||
import { chatInputType } from "../../../types/components";
|
import { chatInputType } from "../../../types/components";
|
||||||
import { classNames } from "../../../utils/utils";
|
import { classNames } from "../../../utils/utils";
|
||||||
import { chatInputPlaceholder, chatInputPlaceholderSend } from "../../../constants/constants";
|
import { chatInputPlaceholder, chatInputPlaceholderSend } from "../../../constants/constants";
|
||||||
|
import useFlowsManagerStore from "../../../stores/flowsManagerStore";
|
||||||
|
|
||||||
export default function ChatInput({
|
export default function ChatInput({
|
||||||
lockChat,
|
lockChat,
|
||||||
|
|
@ -14,20 +15,21 @@ export default function ChatInput({
|
||||||
noInput,
|
noInput,
|
||||||
}: chatInputType): JSX.Element {
|
}: chatInputType): JSX.Element {
|
||||||
const [repeat, setRepeat] = useState(1);
|
const [repeat, setRepeat] = useState(1);
|
||||||
|
const saveLoading = useFlowsManagerStore((state) => state.saveLoading);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!lockChat && inputRef.current) {
|
if (!lockChat && inputRef.current) {
|
||||||
inputRef.current.focus();
|
inputRef.current.focus();
|
||||||
}
|
}
|
||||||
}, [lockChat, inputRef]);
|
}, [lockChat, inputRef]);
|
||||||
|
|
||||||
function handleChange(value: number) {
|
/* function handleChange(value: number) {
|
||||||
console.log(value);
|
console.log(value);
|
||||||
if (value > 0) {
|
if (value > 0) {
|
||||||
setRepeat(value);
|
setRepeat(value);
|
||||||
} else {
|
} else {
|
||||||
setRepeat(1);
|
setRepeat(1);
|
||||||
}
|
}
|
||||||
}
|
} */
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (inputRef.current) {
|
if (inputRef.current) {
|
||||||
|
|
@ -41,13 +43,13 @@ export default function ChatInput({
|
||||||
<div className="relative w-full">
|
<div className="relative w-full">
|
||||||
<Textarea
|
<Textarea
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
if (event.key === "Enter" && !lockChat && !event.shiftKey) {
|
if (event.key === "Enter" && !lockChat && !saveLoading && !event.shiftKey) {
|
||||||
sendMessage(repeat);
|
sendMessage(repeat);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
rows={1}
|
rows={1}
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
disabled={lockChat || noInput}
|
disabled={lockChat || noInput || saveLoading}
|
||||||
style={{
|
style={{
|
||||||
resize: "none",
|
resize: "none",
|
||||||
bottom: `${inputRef?.current?.scrollHeight}px`,
|
bottom: `${inputRef?.current?.scrollHeight}px`,
|
||||||
|
|
@ -58,12 +60,12 @@ export default function ChatInput({
|
||||||
: "hidden"
|
: "hidden"
|
||||||
}`,
|
}`,
|
||||||
}}
|
}}
|
||||||
value={lockChat ? "Thinking..." : chatValue}
|
value={lockChat ? "Thinking..." : (saveLoading ? "Saving..." : chatValue)}
|
||||||
onChange={(event): void => {
|
onChange={(event): void => {
|
||||||
setChatValue(event.target.value);
|
setChatValue(event.target.value);
|
||||||
}}
|
}}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
lockChat
|
(lockChat || saveLoading)
|
||||||
? " form-modal-lock-true bg-input"
|
? " form-modal-lock-true bg-input"
|
||||||
: noInput
|
: noInput
|
||||||
? "form-modal-no-input bg-input"
|
? "form-modal-no-input bg-input"
|
||||||
|
|
@ -87,10 +89,10 @@ export default function ChatInput({
|
||||||
? "text-primary"
|
? "text-primary"
|
||||||
: "bg-chat-send text-background"
|
: "bg-chat-send text-background"
|
||||||
)}
|
)}
|
||||||
disabled={lockChat}
|
disabled={lockChat || saveLoading}
|
||||||
onClick={(): void => sendMessage(repeat)}
|
onClick={(): void => sendMessage(repeat)}
|
||||||
>
|
>
|
||||||
{lockChat ? (
|
{lockChat || saveLoading ? (
|
||||||
<IconComponent
|
<IconComponent
|
||||||
name="Lock"
|
name="Lock"
|
||||||
className="form-modal-lock-icon"
|
className="form-modal-lock-icon"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import Convert from "ansi-to-html";
|
import Convert from "ansi-to-html";
|
||||||
import { useEffect, useMemo, useState, useRef } from "react";
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import Markdown from "react-markdown";
|
import Markdown from "react-markdown";
|
||||||
import rehypeMathjax from "rehype-mathjax";
|
import rehypeMathjax from "rehype-mathjax";
|
||||||
import remarkGfm from "remark-gfm";
|
import remarkGfm from "remark-gfm";
|
||||||
|
|
@ -9,17 +9,17 @@ import Robot from "../../../assets/robot.png";
|
||||||
import SanitizedHTMLWrapper from "../../../components/SanitizedHTMLWrapper";
|
import SanitizedHTMLWrapper from "../../../components/SanitizedHTMLWrapper";
|
||||||
import CodeTabsComponent from "../../../components/codeTabsComponent";
|
import CodeTabsComponent from "../../../components/codeTabsComponent";
|
||||||
import IconComponent from "../../../components/genericIconComponent";
|
import IconComponent from "../../../components/genericIconComponent";
|
||||||
|
import useFlowStore from "../../../stores/flowStore";
|
||||||
import { chatMessagePropsType } from "../../../types/components";
|
import { chatMessagePropsType } from "../../../types/components";
|
||||||
import { classNames } from "../../../utils/utils";
|
import { classNames } from "../../../utils/utils";
|
||||||
import FileCard from "../fileComponent";
|
import FileCard from "../fileComponent";
|
||||||
import useFlowStore from "../../../stores/flowStore";
|
|
||||||
|
|
||||||
export default function ChatMessage({
|
export default function ChatMessage({
|
||||||
chat,
|
chat,
|
||||||
lockChat,
|
lockChat,
|
||||||
lastMessage,
|
lastMessage,
|
||||||
updateChat,
|
updateChat,
|
||||||
setLockChat
|
setLockChat,
|
||||||
}: chatMessagePropsType): JSX.Element {
|
}: chatMessagePropsType): JSX.Element {
|
||||||
const convert = new Convert({ newline: true });
|
const convert = new Convert({ newline: true });
|
||||||
const [hidden, setHidden] = useState(true);
|
const [hidden, setHidden] = useState(true);
|
||||||
|
|
@ -40,8 +40,6 @@ export default function ChatMessage({
|
||||||
chatMessageRef.current = chatMessage;
|
chatMessageRef.current = chatMessage;
|
||||||
}, [chatMessage]);
|
}, [chatMessage]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// The idea now is that chat.stream_url MAY be a URL if we should stream the output of the chat
|
// The idea now is that chat.stream_url MAY be a URL if we should stream the output of the chat
|
||||||
// probably the message is empty when we have a stream_url
|
// probably the message is empty when we have a stream_url
|
||||||
// what we need is to update the chat_message with the SSE data
|
// what we need is to update the chat_message with the SSE data
|
||||||
|
|
@ -70,9 +68,7 @@ export default function ChatMessage({
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log("chatMessage", chatMessage);
|
|
||||||
if (streamUrl && !isStreaming) {
|
if (streamUrl && !isStreaming) {
|
||||||
setLockChat(true);
|
setLockChat(true);
|
||||||
streamChunks(streamUrl)
|
streamChunks(streamUrl)
|
||||||
|
|
@ -92,8 +88,8 @@ export default function ChatMessage({
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
eventSource.current?.close();
|
eventSource.current?.close();
|
||||||
}
|
};
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const element = document.getElementById("last-chat-message");
|
const element = document.getElementById("last-chat-message");
|
||||||
|
|
@ -222,7 +218,7 @@ dark:prose-invert"
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
activeTab={"0"}
|
activeTab={"0"}
|
||||||
setActiveTab={() => { }}
|
setActiveTab={() => {}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<code className={className} {...props}>
|
<code className={className} {...props}>
|
||||||
|
|
@ -279,33 +275,33 @@ dark:prose-invert"
|
||||||
<span className="prose text-primary word-break-break-word dark:prose-invert">
|
<span className="prose text-primary word-break-break-word dark:prose-invert">
|
||||||
{promptOpen
|
{promptOpen
|
||||||
? template?.split("\n")?.map((line, index) => {
|
? template?.split("\n")?.map((line, index) => {
|
||||||
const regex = /{([^}]+)}/g;
|
const regex = /{([^}]+)}/g;
|
||||||
let match;
|
let match;
|
||||||
let parts: Array<JSX.Element | string> = [];
|
let parts: Array<JSX.Element | string> = [];
|
||||||
let lastIndex = 0;
|
let lastIndex = 0;
|
||||||
while ((match = regex.exec(line)) !== null) {
|
while ((match = regex.exec(line)) !== null) {
|
||||||
// Push text up to the match
|
// Push text up to the match
|
||||||
if (match.index !== lastIndex) {
|
if (match.index !== lastIndex) {
|
||||||
parts.push(line.substring(lastIndex, match.index));
|
parts.push(line.substring(lastIndex, match.index));
|
||||||
}
|
}
|
||||||
// Push div with matched text
|
// Push div with matched text
|
||||||
if (chat.message[match[1]]) {
|
if (chat.message[match[1]]) {
|
||||||
parts.push(
|
parts.push(
|
||||||
<span className="chat-message-highlight">
|
<span className="chat-message-highlight">
|
||||||
{chat.message[match[1]]}
|
{chat.message[match[1]]}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update last index
|
// Update last index
|
||||||
lastIndex = regex.lastIndex;
|
lastIndex = regex.lastIndex;
|
||||||
}
|
}
|
||||||
// Push text after the last match
|
// Push text after the last match
|
||||||
if (lastIndex !== line.length) {
|
if (lastIndex !== line.length) {
|
||||||
parts.push(line.substring(lastIndex));
|
parts.push(line.substring(lastIndex));
|
||||||
}
|
}
|
||||||
return <p>{parts}</p>;
|
return <p>{parts}</p>;
|
||||||
})
|
})
|
||||||
: chatMessage}
|
: chatMessage}
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
import _ from "lodash";
|
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import IconComponent from "../../components/genericIconComponent";
|
import IconComponent from "../../components/genericIconComponent";
|
||||||
|
import { NOCHATOUTPUT_NOTICE_ALERT } from "../../constants/alerts_constants";
|
||||||
|
import {
|
||||||
|
chatFirstInitialText,
|
||||||
|
chatSecondInitialText,
|
||||||
|
} from "../../constants/constants";
|
||||||
import { deleteFlowPool } from "../../controllers/API";
|
import { deleteFlowPool } from "../../controllers/API";
|
||||||
import useAlertStore from "../../stores/alertStore";
|
import useAlertStore from "../../stores/alertStore";
|
||||||
import useFlowStore from "../../stores/flowStore";
|
import useFlowStore from "../../stores/flowStore";
|
||||||
|
|
@ -14,8 +18,6 @@ import {
|
||||||
import { classNames } from "../../utils/utils";
|
import { classNames } from "../../utils/utils";
|
||||||
import ChatInput from "./chatInput";
|
import ChatInput from "./chatInput";
|
||||||
import ChatMessage from "./chatMessage";
|
import ChatMessage from "./chatMessage";
|
||||||
import { INFO_MISSING_ALERT, NOCHATOUTPUT_NOTICE_ALERT } from "../../constants/alerts_constants";
|
|
||||||
import { chatFirstInitialText, chatSecondInitialText } from "../../constants/constants";
|
|
||||||
|
|
||||||
export default function NewChatView({
|
export default function NewChatView({
|
||||||
sendMessage,
|
sendMessage,
|
||||||
|
|
@ -34,7 +36,7 @@ export default function NewChatView({
|
||||||
const inputIds = inputs.map((obj) => obj.id);
|
const inputIds = inputs.map((obj) => obj.id);
|
||||||
const outputIds = outputs.map((obj) => obj.id);
|
const outputIds = outputs.map((obj) => obj.id);
|
||||||
const outputTypes = outputs.map((obj) => obj.type);
|
const outputTypes = outputs.map((obj) => obj.type);
|
||||||
const updateFlowPool = useFlowStore((state)=>state.updateFlowPool)
|
const updateFlowPool = useFlowStore((state) => state.updateFlowPool);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!outputTypes.includes("ChatOutput")) {
|
if (!outputTypes.includes("ChatOutput")) {
|
||||||
|
|
@ -73,7 +75,7 @@ export default function NewChatView({
|
||||||
isSend: !is_ai,
|
isSend: !is_ai,
|
||||||
message: message,
|
message: message,
|
||||||
sender_name,
|
sender_name,
|
||||||
componentId: output.id,
|
componentId: output.id,
|
||||||
stream_url: stream_url,
|
stream_url: stream_url,
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -120,22 +122,26 @@ export default function NewChatView({
|
||||||
chat: ChatMessageType,
|
chat: ChatMessageType,
|
||||||
message: string,
|
message: string,
|
||||||
stream_url?: string
|
stream_url?: string
|
||||||
) {
|
) {
|
||||||
if (message === "") return;
|
if (message === "") return;
|
||||||
chat.message = message;
|
chat.message = message;
|
||||||
// chat is one of the chatHistory
|
// chat is one of the chatHistory
|
||||||
updateFlowPool(chat.componentId,{message,sender_name:chat.sender_name??"Bot",sender:"Machine"})
|
updateFlowPool(chat.componentId, {
|
||||||
|
message,
|
||||||
|
sender_name: chat.sender_name ?? "Bot",
|
||||||
|
sender: chat.isSend ? "User" : "Machine",
|
||||||
|
});
|
||||||
// setChatHistory((oldChatHistory) => {
|
// setChatHistory((oldChatHistory) => {
|
||||||
// const index = oldChatHistory.findIndex((ch) => ch.id === chat.id);
|
// const index = oldChatHistory.findIndex((ch) => ch.id === chat.id);
|
||||||
// if (index === -1) return oldChatHistory;
|
// if (index === -1) return oldChatHistory;
|
||||||
// let newChatHistory = _.cloneDeep(oldChatHistory);
|
// let newChatHistory = _.cloneDeep(oldChatHistory);
|
||||||
// newChatHistory = [
|
// newChatHistory = [
|
||||||
// ...newChatHistory.slice(0, index),
|
// ...newChatHistory.slice(0, index),
|
||||||
// chat,
|
// chat,
|
||||||
// ...newChatHistory.slice(index + 1),
|
// ...newChatHistory.slice(index + 1),
|
||||||
// ];
|
// ];
|
||||||
// console.log("newChatHistory:", newChatHistory);
|
// console.log("newChatHistory:", newChatHistory);
|
||||||
// return newChatHistory;
|
// return newChatHistory;
|
||||||
// });
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -160,7 +166,7 @@ export default function NewChatView({
|
||||||
{chatHistory?.length > 0 ? (
|
{chatHistory?.length > 0 ? (
|
||||||
chatHistory.map((chat, index) => (
|
chatHistory.map((chat, index) => (
|
||||||
<ChatMessage
|
<ChatMessage
|
||||||
setLockChat={setLockChat}
|
setLockChat={setLockChat}
|
||||||
lockChat={lockChat}
|
lockChat={lockChat}
|
||||||
chat={chat}
|
chat={chat}
|
||||||
lastMessage={chatHistory.length - 1 === index ? true : false}
|
lastMessage={chatHistory.length - 1 === index ? true : false}
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,7 @@ function ApiInterceptor() {
|
||||||
async function clearBuildVerticesState(error) {
|
async function clearBuildVerticesState(error) {
|
||||||
if (error?.response?.status === 500) {
|
if (error?.response?.status === 500) {
|
||||||
const vertices = useFlowStore.getState().verticesBuild;
|
const vertices = useFlowStore.getState().verticesBuild;
|
||||||
useFlowStore.getState().updateBuildStatus(vertices, BuildStatus.BUILT);
|
useFlowStore.getState().updateBuildStatus(vertices?.verticesIds ?? [], BuildStatus.BUILT);
|
||||||
useFlowStore.getState().setIsBuilding(false);
|
useFlowStore.getState().setIsBuilding(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -869,9 +869,10 @@ export async function getVerticesOrder(
|
||||||
|
|
||||||
export async function postBuildVertex(
|
export async function postBuildVertex(
|
||||||
flowId: string,
|
flowId: string,
|
||||||
vertexId: string
|
vertexId: string,
|
||||||
|
input_value: string,
|
||||||
): Promise<AxiosResponse<VertexBuildTypeAPI>> {
|
): Promise<AxiosResponse<VertexBuildTypeAPI>> {
|
||||||
return await api.post(`${BASE_URL_API}build/${flowId}/vertices/${vertexId}`);
|
return await api.post(`${BASE_URL_API}build/${flowId}/vertices/${vertexId}`, input_value ? {inputs: {input_value: input_value}} : undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function downloadImage({ flowId, fileName }): Promise<any> {
|
export async function downloadImage({ flowId, fileName }): Promise<any> {
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 156 KiB After Width: | Height: | Size: 406 KiB |
|
|
@ -1,5 +1,6 @@
|
||||||
import _, { cloneDeep } from "lodash";
|
import _, { cloneDeep } from "lodash";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { useUpdateNodeInternals } from "reactflow";
|
||||||
import ShadTooltip from "../../../../components/ShadTooltipComponent";
|
import ShadTooltip from "../../../../components/ShadTooltipComponent";
|
||||||
import CodeAreaComponent from "../../../../components/codeAreaComponent";
|
import CodeAreaComponent from "../../../../components/codeAreaComponent";
|
||||||
import IconComponent from "../../../../components/genericIconComponent";
|
import IconComponent from "../../../../components/genericIconComponent";
|
||||||
|
|
@ -26,7 +27,6 @@ import {
|
||||||
updateFlowPosition,
|
updateFlowPosition,
|
||||||
} from "../../../../utils/reactflowUtils";
|
} from "../../../../utils/reactflowUtils";
|
||||||
import { classNames, cn } from "../../../../utils/utils";
|
import { classNames, cn } from "../../../../utils/utils";
|
||||||
import { useUpdateNodeInternals } from "reactflow";
|
|
||||||
|
|
||||||
export default function NodeToolbarComponent({
|
export default function NodeToolbarComponent({
|
||||||
data,
|
data,
|
||||||
|
|
@ -94,11 +94,12 @@ export default function NodeToolbarComponent({
|
||||||
const handleModalWShortcut = useFlowStore(state => state.handleModalWShortcut);
|
const handleModalWShortcut = useFlowStore(state => state.handleModalWShortcut);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(openCodeModalWShortcut)
|
|
||||||
setOpenModal(openCodeModalWShortcut)
|
setOpenModal(openCodeModalWShortcut)
|
||||||
}, [openCodeModalWShortcut, handleModalWShortcut])
|
}, [openCodeModalWShortcut, handleModalWShortcut])
|
||||||
|
|
||||||
const setLastCopiedSelection = useFlowStore(state => state.setLastCopiedSelection);
|
const setLastCopiedSelection = useFlowStore(
|
||||||
|
(state) => state.setLastCopiedSelection
|
||||||
|
);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setFlowComponent(createFlowComponent(cloneDeep(data), version));
|
setFlowComponent(createFlowComponent(cloneDeep(data), version));
|
||||||
}, [
|
}, [
|
||||||
|
|
@ -153,8 +154,8 @@ export default function NodeToolbarComponent({
|
||||||
deleteNode(data.id);
|
deleteNode(data.id);
|
||||||
break;
|
break;
|
||||||
case "copy":
|
case "copy":
|
||||||
const node = nodes.filter(node => node.id === data.id)
|
const node = nodes.filter((node) => node.id === data.id);
|
||||||
setLastCopiedSelection({ nodes: _.cloneDeep(node), edges: [] })
|
setLastCopiedSelection({ nodes: _.cloneDeep(node), edges: [] });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -242,7 +243,7 @@ export default function NodeToolbarComponent({
|
||||||
id={"code-input-node-toolbar-" + name}
|
id={"code-input-node-toolbar-" + name}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<IconComponent name="Code" className="h-4 w-4" />
|
<IconComponent name="TerminalSquare" className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</ShadTooltip>
|
</ShadTooltip>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -380,13 +381,11 @@ export default function NodeToolbarComponent({
|
||||||
className="relative top-0.5 mr-2 h-4 w-4 "
|
className="relative top-0.5 mr-2 h-4 w-4 "
|
||||||
/>{" "}
|
/>{" "}
|
||||||
<span className="">Copy</span>{" "}
|
<span className="">Copy</span>{" "}
|
||||||
|
<IconComponent
|
||||||
<IconComponent
|
name="Command"
|
||||||
name="Command"
|
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
||||||
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
></IconComponent>
|
||||||
></IconComponent>
|
<span className="absolute right-2 top-[0.5em]">C</span>
|
||||||
<span className="absolute right-2 top-[0.5em]">C</span>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
{hasStore && (
|
{hasStore && (
|
||||||
|
|
@ -459,7 +458,7 @@ export default function NodeToolbarComponent({
|
||||||
<span>
|
<span>
|
||||||
<IconComponent
|
<IconComponent
|
||||||
name="Delete"
|
name="Delete"
|
||||||
className="absolute right-2 top-2 h-4 w-4 text-red-400 stroke-2"
|
className="absolute right-2 top-2 h-4 w-4 stroke-2 text-red-400"
|
||||||
></IconComponent>
|
></IconComponent>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,12 @@ import {
|
||||||
applyNodeChanges,
|
applyNodeChanges,
|
||||||
} from "reactflow";
|
} from "reactflow";
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { FLOW_BUILD_SUCCESS_ALERT, MISSED_ERROR_ALERT } from "../constants/alerts_constants";
|
import {
|
||||||
|
FLOW_BUILD_SUCCESS_ALERT,
|
||||||
|
MISSED_ERROR_ALERT,
|
||||||
|
} from "../constants/alerts_constants";
|
||||||
import { BuildStatus } from "../constants/enums";
|
import { BuildStatus } from "../constants/enums";
|
||||||
import { getFlowPool, updateFlowInDatabase } from "../controllers/API";
|
import { getFlowPool } from "../controllers/API";
|
||||||
import { VertexBuildTypeAPI } from "../types/api";
|
import { VertexBuildTypeAPI } from "../types/api";
|
||||||
import {
|
import {
|
||||||
NodeDataType,
|
NodeDataType,
|
||||||
|
|
@ -19,7 +22,12 @@ import {
|
||||||
sourceHandleType,
|
sourceHandleType,
|
||||||
targetHandleType,
|
targetHandleType,
|
||||||
} from "../types/flow";
|
} from "../types/flow";
|
||||||
import { ChatOutputType, FlowPoolObjectType, FlowStoreType, chatInputType } from "../types/zustand/flow";
|
import {
|
||||||
|
ChatOutputType,
|
||||||
|
FlowPoolObjectType,
|
||||||
|
FlowStoreType,
|
||||||
|
chatInputType,
|
||||||
|
} from "../types/zustand/flow";
|
||||||
import { buildVertices } from "../utils/buildUtils";
|
import { buildVertices } from "../utils/buildUtils";
|
||||||
import {
|
import {
|
||||||
cleanEdges,
|
cleanEdges,
|
||||||
|
|
@ -69,23 +77,25 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
||||||
}
|
}
|
||||||
get().setFlowPool(newFlowPool);
|
get().setFlowPool(newFlowPool);
|
||||||
},
|
},
|
||||||
updateFlowPool:(nodeId:string,data:FlowPoolObjectType| ChatOutputType | chatInputType,buildId?:string)=>{
|
updateFlowPool: (
|
||||||
|
nodeId: string,
|
||||||
|
data: FlowPoolObjectType | ChatOutputType | chatInputType,
|
||||||
|
buildId?: string
|
||||||
|
) => {
|
||||||
let newFlowPool = cloneDeep({ ...get().flowPool });
|
let newFlowPool = cloneDeep({ ...get().flowPool });
|
||||||
if (!newFlowPool[nodeId]){
|
if (!newFlowPool[nodeId]) {
|
||||||
return;
|
return;
|
||||||
}
|
} else {
|
||||||
else {
|
let index = newFlowPool[nodeId].length - 1;
|
||||||
let index = newFlowPool[nodeId].length-1;
|
if (buildId) {
|
||||||
if(buildId){
|
index = newFlowPool[nodeId].findIndex((flow) => flow.id === buildId);
|
||||||
index = newFlowPool[nodeId].findIndex((flow)=>flow.id===buildId);
|
|
||||||
}
|
}
|
||||||
//check if the data is a flowpool object
|
//check if the data is a flowpool object
|
||||||
if((data as FlowPoolObjectType).data?.artifacts!==undefined){
|
if ((data as FlowPoolObjectType).data?.artifacts !== undefined) {
|
||||||
newFlowPool[nodeId][index] = (data as FlowPoolObjectType);
|
newFlowPool[nodeId][index] = data as FlowPoolObjectType;
|
||||||
}
|
}
|
||||||
//update data artifact
|
//update data artifact
|
||||||
else
|
else {
|
||||||
{
|
|
||||||
newFlowPool[nodeId][index].data.artifacts = data;
|
newFlowPool[nodeId][index].data.artifacts = data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -404,7 +414,13 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
buildFlow: async (nodeId?: string) => {
|
buildFlow: async ({
|
||||||
|
nodeId,
|
||||||
|
input_value,
|
||||||
|
}: {
|
||||||
|
nodeId?: string;
|
||||||
|
input_value?: string;
|
||||||
|
}) => {
|
||||||
get().setIsBuilding(true);
|
get().setIsBuilding(true);
|
||||||
const currentFlow = useFlowsManagerStore.getState().currentFlow;
|
const currentFlow = useFlowsManagerStore.getState().currentFlow;
|
||||||
const setSuccessData = useAlertStore.getState().setSuccessData;
|
const setSuccessData = useAlertStore.getState().setSuccessData;
|
||||||
|
|
@ -427,25 +443,19 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
||||||
function handleBuildUpdate(
|
function handleBuildUpdate(
|
||||||
vertexBuildData: VertexBuildTypeAPI,
|
vertexBuildData: VertexBuildTypeAPI,
|
||||||
status: BuildStatus,
|
status: BuildStatus,
|
||||||
buildId:string
|
buildId: string
|
||||||
) {
|
) {
|
||||||
if (vertexBuildData && vertexBuildData.inactive_vertices) {
|
if (vertexBuildData && vertexBuildData.inactive_vertices) {
|
||||||
get().removeFromVerticesBuild(vertexBuildData.inactive_vertices);
|
get().removeFromVerticesBuild(vertexBuildData.inactive_vertices);
|
||||||
}
|
}
|
||||||
get().addDataToFlowPool({...vertexBuildData,buildId}, vertexBuildData.id);
|
get().addDataToFlowPool(
|
||||||
|
{ ...vertexBuildData, buildId },
|
||||||
|
vertexBuildData.id
|
||||||
|
);
|
||||||
useFlowStore.getState().updateBuildStatus([vertexBuildData.id], status);
|
useFlowStore.getState().updateBuildStatus([vertexBuildData.id], status);
|
||||||
}
|
}
|
||||||
await updateFlowInDatabase({
|
|
||||||
data: {
|
|
||||||
nodes: get().nodes,
|
|
||||||
edges: get().edges,
|
|
||||||
viewport: get().reactFlowInstance?.getViewport()!,
|
|
||||||
},
|
|
||||||
id: currentFlow!.id,
|
|
||||||
name: currentFlow!.name,
|
|
||||||
description: currentFlow!.description,
|
|
||||||
});
|
|
||||||
await buildVertices({
|
await buildVertices({
|
||||||
|
input_value,
|
||||||
flowId: currentFlow!.id,
|
flowId: currentFlow!.id,
|
||||||
nodeId,
|
nodeId,
|
||||||
onGetOrderSuccess: () => {
|
onGetOrderSuccess: () => {
|
||||||
|
|
@ -483,16 +493,22 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
||||||
viewport: get().reactFlowInstance?.getViewport()!,
|
viewport: get().reactFlowInstance?.getViewport()!,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
updateVerticesBuild: (vertices: string[]) => {
|
updateVerticesBuild: (
|
||||||
|
vertices: { verticesIds: string[], verticesOrder: string[][], verticesLayers: string[][], runId: string } | null
|
||||||
|
) => {
|
||||||
set({ verticesBuild: vertices });
|
set({ verticesBuild: vertices });
|
||||||
},
|
},
|
||||||
verticesBuild: [],
|
verticesBuild: null,
|
||||||
|
|
||||||
removeFromVerticesBuild: (vertices: string[]) => {
|
removeFromVerticesBuild: (vertices: string[]) => {
|
||||||
|
const verticesBuild = get().verticesBuild;
|
||||||
|
if (!verticesBuild) return;
|
||||||
set({
|
set({
|
||||||
verticesBuild: get().verticesBuild.filter(
|
verticesBuild: {
|
||||||
(vertex) => !vertices.includes(vertex)
|
...verticesBuild,
|
||||||
),
|
verticesIds: get().verticesBuild!.verticesIds.filter(
|
||||||
|
(vertex) => !vertices.includes(vertex)
|
||||||
|
),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
updateBuildStatus: (nodeIdList: string[], status: BuildStatus) => {
|
updateBuildStatus: (nodeIdList: string[], status: BuildStatus) => {
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,7 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
|
||||||
if (saveTimeoutId) {
|
if (saveTimeoutId) {
|
||||||
clearTimeout(saveTimeoutId);
|
clearTimeout(saveTimeoutId);
|
||||||
}
|
}
|
||||||
|
set({ saveLoading: true });
|
||||||
// Set up a new timeout.
|
// Set up a new timeout.
|
||||||
saveTimeoutId = setTimeout(() => {
|
saveTimeoutId = setTimeout(() => {
|
||||||
if (get().currentFlow) {
|
if (get().currentFlow) {
|
||||||
|
|
@ -92,7 +93,7 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
set({ saveLoading: true });
|
set({ saveLoading: true });
|
||||||
}, 1000); // Delay of 1000ms.
|
}, 500); // Delay of 500ms because chat message depends on it.
|
||||||
},
|
},
|
||||||
saveFlow: (flow: FlowType, silent?: boolean) => {
|
saveFlow: (flow: FlowType, silent?: boolean) => {
|
||||||
set({ saveLoading: true });
|
set({ saveLoading: true });
|
||||||
|
|
|
||||||
|
|
@ -88,11 +88,11 @@ export type FlowStoreType = {
|
||||||
getFilterEdge: any[];
|
getFilterEdge: any[];
|
||||||
onConnect: (connection: Connection) => void;
|
onConnect: (connection: Connection) => void;
|
||||||
unselectAll: () => void;
|
unselectAll: () => void;
|
||||||
buildFlow: (nodeId?: string) => Promise<void>;
|
buildFlow: ({nodeId, input_value}: {nodeId?: string, input_value?: string}) => Promise<void>;
|
||||||
getFlow: () => { nodes: Node[]; edges: Edge[]; viewport: Viewport };
|
getFlow: () => { nodes: Node[]; edges: Edge[]; viewport: Viewport };
|
||||||
updateVerticesBuild: (vertices: string[]) => void;
|
updateVerticesBuild: (vertices: {verticesIds: string[], verticesLayers: string[][], verticesOrder: string[][], runId: string} | null) => void;
|
||||||
removeFromVerticesBuild: (vertices: string[]) => void;
|
removeFromVerticesBuild: (vertices: string[]) => void;
|
||||||
verticesBuild: string[];
|
verticesBuild: {verticesIds: string[], verticesLayers: string[][], verticesOrder: string[][], runId: string} | null;
|
||||||
updateBuildStatus: (nodeId: string[], status: BuildStatus) => void;
|
updateBuildStatus: (nodeId: string[], status: BuildStatus) => void;
|
||||||
revertBuiltStatusFromBuilding: () => void;
|
revertBuiltStatusFromBuilding: () => void;
|
||||||
flowBuildStatus: { [key: string]: BuildStatus };
|
flowBuildStatus: { [key: string]: BuildStatus };
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,14 @@ import { VertexBuildTypeAPI } from "../types/api";
|
||||||
|
|
||||||
type BuildVerticesParams = {
|
type BuildVerticesParams = {
|
||||||
flowId: string; // Assuming FlowType is the type for your flow
|
flowId: string; // Assuming FlowType is the type for your flow
|
||||||
|
input_value?: any; // Replace any with the actual type if it's not any
|
||||||
nodeId?: string | null; // Assuming nodeId is of type string, and it's optional
|
nodeId?: string | null; // Assuming nodeId is of type string, and it's optional
|
||||||
onGetOrderSuccess?: () => void;
|
onGetOrderSuccess?: () => void;
|
||||||
onBuildUpdate?: (data: VertexBuildTypeAPI, status: BuildStatus,buildId:string) => void; // Replace any with the actual type if it's not any
|
onBuildUpdate?: (
|
||||||
|
data: VertexBuildTypeAPI,
|
||||||
|
status: BuildStatus,
|
||||||
|
buildId: string
|
||||||
|
) => void; // Replace any with the actual type if it's not any
|
||||||
onBuildComplete?: (allNodesValid: boolean) => void;
|
onBuildComplete?: (allNodesValid: boolean) => void;
|
||||||
onBuildError?: (title, list, idList: string[]) => void;
|
onBuildError?: (title, list, idList: string[]) => void;
|
||||||
onBuildStart?: (idList: string[]) => void;
|
onBuildStart?: (idList: string[]) => void;
|
||||||
|
|
@ -34,8 +39,54 @@ function getInactiveVertexData(vertexId: string): VertexBuildTypeAPI {
|
||||||
return inactiveVertexData;
|
return inactiveVertexData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function updateVerticesOrder(flowId: string, nodeId: string | null): Promise<{ verticesLayers: string[][], verticesIds: string[], verticesOrder: string[][], runId: string }> {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
const setErrorData = useAlertStore.getState().setErrorData;
|
||||||
|
let orderResponse;
|
||||||
|
try {
|
||||||
|
orderResponse = await getVerticesOrder(flowId, nodeId);
|
||||||
|
} catch (error: any) {
|
||||||
|
console.log(error);
|
||||||
|
setErrorData({
|
||||||
|
title: "Oops! Looks like you missed something",
|
||||||
|
list: [error.response?.data?.detail ?? "Unknown Error"],
|
||||||
|
});
|
||||||
|
useFlowStore.getState().setIsBuilding(false);
|
||||||
|
throw new Error("Invalid nodes");
|
||||||
|
}
|
||||||
|
let verticesOrder: Array<Array<string>> = orderResponse.data.ids;
|
||||||
|
const runId = orderResponse.data.run_id;
|
||||||
|
let verticesLayers: Array<Array<string>> = [];
|
||||||
|
|
||||||
|
if (nodeId) {
|
||||||
|
for (let i = 0; i < verticesOrder.length; i += 1) {
|
||||||
|
const innerArray = verticesOrder[i];
|
||||||
|
const idIndex = innerArray.indexOf(nodeId);
|
||||||
|
if (idIndex !== -1) {
|
||||||
|
// If there's a nodeId, we want to run just that component and not the entire layer
|
||||||
|
// because a layer contains dependencies for the next layer
|
||||||
|
// and we are stopping at the layer that contains the nodeId
|
||||||
|
verticesLayers.push([innerArray[idIndex]]);
|
||||||
|
break; // Stop searching after finding the first occurrence
|
||||||
|
}
|
||||||
|
// If the targetId is not found, include the entire inner array
|
||||||
|
verticesLayers.push(innerArray);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
verticesLayers = verticesOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
const verticesIds = verticesLayers.flat();
|
||||||
|
useFlowStore
|
||||||
|
.getState()
|
||||||
|
.updateVerticesBuild({ verticesLayers, verticesIds, verticesOrder, runId });
|
||||||
|
resolve({ verticesLayers, verticesIds, verticesOrder, runId });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function buildVertices({
|
export async function buildVertices({
|
||||||
flowId,
|
flowId,
|
||||||
|
input_value,
|
||||||
nodeId = null,
|
nodeId = null,
|
||||||
onGetOrderSuccess,
|
onGetOrderSuccess,
|
||||||
onBuildUpdate,
|
onBuildUpdate,
|
||||||
|
|
@ -44,24 +95,18 @@ export async function buildVertices({
|
||||||
onBuildStart,
|
onBuildStart,
|
||||||
validateNodes,
|
validateNodes,
|
||||||
}: BuildVerticesParams) {
|
}: BuildVerticesParams) {
|
||||||
const setErrorData = useAlertStore.getState().setErrorData;
|
let verticesBuild = useFlowStore.getState().verticesBuild;
|
||||||
let orderResponse;
|
if (!verticesBuild || nodeId) {
|
||||||
try {
|
verticesBuild = await updateVerticesOrder(flowId, nodeId);
|
||||||
orderResponse = await getVerticesOrder(flowId, nodeId);
|
|
||||||
} catch (error:any) {
|
|
||||||
console.log(error);
|
|
||||||
setErrorData({
|
|
||||||
title: "Oops! Looks like you missed something",
|
|
||||||
list: [error.response?.data?.detail ?? "Unknown Error"],
|
|
||||||
});
|
|
||||||
useFlowStore.getState().setIsBuilding(false);
|
|
||||||
throw new Error("Invalid nodes");
|
|
||||||
}
|
}
|
||||||
if (onGetOrderSuccess) onGetOrderSuccess();
|
const verticesIds = verticesBuild?.verticesIds!;
|
||||||
let verticesOrder: Array<Array<string>> = orderResponse.data.ids;
|
const verticesLayers = verticesBuild?.verticesLayers!;
|
||||||
const runId = orderResponse.data.run_id;
|
const verticesOrder = verticesBuild?.verticesOrder!;
|
||||||
let vertices_layers: Array<Array<string>> = [];
|
const runId = verticesBuild?.runId!;
|
||||||
let stop = false;
|
let stop = false;
|
||||||
|
|
||||||
|
if (onGetOrderSuccess) onGetOrderSuccess();
|
||||||
|
|
||||||
if (validateNodes) {
|
if (validateNodes) {
|
||||||
try {
|
try {
|
||||||
validateNodes(verticesOrder.flatMap((id) => id));
|
validateNodes(verticesOrder.flatMap((id) => id));
|
||||||
|
|
@ -69,48 +114,29 @@ export async function buildVertices({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (nodeId) {
|
|
||||||
for (let i = 0; i < verticesOrder.length; i += 1) {
|
|
||||||
const innerArray = verticesOrder[i];
|
|
||||||
const idIndex = innerArray.indexOf(nodeId);
|
|
||||||
if (idIndex !== -1) {
|
|
||||||
// If there's a nodeId, we want to run just that component and not the entire layer
|
|
||||||
// because a layer contains dependencies for the next layer
|
|
||||||
// and we are stopping at the layer that contains the nodeId
|
|
||||||
vertices_layers.push([innerArray[idIndex]]);
|
|
||||||
break; // Stop searching after finding the first occurrence
|
|
||||||
}
|
|
||||||
// If the targetId is not found, include the entire inner array
|
|
||||||
vertices_layers.push(innerArray);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
vertices_layers = verticesOrder;
|
|
||||||
}
|
|
||||||
|
|
||||||
const verticesIds = vertices_layers.flat();
|
|
||||||
useFlowStore.getState().updateBuildStatus(verticesIds, BuildStatus.TO_BUILD);
|
useFlowStore.getState().updateBuildStatus(verticesIds, BuildStatus.TO_BUILD);
|
||||||
useFlowStore.getState().updateVerticesBuild(verticesIds);
|
|
||||||
useFlowStore.getState().setIsBuilding(true);
|
useFlowStore.getState().setIsBuilding(true);
|
||||||
|
|
||||||
// Set each vertex state to building
|
// Set each vertex state to building
|
||||||
const buildResults: Array<boolean> = [];
|
const buildResults: Array<boolean> = [];
|
||||||
for (const layer of vertices_layers) {
|
for (const layer of verticesLayers) {
|
||||||
if (onBuildStart) onBuildStart(layer);
|
if (onBuildStart) onBuildStart(layer);
|
||||||
for (const id of layer) {
|
for (const id of layer) {
|
||||||
// Check if id is in the list of inactive nodes
|
// Check if id is in the list of inactive nodes
|
||||||
if (
|
if (!verticesIds.includes(id) && onBuildUpdate) {
|
||||||
!useFlowStore.getState().verticesBuild.includes(id) &&
|
|
||||||
onBuildUpdate
|
|
||||||
) {
|
|
||||||
// If it is, skip building and set the state to inactive
|
// If it is, skip building and set the state to inactive
|
||||||
onBuildUpdate(getInactiveVertexData(id), BuildStatus.INACTIVE,runId);
|
onBuildUpdate(getInactiveVertexData(id), BuildStatus.INACTIVE, runId);
|
||||||
buildResults.push(false);
|
buildResults.push(false);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
await buildVertex({
|
await buildVertex({
|
||||||
flowId,
|
flowId,
|
||||||
id,
|
id,
|
||||||
onBuildUpdate:(data: VertexBuildTypeAPI, status: BuildStatus) => {if(onBuildUpdate) onBuildUpdate(data, status,runId)},
|
input_value,
|
||||||
|
onBuildUpdate: (data: VertexBuildTypeAPI, status: BuildStatus) => {
|
||||||
|
if (onBuildUpdate) onBuildUpdate(data, status, runId);
|
||||||
|
},
|
||||||
onBuildError,
|
onBuildError,
|
||||||
verticesIds,
|
verticesIds,
|
||||||
buildResults,
|
buildResults,
|
||||||
|
|
@ -137,6 +163,7 @@ export async function buildVertices({
|
||||||
async function buildVertex({
|
async function buildVertex({
|
||||||
flowId,
|
flowId,
|
||||||
id,
|
id,
|
||||||
|
input_value,
|
||||||
onBuildUpdate,
|
onBuildUpdate,
|
||||||
onBuildError,
|
onBuildError,
|
||||||
verticesIds,
|
verticesIds,
|
||||||
|
|
@ -145,6 +172,7 @@ async function buildVertex({
|
||||||
}: {
|
}: {
|
||||||
flowId: string;
|
flowId: string;
|
||||||
id: string;
|
id: string;
|
||||||
|
input_value: string;
|
||||||
onBuildUpdate?: (data: any, status: BuildStatus) => void;
|
onBuildUpdate?: (data: any, status: BuildStatus) => void;
|
||||||
onBuildError?: (title, list, idList: string[]) => void;
|
onBuildError?: (title, list, idList: string[]) => void;
|
||||||
verticesIds: string[];
|
verticesIds: string[];
|
||||||
|
|
@ -152,7 +180,7 @@ async function buildVertex({
|
||||||
stopBuild: () => void;
|
stopBuild: () => void;
|
||||||
}) {
|
}) {
|
||||||
try {
|
try {
|
||||||
const buildRes = await postBuildVertex(flowId, id);
|
const buildRes = await postBuildVertex(flowId, id, input_value);
|
||||||
const buildData: VertexBuildTypeAPI = buildRes.data;
|
const buildData: VertexBuildTypeAPI = buildRes.data;
|
||||||
if (onBuildUpdate) {
|
if (onBuildUpdate) {
|
||||||
if (!buildData.valid) {
|
if (!buildData.valid) {
|
||||||
|
|
|
||||||
|
|
@ -219,7 +219,7 @@ export const nodeColors: { [char: string]: string } = {
|
||||||
wrappers: "#E6277A",
|
wrappers: "#E6277A",
|
||||||
utilities: "#31A3CC",
|
utilities: "#31A3CC",
|
||||||
output_parsers: "#E6A627",
|
output_parsers: "#E6A627",
|
||||||
str: "#049524",
|
str: "#31a3cc",
|
||||||
retrievers: "#e6b25a",
|
retrievers: "#e6b25a",
|
||||||
unknown: "#9CA3AF",
|
unknown: "#9CA3AF",
|
||||||
custom_components: "#ab11ab",
|
custom_components: "#ab11ab",
|
||||||
|
|
@ -258,6 +258,7 @@ export const nodeIconsLucide: iconsType = {
|
||||||
Chroma: ChromaIcon,
|
Chroma: ChromaIcon,
|
||||||
AirbyteJSONLoader: AirbyteIcon,
|
AirbyteJSONLoader: AirbyteIcon,
|
||||||
AmazonBedrockEmbeddings: AWSIcon,
|
AmazonBedrockEmbeddings: AWSIcon,
|
||||||
|
Amazon: AWSIcon,
|
||||||
Anthropic: AnthropicIcon,
|
Anthropic: AnthropicIcon,
|
||||||
ChatAnthropic: AnthropicIcon,
|
ChatAnthropic: AnthropicIcon,
|
||||||
BingSearchAPIWrapper: BingIcon,
|
BingSearchAPIWrapper: BingIcon,
|
||||||
|
|
@ -270,13 +271,17 @@ export const nodeIconsLucide: iconsType = {
|
||||||
GoogleSearchAPIWrapper: GoogleIcon,
|
GoogleSearchAPIWrapper: GoogleIcon,
|
||||||
GoogleSearchResults: GoogleIcon,
|
GoogleSearchResults: GoogleIcon,
|
||||||
GoogleSearchRun: GoogleIcon,
|
GoogleSearchRun: GoogleIcon,
|
||||||
|
Google: GoogleIcon,
|
||||||
HNLoader: HackerNewsIcon,
|
HNLoader: HackerNewsIcon,
|
||||||
HuggingFaceHub: HuggingFaceIcon,
|
HuggingFaceHub: HuggingFaceIcon,
|
||||||
|
HuggingFace: HuggingFaceIcon,
|
||||||
HuggingFaceEmbeddings: HuggingFaceIcon,
|
HuggingFaceEmbeddings: HuggingFaceIcon,
|
||||||
IFixitLoader: IFixIcon,
|
IFixitLoader: IFixIcon,
|
||||||
Meta: MetaIcon,
|
Meta: MetaIcon,
|
||||||
Midjorney: MidjourneyIcon,
|
Midjorney: MidjourneyIcon,
|
||||||
MongoDBAtlasVectorSearch: MongoDBIcon,
|
MongoDBAtlasVectorSearch: MongoDBIcon,
|
||||||
|
MongoDB:MongoDBIcon,
|
||||||
|
MongoDBChatMessageHistory: MongoDBIcon,
|
||||||
NotionDirectoryLoader: NotionIcon,
|
NotionDirectoryLoader: NotionIcon,
|
||||||
ChatOpenAI: OpenAiIcon,
|
ChatOpenAI: OpenAiIcon,
|
||||||
AzureChatOpenAI: OpenAiIcon,
|
AzureChatOpenAI: OpenAiIcon,
|
||||||
|
|
@ -289,6 +294,7 @@ export const nodeIconsLucide: iconsType = {
|
||||||
Searx: SearxIcon,
|
Searx: SearxIcon,
|
||||||
SlackDirectoryLoader: SvgSlackIcon,
|
SlackDirectoryLoader: SvgSlackIcon,
|
||||||
SupabaseVectorStore: SupabaseIcon,
|
SupabaseVectorStore: SupabaseIcon,
|
||||||
|
Supabase: SupabaseIcon,
|
||||||
VertexAI: VertexAIIcon,
|
VertexAI: VertexAIIcon,
|
||||||
ChatVertexAI: VertexAIIcon,
|
ChatVertexAI: VertexAIIcon,
|
||||||
VertexAIEmbeddings: VertexAIIcon,
|
VertexAIEmbeddings: VertexAIIcon,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue