Format py
This commit is contained in:
parent
be32d57434
commit
2450161fc1
68 changed files with 408 additions and 972 deletions
|
|
@ -86,9 +86,7 @@ def validate_prompt(template: str):
|
||||||
# Check if there are invalid characters in the input_variables
|
# Check if there are invalid characters in the input_variables
|
||||||
input_variables = check_input_variables(input_variables)
|
input_variables = check_input_variables(input_variables)
|
||||||
if any(var in INVALID_NAMES for var in input_variables):
|
if any(var in INVALID_NAMES for var in input_variables):
|
||||||
raise ValueError(
|
raise ValueError(f"Invalid input variables. None of the variables can be named {', '.join(input_variables)}. ")
|
||||||
f"Invalid input variables. None of the variables can be named {', '.join(input_variables)}. "
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
PromptTemplate(template=template, input_variables=input_variables)
|
PromptTemplate(template=template, input_variables=input_variables)
|
||||||
|
|
@ -123,9 +121,7 @@ def fix_variable(var, invalid_chars, wrong_variables):
|
||||||
# Handle variables starting with a number
|
# Handle variables starting with a number
|
||||||
if var[0].isdigit():
|
if var[0].isdigit():
|
||||||
invalid_chars.append(var[0])
|
invalid_chars.append(var[0])
|
||||||
new_var, invalid_chars, wrong_variables = fix_variable(
|
new_var, invalid_chars, wrong_variables = fix_variable(var[1:], invalid_chars, wrong_variables)
|
||||||
var[1:], invalid_chars, wrong_variables
|
|
||||||
)
|
|
||||||
|
|
||||||
# Temporarily replace {{ and }} to avoid treating them as invalid
|
# Temporarily replace {{ and }} to avoid treating them as invalid
|
||||||
new_var = new_var.replace("{{", "ᴛᴇᴍᴘᴏᴘᴇɴ").replace("}}", "ᴛᴇᴍᴘᴄʟᴏsᴇ")
|
new_var = new_var.replace("{{", "ᴛᴇᴍᴘᴏᴘᴇɴ").replace("}}", "ᴛᴇᴍᴘᴄʟᴏsᴇ")
|
||||||
|
|
@ -152,9 +148,7 @@ def check_variable(var, invalid_chars, wrong_variables, empty_variables):
|
||||||
return wrong_variables, empty_variables
|
return wrong_variables, empty_variables
|
||||||
|
|
||||||
|
|
||||||
def check_for_errors(
|
def check_for_errors(input_variables, fixed_variables, wrong_variables, empty_variables):
|
||||||
input_variables, fixed_variables, wrong_variables, empty_variables
|
|
||||||
):
|
|
||||||
if any(var for var in input_variables if var not in fixed_variables):
|
if any(var for var in input_variables if var not in fixed_variables):
|
||||||
error_message = (
|
error_message = (
|
||||||
f"Error: Input variables contain invalid characters or formats. \n"
|
f"Error: Input variables contain invalid characters or formats. \n"
|
||||||
|
|
@ -179,17 +173,11 @@ def check_input_variables(input_variables):
|
||||||
if is_json_like(var):
|
if is_json_like(var):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
new_var, wrong_variables, empty_variables = fix_variable(
|
new_var, wrong_variables, empty_variables = fix_variable(var, invalid_chars, wrong_variables)
|
||||||
var, invalid_chars, wrong_variables
|
wrong_variables, empty_variables = check_variable(var, INVALID_CHARACTERS, wrong_variables, empty_variables)
|
||||||
)
|
|
||||||
wrong_variables, empty_variables = check_variable(
|
|
||||||
var, INVALID_CHARACTERS, wrong_variables, empty_variables
|
|
||||||
)
|
|
||||||
fixed_variables.append(new_var)
|
fixed_variables.append(new_var)
|
||||||
variables_to_check.append(var)
|
variables_to_check.append(var)
|
||||||
|
|
||||||
check_for_errors(
|
check_for_errors(variables_to_check, fixed_variables, wrong_variables, empty_variables)
|
||||||
variables_to_check, fixed_variables, wrong_variables, empty_variables
|
|
||||||
)
|
|
||||||
|
|
||||||
return fixed_variables
|
return fixed_variables
|
||||||
|
|
|
||||||
|
|
@ -33,9 +33,7 @@ class AsyncStreamingLLMCallbackHandleSIO(AsyncCallbackHandler):
|
||||||
resp = ChatResponse(message=token, type="stream", intermediate_steps="")
|
resp = ChatResponse(message=token, type="stream", intermediate_steps="")
|
||||||
await self.socketio_service.emit_token(to=self.sid, data=resp.model_dump())
|
await self.socketio_service.emit_token(to=self.sid, data=resp.model_dump())
|
||||||
|
|
||||||
async def on_tool_start(
|
async def on_tool_start(self, serialized: Dict[str, Any], input_str: str, **kwargs: Any) -> Any:
|
||||||
self, serialized: Dict[str, Any], input_str: str, **kwargs: Any
|
|
||||||
) -> Any:
|
|
||||||
"""Run when tool starts running."""
|
"""Run when tool starts running."""
|
||||||
resp = ChatResponse(
|
resp = ChatResponse(
|
||||||
message="",
|
message="",
|
||||||
|
|
@ -73,9 +71,7 @@ class AsyncStreamingLLMCallbackHandleSIO(AsyncCallbackHandler):
|
||||||
try:
|
try:
|
||||||
# This is to emulate the stream of tokens
|
# This is to emulate the stream of tokens
|
||||||
for resp in resps:
|
for resp in resps:
|
||||||
await self.socketio_service.emit_token(
|
await self.socketio_service.emit_token(to=self.sid, data=resp.model_dump())
|
||||||
to=self.sid, data=resp.model_dump()
|
|
||||||
)
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error(f"Error sending response: {exc}")
|
logger.error(f"Error sending response: {exc}")
|
||||||
|
|
||||||
|
|
@ -101,9 +97,7 @@ class AsyncStreamingLLMCallbackHandleSIO(AsyncCallbackHandler):
|
||||||
resp = PromptResponse(
|
resp = PromptResponse(
|
||||||
prompt=text,
|
prompt=text,
|
||||||
)
|
)
|
||||||
await self.socketio_service.emit_message(
|
await self.socketio_service.emit_message(to=self.sid, data=resp.model_dump())
|
||||||
to=self.sid, data=resp.model_dump()
|
|
||||||
)
|
|
||||||
|
|
||||||
async def on_agent_action(self, action: AgentAction, **kwargs: Any):
|
async def on_agent_action(self, action: AgentAction, **kwargs: Any):
|
||||||
log = f"Thought: {action.log}"
|
log = f"Thought: {action.log}"
|
||||||
|
|
@ -113,9 +107,7 @@ class AsyncStreamingLLMCallbackHandleSIO(AsyncCallbackHandler):
|
||||||
logs = log.split("\n")
|
logs = log.split("\n")
|
||||||
for log in logs:
|
for log in logs:
|
||||||
resp = ChatResponse(message="", type="stream", intermediate_steps=log)
|
resp = ChatResponse(message="", type="stream", intermediate_steps=log)
|
||||||
await self.socketio_service.emit_token(
|
await self.socketio_service.emit_token(to=self.sid, data=resp.model_dump())
|
||||||
to=self.sid, data=resp.model_dump()
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
resp = ChatResponse(message="", type="stream", intermediate_steps=log)
|
resp = ChatResponse(message="", type="stream", intermediate_steps=log)
|
||||||
await self.socketio_service.emit_token(to=self.sid, data=resp.model_dump())
|
await self.socketio_service.emit_token(to=self.sid, data=resp.model_dump())
|
||||||
|
|
|
||||||
|
|
@ -101,12 +101,8 @@ async def build_vertex(
|
||||||
cache = chat_service.get_cache(flow_id)
|
cache = chat_service.get_cache(flow_id)
|
||||||
if not cache:
|
if not cache:
|
||||||
# If there's no cache
|
# If there's no cache
|
||||||
logger.warning(
|
logger.warning(f"No cache found for {flow_id}. Building graph starting at {vertex_id}")
|
||||||
f"No cache found for {flow_id}. Building graph starting at {vertex_id}"
|
graph = build_and_cache_graph(flow_id=flow_id, session=next(get_session()), chat_service=chat_service)
|
||||||
)
|
|
||||||
graph = build_and_cache_graph(
|
|
||||||
flow_id=flow_id, session=next(get_session()), chat_service=chat_service
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
graph = cache.get("result")
|
graph = cache.get("result")
|
||||||
result_data_response = ResultDataResponse(results={})
|
result_data_response = ResultDataResponse(results={})
|
||||||
|
|
@ -126,9 +122,7 @@ async def build_vertex(
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"No result found for vertex {vertex_id}")
|
raise ValueError(f"No result found for vertex {vertex_id}")
|
||||||
next_vertices_ids = vertex.successors_ids
|
next_vertices_ids = vertex.successors_ids
|
||||||
next_vertices_ids = [
|
next_vertices_ids = [v for v in next_vertices_ids if graph.should_run_vertex(v)]
|
||||||
v for v in next_vertices_ids if graph.should_run_vertex(v)
|
|
||||||
]
|
|
||||||
|
|
||||||
result_data_response = ResultDataResponse(**result_dict.model_dump())
|
result_data_response = ResultDataResponse(**result_dict.model_dump())
|
||||||
|
|
||||||
|
|
@ -211,9 +205,7 @@ async def build_vertex_stream(
|
||||||
else:
|
else:
|
||||||
graph = cache.get("result")
|
graph = cache.get("result")
|
||||||
else:
|
else:
|
||||||
session_data = await session_service.load_session(
|
session_data = await session_service.load_session(session_id, flow_id=flow_id)
|
||||||
session_id, flow_id=flow_id
|
|
||||||
)
|
|
||||||
graph, artifacts = session_data if session_data else (None, None)
|
graph, artifacts = session_data if session_data else (None, None)
|
||||||
if not graph:
|
if not graph:
|
||||||
raise ValueError(f"No graph found for {flow_id}.")
|
raise ValueError(f"No graph found for {flow_id}.")
|
||||||
|
|
|
||||||
|
|
@ -52,9 +52,7 @@ def get_all(
|
||||||
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post("/run/{flow_id}", response_model=RunResponse, response_model_exclude_none=True)
|
||||||
"/run/{flow_id}", response_model=RunResponse, response_model_exclude_none=True
|
|
||||||
)
|
|
||||||
async def run_flow_with_caching(
|
async def run_flow_with_caching(
|
||||||
session: Annotated[Session, Depends(get_session)],
|
session: Annotated[Session, Depends(get_session)],
|
||||||
flow_id: str,
|
flow_id: str,
|
||||||
|
|
@ -113,9 +111,7 @@ async def run_flow_with_caching(
|
||||||
outputs = []
|
outputs = []
|
||||||
|
|
||||||
if session_id:
|
if session_id:
|
||||||
session_data = await session_service.load_session(
|
session_data = await session_service.load_session(session_id, flow_id=flow_id)
|
||||||
session_id, flow_id=flow_id
|
|
||||||
)
|
|
||||||
graph, artifacts = session_data if session_data else (None, None)
|
graph, artifacts = session_data if session_data else (None, None)
|
||||||
task_result: Any = None
|
task_result: Any = None
|
||||||
if not graph:
|
if not graph:
|
||||||
|
|
@ -134,11 +130,7 @@ async def run_flow_with_caching(
|
||||||
else:
|
else:
|
||||||
# Get the flow that matches the flow_id and belongs to the user
|
# Get the flow that matches the flow_id and belongs to the user
|
||||||
# flow = session.query(Flow).filter(Flow.id == flow_id).filter(Flow.user_id == api_key_user.id).first()
|
# flow = session.query(Flow).filter(Flow.id == flow_id).filter(Flow.user_id == api_key_user.id).first()
|
||||||
flow = session.exec(
|
flow = session.exec(select(Flow).where(Flow.id == flow_id).where(Flow.user_id == api_key_user.id)).first()
|
||||||
select(Flow)
|
|
||||||
.where(Flow.id == flow_id)
|
|
||||||
.where(Flow.user_id == api_key_user.id)
|
|
||||||
).first()
|
|
||||||
if flow is None:
|
if flow is None:
|
||||||
raise ValueError(f"Flow {flow_id} not found")
|
raise ValueError(f"Flow {flow_id} not found")
|
||||||
|
|
||||||
|
|
@ -162,18 +154,12 @@ async def run_flow_with_caching(
|
||||||
# StatementError('(builtins.ValueError) badly formed hexadecimal UUID string')
|
# StatementError('(builtins.ValueError) badly formed hexadecimal UUID string')
|
||||||
if "badly formed hexadecimal UUID string" in str(exc):
|
if "badly formed hexadecimal UUID string" in str(exc):
|
||||||
# This means the Flow ID is not a valid UUID which means it can't find the flow
|
# This means the Flow ID is not a valid UUID which means it can't find the flow
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
|
||||||
status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)
|
|
||||||
) from exc
|
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
if f"Flow {flow_id} not found" in str(exc):
|
if f"Flow {flow_id} not found" in str(exc):
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
|
||||||
status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)
|
|
||||||
) from exc
|
|
||||||
else:
|
else:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)) from exc
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)
|
|
||||||
) from exc
|
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
|
|
@ -202,8 +188,7 @@ async def process(
|
||||||
"""
|
"""
|
||||||
# Raise a depreciation warning
|
# Raise a depreciation warning
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"The /process endpoint is deprecated and will be removed in a future version. "
|
"The /process endpoint is deprecated and will be removed in a future version. " "Please use /run instead."
|
||||||
"Please use /run instead."
|
|
||||||
)
|
)
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
|
@ -275,16 +260,12 @@ async def custom_component(
|
||||||
|
|
||||||
built_frontend_node = build_custom_component_template(component, user_id=user.id)
|
built_frontend_node = build_custom_component_template(component, user_id=user.id)
|
||||||
|
|
||||||
built_frontend_node = update_frontend_node_with_template_values(
|
built_frontend_node = update_frontend_node_with_template_values(built_frontend_node, raw_code.frontend_node)
|
||||||
built_frontend_node, raw_code.frontend_node
|
|
||||||
)
|
|
||||||
return built_frontend_node
|
return built_frontend_node
|
||||||
|
|
||||||
|
|
||||||
@router.post("/custom_component/reload", status_code=HTTPStatus.OK)
|
@router.post("/custom_component/reload", status_code=HTTPStatus.OK)
|
||||||
async def reload_custom_component(
|
async def reload_custom_component(path: str, user: User = Depends(get_current_active_user)):
|
||||||
path: str, user: User = Depends(get_current_active_user)
|
|
||||||
):
|
|
||||||
from langflow.interface.custom.utils import build_custom_component_template
|
from langflow.interface.custom.utils import build_custom_component_template
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -57,11 +57,7 @@ def read_flows(
|
||||||
try:
|
try:
|
||||||
auth_settings = settings_service.auth_settings
|
auth_settings = settings_service.auth_settings
|
||||||
if auth_settings.AUTO_LOGIN:
|
if auth_settings.AUTO_LOGIN:
|
||||||
flows = session.exec(
|
flows = session.exec(select(Flow).where(Flow.user_id == None | Flow.user_id == current_user.id)).all() # noqa
|
||||||
select(Flow).where(
|
|
||||||
Flow.user_id == None | Flow.user_id == current_user.id
|
|
||||||
)
|
|
||||||
).all() # noqa
|
|
||||||
else:
|
else:
|
||||||
flows = current_user.flows
|
flows = current_user.flows
|
||||||
|
|
||||||
|
|
@ -71,7 +67,8 @@ def read_flows(
|
||||||
try:
|
try:
|
||||||
example_flows = session.exec(
|
example_flows = session.exec(
|
||||||
select(Flow).where(
|
select(Flow).where(
|
||||||
Flow.user_id == None, Flow.folder == STARTER_FOLDER_NAME # noqa
|
Flow.user_id == None,
|
||||||
|
Flow.folder == STARTER_FOLDER_NAME, # noqa
|
||||||
)
|
)
|
||||||
).all() # noqa
|
).all() # noqa
|
||||||
for example_flow in example_flows:
|
for example_flow in example_flows:
|
||||||
|
|
@ -98,7 +95,9 @@ def read_flow(
|
||||||
if auth_settings.AUTO_LOGIN:
|
if auth_settings.AUTO_LOGIN:
|
||||||
# If auto login is enable user_id can be current_user.id or None
|
# If auto login is enable user_id can be current_user.id or None
|
||||||
# so write an OR
|
# so write an OR
|
||||||
stmt = stmt.where((Flow.user_id == current_user.id) | (Flow.user_id == None))
|
stmt = stmt.where(
|
||||||
|
(Flow.user_id == current_user.id) | (Flow.user_id == None) # noqa
|
||||||
|
) # noqa
|
||||||
if user_flow := session.exec(stmt).first():
|
if user_flow := session.exec(stmt).first():
|
||||||
return user_flow
|
return user_flow
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -158,9 +158,7 @@ class StreamData(BaseModel):
|
||||||
data: dict
|
data: dict
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return (
|
return f"event: {self.event}\ndata: {orjson_dumps(self.data, indent_2=False)}\n\n"
|
||||||
f"event: {self.event}\ndata: {orjson_dumps(self.data, indent_2=False)}\n\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CustomComponentCode(BaseModel):
|
class CustomComponentCode(BaseModel):
|
||||||
|
|
|
||||||
|
|
@ -41,9 +41,7 @@ def post_validate_prompt(prompt_request: ValidatePromptRequest):
|
||||||
|
|
||||||
add_new_variables_to_template(input_variables, prompt_request)
|
add_new_variables_to_template(input_variables, prompt_request)
|
||||||
|
|
||||||
remove_old_variables_from_template(
|
remove_old_variables_from_template(old_custom_fields, input_variables, prompt_request)
|
||||||
old_custom_fields, input_variables, prompt_request
|
|
||||||
)
|
|
||||||
|
|
||||||
update_input_variables_field(input_variables, prompt_request)
|
update_input_variables_field(input_variables, prompt_request)
|
||||||
|
|
||||||
|
|
@ -58,19 +56,12 @@ def post_validate_prompt(prompt_request: ValidatePromptRequest):
|
||||||
|
|
||||||
def get_old_custom_fields(prompt_request):
|
def get_old_custom_fields(prompt_request):
|
||||||
try:
|
try:
|
||||||
if (
|
if len(prompt_request.frontend_node.custom_fields) == 1 and prompt_request.name == "":
|
||||||
len(prompt_request.frontend_node.custom_fields) == 1
|
|
||||||
and prompt_request.name == ""
|
|
||||||
):
|
|
||||||
# If there is only one custom field and the name is empty string
|
# If there is only one custom field and the name is empty string
|
||||||
# then we are dealing with the first prompt request after the node was created
|
# then we are dealing with the first prompt request after the node was created
|
||||||
prompt_request.name = list(
|
prompt_request.name = list(prompt_request.frontend_node.custom_fields.keys())[0]
|
||||||
prompt_request.frontend_node.custom_fields.keys()
|
|
||||||
)[0]
|
|
||||||
|
|
||||||
old_custom_fields = prompt_request.frontend_node.custom_fields[
|
old_custom_fields = prompt_request.frontend_node.custom_fields[prompt_request.name]
|
||||||
prompt_request.name
|
|
||||||
]
|
|
||||||
if old_custom_fields is None:
|
if old_custom_fields is None:
|
||||||
old_custom_fields = []
|
old_custom_fields = []
|
||||||
|
|
||||||
|
|
@ -87,40 +78,26 @@ def add_new_variables_to_template(input_variables, prompt_request):
|
||||||
template_field = DefaultPromptField(name=variable, display_name=variable)
|
template_field = DefaultPromptField(name=variable, display_name=variable)
|
||||||
if variable in prompt_request.frontend_node.template:
|
if variable in prompt_request.frontend_node.template:
|
||||||
# Set the new field with the old value
|
# Set the new field with the old value
|
||||||
template_field.value = prompt_request.frontend_node.template[variable][
|
template_field.value = prompt_request.frontend_node.template[variable]["value"]
|
||||||
"value"
|
|
||||||
]
|
|
||||||
|
|
||||||
prompt_request.frontend_node.template[variable] = template_field.to_dict()
|
prompt_request.frontend_node.template[variable] = template_field.to_dict()
|
||||||
|
|
||||||
# Check if variable is not already in the list before appending
|
# Check if variable is not already in the list before appending
|
||||||
if (
|
if variable not in prompt_request.frontend_node.custom_fields[prompt_request.name]:
|
||||||
variable
|
prompt_request.frontend_node.custom_fields[prompt_request.name].append(variable)
|
||||||
not in prompt_request.frontend_node.custom_fields[prompt_request.name]
|
|
||||||
):
|
|
||||||
prompt_request.frontend_node.custom_fields[prompt_request.name].append(
|
|
||||||
variable
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception(exc)
|
logger.exception(exc)
|
||||||
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
||||||
|
|
||||||
|
|
||||||
def remove_old_variables_from_template(
|
def remove_old_variables_from_template(old_custom_fields, input_variables, prompt_request):
|
||||||
old_custom_fields, input_variables, prompt_request
|
|
||||||
):
|
|
||||||
for variable in old_custom_fields:
|
for variable in old_custom_fields:
|
||||||
if variable not in input_variables:
|
if variable not in input_variables:
|
||||||
try:
|
try:
|
||||||
# Remove the variable from custom_fields associated with the given name
|
# Remove the variable from custom_fields associated with the given name
|
||||||
if (
|
if variable in prompt_request.frontend_node.custom_fields[prompt_request.name]:
|
||||||
variable
|
prompt_request.frontend_node.custom_fields[prompt_request.name].remove(variable)
|
||||||
in prompt_request.frontend_node.custom_fields[prompt_request.name]
|
|
||||||
):
|
|
||||||
prompt_request.frontend_node.custom_fields[
|
|
||||||
prompt_request.name
|
|
||||||
].remove(variable)
|
|
||||||
|
|
||||||
# Remove the variable from the template
|
# Remove the variable from the template
|
||||||
prompt_request.frontend_node.template.pop(variable, None)
|
prompt_request.frontend_node.template.pop(variable, None)
|
||||||
|
|
@ -132,6 +109,4 @@ def remove_old_variables_from_template(
|
||||||
|
|
||||||
def update_input_variables_field(input_variables, prompt_request):
|
def update_input_variables_field(input_variables, prompt_request):
|
||||||
if "input_variables" in prompt_request.frontend_node.template:
|
if "input_variables" in prompt_request.frontend_node.template:
|
||||||
prompt_request.frontend_node.template["input_variables"][
|
prompt_request.frontend_node.template["input_variables"]["value"] = input_variables
|
||||||
"value"
|
|
||||||
] = input_variables
|
|
||||||
|
|
|
||||||
|
|
@ -35,9 +35,7 @@ def retrieve_file_paths(
|
||||||
|
|
||||||
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 = [
|
file_paths = [Text(p) for p in paths if p.is_file() and match_types(p) and is_not_hidden(p)]
|
||||||
Text(p) for p in paths if p.is_file() and match_types(p) and is_not_hidden(p)
|
|
||||||
]
|
|
||||||
|
|
||||||
return file_paths
|
return file_paths
|
||||||
|
|
||||||
|
|
@ -70,16 +68,12 @@ def get_elements(
|
||||||
if use_multithreading:
|
if use_multithreading:
|
||||||
records = parallel_load_records(file_paths, silent_errors, max_concurrency)
|
records = parallel_load_records(file_paths, silent_errors, max_concurrency)
|
||||||
else:
|
else:
|
||||||
records = [
|
records = [parse_file_to_record(file_path, silent_errors) for file_path in file_paths]
|
||||||
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(
|
def parallel_load_records(file_paths: List[str], silent_errors: bool, max_concurrency: int) -> List[Optional[Record]]:
|
||||||
file_paths: List[str], silent_errors: bool, max_concurrency: int
|
|
||||||
) -> List[Optional[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: parse_file_to_record(file_path, silent_errors),
|
lambda file_path: parse_file_to_record(file_path, silent_errors),
|
||||||
|
|
|
||||||
|
|
@ -45,9 +45,7 @@ class ChatComponent(CustomComponent):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
if not session_id or not sender or not sender_name:
|
if not session_id or not sender or not sender_name:
|
||||||
raise ValueError(
|
raise ValueError("All of session_id, sender, and sender_name must be provided.")
|
||||||
"All of session_id, sender, and sender_name must be provided."
|
|
||||||
)
|
|
||||||
if isinstance(message, Record):
|
if isinstance(message, Record):
|
||||||
record = message
|
record = message
|
||||||
record.data.update(
|
record.data.update(
|
||||||
|
|
|
||||||
|
|
@ -27,12 +27,12 @@ class APIRequest(CustomComponent):
|
||||||
"headers": {
|
"headers": {
|
||||||
"display_name": "Headers",
|
"display_name": "Headers",
|
||||||
"info": "The headers to send with the request.",
|
"info": "The headers to send with the request.",
|
||||||
"input_types": ["dict"]
|
"input_types": ["dict"],
|
||||||
},
|
},
|
||||||
"body": {
|
"body": {
|
||||||
"display_name": "Body",
|
"display_name": "Body",
|
||||||
"info": "The body to send with the request (for POST, PATCH, PUT).",
|
"info": "The body to send with the request (for POST, PATCH, PUT).",
|
||||||
"input_types": ["dict"]
|
"input_types": ["dict"],
|
||||||
},
|
},
|
||||||
"timeout": {
|
"timeout": {
|
||||||
"display_name": "Timeout",
|
"display_name": "Timeout",
|
||||||
|
|
@ -58,9 +58,7 @@ class APIRequest(CustomComponent):
|
||||||
data = body if body else None
|
data = body if body else None
|
||||||
data = json.dumps(data)
|
data = json.dumps(data)
|
||||||
try:
|
try:
|
||||||
response = await client.request(
|
response = await client.request(method, url, headers=headers, content=data, timeout=timeout)
|
||||||
method, url, headers=headers, content=data, timeout=timeout
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
response_json = response.json()
|
response_json = response.json()
|
||||||
result = orjson_dumps(response_json, indent_2=False)
|
result = orjson_dumps(response_json, indent_2=False)
|
||||||
|
|
@ -96,16 +94,9 @@ class APIRequest(CustomComponent):
|
||||||
if headers is None:
|
if headers is None:
|
||||||
headers = {}
|
headers = {}
|
||||||
urls = url if isinstance(url, list) else [url]
|
urls = url if isinstance(url, list) else [url]
|
||||||
bodies = (
|
bodies = body if isinstance(body, list) else [body] if body else [None] * len(urls)
|
||||||
body
|
|
||||||
if isinstance(body, list)
|
|
||||||
else [body] if body else [None] * len(urls)
|
|
||||||
)
|
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
results = await asyncio.gather(
|
results = await asyncio.gather(
|
||||||
*[
|
*[self.make_request(client, method, u, headers, rec, timeout) for u, rec in zip(urls, bodies)]
|
||||||
self.make_request(client, method, u, headers, rec, timeout)
|
|
||||||
for u, rec in zip(urls, bodies)
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
return results
|
return results
|
||||||
|
|
|
||||||
|
|
@ -57,20 +57,13 @@ class DirectoryComponent(CustomComponent):
|
||||||
if types is None:
|
if types is None:
|
||||||
types = []
|
types = []
|
||||||
resolved_path = self.resolve_path(path)
|
resolved_path = self.resolve_path(path)
|
||||||
file_paths = retrieve_file_paths(
|
file_paths = retrieve_file_paths(resolved_path, types, load_hidden, recursive, depth)
|
||||||
resolved_path, types, load_hidden, recursive, depth
|
|
||||||
)
|
|
||||||
loaded_records = []
|
loaded_records = []
|
||||||
|
|
||||||
if use_multithreading:
|
if use_multithreading:
|
||||||
loaded_records = parallel_load_records(
|
loaded_records = parallel_load_records(file_paths, silent_errors, max_concurrency)
|
||||||
file_paths, silent_errors, max_concurrency
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
loaded_records = [
|
loaded_records = [parse_file_to_record(file_path, silent_errors) for file_path in file_paths]
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,7 @@ class FileLoaderComponent(CustomComponent):
|
||||||
beta = True
|
beta = True
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
loader_options = ["Automatic"] + [
|
loader_options = ["Automatic"] + [loader_info["name"] for loader_info in LOADERS_INFO]
|
||||||
loader_info["name"] for loader_info in LOADERS_INFO
|
|
||||||
]
|
|
||||||
|
|
||||||
file_types = []
|
file_types = []
|
||||||
suffixes = []
|
suffixes = []
|
||||||
|
|
@ -105,9 +103,7 @@ class FileLoaderComponent(CustomComponent):
|
||||||
if isinstance(selected_loader_info, dict):
|
if isinstance(selected_loader_info, dict):
|
||||||
loader_import: str = selected_loader_info["import"]
|
loader_import: str = selected_loader_info["import"]
|
||||||
else:
|
else:
|
||||||
raise ValueError(
|
raise ValueError(f"Loader info for {loader} is not a dict\nLoader info:\n{selected_loader_info}")
|
||||||
f"Loader info for {loader} is not a dict\nLoader info:\n{selected_loader_info}"
|
|
||||||
)
|
|
||||||
module_name, class_name = loader_import.rsplit(".", 1)
|
module_name, class_name = loader_import.rsplit(".", 1)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -115,9 +111,7 @@ class FileLoaderComponent(CustomComponent):
|
||||||
loader_module = __import__(module_name, fromlist=[class_name])
|
loader_module = __import__(module_name, fromlist=[class_name])
|
||||||
loader_instance = getattr(loader_module, class_name)
|
loader_instance = getattr(loader_module, class_name)
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
raise ValueError(
|
raise ValueError(f"Loader {loader} could not be imported\nLoader info:\n{selected_loader_info}") from e
|
||||||
f"Loader {loader} could not be imported\nLoader info:\n{selected_loader_info}"
|
|
||||||
) from e
|
|
||||||
|
|
||||||
result = loader_instance(file_path=file_path)
|
result = loader_instance(file_path=file_path)
|
||||||
docs = result.load()
|
docs = result.load()
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ class URLComponent(CustomComponent):
|
||||||
self,
|
self,
|
||||||
urls: list[str],
|
urls: list[str],
|
||||||
) -> Record:
|
) -> Record:
|
||||||
|
|
||||||
loader = WebBaseLoader(web_paths=urls)
|
loader = WebBaseLoader(web_paths=urls)
|
||||||
docs = loader.load()
|
docs = loader.load()
|
||||||
records = self.to_records(docs)
|
records = self.to_records(docs)
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,7 @@ class NotifyComponent(CustomComponent):
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def build(
|
def build(self, name: str, record: Optional[Record] = None, append: bool = False) -> Record:
|
||||||
self, name: str, record: Optional[Record] = None, append: bool = False
|
|
||||||
) -> Record:
|
|
||||||
if record and not isinstance(record, Record):
|
if record and not isinstance(record, Record):
|
||||||
if isinstance(record, str):
|
if isinstance(record, str):
|
||||||
record = Record(text=record)
|
record = Record(text=record)
|
||||||
|
|
|
||||||
|
|
@ -39,10 +39,7 @@ class RunFlowComponent(CustomComponent):
|
||||||
records.append(record)
|
records.append(record)
|
||||||
return records
|
return records
|
||||||
|
|
||||||
async def build(
|
async def build(self, input_value: Text, flow_name: str, tweaks: NestedDict) -> Record:
|
||||||
self, input_value: Text, flow_name: str, tweaks: NestedDict
|
|
||||||
) -> Record:
|
|
||||||
|
|
||||||
results: List[Optional[ResultData]] = await self.run_flow(
|
results: List[Optional[ResultData]] = await self.run_flow(
|
||||||
input_value=input_value, flow_name=flow_name, tweaks=tweaks
|
input_value=input_value, flow_name=flow_name, tweaks=tweaks
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ class ExtractKeyFromRecordComponent(CustomComponent):
|
||||||
}
|
}
|
||||||
|
|
||||||
def build(self, record: Record, key: str, silent_error: bool = True) -> dict:
|
def build(self, record: Record, key: str, silent_error: bool = True) -> dict:
|
||||||
|
|
||||||
data = getattr(record, key)
|
data = getattr(record, key)
|
||||||
self.status = data
|
self.status = data
|
||||||
return data
|
return data
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,7 @@ class UUIDGeneratorComponent(CustomComponent):
|
||||||
display_name = "Unique ID Generator"
|
display_name = "Unique ID Generator"
|
||||||
description = "Generates a unique ID."
|
description = "Generates a unique ID."
|
||||||
|
|
||||||
def update_build_config(
|
def update_build_config(self, build_config: dict, field_name: Text, field_value: Any):
|
||||||
self, build_config: dict, field_name: Text, field_value: Any
|
|
||||||
):
|
|
||||||
if field_name == "unique_id":
|
if field_name == "unique_id":
|
||||||
build_config[field_name]["value"] = str(uuid.uuid4())
|
build_config[field_name]["value"] = str(uuid.uuid4())
|
||||||
return build_config
|
return build_config
|
||||||
|
|
|
||||||
47
src/backend/langflow/components/helpers/RecordComponent1.py
Normal file
47
src/backend/langflow/components/helpers/RecordComponent1.py
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from langflow import CustomComponent
|
||||||
|
from langflow.schema import Record
|
||||||
|
from langflow.template.field.base import TemplateField
|
||||||
|
|
||||||
|
|
||||||
|
class RecordComponent(CustomComponent):
|
||||||
|
display_name = "Record Numbers"
|
||||||
|
description = "A component to create a record from key-value pairs."
|
||||||
|
field_order = ["n_keys"]
|
||||||
|
|
||||||
|
def update_build_config(self, build_config: dict, field_name: str, field_value: Any):
|
||||||
|
if field_value is None:
|
||||||
|
return
|
||||||
|
elif int(field_value) == 0:
|
||||||
|
keep = ["n_keys", "code"]
|
||||||
|
for key in build_config.copy():
|
||||||
|
if key in keep:
|
||||||
|
continue
|
||||||
|
del build_config[key]
|
||||||
|
build_config[field_name]["value"] = int(field_value)
|
||||||
|
|
||||||
|
# Add new fields depending on the field value
|
||||||
|
for i in range(int(field_value)):
|
||||||
|
field = TemplateField(
|
||||||
|
name=f"Key and Value {i}",
|
||||||
|
field_type="dict",
|
||||||
|
display_name="",
|
||||||
|
info="The key for the record.",
|
||||||
|
input_types=["Text"],
|
||||||
|
)
|
||||||
|
build_config[field.name] = field.to_dict()
|
||||||
|
|
||||||
|
def build_config(self):
|
||||||
|
return {
|
||||||
|
"n_keys": {
|
||||||
|
"display_name": "Number of Fields",
|
||||||
|
"refresh": True,
|
||||||
|
"info": "The number of keys to create in the record.",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(self, n_keys: int, **kwargs) -> Record:
|
||||||
|
data = {k: v for d in kwargs.values() for k, v in d.items()}
|
||||||
|
record = Record(data=data)
|
||||||
|
return record
|
||||||
51
src/backend/langflow/components/helpers/RecordComponent2.py
Normal file
51
src/backend/langflow/components/helpers/RecordComponent2.py
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
from typing import Any, List
|
||||||
|
|
||||||
|
from langflow import CustomComponent
|
||||||
|
from langflow.schema import Record
|
||||||
|
from langflow.template.field.base import TemplateField
|
||||||
|
|
||||||
|
|
||||||
|
class RecordComponent2(CustomComponent):
|
||||||
|
display_name = "Record Text"
|
||||||
|
description = "A component to create a record from key-value pairs."
|
||||||
|
field_order = ["keys"]
|
||||||
|
|
||||||
|
def update_build_config(self, build_config: dict, field_name: str, field_value: Any):
|
||||||
|
if field_value is None:
|
||||||
|
field_value = []
|
||||||
|
if field_name is None:
|
||||||
|
return build_config
|
||||||
|
elif len(field_value) == 0:
|
||||||
|
keep = ["keys", "code"]
|
||||||
|
for key in build_config.copy():
|
||||||
|
if key in keep:
|
||||||
|
continue
|
||||||
|
del build_config[key]
|
||||||
|
build_config[field_name]["value"] = field_value
|
||||||
|
|
||||||
|
# Add new fields depending on the field value
|
||||||
|
for val in field_value:
|
||||||
|
if not isinstance(val, str) or val == "":
|
||||||
|
continue
|
||||||
|
field = TemplateField(
|
||||||
|
name=val,
|
||||||
|
field_type="str",
|
||||||
|
display_name="",
|
||||||
|
info="The key for the record.",
|
||||||
|
)
|
||||||
|
build_config[field.name] = field.to_dict()
|
||||||
|
|
||||||
|
def build_config(self):
|
||||||
|
return {
|
||||||
|
"keys": {
|
||||||
|
"display_name": "Keys",
|
||||||
|
"refresh": True,
|
||||||
|
"info": "The number of keys to create in the record.",
|
||||||
|
"input_types": [],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(self, keys: List[str], **kwargs) -> Record:
|
||||||
|
record = Record(data=kwargs)
|
||||||
|
self.status = record
|
||||||
|
return record
|
||||||
|
|
@ -9,9 +9,7 @@ from langflow.schema.schema import Record
|
||||||
class LanguageRecursiveTextSplitterComponent(CustomComponent):
|
class LanguageRecursiveTextSplitterComponent(CustomComponent):
|
||||||
display_name: str = "Language Recursive Text Splitter"
|
display_name: str = "Language Recursive Text Splitter"
|
||||||
description: str = "Split text into chunks of a specified length based on language."
|
description: str = "Split text into chunks of a specified length based on language."
|
||||||
documentation: str = (
|
documentation: str = "https://docs.langflow.org/components/text-splitters#languagerecursivetextsplitter"
|
||||||
"https://docs.langflow.org/components/text-splitters#languagerecursivetextsplitter"
|
|
||||||
)
|
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
options = [x.value for x in Language]
|
options = [x.value for x in Language]
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,7 @@ from langflow.utils.util import build_loader_repr_from_documents
|
||||||
class RecursiveCharacterTextSplitterComponent(CustomComponent):
|
class RecursiveCharacterTextSplitterComponent(CustomComponent):
|
||||||
display_name: str = "Recursive Character Text Splitter"
|
display_name: str = "Recursive Character Text Splitter"
|
||||||
description: str = "Split text into chunks of a specified length."
|
description: str = "Split text into chunks of a specified length."
|
||||||
documentation: str = (
|
documentation: str = "https://docs.langflow.org/components/text-splitters#recursivecharactertextsplitter"
|
||||||
"https://docs.langflow.org/components/text-splitters#recursivecharactertextsplitter"
|
|
||||||
)
|
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -85,8 +85,7 @@ class ChromaComponent(CustomComponent):
|
||||||
|
|
||||||
if chroma_server_host is not None:
|
if chroma_server_host is not None:
|
||||||
chroma_settings = chromadb.config.Settings(
|
chroma_settings = chromadb.config.Settings(
|
||||||
chroma_server_cors_allow_origins=chroma_server_cors_allow_origins
|
chroma_server_cors_allow_origins=chroma_server_cors_allow_origins or None,
|
||||||
or None,
|
|
||||||
chroma_server_host=chroma_server_host,
|
chroma_server_host=chroma_server_host,
|
||||||
chroma_server_port=chroma_server_port or None,
|
chroma_server_port=chroma_server_port or None,
|
||||||
chroma_server_grpc_port=chroma_server_grpc_port or None,
|
chroma_server_grpc_port=chroma_server_grpc_port or None,
|
||||||
|
|
@ -107,9 +106,7 @@ class ChromaComponent(CustomComponent):
|
||||||
documents.append(_input)
|
documents.append(_input)
|
||||||
if documents is not None and embedding is not None:
|
if documents is not None and embedding is not None:
|
||||||
if len(documents) == 0:
|
if len(documents) == 0:
|
||||||
raise ValueError(
|
raise ValueError("If documents are provided, there must be at least one document.")
|
||||||
"If documents are provided, there must be at least one document."
|
|
||||||
)
|
|
||||||
chroma = Chroma.from_documents(
|
chroma = Chroma.from_documents(
|
||||||
documents=documents, # type: ignore
|
documents=documents, # type: ignore
|
||||||
persist_directory=index_directory,
|
persist_directory=index_directory,
|
||||||
|
|
|
||||||
|
|
@ -92,8 +92,7 @@ class ChromaSearchComponent(LCVectorStoreComponent):
|
||||||
|
|
||||||
if chroma_server_host is not None:
|
if chroma_server_host is not None:
|
||||||
chroma_settings = chromadb.config.Settings(
|
chroma_settings = chromadb.config.Settings(
|
||||||
chroma_server_cors_allow_origins=chroma_server_cors_allow_origins
|
chroma_server_cors_allow_origins=chroma_server_cors_allow_origins or None,
|
||||||
or None,
|
|
||||||
chroma_server_host=chroma_server_host,
|
chroma_server_host=chroma_server_host,
|
||||||
chroma_server_port=chroma_server_port or None,
|
chroma_server_port=chroma_server_port or None,
|
||||||
chroma_server_grpc_port=chroma_server_grpc_port or None,
|
chroma_server_grpc_port=chroma_server_grpc_port or None,
|
||||||
|
|
|
||||||
|
|
@ -33,9 +33,7 @@ class FAISSSearchComponent(LCVectorStoreComponent):
|
||||||
if not folder_path:
|
if not folder_path:
|
||||||
raise ValueError("Folder path is required to save the FAISS index.")
|
raise ValueError("Folder path is required to save the FAISS index.")
|
||||||
path = self.resolve_path(folder_path)
|
path = self.resolve_path(folder_path)
|
||||||
vector_store = FAISS.load_local(
|
vector_store = FAISS.load_local(folder_path=Text(path), embeddings=embedding, index_name=index_name)
|
||||||
folder_path=Text(path), embeddings=embedding, index_name=index_name
|
|
||||||
)
|
|
||||||
if not vector_store:
|
if not vector_store:
|
||||||
raise ValueError("Failed to load the FAISS index.")
|
raise ValueError("Failed to load the FAISS index.")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,7 @@ from langflow.schema.schema import Record
|
||||||
|
|
||||||
class MongoDBAtlasComponent(CustomComponent):
|
class MongoDBAtlasComponent(CustomComponent):
|
||||||
display_name = "MongoDB Atlas"
|
display_name = "MongoDB Atlas"
|
||||||
description = (
|
description = "Construct a `MongoDB Atlas Vector Search` vector store from raw documents."
|
||||||
"Construct a `MongoDB Atlas Vector Search` vector store from raw documents."
|
|
||||||
)
|
|
||||||
icon = "MongoDB"
|
icon = "MongoDB"
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
|
|
@ -39,9 +37,7 @@ class MongoDBAtlasComponent(CustomComponent):
|
||||||
try:
|
try:
|
||||||
from pymongo import MongoClient
|
from pymongo import MongoClient
|
||||||
except ImportError:
|
except ImportError:
|
||||||
raise ImportError(
|
raise ImportError("Please install pymongo to use MongoDB Atlas Vector Store")
|
||||||
"Please install pymongo to use MongoDB Atlas Vector Store"
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
mongo_client: MongoClient = MongoClient(mongodb_atlas_cluster_uri)
|
mongo_client: MongoClient = MongoClient(mongodb_atlas_cluster_uri)
|
||||||
collection = mongo_client[db_name][collection_name]
|
collection = mongo_client[db_name][collection_name]
|
||||||
|
|
|
||||||
|
|
@ -67,9 +67,7 @@ class RedisComponent(CustomComponent):
|
||||||
documents.append(_input)
|
documents.append(_input)
|
||||||
if not documents:
|
if not documents:
|
||||||
if schema is None:
|
if schema is None:
|
||||||
raise ValueError(
|
raise ValueError("If no documents are provided, a schema must be provided.")
|
||||||
"If no documents are provided, a schema must be provided."
|
|
||||||
)
|
|
||||||
redis_vs = Redis.from_existing_index(
|
redis_vs = Redis.from_existing_index(
|
||||||
embedding=embedding,
|
embedding=embedding,
|
||||||
index_name=redis_index_name,
|
index_name=redis_index_name,
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,6 @@ class RedisSearchComponent(RedisComponent, LCVectorStoreComponent):
|
||||||
"input_value": {"display_name": "Input"},
|
"input_value": {"display_name": "Input"},
|
||||||
"index_name": {"display_name": "Index Name", "value": "your_index"},
|
"index_name": {"display_name": "Index Name", "value": "your_index"},
|
||||||
"code": {"show": False, "display_name": "Code"},
|
"code": {"show": False, "display_name": "Code"},
|
||||||
|
|
||||||
"embedding": {"display_name": "Embedding"},
|
"embedding": {"display_name": "Embedding"},
|
||||||
"schema": {"display_name": "Schema", "file_types": [".yaml"]},
|
"schema": {"display_name": "Schema", "file_types": [".yaml"]},
|
||||||
"redis_server_url": {
|
"redis_server_url": {
|
||||||
|
|
|
||||||
|
|
@ -35,9 +35,7 @@ class SupabaseComponent(CustomComponent):
|
||||||
supabase_url: str = "",
|
supabase_url: str = "",
|
||||||
table_name: str = "",
|
table_name: str = "",
|
||||||
) -> Union[VectorStore, SupabaseVectorStore, BaseRetriever]:
|
) -> Union[VectorStore, SupabaseVectorStore, BaseRetriever]:
|
||||||
supabase: Client = create_client(
|
supabase: Client = create_client(supabase_url, supabase_key=supabase_service_key)
|
||||||
supabase_url, supabase_key=supabase_service_key
|
|
||||||
)
|
|
||||||
documents = []
|
documents = []
|
||||||
for _input in inputs:
|
for _input in inputs:
|
||||||
if isinstance(_input, Record):
|
if isinstance(_input, Record):
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,7 @@ class SupabaseSearchComponent(LCVectorStoreComponent):
|
||||||
supabase_url: str = "",
|
supabase_url: str = "",
|
||||||
table_name: str = "",
|
table_name: str = "",
|
||||||
) -> List[Record]:
|
) -> List[Record]:
|
||||||
supabase: Client = create_client(
|
supabase: Client = create_client(supabase_url, supabase_key=supabase_service_key)
|
||||||
supabase_url, supabase_key=supabase_service_key
|
|
||||||
)
|
|
||||||
vector_store = SupabaseVectorStore(
|
vector_store = SupabaseVectorStore(
|
||||||
client=supabase,
|
client=supabase,
|
||||||
embedding=embedding,
|
embedding=embedding,
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,7 @@ from langflow.schema.schema import Record
|
||||||
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 = (
|
documentation = "https://python.langchain.com/docs/integrations/vectorstores/vectara"
|
||||||
"https://python.langchain.com/docs/integrations/vectorstores/vectara"
|
|
||||||
)
|
|
||||||
beta = True
|
beta = True
|
||||||
icon = "Vectara"
|
icon = "Vectara"
|
||||||
field_config = {
|
field_config = {
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,7 @@ from langflow.schema import Record
|
||||||
class VectaraSearchComponent(VectaraComponent, LCVectorStoreComponent):
|
class VectaraSearchComponent(VectaraComponent, LCVectorStoreComponent):
|
||||||
display_name: str = "Vectara Search"
|
display_name: str = "Vectara Search"
|
||||||
description: str = "Search a Vectara Vector Store for similar documents."
|
description: str = "Search a Vectara Vector Store for similar documents."
|
||||||
documentation = (
|
documentation = "https://python.langchain.com/docs/integrations/vectorstores/vectara"
|
||||||
"https://python.langchain.com/docs/integrations/vectorstores/vectara"
|
|
||||||
)
|
|
||||||
beta = True
|
beta = True
|
||||||
icon = "Vectara"
|
icon = "Vectara"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,7 @@ from langflow.schema.schema import Record
|
||||||
class WeaviateVectorStoreComponent(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 = (
|
documentation = "https://python.langchain.com/docs/integrations/vectorstores/weaviate"
|
||||||
"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"},
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,7 @@ from langflow.schema import Record
|
||||||
class WeaviateSearchVectorStore(WeaviateVectorStoreComponent, LCVectorStoreComponent):
|
class WeaviateSearchVectorStore(WeaviateVectorStoreComponent, LCVectorStoreComponent):
|
||||||
display_name: str = "Weaviate Search"
|
display_name: str = "Weaviate Search"
|
||||||
description: str = "Search a Weaviate Vector Store for similar documents."
|
description: str = "Search a Weaviate Vector Store for similar documents."
|
||||||
documentation = (
|
documentation = "https://python.langchain.com/docs/integrations/vectorstores/weaviate"
|
||||||
"https://python.langchain.com/docs/integrations/vectorstores/weaviate"
|
|
||||||
)
|
|
||||||
beta = True
|
beta = True
|
||||||
icon = "Weaviate"
|
icon = "Weaviate"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,14 +37,8 @@ class LCVectorStoreComponent(CustomComponent):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
docs: List[Document] = []
|
docs: List[Document] = []
|
||||||
if (
|
if input_value and isinstance(input_value, str) and hasattr(vector_store, "search"):
|
||||||
input_value
|
docs = vector_store.search(query=input_value, search_type=search_type.lower())
|
||||||
and isinstance(input_value, str)
|
|
||||||
and hasattr(vector_store, "search")
|
|
||||||
):
|
|
||||||
docs = vector_store.search(
|
|
||||||
query=input_value, search_type=search_type.lower()
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
raise ValueError("Invalid inputs provided.")
|
raise ValueError("Invalid inputs provided.")
|
||||||
return docs_to_records(docs)
|
return docs_to_records(docs)
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,7 @@ class PGVectorComponent(CustomComponent):
|
||||||
|
|
||||||
display_name: str = "PGVector"
|
display_name: str = "PGVector"
|
||||||
description: str = "Implementation of Vector Store using PostgreSQL"
|
description: str = "Implementation of Vector Store using PostgreSQL"
|
||||||
documentation = (
|
documentation = "https://python.langchain.com/docs/integrations/vectorstores/pgvector"
|
||||||
"https://python.langchain.com/docs/integrations/vectorstores/pgvector"
|
|
||||||
)
|
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,7 @@ class PGVectorSearchComponent(PGVectorComponent, LCVectorStoreComponent):
|
||||||
|
|
||||||
display_name: str = "PGVector Search"
|
display_name: str = "PGVector Search"
|
||||||
description: str = "Search a PGVector Store for similar documents."
|
description: str = "Search a PGVector Store for similar documents."
|
||||||
documentation = (
|
documentation = "https://python.langchain.com/docs/integrations/vectorstores/pgvector"
|
||||||
"https://python.langchain.com/docs/integrations/vectorstores/pgvector"
|
|
||||||
)
|
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class SourceHandle(BaseModel):
|
class SourceHandle(BaseModel):
|
||||||
baseClasses: List[str] = Field(
|
baseClasses: List[str] = Field(..., description="List of base classes for the source handle.")
|
||||||
..., description="List of base classes for the source handle."
|
|
||||||
)
|
|
||||||
dataType: str = Field(..., description="Data type for the source handle.")
|
dataType: str = Field(..., description="Data type for the source handle.")
|
||||||
id: str = Field(..., description="Unique identifier for the source handle.")
|
id: str = Field(..., description="Unique identifier for the source handle.")
|
||||||
|
|
||||||
|
|
@ -22,9 +20,7 @@ class SourceHandle(BaseModel):
|
||||||
class TargetHandle(BaseModel):
|
class TargetHandle(BaseModel):
|
||||||
fieldName: str = Field(..., description="Field name for the target handle.")
|
fieldName: str = Field(..., description="Field name for the target handle.")
|
||||||
id: str = Field(..., description="Unique identifier for the target handle.")
|
id: str = Field(..., description="Unique identifier for the target handle.")
|
||||||
inputTypes: Optional[List[str]] = Field(
|
inputTypes: Optional[List[str]] = Field(None, description="List of input types for the target handle.")
|
||||||
None, description="List of input types for the target handle."
|
|
||||||
)
|
|
||||||
type: str = Field(..., description="Type of the target handle.")
|
type: str = Field(..., description="Type of the target handle.")
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -53,24 +49,16 @@ class Edge:
|
||||||
|
|
||||||
def validate_handles(self, source, target) -> None:
|
def validate_handles(self, source, target) -> None:
|
||||||
if self.target_handle.inputTypes is None:
|
if self.target_handle.inputTypes is None:
|
||||||
self.valid_handles = (
|
self.valid_handles = self.target_handle.type in self.source_handle.baseClasses
|
||||||
self.target_handle.type in self.source_handle.baseClasses
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
self.valid_handles = (
|
self.valid_handles = (
|
||||||
any(
|
any(baseClass in self.target_handle.inputTypes for baseClass in self.source_handle.baseClasses)
|
||||||
baseClass in self.target_handle.inputTypes
|
|
||||||
for baseClass in self.source_handle.baseClasses
|
|
||||||
)
|
|
||||||
or self.target_handle.type in self.source_handle.baseClasses
|
or self.target_handle.type in self.source_handle.baseClasses
|
||||||
)
|
)
|
||||||
if not self.valid_handles:
|
if not self.valid_handles:
|
||||||
logger.debug(self.source_handle)
|
logger.debug(self.source_handle)
|
||||||
logger.debug(self.target_handle)
|
logger.debug(self.target_handle)
|
||||||
raise ValueError(
|
raise ValueError(f"Edge between {source.vertex_type} and {target.vertex_type} " f"has invalid handles")
|
||||||
f"Edge between {source.vertex_type} and {target.vertex_type} "
|
|
||||||
f"has invalid handles"
|
|
||||||
)
|
|
||||||
|
|
||||||
def __setstate__(self, state):
|
def __setstate__(self, state):
|
||||||
self.source_id = state["source_id"]
|
self.source_id = state["source_id"]
|
||||||
|
|
@ -87,11 +75,7 @@ class Edge:
|
||||||
# Both lists contain strings and sometimes a string contains the value we are
|
# Both lists contain strings and sometimes a string contains the value we are
|
||||||
# looking for e.g. comgin_out=["Chain"] and target_reqs=["LLMChain"]
|
# looking for e.g. comgin_out=["Chain"] and target_reqs=["LLMChain"]
|
||||||
# so we need to check if any of the strings in source_types is in target_reqs
|
# so we need to check if any of the strings in source_types is in target_reqs
|
||||||
self.valid = any(
|
self.valid = any(output in target_req for output in self.source_types for target_req in self.target_reqs)
|
||||||
output in target_req
|
|
||||||
for output in self.source_types
|
|
||||||
for target_req in self.target_reqs
|
|
||||||
)
|
|
||||||
# Get what type of input the target node is expecting
|
# Get what type of input the target node is expecting
|
||||||
|
|
||||||
self.matched_type = next(
|
self.matched_type = next(
|
||||||
|
|
@ -102,10 +86,7 @@ class Edge:
|
||||||
if no_matched_type:
|
if no_matched_type:
|
||||||
logger.debug(self.source_types)
|
logger.debug(self.source_types)
|
||||||
logger.debug(self.target_reqs)
|
logger.debug(self.target_reqs)
|
||||||
raise ValueError(
|
raise ValueError(f"Edge between {source.vertex_type} and {target.vertex_type} " f"has no matched type")
|
||||||
f"Edge between {source.vertex_type} and {target.vertex_type} "
|
|
||||||
f"has no matched type"
|
|
||||||
)
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
|
|
@ -118,10 +99,7 @@ class Edge:
|
||||||
|
|
||||||
def __eq__(self, __o: object) -> bool:
|
def __eq__(self, __o: object) -> bool:
|
||||||
# Create a better way to compare edges
|
# Create a better way to compare edges
|
||||||
return (
|
return self._source_handle == __o._source_handle and self._target_handle == __o._target_handle
|
||||||
self._source_handle == __o._source_handle
|
|
||||||
and self._target_handle == __o._target_handle
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ContractEdge(Edge):
|
class ContractEdge(Edge):
|
||||||
|
|
@ -178,9 +156,7 @@ class ContractEdge(Edge):
|
||||||
return f"{self.source_id} -[{self.target_param}]-> {self.target_id}"
|
return f"{self.source_id} -[{self.target_param}]-> {self.target_id}"
|
||||||
|
|
||||||
|
|
||||||
def log_transaction(
|
def log_transaction(edge: ContractEdge, source: "Vertex", target: "Vertex", status, error=None):
|
||||||
edge: ContractEdge, source: "Vertex", target: "Vertex", status, error=None
|
|
||||||
):
|
|
||||||
try:
|
try:
|
||||||
monitor_service = get_monitor_service()
|
monitor_service = get_monitor_service()
|
||||||
clean_params = build_clean_params(target)
|
clean_params = build_clean_params(target)
|
||||||
|
|
|
||||||
|
|
@ -76,9 +76,7 @@ class Graph:
|
||||||
"""Returns the state of the graph."""
|
"""Returns the state of the graph."""
|
||||||
return self.state_manager.get_state(name, run_id=self._run_id)
|
return self.state_manager.get_state(name, run_id=self._run_id)
|
||||||
|
|
||||||
def update_state(
|
def update_state(self, name: str, record: Union[str, Record], caller: Optional[str] = None) -> None:
|
||||||
self, name: str, record: Union[str, Record], caller: Optional[str] = None
|
|
||||||
) -> None:
|
|
||||||
"""Updates the state of the graph."""
|
"""Updates the state of the graph."""
|
||||||
if caller:
|
if caller:
|
||||||
# If there is a caller which is a vertex_id, I want to activate
|
# If there is a caller which is a vertex_id, I want to activate
|
||||||
|
|
@ -110,12 +108,9 @@ class Graph:
|
||||||
def reset_activated_vertices(self):
|
def reset_activated_vertices(self):
|
||||||
self.activated_vertices = []
|
self.activated_vertices = []
|
||||||
|
|
||||||
def append_state(
|
def append_state(self, name: str, record: Union[str, Record], caller: Optional[str] = None) -> None:
|
||||||
self, name: str, record: Union[str, Record], caller: Optional[str] = None
|
|
||||||
) -> None:
|
|
||||||
"""Appends the state of the graph."""
|
"""Appends the state of the graph."""
|
||||||
if caller:
|
if caller:
|
||||||
|
|
||||||
self.activate_state_vertices(name, caller)
|
self.activate_state_vertices(name, caller)
|
||||||
|
|
||||||
self.state_manager.append_state(name, record, run_id=self._run_id)
|
self.state_manager.append_state(name, record, run_id=self._run_id)
|
||||||
|
|
@ -161,10 +156,7 @@ class Graph:
|
||||||
"""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)
|
||||||
if input_components and (
|
if input_components and (vertex_id not in input_components or vertex.display_name not in input_components):
|
||||||
vertex_id not in input_components
|
|
||||||
or vertex.display_name not in input_components
|
|
||||||
):
|
|
||||||
continue
|
continue
|
||||||
if vertex is None:
|
if vertex is None:
|
||||||
raise ValueError(f"Vertex {vertex_id} not found")
|
raise ValueError(f"Vertex {vertex_id} not found")
|
||||||
|
|
@ -187,11 +179,7 @@ class Graph:
|
||||||
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 (
|
if not vertex.result and not stream and hasattr(vertex, "consume_async_generator"):
|
||||||
not vertex.result
|
|
||||||
and not stream
|
|
||||||
and hasattr(vertex, "consume_async_generator")
|
|
||||||
):
|
|
||||||
await vertex.consume_async_generator()
|
await vertex.consume_async_generator()
|
||||||
if vertex.display_name in outputs or vertex.id in outputs:
|
if vertex.display_name in outputs or vertex.id in outputs:
|
||||||
vertex_outputs.append(vertex.result)
|
vertex_outputs.append(vertex.result)
|
||||||
|
|
@ -269,9 +257,7 @@ class Graph:
|
||||||
def build_parent_child_map(self):
|
def build_parent_child_map(self):
|
||||||
parent_child_map = defaultdict(list)
|
parent_child_map = defaultdict(list)
|
||||||
for vertex in self.vertices:
|
for vertex in self.vertices:
|
||||||
parent_child_map[vertex.id] = [
|
parent_child_map[vertex.id] = [child.id for child in self.get_successors(vertex)]
|
||||||
child.id for child in self.get_successors(vertex)
|
|
||||||
]
|
|
||||||
return parent_child_map
|
return parent_child_map
|
||||||
|
|
||||||
def increment_run_count(self):
|
def increment_run_count(self):
|
||||||
|
|
@ -456,11 +442,7 @@ class Graph:
|
||||||
"""Updates the edges of a vertex."""
|
"""Updates the edges of a vertex."""
|
||||||
# Vertex has edges, so we need to update the edges
|
# Vertex has edges, so we need to update the edges
|
||||||
for edge in vertex.edges:
|
for edge in vertex.edges:
|
||||||
if (
|
if edge not in self.edges and edge.source_id in self.vertex_map and edge.target_id in self.vertex_map:
|
||||||
edge not in self.edges
|
|
||||||
and edge.source_id in self.vertex_map
|
|
||||||
and edge.target_id in self.vertex_map
|
|
||||||
):
|
|
||||||
self.edges.append(edge)
|
self.edges.append(edge)
|
||||||
|
|
||||||
def _build_graph(self) -> None:
|
def _build_graph(self) -> None:
|
||||||
|
|
@ -485,11 +467,7 @@ class Graph:
|
||||||
return
|
return
|
||||||
self.vertices.remove(vertex)
|
self.vertices.remove(vertex)
|
||||||
self.vertex_map.pop(vertex_id)
|
self.vertex_map.pop(vertex_id)
|
||||||
self.edges = [
|
self.edges = [edge for edge in self.edges if edge.source_id != vertex_id and edge.target_id != vertex_id]
|
||||||
edge
|
|
||||||
for edge in self.edges
|
|
||||||
if edge.source_id != vertex_id and edge.target_id != vertex_id
|
|
||||||
]
|
|
||||||
|
|
||||||
def _build_vertex_params(self) -> None:
|
def _build_vertex_params(self) -> None:
|
||||||
"""Identifies and handles the LLM vertex within the graph."""
|
"""Identifies and handles the LLM vertex within the graph."""
|
||||||
|
|
@ -510,9 +488,7 @@ class Graph:
|
||||||
return
|
return
|
||||||
for vertex in self.vertices:
|
for vertex in self.vertices:
|
||||||
if not self._validate_vertex(vertex):
|
if not self._validate_vertex(vertex):
|
||||||
raise ValueError(
|
raise ValueError(f"{vertex.display_name} is not connected to any other components")
|
||||||
f"{vertex.display_name} is not connected to any other components"
|
|
||||||
)
|
|
||||||
|
|
||||||
def _validate_vertex(self, vertex: Vertex) -> bool:
|
def _validate_vertex(self, vertex: Vertex) -> bool:
|
||||||
"""Validates a vertex."""
|
"""Validates a vertex."""
|
||||||
|
|
@ -574,9 +550,7 @@ class Graph:
|
||||||
name=f"{vertex.display_name} Run {vertex_task_run_count.get(vertex_id, 0)}",
|
name=f"{vertex.display_name} Run {vertex_task_run_count.get(vertex_id, 0)}",
|
||||||
)
|
)
|
||||||
tasks.append(task)
|
tasks.append(task)
|
||||||
vertex_task_run_count[vertex_id] = (
|
vertex_task_run_count[vertex_id] = vertex_task_run_count.get(vertex_id, 0) + 1
|
||||||
vertex_task_run_count.get(vertex_id, 0) + 1
|
|
||||||
)
|
|
||||||
logger.debug(f"Running layer {layer_index} with {len(tasks)} tasks")
|
logger.debug(f"Running layer {layer_index} with {len(tasks)} tasks")
|
||||||
await self._execute_tasks(tasks)
|
await self._execute_tasks(tasks)
|
||||||
logger.debug("Graph processing complete")
|
logger.debug("Graph processing complete")
|
||||||
|
|
@ -618,9 +592,7 @@ class Graph:
|
||||||
def dfs(vertex):
|
def dfs(vertex):
|
||||||
if state[vertex] == 1:
|
if state[vertex] == 1:
|
||||||
# We have a cycle
|
# We have a cycle
|
||||||
raise ValueError(
|
raise ValueError("Graph contains a cycle, cannot perform topological sort")
|
||||||
"Graph contains a cycle, cannot perform topological sort"
|
|
||||||
)
|
|
||||||
if state[vertex] == 0:
|
if state[vertex] == 0:
|
||||||
state[vertex] = 1
|
state[vertex] = 1
|
||||||
for edge in vertex.edges:
|
for edge in vertex.edges:
|
||||||
|
|
@ -644,10 +616,7 @@ class Graph:
|
||||||
|
|
||||||
def get_predecessors(self, vertex):
|
def get_predecessors(self, vertex):
|
||||||
"""Returns the predecessors of a vertex."""
|
"""Returns the predecessors of a vertex."""
|
||||||
return [
|
return [self.get_vertex(source_id) for source_id in self.predecessor_map.get(vertex.id, [])]
|
||||||
self.get_vertex(source_id)
|
|
||||||
for source_id in self.predecessor_map.get(vertex.id, [])
|
|
||||||
]
|
|
||||||
|
|
||||||
def get_all_successors(self, vertex, recursive=True, flat=True):
|
def get_all_successors(self, vertex, recursive=True, flat=True):
|
||||||
# Recursively get the successors of the current vertex
|
# Recursively get the successors of the current vertex
|
||||||
|
|
@ -688,10 +657,7 @@ class Graph:
|
||||||
|
|
||||||
def get_successors(self, vertex):
|
def get_successors(self, vertex):
|
||||||
"""Returns the successors of a vertex."""
|
"""Returns the successors of a vertex."""
|
||||||
return [
|
return [self.get_vertex(target_id) for target_id in self.successor_map.get(vertex.id, [])]
|
||||||
self.get_vertex(target_id)
|
|
||||||
for target_id in self.successor_map.get(vertex.id, [])
|
|
||||||
]
|
|
||||||
|
|
||||||
def get_vertex_neighbors(self, vertex: Vertex) -> Dict[Vertex, int]:
|
def get_vertex_neighbors(self, vertex: Vertex) -> Dict[Vertex, int]:
|
||||||
"""Returns the neighbors of a vertex."""
|
"""Returns the neighbors of a vertex."""
|
||||||
|
|
@ -737,9 +703,7 @@ class Graph:
|
||||||
edges_added.add((source.id, target.id))
|
edges_added.add((source.id, target.id))
|
||||||
return edges
|
return edges
|
||||||
|
|
||||||
def _get_vertex_class(
|
def _get_vertex_class(self, node_type: str, node_base_type: str, node_id: str) -> Type[Vertex]:
|
||||||
self, node_type: str, node_base_type: str, node_id: str
|
|
||||||
) -> Type[Vertex]:
|
|
||||||
"""Returns the node class based on the node type."""
|
"""Returns the node class based on the node type."""
|
||||||
# First we check for the node_base_type
|
# First we check for the node_base_type
|
||||||
node_name = node_id.split("-")[0]
|
node_name = node_id.split("-")[0]
|
||||||
|
|
@ -772,18 +736,14 @@ class Graph:
|
||||||
vertex_type: str = vertex_data["type"] # type: ignore
|
vertex_type: str = vertex_data["type"] # type: ignore
|
||||||
vertex_base_type: str = vertex_data["node"]["template"]["_type"] # type: ignore
|
vertex_base_type: str = vertex_data["node"]["template"]["_type"] # type: ignore
|
||||||
|
|
||||||
VertexClass = self._get_vertex_class(
|
VertexClass = self._get_vertex_class(vertex_type, vertex_base_type, vertex_data["id"])
|
||||||
vertex_type, vertex_base_type, vertex_data["id"]
|
|
||||||
)
|
|
||||||
vertex_instance = VertexClass(vertex, graph=self)
|
vertex_instance = VertexClass(vertex, graph=self)
|
||||||
vertex_instance.set_top_level(self.top_level_vertices)
|
vertex_instance.set_top_level(self.top_level_vertices)
|
||||||
vertices.append(vertex_instance)
|
vertices.append(vertex_instance)
|
||||||
|
|
||||||
return vertices
|
return vertices
|
||||||
|
|
||||||
def get_children_by_vertex_type(
|
def get_children_by_vertex_type(self, vertex: Vertex, vertex_type: str) -> List[Vertex]:
|
||||||
self, vertex: Vertex, vertex_type: str
|
|
||||||
) -> List[Vertex]:
|
|
||||||
"""Returns the children of a vertex based on the vertex type."""
|
"""Returns the children of a vertex based on the vertex type."""
|
||||||
children = []
|
children = []
|
||||||
vertex_types = [vertex.data["type"]]
|
vertex_types = [vertex.data["type"]]
|
||||||
|
|
@ -795,9 +755,7 @@ class Graph:
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
vertex_ids = [vertex.id for vertex in self.vertices]
|
vertex_ids = [vertex.id for vertex in self.vertices]
|
||||||
edges_repr = "\n".join(
|
edges_repr = "\n".join([f"{edge.source_id} --> {edge.target_id}" for edge in self.edges])
|
||||||
[f"{edge.source_id} --> {edge.target_id}" for edge in self.edges]
|
|
||||||
)
|
|
||||||
return f"Graph:\nNodes: {vertex_ids}\nConnections:\n{edges_repr}"
|
return f"Graph:\nNodes: {vertex_ids}\nConnections:\n{edges_repr}"
|
||||||
|
|
||||||
def sort_up_to_vertex(self, vertex_id: str, is_start: bool = False) -> List[Vertex]:
|
def sort_up_to_vertex(self, vertex_id: str, is_start: bool = False) -> List[Vertex]:
|
||||||
|
|
@ -865,8 +823,7 @@ class Graph:
|
||||||
vertex.id
|
vertex.id
|
||||||
for vertex in vertices
|
for vertex in vertices
|
||||||
# if filter_graphs then only vertex.is_input will be considered
|
# if filter_graphs then only vertex.is_input will be considered
|
||||||
if self.in_degree_map[vertex.id] == 0
|
if self.in_degree_map[vertex.id] == 0 and (not filter_graphs or vertex.is_input)
|
||||||
and (not filter_graphs or vertex.is_input)
|
|
||||||
)
|
)
|
||||||
layers: List[List[str]] = []
|
layers: List[List[str]] = []
|
||||||
visited = set(queue)
|
visited = set(queue)
|
||||||
|
|
@ -940,9 +897,7 @@ class Graph:
|
||||||
|
|
||||||
return refined_layers
|
return refined_layers
|
||||||
|
|
||||||
def sort_chat_inputs_first(
|
def sort_chat_inputs_first(self, vertices_layers: List[List[str]]) -> List[List[str]]:
|
||||||
self, vertices_layers: List[List[str]]
|
|
||||||
) -> List[List[str]]:
|
|
||||||
chat_inputs_first = []
|
chat_inputs_first = []
|
||||||
for layer in vertices_layers:
|
for layer in vertices_layers:
|
||||||
for vertex_id in layer:
|
for vertex_id in layer:
|
||||||
|
|
@ -983,9 +938,7 @@ class Graph:
|
||||||
first_layer = vertices_layers[0]
|
first_layer = vertices_layers[0]
|
||||||
# save the only the rest
|
# save the only the rest
|
||||||
self.vertices_layers = vertices_layers[1:]
|
self.vertices_layers = vertices_layers[1:]
|
||||||
self.vertices_to_run = {
|
self.vertices_to_run = {vertex_id for vertex_id in chain.from_iterable(vertices_layers)}
|
||||||
vertex_id for vertex_id in chain.from_iterable(vertices_layers)
|
|
||||||
}
|
|
||||||
# Return just the first layer
|
# Return just the first layer
|
||||||
return first_layer
|
return first_layer
|
||||||
|
|
||||||
|
|
@ -996,15 +949,11 @@ class Graph:
|
||||||
self.vertices_to_run.remove(vertex_id)
|
self.vertices_to_run.remove(vertex_id)
|
||||||
return should_run
|
return should_run
|
||||||
|
|
||||||
def sort_interface_components_first(
|
def sort_interface_components_first(self, vertices_layers: List[List[str]]) -> List[List[str]]:
|
||||||
self, vertices_layers: List[List[str]]
|
|
||||||
) -> List[List[str]]:
|
|
||||||
"""Sorts the vertices in the graph so that vertices containing ChatInput or ChatOutput come first."""
|
"""Sorts the vertices in the graph so that vertices containing ChatInput or ChatOutput come first."""
|
||||||
|
|
||||||
def contains_interface_component(vertex):
|
def contains_interface_component(vertex):
|
||||||
return any(
|
return any(component.value in vertex for component in InterfaceComponentTypes)
|
||||||
component.value in vertex for component in InterfaceComponentTypes
|
|
||||||
)
|
|
||||||
|
|
||||||
# Sort each inner list so that vertices containing ChatInput or ChatOutput come first
|
# Sort each inner list so that vertices containing ChatInput or ChatOutput come first
|
||||||
sorted_vertices = [
|
sorted_vertices = [
|
||||||
|
|
@ -1016,22 +965,16 @@ class Graph:
|
||||||
]
|
]
|
||||||
return sorted_vertices
|
return sorted_vertices
|
||||||
|
|
||||||
def sort_by_avg_build_time(
|
def sort_by_avg_build_time(self, vertices_layers: List[List[str]]) -> List[List[str]]:
|
||||||
self, vertices_layers: List[List[str]]
|
|
||||||
) -> List[List[str]]:
|
|
||||||
"""Sorts the vertices in the graph so that vertices with the lowest average build time come first."""
|
"""Sorts the vertices in the graph so that vertices with the lowest average build time come first."""
|
||||||
|
|
||||||
def sort_layer_by_avg_build_time(vertices_ids: List[str]) -> List[str]:
|
def sort_layer_by_avg_build_time(vertices_ids: List[str]) -> List[str]:
|
||||||
"""Sorts the vertices in the graph so that vertices with the lowest average build time come first."""
|
"""Sorts the vertices in the graph so that vertices with the lowest average build time come first."""
|
||||||
if len(vertices_ids) == 1:
|
if len(vertices_ids) == 1:
|
||||||
return vertices_ids
|
return vertices_ids
|
||||||
vertices_ids.sort(
|
vertices_ids.sort(key=lambda vertex_id: self.get_vertex(vertex_id).avg_build_time)
|
||||||
key=lambda vertex_id: self.get_vertex(vertex_id).avg_build_time
|
|
||||||
)
|
|
||||||
|
|
||||||
return vertices_ids
|
return vertices_ids
|
||||||
|
|
||||||
sorted_vertices = [
|
sorted_vertices = [sort_layer_by_avg_build_time(layer) for layer in vertices_layers]
|
||||||
sort_layer_by_avg_build_time(layer) for layer in vertices_layers
|
|
||||||
]
|
|
||||||
return sorted_vertices
|
return sorted_vertices
|
||||||
|
|
|
||||||
|
|
@ -47,10 +47,7 @@ class VertexTypesDict(LazyLoadDictBase):
|
||||||
**{t: types.DocumentLoaderVertex for t in documentloader_creator.to_list()},
|
**{t: types.DocumentLoaderVertex for t in documentloader_creator.to_list()},
|
||||||
**{t: types.TextSplitterVertex for t in textsplitter_creator.to_list()},
|
**{t: types.TextSplitterVertex for t in textsplitter_creator.to_list()},
|
||||||
**{t: types.OutputParserVertex for t in output_parser_creator.to_list()},
|
**{t: types.OutputParserVertex for t in output_parser_creator.to_list()},
|
||||||
**{
|
**{t: types.CustomComponentVertex for t in custom_component_creator.to_list()},
|
||||||
t: types.CustomComponentVertex
|
|
||||||
for t in custom_component_creator.to_list()
|
|
||||||
},
|
|
||||||
**{t: types.RetrieverVertex for t in retriever_creator.to_list()},
|
**{t: types.RetrieverVertex for t in retriever_creator.to_list()},
|
||||||
**{t: types.ChatVertex for t in CHAT_COMPONENTS},
|
**{t: types.ChatVertex for t in CHAT_COMPONENTS},
|
||||||
**{t: types.RoutingVertex for t in ROUTING_COMPONENTS},
|
**{t: types.RoutingVertex for t in ROUTING_COMPONENTS},
|
||||||
|
|
|
||||||
|
|
@ -59,13 +59,8 @@ class Vertex:
|
||||||
self.updated_raw_params = False
|
self.updated_raw_params = False
|
||||||
self.id: str = data["id"]
|
self.id: str = data["id"]
|
||||||
self.is_state = False
|
self.is_state = False
|
||||||
self.is_input = any(
|
self.is_input = any(input_component_name in self.id for input_component_name in INPUT_COMPONENTS)
|
||||||
input_component_name in self.id for input_component_name in INPUT_COMPONENTS
|
self.is_output = any(output_component_name in self.id for output_component_name in OUTPUT_COMPONENTS)
|
||||||
)
|
|
||||||
self.is_output = any(
|
|
||||||
output_component_name in self.id
|
|
||||||
for output_component_name in OUTPUT_COMPONENTS
|
|
||||||
)
|
|
||||||
self.has_session_id = None
|
self.has_session_id = None
|
||||||
self._custom_component = None
|
self._custom_component = None
|
||||||
self.has_external_input = False
|
self.has_external_input = False
|
||||||
|
|
@ -106,17 +101,11 @@ class Vertex:
|
||||||
|
|
||||||
def set_state(self, state: str):
|
def set_state(self, state: str):
|
||||||
self.state = VertexStates[state]
|
self.state = VertexStates[state]
|
||||||
if (
|
if self.state == VertexStates.INACTIVE and self.graph.in_degree_map[self.id] < 2:
|
||||||
self.state == VertexStates.INACTIVE
|
|
||||||
and self.graph.in_degree_map[self.id] < 2
|
|
||||||
):
|
|
||||||
# If the vertex is inactive and has only one in degree
|
# If the vertex is inactive and has only one in degree
|
||||||
# it means that it is not a merge point in the graph
|
# it means that it is not a merge point in the graph
|
||||||
self.graph.inactivated_vertices.add(self.id)
|
self.graph.inactivated_vertices.add(self.id)
|
||||||
elif (
|
elif self.state == VertexStates.ACTIVE and self.id in self.graph.inactivated_vertices:
|
||||||
self.state == VertexStates.ACTIVE
|
|
||||||
and self.id in self.graph.inactivated_vertices
|
|
||||||
):
|
|
||||||
self.graph.inactivated_vertices.remove(self.id)
|
self.graph.inactivated_vertices.remove(self.id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -133,9 +122,7 @@ class Vertex:
|
||||||
# If the Vertex.type is a power component
|
# If the Vertex.type is a power component
|
||||||
# then we need to return the built object
|
# then we need to return the built object
|
||||||
# instead of the result dict
|
# instead of the result dict
|
||||||
if self.is_interface_component and not isinstance(
|
if self.is_interface_component and not isinstance(self._built_object, UnbuiltObject):
|
||||||
self._built_object, UnbuiltObject
|
|
||||||
):
|
|
||||||
result = self._built_object
|
result = self._built_object
|
||||||
# if it is not a dict or a string and hasattr model_dump then
|
# if it is not a dict or a string and hasattr model_dump then
|
||||||
# return the model_dump
|
# return the model_dump
|
||||||
|
|
@ -147,11 +134,7 @@ class Vertex:
|
||||||
|
|
||||||
if isinstance(self._built_result, UnbuiltResult):
|
if isinstance(self._built_result, UnbuiltResult):
|
||||||
return {}
|
return {}
|
||||||
return (
|
return self._built_result if isinstance(self._built_result, dict) else {"result": self._built_result}
|
||||||
self._built_result
|
|
||||||
if isinstance(self._built_result, dict)
|
|
||||||
else {"result": self._built_result}
|
|
||||||
)
|
|
||||||
|
|
||||||
def set_artifacts(self) -> None:
|
def set_artifacts(self) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
@ -221,31 +204,19 @@ class Vertex:
|
||||||
self.selected_output_type = self.data["node"].get("selected_output_type")
|
self.selected_output_type = self.data["node"].get("selected_output_type")
|
||||||
self.is_input = self.data["node"].get("is_input") or self.is_input
|
self.is_input = self.data["node"].get("is_input") or self.is_input
|
||||||
self.is_output = self.data["node"].get("is_output") or self.is_output
|
self.is_output = self.data["node"].get("is_output") or self.is_output
|
||||||
template_dicts = {
|
template_dicts = {key: value for key, value in self.data["node"]["template"].items() if isinstance(value, dict)}
|
||||||
key: value
|
|
||||||
for key, value in self.data["node"]["template"].items()
|
|
||||||
if isinstance(value, dict)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.has_session_id = "session_id" in template_dicts
|
self.has_session_id = "session_id" in template_dicts
|
||||||
|
|
||||||
self.required_inputs = [
|
self.required_inputs = [
|
||||||
template_dicts[key]["type"]
|
template_dicts[key]["type"] for key, value in template_dicts.items() if value["required"]
|
||||||
for key, value in template_dicts.items()
|
|
||||||
if value["required"]
|
|
||||||
]
|
]
|
||||||
self.optional_inputs = [
|
self.optional_inputs = [
|
||||||
template_dicts[key]["type"]
|
template_dicts[key]["type"] for key, value in template_dicts.items() if not value["required"]
|
||||||
for key, value in template_dicts.items()
|
|
||||||
if not value["required"]
|
|
||||||
]
|
]
|
||||||
# Add the template_dicts[key]["input_types"] to the optional_inputs
|
# Add the template_dicts[key]["input_types"] to the optional_inputs
|
||||||
self.optional_inputs.extend(
|
self.optional_inputs.extend(
|
||||||
[
|
[input_type for value in template_dicts.values() for input_type in value.get("input_types", [])]
|
||||||
input_type
|
|
||||||
for value in template_dicts.values()
|
|
||||||
for input_type in value.get("input_types", [])
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
template_dict = self.data["node"]["template"]
|
template_dict = self.data["node"]["template"]
|
||||||
|
|
@ -292,11 +263,7 @@ class Vertex:
|
||||||
self.updated_raw_params = False
|
self.updated_raw_params = False
|
||||||
return
|
return
|
||||||
|
|
||||||
template_dict = {
|
template_dict = {key: value for key, value in self.data["node"]["template"].items() if isinstance(value, dict)}
|
||||||
key: value
|
|
||||||
for key, value in self.data["node"]["template"].items()
|
|
||||||
if isinstance(value, dict)
|
|
||||||
}
|
|
||||||
params = {}
|
params = {}
|
||||||
|
|
||||||
for edge in self.edges:
|
for edge in self.edges:
|
||||||
|
|
@ -317,10 +284,7 @@ class Vertex:
|
||||||
# we don't know the key of the dict but we need to set the value
|
# we don't know the key of the dict but we need to set the value
|
||||||
# to the vertex that is the source of the edge
|
# to the vertex that is the source of the edge
|
||||||
param_dict = template_dict[param_key]["value"]
|
param_dict = template_dict[param_key]["value"]
|
||||||
params[param_key] = {
|
params[param_key] = {key: self.graph.get_vertex(edge.source_id) for key in param_dict.keys()}
|
||||||
key: self.graph.get_vertex(edge.source_id)
|
|
||||||
for key in param_dict.keys()
|
|
||||||
}
|
|
||||||
else:
|
else:
|
||||||
params[param_key] = self.graph.get_vertex(edge.source_id)
|
params[param_key] = self.graph.get_vertex(edge.source_id)
|
||||||
|
|
||||||
|
|
@ -356,11 +320,7 @@ class Vertex:
|
||||||
# list of dicts, so we need to convert it to a dict
|
# list of dicts, so we need to convert it to a dict
|
||||||
# before passing it to the build method
|
# before passing it to the build method
|
||||||
if isinstance(val, list):
|
if isinstance(val, list):
|
||||||
params[key] = {
|
params[key] = {k: v for item in value.get("value", []) for k, v in item.items()}
|
||||||
k: v
|
|
||||||
for item in value.get("value", [])
|
|
||||||
for k, v in item.items()
|
|
||||||
}
|
|
||||||
elif isinstance(val, dict):
|
elif isinstance(val, dict):
|
||||||
params[key] = val
|
params[key] = val
|
||||||
elif value.get("type") == "int" and val is not None:
|
elif value.get("type") == "int" and val is not None:
|
||||||
|
|
@ -485,9 +445,7 @@ class Vertex:
|
||||||
if isinstance(self._built_object, str):
|
if isinstance(self._built_object, str):
|
||||||
self._built_result = self._built_object
|
self._built_result = self._built_object
|
||||||
|
|
||||||
result = await generate_result(
|
result = await generate_result(self._built_object, inputs, self.has_external_output, session_id)
|
||||||
self._built_object, inputs, self.has_external_output, session_id
|
|
||||||
)
|
|
||||||
self._built_result = result
|
self._built_result = result
|
||||||
|
|
||||||
async def _build_each_node_in_params_dict(self, user_id=None):
|
async def _build_each_node_in_params_dict(self, user_id=None):
|
||||||
|
|
@ -507,9 +465,7 @@ class Vertex:
|
||||||
elif key not in self.params or self.updated_raw_params:
|
elif key not in self.params or self.updated_raw_params:
|
||||||
self.params[key] = value
|
self.params[key] = value
|
||||||
|
|
||||||
async def _build_dict_of_nodes_and_update_params(
|
async def _build_dict_of_nodes_and_update_params(self, key, nodes: Dict[str, "Vertex"], user_id=None):
|
||||||
self, key, nodes: Dict[str, "Vertex"], user_id=None
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Iterates over a dictionary of nodes, builds each and updates the params dictionary.
|
Iterates over a dictionary of nodes, builds each and updates the params dictionary.
|
||||||
"""
|
"""
|
||||||
|
|
@ -529,9 +485,7 @@ class Vertex:
|
||||||
"""
|
"""
|
||||||
return all(self._is_node(node) for node in value)
|
return all(self._is_node(node) for node in value)
|
||||||
|
|
||||||
async def get_result(
|
async def get_result(self, requester: Optional["Vertex"] = None, user_id=None, timeout=None) -> Any:
|
||||||
self, requester: Optional["Vertex"] = None, user_id=None, timeout=None
|
|
||||||
) -> Any:
|
|
||||||
# PLEASE REVIEW THIS IF STATEMENT
|
# PLEASE REVIEW THIS IF STATEMENT
|
||||||
# Check if the Vertex was built already
|
# Check if the Vertex was built already
|
||||||
if self._built:
|
if self._built:
|
||||||
|
|
@ -565,9 +519,7 @@ class Vertex:
|
||||||
self._extend_params_list_with_result(key, result)
|
self._extend_params_list_with_result(key, result)
|
||||||
self.params[key] = result
|
self.params[key] = result
|
||||||
|
|
||||||
async def _build_list_of_nodes_and_update_params(
|
async def _build_list_of_nodes_and_update_params(self, key, nodes: List["Vertex"], user_id=None):
|
||||||
self, key, nodes: List["Vertex"], user_id=None
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Iterates over a list of nodes, builds each and updates the params dictionary.
|
Iterates over a list of nodes, builds each and updates the params dictionary.
|
||||||
"""
|
"""
|
||||||
|
|
@ -634,9 +586,7 @@ class Vertex:
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception(exc)
|
logger.exception(exc)
|
||||||
|
|
||||||
raise ValueError(
|
raise ValueError(f"Error building node {self.display_name}: {str(exc)}") from exc
|
||||||
f"Error building node {self.display_name}: {str(exc)}"
|
|
||||||
) from exc
|
|
||||||
|
|
||||||
def _update_built_object_and_artifacts(self, result):
|
def _update_built_object_and_artifacts(self, result):
|
||||||
"""
|
"""
|
||||||
|
|
@ -664,9 +614,7 @@ class Vertex:
|
||||||
logger.warning(message)
|
logger.warning(message)
|
||||||
elif isinstance(self._built_object, (Iterator, AsyncIterator)):
|
elif isinstance(self._built_object, (Iterator, AsyncIterator)):
|
||||||
if self.display_name in ["Text Output"]:
|
if self.display_name in ["Text Output"]:
|
||||||
raise ValueError(
|
raise ValueError(f"You are trying to stream to a {self.display_name}. Try using a Chat Output instead.")
|
||||||
f"You are trying to stream to a {self.display_name}. Try using a Chat Output instead."
|
|
||||||
)
|
|
||||||
|
|
||||||
def _reset(self, params_update: Optional[Dict[str, Any]] = None):
|
def _reset(self, params_update: Optional[Dict[str, Any]] = None):
|
||||||
self._built = False
|
self._built = False
|
||||||
|
|
@ -728,24 +676,16 @@ class Vertex:
|
||||||
return self._built_object
|
return self._built_object
|
||||||
|
|
||||||
# Get the requester edge
|
# Get the requester edge
|
||||||
requester_edge = next(
|
requester_edge = next((edge for edge in self.edges if edge.target_id == requester.id), None)
|
||||||
(edge for edge in self.edges if edge.target_id == requester.id), None
|
|
||||||
)
|
|
||||||
# Return the result of the requester edge
|
# Return the result of the requester edge
|
||||||
return (
|
return None if requester_edge is None else await requester_edge.get_result(source=self, target=requester)
|
||||||
None
|
|
||||||
if requester_edge is None
|
|
||||||
else await requester_edge.get_result(source=self, target=requester)
|
|
||||||
)
|
|
||||||
|
|
||||||
def add_edge(self, edge: "ContractEdge") -> None:
|
def add_edge(self, edge: "ContractEdge") -> None:
|
||||||
if edge not in self.edges:
|
if edge not in self.edges:
|
||||||
self.edges.append(edge)
|
self.edges.append(edge)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return f"Vertex(display_name={self.display_name}, id={self.id}, data={self.data})"
|
||||||
f"Vertex(display_name={self.display_name}, id={self.id}, data={self.data})"
|
|
||||||
)
|
|
||||||
|
|
||||||
def __eq__(self, __o: object) -> bool:
|
def __eq__(self, __o: object) -> bool:
|
||||||
try:
|
try:
|
||||||
|
|
@ -766,8 +706,4 @@ class Vertex:
|
||||||
|
|
||||||
def _built_object_repr(self):
|
def _built_object_repr(self):
|
||||||
# Add a message with an emoji, stars for sucess,
|
# Add a message with an emoji, stars for sucess,
|
||||||
return (
|
return "Built sucessfully ✨" if self._built_object is not None else "Failed to build 😵💫"
|
||||||
"Built sucessfully ✨"
|
|
||||||
if self._built_object is not None
|
|
||||||
else "Failed to build 😵💫"
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import ast
|
import ast
|
||||||
import json
|
import json
|
||||||
from typing import (AsyncIterator, Callable, Dict, Iterator, List, Optional,
|
from typing import AsyncIterator, Callable, Dict, Iterator, List, Optional, Union
|
||||||
Union)
|
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
from langchain_core.messages import AIMessage
|
from langchain_core.messages import AIMessage
|
||||||
|
|
@ -124,11 +123,9 @@ class DocumentLoaderVertex(Vertex):
|
||||||
# show how many documents are in the list?
|
# show how many documents are in the list?
|
||||||
|
|
||||||
if not isinstance(self._built_object, UnbuiltObject):
|
if not isinstance(self._built_object, UnbuiltObject):
|
||||||
avg_length = sum(
|
avg_length = sum(len(record.text) for record in self._built_object if hasattr(record, "text")) / len(
|
||||||
len(record.text)
|
self._built_object
|
||||||
for record in self._built_object
|
)
|
||||||
if hasattr(record, "text")
|
|
||||||
) / len(self._built_object)
|
|
||||||
return f"""{self.display_name}({len(self._built_object)} records)
|
return f"""{self.display_name}({len(self._built_object)} records)
|
||||||
\nAvg. Record Length (characters): {int(avg_length)}
|
\nAvg. Record Length (characters): {int(avg_length)}
|
||||||
Records: {self._built_object[:3]}..."""
|
Records: {self._built_object[:3]}..."""
|
||||||
|
|
@ -201,9 +198,7 @@ class TextSplitterVertex(Vertex):
|
||||||
# show how many documents are in the list?
|
# show how many documents are in the list?
|
||||||
|
|
||||||
if not isinstance(self._built_object, UnbuiltObject):
|
if not isinstance(self._built_object, UnbuiltObject):
|
||||||
avg_length = sum(len(doc.page_content) for doc in self._built_object) / len(
|
avg_length = sum(len(doc.page_content) for doc in self._built_object) / len(self._built_object)
|
||||||
self._built_object
|
|
||||||
)
|
|
||||||
return f"""{self.vertex_type}({len(self._built_object)} documents)
|
return f"""{self.vertex_type}({len(self._built_object)} documents)
|
||||||
\nAvg. Document Length (characters): {int(avg_length)}
|
\nAvg. Document Length (characters): {int(avg_length)}
|
||||||
\nDocuments: {self._built_object[:3]}..."""
|
\nDocuments: {self._built_object[:3]}..."""
|
||||||
|
|
@ -250,27 +245,18 @@ class PromptVertex(Vertex):
|
||||||
user_id = kwargs.get("user_id", None)
|
user_id = kwargs.get("user_id", None)
|
||||||
tools = kwargs.get("tools", [])
|
tools = kwargs.get("tools", [])
|
||||||
if not self._built or force:
|
if not self._built or force:
|
||||||
if (
|
if "input_variables" not in self.params or self.params["input_variables"] is None:
|
||||||
"input_variables" not in self.params
|
|
||||||
or self.params["input_variables"] is None
|
|
||||||
):
|
|
||||||
self.params["input_variables"] = []
|
self.params["input_variables"] = []
|
||||||
# Check if it is a ZeroShotPrompt and needs a tool
|
# Check if it is a ZeroShotPrompt and needs a tool
|
||||||
if "ShotPrompt" in self.vertex_type:
|
if "ShotPrompt" in self.vertex_type:
|
||||||
tools = (
|
tools = [tool_node.build(user_id=user_id) for tool_node in tools] if tools is not None else []
|
||||||
[tool_node.build(user_id=user_id) for tool_node in tools]
|
|
||||||
if tools is not None
|
|
||||||
else []
|
|
||||||
)
|
|
||||||
# flatten the list of tools if it is a list of lists
|
# flatten the list of tools if it is a list of lists
|
||||||
# first check if it is a list
|
# first check if it is a list
|
||||||
if tools and isinstance(tools, list) and isinstance(tools[0], list):
|
if tools and isinstance(tools, list) and isinstance(tools[0], list):
|
||||||
tools = flatten_list(tools)
|
tools = flatten_list(tools)
|
||||||
self.params["tools"] = tools
|
self.params["tools"] = tools
|
||||||
prompt_params = [
|
prompt_params = [
|
||||||
key
|
key for key, value in self.params.items() if isinstance(value, str) and key != "format_instructions"
|
||||||
for key, value in self.params.items()
|
|
||||||
if isinstance(value, str) and key != "format_instructions"
|
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
prompt_params = ["template"]
|
prompt_params = ["template"]
|
||||||
|
|
@ -280,20 +266,14 @@ class PromptVertex(Vertex):
|
||||||
prompt_text = self.params[param]
|
prompt_text = self.params[param]
|
||||||
variables = extract_input_variables_from_prompt(prompt_text)
|
variables = extract_input_variables_from_prompt(prompt_text)
|
||||||
self.params["input_variables"].extend(variables)
|
self.params["input_variables"].extend(variables)
|
||||||
self.params["input_variables"] = list(
|
self.params["input_variables"] = list(set(self.params["input_variables"]))
|
||||||
set(self.params["input_variables"])
|
|
||||||
)
|
|
||||||
elif isinstance(self.params, dict):
|
elif isinstance(self.params, dict):
|
||||||
self.params.pop("input_variables", None)
|
self.params.pop("input_variables", None)
|
||||||
|
|
||||||
await self._build(user_id=user_id)
|
await self._build(user_id=user_id)
|
||||||
|
|
||||||
def _built_object_repr(self):
|
def _built_object_repr(self):
|
||||||
if (
|
if not self.artifacts or self._built_object is None or not hasattr(self._built_object, "format"):
|
||||||
not self.artifacts
|
|
||||||
or self._built_object is None
|
|
||||||
or not hasattr(self._built_object, "format")
|
|
||||||
):
|
|
||||||
return super()._built_object_repr()
|
return super()._built_object_repr()
|
||||||
elif isinstance(self._built_object, UnbuiltObject):
|
elif isinstance(self._built_object, UnbuiltObject):
|
||||||
return super()._built_object_repr()
|
return super()._built_object_repr()
|
||||||
|
|
@ -305,9 +285,7 @@ class PromptVertex(Vertex):
|
||||||
# so the prompt format doesn't break
|
# so the prompt format doesn't break
|
||||||
artifacts.pop("handle_keys", None)
|
artifacts.pop("handle_keys", None)
|
||||||
try:
|
try:
|
||||||
if not hasattr(self._built_object, "template") and hasattr(
|
if not hasattr(self._built_object, "template") and hasattr(self._built_object, "prompt"):
|
||||||
self._built_object, "prompt"
|
|
||||||
):
|
|
||||||
template = self._built_object.prompt.template
|
template = self._built_object.prompt.template
|
||||||
else:
|
else:
|
||||||
template = self._built_object.template
|
template = self._built_object.template
|
||||||
|
|
@ -315,11 +293,7 @@ class PromptVertex(Vertex):
|
||||||
if value:
|
if value:
|
||||||
replace_key = "{" + key + "}"
|
replace_key = "{" + key + "}"
|
||||||
template = template.replace(replace_key, value)
|
template = template.replace(replace_key, value)
|
||||||
return (
|
return template if isinstance(template, str) else f"{self.vertex_type}({template})"
|
||||||
template
|
|
||||||
if isinstance(template, str)
|
|
||||||
else f"{self.vertex_type}({template})"
|
|
||||||
)
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return str(self._built_object)
|
return str(self._built_object)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,5 @@ def records_to_text(template: str, records: list[Record]) -> list[str]:
|
||||||
records = [records]
|
records = [records]
|
||||||
# Check if there are any format strings in the template
|
# Check if there are any format strings in the template
|
||||||
|
|
||||||
formated_records = [
|
formated_records = [template.format(text=record.text, data=record.data, **record.data) for record in records]
|
||||||
template.format(text=record.text, data=record.data, **record.data)
|
|
||||||
for record in records
|
|
||||||
]
|
|
||||||
return "\n".join(formated_records)
|
return "\n".join(formated_records)
|
||||||
|
|
|
||||||
|
|
@ -89,11 +89,6 @@ def create_new_project(
|
||||||
)
|
)
|
||||||
db_flow = Flow.model_validate(new_project, from_attributes=True)
|
db_flow = Flow.model_validate(new_project, from_attributes=True)
|
||||||
session.add(db_flow)
|
session.add(db_flow)
|
||||||
flows = session.exec(
|
|
||||||
select(Flow).where(
|
|
||||||
Flow.name == project_name,
|
|
||||||
)
|
|
||||||
).all()
|
|
||||||
|
|
||||||
|
|
||||||
def get_all_flows_similar_to_project(session, project_name):
|
def get_all_flows_similar_to_project(session, project_name):
|
||||||
|
|
@ -117,7 +112,6 @@ def delete_start_projects(session):
|
||||||
|
|
||||||
|
|
||||||
def create_or_update_starter_projects():
|
def create_or_update_starter_projects():
|
||||||
|
|
||||||
with session_scope() as session:
|
with session_scope() as session:
|
||||||
starter_projects = load_starter_projects()
|
starter_projects = load_starter_projects()
|
||||||
delete_start_projects(session)
|
delete_start_projects(session)
|
||||||
|
|
@ -132,9 +126,7 @@ def create_or_update_starter_projects():
|
||||||
project_icon_bg_color,
|
project_icon_bg_color,
|
||||||
) = get_project_data(project)
|
) = get_project_data(project)
|
||||||
if project_name and project_data:
|
if project_name and project_data:
|
||||||
for existing_project in get_all_flows_similar_to_project(
|
for existing_project in get_all_flows_similar_to_project(session, project_name):
|
||||||
session, project_name
|
|
||||||
):
|
|
||||||
session.delete(existing_project)
|
session.delete(existing_project)
|
||||||
|
|
||||||
create_new_project(
|
create_new_project(
|
||||||
|
|
|
||||||
|
|
@ -95,9 +95,7 @@ class CodeParser:
|
||||||
elif isinstance(node, ast.ImportFrom):
|
elif isinstance(node, ast.ImportFrom):
|
||||||
for alias in node.names:
|
for alias in node.names:
|
||||||
if alias.asname:
|
if alias.asname:
|
||||||
self.data["imports"].append(
|
self.data["imports"].append((node.module, f"{alias.name} as {alias.asname}"))
|
||||||
(node.module, f"{alias.name} as {alias.asname}")
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
self.data["imports"].append((node.module, alias.name))
|
self.data["imports"].append((node.module, alias.name))
|
||||||
|
|
||||||
|
|
@ -146,9 +144,7 @@ class CodeParser:
|
||||||
return_type = None
|
return_type = None
|
||||||
if node.returns:
|
if node.returns:
|
||||||
return_type_str = ast.unparse(node.returns)
|
return_type_str = ast.unparse(node.returns)
|
||||||
eval_env = self.construct_eval_env(
|
eval_env = self.construct_eval_env(return_type_str, tuple(self.data["imports"]))
|
||||||
return_type_str, tuple(self.data["imports"])
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return_type = eval(return_type_str, eval_env)
|
return_type = eval(return_type_str, eval_env)
|
||||||
|
|
@ -190,22 +186,14 @@ class CodeParser:
|
||||||
num_defaults = len(node.args.defaults)
|
num_defaults = len(node.args.defaults)
|
||||||
num_missing_defaults = num_args - num_defaults
|
num_missing_defaults = num_args - num_defaults
|
||||||
missing_defaults = [None] * num_missing_defaults
|
missing_defaults = [None] * num_missing_defaults
|
||||||
default_values = [
|
default_values = [ast.unparse(default).strip("'") if default else None for default in node.args.defaults]
|
||||||
ast.unparse(default).strip("'") if default else None
|
|
||||||
for default in node.args.defaults
|
|
||||||
]
|
|
||||||
# Now check all default values to see if there
|
# Now check all default values to see if there
|
||||||
# are any "None" values in the middle
|
# are any "None" values in the middle
|
||||||
default_values = [
|
default_values = [None if value == "None" else value for value in default_values]
|
||||||
None if value == "None" else value for value in default_values
|
|
||||||
]
|
|
||||||
|
|
||||||
defaults = missing_defaults + default_values
|
defaults = missing_defaults + default_values
|
||||||
|
|
||||||
args = [
|
args = [self.parse_arg(arg, default) for arg, default in zip(node.args.args, defaults)]
|
||||||
self.parse_arg(arg, default)
|
|
||||||
for arg, default in zip(node.args.args, defaults)
|
|
||||||
]
|
|
||||||
return args
|
return args
|
||||||
|
|
||||||
def parse_varargs(self, node: ast.FunctionDef) -> List[Dict[str, Any]]:
|
def parse_varargs(self, node: ast.FunctionDef) -> List[Dict[str, Any]]:
|
||||||
|
|
@ -223,17 +211,11 @@ class CodeParser:
|
||||||
"""
|
"""
|
||||||
Parses the keyword-only arguments of a function or method node.
|
Parses the keyword-only arguments of a function or method node.
|
||||||
"""
|
"""
|
||||||
kw_defaults = [None] * (
|
kw_defaults = [None] * (len(node.args.kwonlyargs) - len(node.args.kw_defaults)) + [
|
||||||
len(node.args.kwonlyargs) - len(node.args.kw_defaults)
|
ast.unparse(default) if default else None for default in node.args.kw_defaults
|
||||||
) + [
|
|
||||||
ast.unparse(default) if default else None
|
|
||||||
for default in node.args.kw_defaults
|
|
||||||
]
|
]
|
||||||
|
|
||||||
args = [
|
args = [self.parse_arg(arg, default) for arg, default in zip(node.args.kwonlyargs, kw_defaults)]
|
||||||
self.parse_arg(arg, default)
|
|
||||||
for arg, default in zip(node.args.kwonlyargs, kw_defaults)
|
|
||||||
]
|
|
||||||
return args
|
return args
|
||||||
|
|
||||||
def parse_kwargs(self, node: ast.FunctionDef) -> List[Dict[str, Any]]:
|
def parse_kwargs(self, node: ast.FunctionDef) -> List[Dict[str, Any]]:
|
||||||
|
|
@ -337,9 +319,7 @@ class CodeParser:
|
||||||
Extracts global variables from the code.
|
Extracts global variables from the code.
|
||||||
"""
|
"""
|
||||||
global_var = {
|
global_var = {
|
||||||
"targets": [
|
"targets": [t.id if hasattr(t, "id") else ast.dump(t) for t in node.targets],
|
||||||
t.id if hasattr(t, "id") else ast.dump(t) for t in node.targets
|
|
||||||
],
|
|
||||||
"value": ast.unparse(node.value),
|
"value": ast.unparse(node.value),
|
||||||
}
|
}
|
||||||
self.data["global_vars"].append(global_var)
|
self.data["global_vars"].append(global_var)
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,7 @@ class ComponentFunctionEntrypointNameNullError(HTTPException):
|
||||||
|
|
||||||
class Component:
|
class Component:
|
||||||
ERROR_CODE_NULL: ClassVar[str] = "Python code must be provided."
|
ERROR_CODE_NULL: ClassVar[str] = "Python code must be provided."
|
||||||
ERROR_FUNCTION_ENTRYPOINT_NAME_NULL: ClassVar[str] = (
|
ERROR_FUNCTION_ENTRYPOINT_NAME_NULL: ClassVar[str] = "The name of the entrypoint function must be provided."
|
||||||
"The name of the entrypoint function must be provided."
|
|
||||||
)
|
|
||||||
|
|
||||||
code: Optional[str] = None
|
code: Optional[str] = None
|
||||||
_function_entrypoint_name: str = "build"
|
_function_entrypoint_name: str = "build"
|
||||||
|
|
|
||||||
|
|
@ -77,17 +77,13 @@ class CustomComponent(Component):
|
||||||
|
|
||||||
def update_state(self, name: str, value: Any):
|
def update_state(self, name: str, value: Any):
|
||||||
try:
|
try:
|
||||||
self.vertex.graph.update_state(
|
self.vertex.graph.update_state(name=name, record=value, caller=self.vertex.id)
|
||||||
name=name, record=value, caller=self.vertex.id
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError(f"Error updating state: {e}")
|
raise ValueError(f"Error updating state: {e}")
|
||||||
|
|
||||||
def append_state(self, name: str, value: Any):
|
def append_state(self, name: str, value: Any):
|
||||||
try:
|
try:
|
||||||
self.vertex.graph.append_state(
|
self.vertex.graph.append_state(name=name, record=value, caller=self.vertex.id)
|
||||||
name=name, record=value, caller=self.vertex.id
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError(f"Error appending state: {e}")
|
raise ValueError(f"Error appending state: {e}")
|
||||||
|
|
||||||
|
|
@ -138,9 +134,7 @@ class CustomComponent(Component):
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
return self.field_config
|
return self.field_config
|
||||||
|
|
||||||
def update_build_config(
|
def update_build_config(self, build_config: dict, field_name: str, field_value: Any):
|
||||||
self, build_config: dict, field_name: str, field_value: Any
|
|
||||||
):
|
|
||||||
build_config[field_name] = field_value
|
build_config[field_name] = field_value
|
||||||
return build_config
|
return build_config
|
||||||
|
|
||||||
|
|
@ -148,9 +142,7 @@ class CustomComponent(Component):
|
||||||
def tree(self):
|
def tree(self):
|
||||||
return self.get_code_tree(self.code or "")
|
return self.get_code_tree(self.code or "")
|
||||||
|
|
||||||
def to_records(
|
def to_records(self, data: Any, keys: Optional[List[str]] = None, silent_errors: bool = False) -> List[Record]:
|
||||||
self, data: Any, keys: Optional[List[str]] = None, silent_errors: bool = False
|
|
||||||
) -> List[Record]:
|
|
||||||
"""
|
"""
|
||||||
Converts input data into a list of Record objects.
|
Converts input data into a list of Record objects.
|
||||||
|
|
||||||
|
|
@ -199,9 +191,7 @@ class CustomComponent(Component):
|
||||||
|
|
||||||
return records
|
return records
|
||||||
|
|
||||||
def create_references_from_records(
|
def create_references_from_records(self, records: List[Record], include_data: bool = False) -> str:
|
||||||
self, records: List[Record], include_data: bool = False
|
|
||||||
) -> str:
|
|
||||||
"""
|
"""
|
||||||
Create references from a list of records.
|
Create references from a list of records.
|
||||||
|
|
||||||
|
|
@ -240,20 +230,14 @@ class CustomComponent(Component):
|
||||||
if not self.code:
|
if not self.code:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
component_classes = [
|
component_classes = [cls for cls in self.tree["classes"] if self.code_class_base_inheritance in cls["bases"]]
|
||||||
cls
|
|
||||||
for cls in self.tree["classes"]
|
|
||||||
if self.code_class_base_inheritance in cls["bases"]
|
|
||||||
]
|
|
||||||
if not component_classes:
|
if not component_classes:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
# Assume the first Component class is the one we're interested in
|
# Assume the first Component class is the one we're interested in
|
||||||
component_class = component_classes[0]
|
component_class = component_classes[0]
|
||||||
build_methods = [
|
build_methods = [
|
||||||
method
|
method for method in component_class["methods"] if method["name"] == self.function_entrypoint_name
|
||||||
for method in component_class["methods"]
|
|
||||||
if method["name"] == self.function_entrypoint_name
|
|
||||||
]
|
]
|
||||||
|
|
||||||
return build_methods[0] if build_methods else {}
|
return build_methods[0] if build_methods else {}
|
||||||
|
|
@ -310,9 +294,7 @@ class CustomComponent(Component):
|
||||||
# Retrieve and decrypt the credential by name for the current user
|
# Retrieve and decrypt the credential by name for the current user
|
||||||
db_service = get_db_service()
|
db_service = get_db_service()
|
||||||
with session_getter(db_service) as session:
|
with session_getter(db_service) as session:
|
||||||
return credential_service.get_credential(
|
return credential_service.get_credential(user_id=self._user_id or "", name=name, session=session)
|
||||||
user_id=self._user_id or "", name=name, session=session
|
|
||||||
)
|
|
||||||
|
|
||||||
return get_credential
|
return get_credential
|
||||||
|
|
||||||
|
|
@ -322,9 +304,7 @@ class CustomComponent(Component):
|
||||||
credential_service = get_credential_service()
|
credential_service = get_credential_service()
|
||||||
db_service = get_db_service()
|
db_service = get_db_service()
|
||||||
with session_getter(db_service) as session:
|
with session_getter(db_service) as session:
|
||||||
return credential_service.list_credentials(
|
return credential_service.list_credentials(user_id=self._user_id, session=session)
|
||||||
user_id=self._user_id, session=session
|
|
||||||
)
|
|
||||||
|
|
||||||
def index(self, value: int = 0):
|
def index(self, value: int = 0):
|
||||||
"""Returns a function that returns the value at the given index in the iterable."""
|
"""Returns a function that returns the value at the given index in the iterable."""
|
||||||
|
|
@ -363,11 +343,7 @@ class CustomComponent(Component):
|
||||||
if not self._flows_records:
|
if not self._flows_records:
|
||||||
self.list_flows()
|
self.list_flows()
|
||||||
if not flow_id and self._flows_records:
|
if not flow_id and self._flows_records:
|
||||||
flow_ids = [
|
flow_ids = [flow.data["id"] for flow in self._flows_records if flow.data["name"] == flow_name]
|
||||||
flow.data["id"]
|
|
||||||
for flow in self._flows_records
|
|
||||||
if flow.data["name"] == flow_name
|
|
||||||
]
|
|
||||||
if not flow_ids:
|
if not flow_ids:
|
||||||
raise ValueError(f"Flow {flow_name} not found")
|
raise ValueError(f"Flow {flow_name} not found")
|
||||||
elif len(flow_ids) > 1:
|
elif len(flow_ids) > 1:
|
||||||
|
|
@ -389,9 +365,7 @@ class CustomComponent(Component):
|
||||||
db_service = get_db_service()
|
db_service = get_db_service()
|
||||||
with get_session(db_service) as session:
|
with get_session(db_service) as session:
|
||||||
flows = session.exec(
|
flows = session.exec(
|
||||||
select(Flow)
|
select(Flow).where(Flow.user_id == self._user_id).where(Flow.is_component == False) # noqa
|
||||||
.where(Flow.user_id == self._user_id)
|
|
||||||
.where(Flow.is_component == False)
|
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
flows_records = [flow.to_record() for flow in flows]
|
flows_records = [flow.to_record() for flow in flows]
|
||||||
|
|
|
||||||
|
|
@ -80,13 +80,9 @@ 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(
|
items.append({"name": menu["name"], "path": menu["path"], "components": components})
|
||||||
{"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(
|
logger.debug(f'Filtered components {"with errors" if with_errors else ""}: {len(filtered)}')
|
||||||
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):
|
||||||
|
|
@ -119,9 +115,7 @@ 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(
|
raise CustomComponentPathValueError(f"The path needs to start with '{self.base_path}'.")
|
||||||
f"The path needs to start with '{self.base_path}'."
|
|
||||||
)
|
|
||||||
|
|
||||||
file_list = []
|
file_list = []
|
||||||
safe_path_obj = Path(safe_path)
|
safe_path_obj = Path(safe_path)
|
||||||
|
|
@ -131,11 +125,7 @@ class DirectoryReader:
|
||||||
# any folders below [folder] will be ignored
|
# any folders below [folder] will be ignored
|
||||||
# basically the parent folder of the file should be a
|
# basically the parent folder of the file should be a
|
||||||
# folder in the safe_path
|
# folder in the safe_path
|
||||||
if (
|
if file_path.is_file() and file_path.parent.parent == safe_path_obj and not file_path.name.startswith("__"):
|
||||||
file_path.is_file()
|
|
||||||
and file_path.parent.parent == safe_path_obj
|
|
||||||
and not file_path.name.startswith("__")
|
|
||||||
):
|
|
||||||
file_list.append(str(file_path))
|
file_list.append(str(file_path))
|
||||||
return file_list
|
return file_list
|
||||||
|
|
||||||
|
|
@ -173,9 +163,7 @@ 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(
|
if self._is_type_hint_in_arg_annotation(arg.annotation, type_hint_name):
|
||||||
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
|
||||||
|
|
@ -193,16 +181,14 @@ class DirectoryReader:
|
||||||
and annotation.value.id == type_hint_name
|
and annotation.value.id == type_hint_name
|
||||||
)
|
)
|
||||||
|
|
||||||
def is_type_hint_used_but_not_imported(
|
def is_type_hint_used_but_not_imported(self, type_hint_name: str, code: str) -> bool:
|
||||||
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(
|
return self._is_type_hint_used_in_args(type_hint_name, code) and not self._is_type_hint_imported(
|
||||||
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
|
||||||
|
|
@ -223,9 +209,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(
|
elif self._is_type_hint_used_in_args("Optional", file_content) and not self._is_type_hint_imported(
|
||||||
"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.",
|
||||||
|
|
@ -241,18 +227,14 @@ class DirectoryReader:
|
||||||
from the .py files in the directory.
|
from the .py files in the directory.
|
||||||
"""
|
"""
|
||||||
response = {"menu": []}
|
response = {"menu": []}
|
||||||
logger.debug(
|
logger.debug("-------------------- Building component menu list --------------------")
|
||||||
"-------------------- 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))
|
||||||
filename = os.path.basename(file_path)
|
filename = os.path.basename(file_path)
|
||||||
validation_result, result_content = self.process_file(file_path)
|
validation_result, result_content = self.process_file(file_path)
|
||||||
if not validation_result:
|
if not validation_result:
|
||||||
logger.error(
|
logger.error(f"Error while processing file {file_path}: {result_content}")
|
||||||
f"Error while processing file {file_path}: {result_content}"
|
|
||||||
)
|
|
||||||
|
|
||||||
menu_result = self.find_menu(response, menu_name) or {
|
menu_result = self.find_menu(response, menu_name) or {
|
||||||
"name": menu_name,
|
"name": menu_name,
|
||||||
|
|
@ -265,9 +247,7 @@ 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(
|
component_name_camelcase = " ".join(word.title() for word in component_name.split("_"))
|
||||||
word.title() for word in component_name.split("_")
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
component_name_camelcase = component_name
|
component_name_camelcase = component_name
|
||||||
|
|
||||||
|
|
@ -275,9 +255,7 @@ 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(
|
logger.exception(f"Error while getting output types from code: {str(exc)}")
|
||||||
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]
|
||||||
|
|
@ -293,9 +271,7 @@ 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(
|
logger.debug("-------------------- Component menu list built --------------------")
|
||||||
"-------------------- Component menu list built --------------------"
|
|
||||||
)
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
||||||
|
|
@ -32,18 +32,14 @@ class UpdateBuildConfigError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def add_output_types(
|
def add_output_types(frontend_node: CustomComponentFrontendNode, return_types: List[str]):
|
||||||
frontend_node: CustomComponentFrontendNode, return_types: List[str]
|
|
||||||
):
|
|
||||||
"""Add output types to the frontend node"""
|
"""Add output types to the frontend node"""
|
||||||
for return_type in return_types:
|
for return_type in return_types:
|
||||||
if return_type is None:
|
if return_type is None:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=400,
|
status_code=400,
|
||||||
detail={
|
detail={
|
||||||
"error": (
|
"error": ("Invalid return type. Please check your code and try again."),
|
||||||
"Invalid return type. Please check your code and try again."
|
|
||||||
),
|
|
||||||
"traceback": traceback.format_exc(),
|
"traceback": traceback.format_exc(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -75,18 +71,14 @@ def reorder_fields(frontend_node: CustomComponentFrontendNode, field_order: List
|
||||||
frontend_node.field_order = field_order
|
frontend_node.field_order = field_order
|
||||||
|
|
||||||
|
|
||||||
def add_base_classes(
|
def add_base_classes(frontend_node: CustomComponentFrontendNode, return_types: List[str]):
|
||||||
frontend_node: CustomComponentFrontendNode, return_types: List[str]
|
|
||||||
):
|
|
||||||
"""Add base classes to the frontend node"""
|
"""Add base classes to the frontend node"""
|
||||||
for return_type_instance in return_types:
|
for return_type_instance in return_types:
|
||||||
if return_type_instance is None:
|
if return_type_instance is None:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=400,
|
status_code=400,
|
||||||
detail={
|
detail={
|
||||||
"error": (
|
"error": ("Invalid return type. Please check your code and try again."),
|
||||||
"Invalid return type. Please check your code and try again."
|
|
||||||
),
|
|
||||||
"traceback": traceback.format_exc(),
|
"traceback": traceback.format_exc(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -163,14 +155,10 @@ def add_new_custom_field(
|
||||||
# If options is a list, then it's a dropdown
|
# If options is a list, then it's a dropdown
|
||||||
# If options is None, then it's a list of strings
|
# If options is None, then it's a list of strings
|
||||||
is_list = isinstance(field_config.get("options"), list)
|
is_list = isinstance(field_config.get("options"), list)
|
||||||
field_config["is_list"] = (
|
field_config["is_list"] = is_list or field_config.get("is_list", False) or field_contains_list
|
||||||
is_list or field_config.get("is_list", False) or field_contains_list
|
|
||||||
)
|
|
||||||
|
|
||||||
if "name" in field_config:
|
if "name" in field_config:
|
||||||
warnings.warn(
|
warnings.warn("The 'name' key in field_config is used to build the object and can't be changed.")
|
||||||
"The 'name' key in field_config is used to build the object and can't be changed."
|
|
||||||
)
|
|
||||||
required = field_config.pop("required", field_required)
|
required = field_config.pop("required", field_required)
|
||||||
placeholder = field_config.pop("placeholder", "")
|
placeholder = field_config.pop("placeholder", "")
|
||||||
|
|
||||||
|
|
@ -209,9 +197,7 @@ def add_extra_fields(frontend_node, field_config, function_args):
|
||||||
]:
|
]:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
field_name, field_type, field_value, field_required = get_field_properties(
|
field_name, field_type, field_value, field_required = get_field_properties(extra_field)
|
||||||
extra_field
|
|
||||||
)
|
|
||||||
config = _field_config.pop(field_name, {})
|
config = _field_config.pop(field_name, {})
|
||||||
frontend_node = add_new_custom_field(
|
frontend_node = add_new_custom_field(
|
||||||
frontend_node,
|
frontend_node,
|
||||||
|
|
@ -221,17 +207,13 @@ def add_extra_fields(frontend_node, field_config, function_args):
|
||||||
field_required,
|
field_required,
|
||||||
config,
|
config,
|
||||||
)
|
)
|
||||||
if "kwargs" in function_args_names and not all(
|
if "kwargs" in function_args_names and not all(key in function_args_names for key in field_config.keys()):
|
||||||
key in function_args_names for key in field_config.keys()
|
|
||||||
):
|
|
||||||
for field_name, field_config in _field_config.copy().items():
|
for field_name, field_config in _field_config.copy().items():
|
||||||
if "name" not in field_config or field_name == "code":
|
if "name" not in field_config or field_name == "code":
|
||||||
continue
|
continue
|
||||||
config = _field_config.get(field_name, {})
|
config = _field_config.get(field_name, {})
|
||||||
config = config.model_dump() if isinstance(config, BaseModel) else config
|
config = config.model_dump() if isinstance(config, BaseModel) else config
|
||||||
field_name, field_type, field_value, field_required = get_field_properties(
|
field_name, field_type, field_value, field_required = get_field_properties(extra_field=config)
|
||||||
extra_field=config
|
|
||||||
)
|
|
||||||
frontend_node = add_new_custom_field(
|
frontend_node = add_new_custom_field(
|
||||||
frontend_node,
|
frontend_node,
|
||||||
field_name,
|
field_name,
|
||||||
|
|
@ -269,9 +251,7 @@ def run_build_config(
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=400,
|
status_code=400,
|
||||||
detail={
|
detail={
|
||||||
"error": (
|
"error": ("Invalid type convertion. Please check your code and try again."),
|
||||||
"Invalid type convertion. Please check your code and try again."
|
|
||||||
),
|
|
||||||
"traceback": traceback.format_exc(),
|
"traceback": traceback.format_exc(),
|
||||||
},
|
},
|
||||||
) from exc
|
) from exc
|
||||||
|
|
@ -383,16 +363,10 @@ def build_custom_component_template(
|
||||||
|
|
||||||
add_extra_fields(frontend_node, field_config, entrypoint_args)
|
add_extra_fields(frontend_node, field_config, entrypoint_args)
|
||||||
|
|
||||||
frontend_node = add_code_field(
|
frontend_node = add_code_field(frontend_node, custom_component.code, field_config.get("code", {}))
|
||||||
frontend_node, custom_component.code, field_config.get("code", {})
|
|
||||||
)
|
|
||||||
|
|
||||||
add_base_classes(
|
add_base_classes(frontend_node, custom_component.get_function_entrypoint_return_type)
|
||||||
frontend_node, custom_component.get_function_entrypoint_return_type
|
add_output_types(frontend_node, custom_component.get_function_entrypoint_return_type)
|
||||||
)
|
|
||||||
add_output_types(
|
|
||||||
frontend_node, custom_component.get_function_entrypoint_return_type
|
|
||||||
)
|
|
||||||
|
|
||||||
reorder_fields(frontend_node, custom_instance._get_field_order())
|
reorder_fields(frontend_node, custom_instance._get_field_order())
|
||||||
|
|
||||||
|
|
@ -403,9 +377,7 @@ def build_custom_component_template(
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=400,
|
status_code=400,
|
||||||
detail={
|
detail={
|
||||||
"error": (
|
"error": ("Invalid type convertion. Please check your code and try again."),
|
||||||
"Invalid type convertion. Please check your code and try again."
|
|
||||||
),
|
|
||||||
"traceback": traceback.format_exc(),
|
"traceback": traceback.format_exc(),
|
||||||
},
|
},
|
||||||
) from exc
|
) from exc
|
||||||
|
|
@ -431,9 +403,7 @@ def build_custom_components(settings_service):
|
||||||
if not settings_service.settings.COMPONENTS_PATH:
|
if not settings_service.settings.COMPONENTS_PATH:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
logger.info(
|
logger.info(f"Building custom components from {settings_service.settings.COMPONENTS_PATH}")
|
||||||
f"Building custom components from {settings_service.settings.COMPONENTS_PATH}"
|
|
||||||
)
|
|
||||||
custom_components_from_file = {}
|
custom_components_from_file = {}
|
||||||
processed_paths = set()
|
processed_paths = set()
|
||||||
for path in settings_service.settings.COMPONENTS_PATH:
|
for path in settings_service.settings.COMPONENTS_PATH:
|
||||||
|
|
@ -444,9 +414,7 @@ def build_custom_components(settings_service):
|
||||||
custom_component_dict = build_custom_component_list_from_path(path_str)
|
custom_component_dict = build_custom_component_list_from_path(path_str)
|
||||||
if custom_component_dict:
|
if custom_component_dict:
|
||||||
category = next(iter(custom_component_dict))
|
category = next(iter(custom_component_dict))
|
||||||
logger.info(
|
logger.info(f"Loading {len(custom_component_dict[category])} component(s) from category {category}")
|
||||||
f"Loading {len(custom_component_dict[category])} component(s) from category {category}"
|
|
||||||
)
|
|
||||||
custom_components_from_file = merge_nested_dicts_with_renaming(
|
custom_components_from_file = merge_nested_dicts_with_renaming(
|
||||||
custom_components_from_file, custom_component_dict
|
custom_components_from_file, custom_component_dict
|
||||||
)
|
)
|
||||||
|
|
@ -467,14 +435,10 @@ def update_field_dict(
|
||||||
if "refresh" in field_dict:
|
if "refresh" in field_dict:
|
||||||
if call:
|
if call:
|
||||||
try:
|
try:
|
||||||
custom_component_instance.update_build_config(
|
custom_component_instance.update_build_config(build_config, update_field, update_field_value)
|
||||||
build_config, update_field, update_field_value
|
|
||||||
)
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error(f"Error while running update_build_config: {str(exc)}")
|
logger.error(f"Error while running update_build_config: {str(exc)}")
|
||||||
raise UpdateBuildConfigError(
|
raise UpdateBuildConfigError(f"Error while running update_build_config: {str(exc)}") from exc
|
||||||
f"Error while running update_build_config: {str(exc)}"
|
|
||||||
) from exc
|
|
||||||
field_dict["refresh"] = True
|
field_dict["refresh"] = True
|
||||||
|
|
||||||
# Let's check if "range_spec" is a RangeSpec object
|
# Let's check if "range_spec" is a RangeSpec object
|
||||||
|
|
|
||||||
|
|
@ -144,13 +144,9 @@ async def instantiate_based_on_type(
|
||||||
return class_object(**params)
|
return class_object(**params)
|
||||||
|
|
||||||
|
|
||||||
async def instantiate_custom_component(
|
async def instantiate_custom_component(node_type, class_object, params, user_id, vertex):
|
||||||
node_type, class_object, params, user_id, vertex
|
|
||||||
):
|
|
||||||
params_copy = params.copy()
|
params_copy = params.copy()
|
||||||
class_object: Type["CustomComponent"] = eval_custom_component_code(
|
class_object: Type["CustomComponent"] = eval_custom_component_code(params_copy.pop("code"))
|
||||||
params_copy.pop("code")
|
|
||||||
)
|
|
||||||
custom_component: "CustomComponent" = class_object(
|
custom_component: "CustomComponent" = class_object(
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
parameters=params_copy,
|
parameters=params_copy,
|
||||||
|
|
@ -226,9 +222,7 @@ def instantiate_memory(node_type, class_object, params):
|
||||||
# I want to catch a specific attribute error that happens
|
# I want to catch a specific attribute error that happens
|
||||||
# when the object does not have a cursor attribute
|
# when the object does not have a cursor attribute
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
if "object has no attribute 'cursor'" in str(
|
if "object has no attribute 'cursor'" in str(exc) or 'object has no field "conn"' in str(exc):
|
||||||
exc
|
|
||||||
) or 'object has no field "conn"' in str(exc):
|
|
||||||
raise AttributeError(
|
raise AttributeError(
|
||||||
(
|
(
|
||||||
"Failed to build connection to database."
|
"Failed to build connection to database."
|
||||||
|
|
@ -271,9 +265,7 @@ def instantiate_agent(node_type, class_object: Type[agent_module.Agent], params:
|
||||||
if class_method := getattr(class_object, method, None):
|
if class_method := getattr(class_object, method, None):
|
||||||
agent = class_method(**params)
|
agent = class_method(**params)
|
||||||
tools = params.get("tools", [])
|
tools = params.get("tools", [])
|
||||||
return AgentExecutor.from_agent_and_tools(
|
return AgentExecutor.from_agent_and_tools(agent=agent, tools=tools, handle_parsing_errors=True)
|
||||||
agent=agent, tools=tools, handle_parsing_errors=True
|
|
||||||
)
|
|
||||||
return load_agent_executor(class_object, params)
|
return load_agent_executor(class_object, params)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -329,11 +321,7 @@ def instantiate_embedding(node_type, class_object, params: Dict):
|
||||||
try:
|
try:
|
||||||
return class_object(**params)
|
return class_object(**params)
|
||||||
except ValidationError:
|
except ValidationError:
|
||||||
params = {
|
params = {key: value for key, value in params.items() if key in class_object.model_fields}
|
||||||
key: value
|
|
||||||
for key, value in params.items()
|
|
||||||
if key in class_object.model_fields
|
|
||||||
}
|
|
||||||
return class_object(**params)
|
return class_object(**params)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -345,9 +333,7 @@ def instantiate_vectorstore(class_object: Type[VectorStore], params: Dict):
|
||||||
if "texts" in params:
|
if "texts" in params:
|
||||||
params["documents"] = params.pop("texts")
|
params["documents"] = params.pop("texts")
|
||||||
if "documents" in params:
|
if "documents" in params:
|
||||||
params["documents"] = [
|
params["documents"] = [doc for doc in params["documents"] if isinstance(doc, Document)]
|
||||||
doc for doc in params["documents"] if isinstance(doc, Document)
|
|
||||||
]
|
|
||||||
if initializer := vecstore_initializer.get(class_object.__name__):
|
if initializer := vecstore_initializer.get(class_object.__name__):
|
||||||
vecstore = initializer(class_object, params)
|
vecstore = initializer(class_object, params)
|
||||||
else:
|
else:
|
||||||
|
|
@ -362,9 +348,7 @@ def instantiate_vectorstore(class_object: Type[VectorStore], params: Dict):
|
||||||
return vecstore
|
return vecstore
|
||||||
|
|
||||||
|
|
||||||
def instantiate_documentloader(
|
def instantiate_documentloader(node_type: str, class_object: Type[BaseLoader], params: Dict):
|
||||||
node_type: str, class_object: Type[BaseLoader], params: Dict
|
|
||||||
):
|
|
||||||
if "file_filter" in params:
|
if "file_filter" in params:
|
||||||
# file_filter will be a string but we need a function
|
# file_filter will be a string but we need a function
|
||||||
# that will be used to filter the files using file_filter
|
# that will be used to filter the files using file_filter
|
||||||
|
|
@ -373,17 +357,13 @@ def instantiate_documentloader(
|
||||||
# in x and if it is, we will return True
|
# in x and if it is, we will return True
|
||||||
file_filter = params.pop("file_filter")
|
file_filter = params.pop("file_filter")
|
||||||
extensions = file_filter.split(",")
|
extensions = file_filter.split(",")
|
||||||
params["file_filter"] = lambda x: any(
|
params["file_filter"] = lambda x: any(extension.strip() in x for extension in extensions)
|
||||||
extension.strip() in x for extension in extensions
|
|
||||||
)
|
|
||||||
metadata = params.pop("metadata", None)
|
metadata = params.pop("metadata", None)
|
||||||
if metadata and isinstance(metadata, str):
|
if metadata and isinstance(metadata, str):
|
||||||
try:
|
try:
|
||||||
metadata = orjson.loads(metadata)
|
metadata = orjson.loads(metadata)
|
||||||
except json.JSONDecodeError as exc:
|
except json.JSONDecodeError as exc:
|
||||||
raise ValueError(
|
raise ValueError("The metadata you provided is not a valid JSON string.") from exc
|
||||||
"The metadata you provided is not a valid JSON string."
|
|
||||||
) from exc
|
|
||||||
|
|
||||||
if node_type == "WebBaseLoader":
|
if node_type == "WebBaseLoader":
|
||||||
if web_path := params.pop("web_path", None):
|
if web_path := params.pop("web_path", None):
|
||||||
|
|
@ -416,16 +396,12 @@ def instantiate_textsplitter(
|
||||||
"Try changing the chunk_size of the Text Splitter."
|
"Try changing the chunk_size of the Text Splitter."
|
||||||
) from exc
|
) from exc
|
||||||
|
|
||||||
if (
|
if ("separator_type" in params and params["separator_type"] == "Text") or "separator_type" not in params:
|
||||||
"separator_type" in params and params["separator_type"] == "Text"
|
|
||||||
) or "separator_type" not in params:
|
|
||||||
params.pop("separator_type", None)
|
params.pop("separator_type", None)
|
||||||
# separators might come in as an escaped string like \\n
|
# separators might come in as an escaped string like \\n
|
||||||
# so we need to convert it to a string
|
# so we need to convert it to a string
|
||||||
if "separators" in params:
|
if "separators" in params:
|
||||||
params["separators"] = (
|
params["separators"] = params["separators"].encode().decode("unicode-escape")
|
||||||
params["separators"].encode().decode("unicode-escape")
|
|
||||||
)
|
|
||||||
text_splitter = class_object(**params)
|
text_splitter = class_object(**params)
|
||||||
else:
|
else:
|
||||||
from langchain.text_splitter import Language
|
from langchain.text_splitter import Language
|
||||||
|
|
@ -452,8 +428,7 @@ def replace_zero_shot_prompt_with_prompt_template(nodes):
|
||||||
tools = [
|
tools = [
|
||||||
tool
|
tool
|
||||||
for tool in nodes
|
for tool in nodes
|
||||||
if tool["type"] != "chatOutputNode"
|
if tool["type"] != "chatOutputNode" and "Tool" in tool["data"]["node"]["base_classes"]
|
||||||
and "Tool" in tool["data"]["node"]["base_classes"]
|
|
||||||
]
|
]
|
||||||
node["data"] = build_prompt_template(prompt=node["data"], tools=tools)
|
node["data"] = build_prompt_template(prompt=node["data"], tools=tools)
|
||||||
break
|
break
|
||||||
|
|
@ -467,9 +442,7 @@ def load_agent_executor(agent_class: type[agent_module.Agent], params, **kwargs)
|
||||||
# agent has hidden args for memory. might need to be support
|
# agent has hidden args for memory. might need to be support
|
||||||
# memory = params["memory"]
|
# memory = params["memory"]
|
||||||
# if allowed_tools is not a list or set, make it a list
|
# if allowed_tools is not a list or set, make it a list
|
||||||
if not isinstance(allowed_tools, (list, set)) and isinstance(
|
if not isinstance(allowed_tools, (list, set)) and isinstance(allowed_tools, BaseTool):
|
||||||
allowed_tools, BaseTool
|
|
||||||
):
|
|
||||||
allowed_tools = [allowed_tools]
|
allowed_tools = [allowed_tools]
|
||||||
tool_names = [tool.name for tool in allowed_tools]
|
tool_names = [tool.name for tool in allowed_tools]
|
||||||
# Agent class requires an output_parser but Agent classes
|
# Agent class requires an output_parser but Agent classes
|
||||||
|
|
@ -497,10 +470,7 @@ def build_prompt_template(prompt, tools):
|
||||||
format_instructions = prompt["node"]["template"]["format_instructions"]["value"]
|
format_instructions = prompt["node"]["template"]["format_instructions"]["value"]
|
||||||
|
|
||||||
tool_strings = "\n".join(
|
tool_strings = "\n".join(
|
||||||
[
|
[f"{tool['data']['node']['name']}: {tool['data']['node']['description']}" for tool in tools]
|
||||||
f"{tool['data']['node']['name']}: {tool['data']['node']['description']}"
|
|
||||||
for tool in tools
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
tool_names = ", ".join([tool["data"]["node"]["name"] for tool in tools])
|
tool_names = ", ".join([tool["data"]["node"]["name"] for tool in tools])
|
||||||
format_instructions = format_instructions.format(tool_names=tool_names)
|
format_instructions = format_instructions.format(tool_names=tool_names)
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,4 @@ def get_all_types_dict(settings_service):
|
||||||
"""Get all types dictionary combining native and custom components."""
|
"""Get all types dictionary combining native and custom components."""
|
||||||
native_components = build_langchain_types_dict()
|
native_components = build_langchain_types_dict()
|
||||||
custom_components_from_file = build_custom_components(settings_service)
|
custom_components_from_file = build_custom_components(settings_service)
|
||||||
return merge_nested_dicts_with_renaming(
|
return merge_nested_dicts_with_renaming(native_components, custom_components_from_file)
|
||||||
native_components, custom_components_from_file
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,7 @@ def try_setting_streaming_options(langchain_object):
|
||||||
llm = None
|
llm = None
|
||||||
if hasattr(langchain_object, "llm"):
|
if hasattr(langchain_object, "llm"):
|
||||||
llm = langchain_object.llm
|
llm = langchain_object.llm
|
||||||
elif hasattr(langchain_object, "llm_chain") and hasattr(
|
elif hasattr(langchain_object, "llm_chain") and hasattr(langchain_object.llm_chain, "llm"):
|
||||||
langchain_object.llm_chain, "llm"
|
|
||||||
):
|
|
||||||
llm = langchain_object.llm_chain.llm
|
llm = langchain_object.llm_chain.llm
|
||||||
|
|
||||||
if isinstance(llm, BaseLanguageModel):
|
if isinstance(llm, BaseLanguageModel):
|
||||||
|
|
@ -71,9 +69,7 @@ def extract_input_variables_from_prompt(prompt: str) -> list[str]:
|
||||||
|
|
||||||
# Extract the variable name from either the single or double brace match
|
# Extract the variable name from either the single or double brace match
|
||||||
if match.group(1): # Match found in double braces
|
if match.group(1): # Match found in double braces
|
||||||
variable_name = (
|
variable_name = "{{" + match.group(1) + "}}" # Re-add single braces for JSON strings
|
||||||
"{{" + match.group(1) + "}}"
|
|
||||||
) # Re-add single braces for JSON strings
|
|
||||||
else: # Match found in single braces
|
else: # Match found in single braces
|
||||||
variable_name = match.group(2)
|
variable_name = match.group(2)
|
||||||
if variable_name is not None:
|
if variable_name is not None:
|
||||||
|
|
@ -109,9 +105,7 @@ def set_langchain_cache(settings):
|
||||||
|
|
||||||
if cache_type := os.getenv("LANGFLOW_LANGCHAIN_CACHE"):
|
if cache_type := os.getenv("LANGFLOW_LANGCHAIN_CACHE"):
|
||||||
try:
|
try:
|
||||||
cache_class = import_class(
|
cache_class = import_class(f"langchain.cache.{cache_type or settings.LANGCHAIN_CACHE}")
|
||||||
f"langchain.cache.{cache_type or settings.LANGCHAIN_CACHE}"
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.debug(f"Setting up LLM caching with {cache_class.__name__}")
|
logger.debug(f"Setting up LLM caching with {cache_class.__name__}")
|
||||||
set_llm_cache(cache_class())
|
set_llm_cache(cache_class())
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,7 @@ from langflow.utils.logger import configure
|
||||||
def get_lifespan(fix_migration=False, socketio_server=None):
|
def get_lifespan(fix_migration=False, socketio_server=None):
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def lifespan(app: FastAPI):
|
async def lifespan(app: FastAPI):
|
||||||
initialize_services(
|
initialize_services(fix_migration=fix_migration, socketio_server=socketio_server)
|
||||||
fix_migration=fix_migration, socketio_server=socketio_server
|
|
||||||
)
|
|
||||||
setup_llm_caching()
|
setup_llm_caching()
|
||||||
LangfuseInstance.update()
|
LangfuseInstance.update()
|
||||||
create_or_update_starter_projects()
|
create_or_update_starter_projects()
|
||||||
|
|
@ -36,9 +34,7 @@ def create_app():
|
||||||
"""Create the FastAPI app and include the router."""
|
"""Create the FastAPI app and include the router."""
|
||||||
|
|
||||||
configure()
|
configure()
|
||||||
socketio_server = socketio.AsyncServer(
|
socketio_server = socketio.AsyncServer(async_mode="asgi", cors_allowed_origins="*", logger=True)
|
||||||
async_mode="asgi", cors_allowed_origins="*", logger=True
|
|
||||||
)
|
|
||||||
lifespan = get_lifespan(socketio_server=socketio_server)
|
lifespan = get_lifespan(socketio_server=socketio_server)
|
||||||
app = FastAPI(lifespan=lifespan)
|
app = FastAPI(lifespan=lifespan)
|
||||||
origins = ["*"]
|
origins = ["*"]
|
||||||
|
|
@ -105,9 +101,7 @@ def get_static_files_dir():
|
||||||
return frontend_path / "frontend"
|
return frontend_path / "frontend"
|
||||||
|
|
||||||
|
|
||||||
def setup_app(
|
def setup_app(static_files_dir: Optional[Path] = None, backend_only: bool = False) -> FastAPI:
|
||||||
static_files_dir: Optional[Path] = None, backend_only: bool = False
|
|
||||||
) -> FastAPI:
|
|
||||||
"""Setup the FastAPI app."""
|
"""Setup the FastAPI app."""
|
||||||
# get the directory of the current file
|
# get the directory of the current file
|
||||||
if not static_files_dir:
|
if not static_files_dir:
|
||||||
|
|
|
||||||
|
|
@ -126,9 +126,7 @@ async def process_runnable(runnable: Runnable, inputs: Union[dict, List[dict]]):
|
||||||
elif isinstance(inputs, dict) and hasattr(runnable, "ainvoke"):
|
elif isinstance(inputs, dict) and hasattr(runnable, "ainvoke"):
|
||||||
result = await runnable.ainvoke(inputs)
|
result = await runnable.ainvoke(inputs)
|
||||||
else:
|
else:
|
||||||
raise ValueError(
|
raise ValueError(f"Runnable {runnable} does not support inputs of type {type(inputs)}")
|
||||||
f"Runnable {runnable} does not support inputs of type {type(inputs)}"
|
|
||||||
)
|
|
||||||
# Check if the result is a list of AIMessages
|
# Check if the result is a list of AIMessages
|
||||||
if isinstance(result, list) and all(isinstance(r, AIMessage) for r in result):
|
if isinstance(result, list) and all(isinstance(r, AIMessage) for r in result):
|
||||||
result = [r.content for r in result]
|
result = [r.content for r in result]
|
||||||
|
|
@ -137,9 +135,7 @@ async def process_runnable(runnable: Runnable, inputs: Union[dict, List[dict]]):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
async def process_inputs_dict(
|
async def process_inputs_dict(built_object: Union[Chain, VectorStore, Runnable], inputs: dict):
|
||||||
built_object: Union[Chain, VectorStore, Runnable], inputs: dict
|
|
||||||
):
|
|
||||||
if isinstance(built_object, Chain):
|
if isinstance(built_object, Chain):
|
||||||
if inputs is None:
|
if inputs is None:
|
||||||
raise ValueError("Inputs must be provided for a Chain")
|
raise ValueError("Inputs must be provided for a Chain")
|
||||||
|
|
@ -174,9 +170,7 @@ async def process_inputs_list(built_object: Runnable, inputs: List[dict]):
|
||||||
return await process_runnable(built_object, inputs)
|
return await process_runnable(built_object, inputs)
|
||||||
|
|
||||||
|
|
||||||
async def generate_result(
|
async def generate_result(built_object: Union[Chain, VectorStore, Runnable], inputs: Union[dict, List[dict]]):
|
||||||
built_object: Union[Chain, VectorStore, Runnable], inputs: Union[dict, List[dict]]
|
|
||||||
):
|
|
||||||
if isinstance(inputs, dict):
|
if isinstance(inputs, dict):
|
||||||
result = await process_inputs_dict(built_object, inputs)
|
result = await process_inputs_dict(built_object, inputs)
|
||||||
elif isinstance(inputs, List) and isinstance(built_object, Runnable):
|
elif isinstance(inputs, List) and isinstance(built_object, Runnable):
|
||||||
|
|
@ -215,9 +209,7 @@ async def run_graph(
|
||||||
else:
|
else:
|
||||||
graph_data = graph._graph_data
|
graph_data = graph._graph_data
|
||||||
if not session_id and session_service is not None:
|
if not session_id and session_service is not None:
|
||||||
session_id = session_service.generate_key(
|
session_id = session_service.generate_key(session_id=flow_id, data_graph=graph_data)
|
||||||
session_id=flow_id, data_graph=graph_data
|
|
||||||
)
|
|
||||||
if inputs is None:
|
if inputs is None:
|
||||||
inputs = {}
|
inputs = {}
|
||||||
|
|
||||||
|
|
@ -232,18 +224,14 @@ async def run_graph(
|
||||||
return outputs, session_id
|
return outputs, session_id
|
||||||
|
|
||||||
|
|
||||||
def validate_input(
|
def validate_input(graph_data: Dict[str, Any], tweaks: Dict[str, Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||||
graph_data: Dict[str, Any], tweaks: Dict[str, Dict[str, Any]]
|
|
||||||
) -> List[Dict[str, Any]]:
|
|
||||||
if not isinstance(graph_data, dict) or not isinstance(tweaks, dict):
|
if not isinstance(graph_data, dict) or not isinstance(tweaks, dict):
|
||||||
raise ValueError("graph_data and tweaks should be dictionaries")
|
raise ValueError("graph_data and tweaks should be dictionaries")
|
||||||
|
|
||||||
nodes = graph_data.get("data", {}).get("nodes") or graph_data.get("nodes")
|
nodes = graph_data.get("data", {}).get("nodes") or graph_data.get("nodes")
|
||||||
|
|
||||||
if not isinstance(nodes, list):
|
if not isinstance(nodes, list):
|
||||||
raise ValueError(
|
raise ValueError("graph_data should contain a list of nodes under 'data' key or directly under 'nodes' key")
|
||||||
"graph_data should contain a list of nodes under 'data' key or directly under 'nodes' key"
|
|
||||||
)
|
|
||||||
|
|
||||||
return nodes
|
return nodes
|
||||||
|
|
||||||
|
|
@ -252,9 +240,7 @@ def apply_tweaks(node: Dict[str, Any], node_tweaks: Dict[str, Any]) -> None:
|
||||||
template_data = node.get("data", {}).get("node", {}).get("template")
|
template_data = node.get("data", {}).get("node", {}).get("template")
|
||||||
|
|
||||||
if not isinstance(template_data, dict):
|
if not isinstance(template_data, dict):
|
||||||
logger.warning(
|
logger.warning(f"Template data for node {node.get('id')} should be a dictionary")
|
||||||
f"Template data for node {node.get('id')} should be a dictionary"
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
for tweak_name, tweak_value in node_tweaks.items():
|
for tweak_name, tweak_value in node_tweaks.items():
|
||||||
|
|
@ -269,9 +255,7 @@ def apply_tweaks_on_vertex(vertex: Vertex, node_tweaks: Dict[str, Any]) -> None:
|
||||||
vertex.params[tweak_name] = tweak_value
|
vertex.params[tweak_name] = tweak_value
|
||||||
|
|
||||||
|
|
||||||
def process_tweaks(
|
def process_tweaks(graph_data: Dict[str, Any], tweaks: Dict[str, Dict[str, Any]]) -> Dict[str, Any]:
|
||||||
graph_data: Dict[str, Any], tweaks: Dict[str, Dict[str, Any]]
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""
|
"""
|
||||||
This function is used to tweak the graph data using the node id and the tweaks dict.
|
This function is used to tweak the graph data using the node id and the tweaks dict.
|
||||||
|
|
||||||
|
|
@ -307,8 +291,6 @@ def process_tweaks_on_graph(graph: Graph, tweaks: Dict[str, Dict[str, Any]]):
|
||||||
if node_tweaks := tweaks.get(node_id):
|
if node_tweaks := tweaks.get(node_id):
|
||||||
apply_tweaks_on_vertex(vertex, node_tweaks)
|
apply_tweaks_on_vertex(vertex, node_tweaks)
|
||||||
else:
|
else:
|
||||||
logger.warning(
|
logger.warning("Each node should be a Vertex with an 'id' attribute of type str")
|
||||||
"Each node should be a Vertex with an 'id' attribute of type str"
|
|
||||||
)
|
|
||||||
|
|
||||||
return graph
|
return graph
|
||||||
|
|
|
||||||
|
|
@ -63,9 +63,7 @@ class Record(BaseModel):
|
||||||
return self.data.get(key, self._default_value)
|
return self.data.get(key, self._default_value)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# Fallback to default behavior to raise AttributeError for undefined attributes
|
# Fallback to default behavior to raise AttributeError for undefined attributes
|
||||||
raise AttributeError(
|
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{key}'")
|
||||||
f"'{type(self).__name__}' object has no attribute '{key}'"
|
|
||||||
)
|
|
||||||
|
|
||||||
def __setattr__(self, key, value):
|
def __setattr__(self, key, value):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,7 @@ async def process_graph(
|
||||||
|
|
||||||
if build_result is None:
|
if build_result is None:
|
||||||
# Raise user facing error
|
# Raise user facing error
|
||||||
raise ValueError(
|
raise ValueError("There was an error loading the langchain_object. Please, check all the nodes and try again.")
|
||||||
"There was an error loading the langchain_object. Please, check all the nodes and try again."
|
|
||||||
)
|
|
||||||
|
|
||||||
# Generate result and thought
|
# Generate result and thought
|
||||||
try:
|
try:
|
||||||
|
|
@ -50,7 +48,5 @@ async def process_graph(
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
|
||||||
async def run_build_result(
|
async def run_build_result(build_result: Any, chat_inputs: ChatMessage, client_id: str, session_id: str):
|
||||||
build_result: Any, chat_inputs: ChatMessage, client_id: str, session_id: str
|
|
||||||
):
|
|
||||||
return build_result(inputs=chat_inputs.message)
|
return build_result(inputs=chat_inputs.message)
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,7 @@ class FlowBase(SQLModel):
|
||||||
icon_bg_color: Optional[str] = Field(default=None, nullable=True)
|
icon_bg_color: Optional[str] = Field(default=None, nullable=True)
|
||||||
data: Optional[Dict] = Field(default=None, nullable=True)
|
data: Optional[Dict] = Field(default=None, nullable=True)
|
||||||
is_component: Optional[bool] = Field(default=False, nullable=True)
|
is_component: Optional[bool] = Field(default=False, nullable=True)
|
||||||
updated_at: Optional[datetime] = Field(
|
updated_at: Optional[datetime] = Field(default_factory=datetime.utcnow, nullable=True)
|
||||||
default_factory=datetime.utcnow, nullable=True
|
|
||||||
)
|
|
||||||
folder: Optional[str] = Field(default=None, nullable=True)
|
folder: Optional[str] = Field(default=None, nullable=True)
|
||||||
|
|
||||||
@field_validator("icon_bg_color")
|
@field_validator("icon_bg_color")
|
||||||
|
|
|
||||||
|
|
@ -36,10 +36,7 @@ class DatabaseService(Service):
|
||||||
def _create_engine(self) -> "Engine":
|
def _create_engine(self) -> "Engine":
|
||||||
"""Create the engine for the database."""
|
"""Create the engine for the database."""
|
||||||
settings_service = get_settings_service()
|
settings_service = get_settings_service()
|
||||||
if (
|
if settings_service.settings.DATABASE_URL and settings_service.settings.DATABASE_URL.startswith("sqlite"):
|
||||||
settings_service.settings.DATABASE_URL
|
|
||||||
and settings_service.settings.DATABASE_URL.startswith("sqlite")
|
|
||||||
):
|
|
||||||
connect_args = {"check_same_thread": False}
|
connect_args = {"check_same_thread": False}
|
||||||
else:
|
else:
|
||||||
connect_args = {}
|
connect_args = {}
|
||||||
|
|
@ -51,9 +48,7 @@ class DatabaseService(Service):
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_value, traceback):
|
def __exit__(self, exc_type, exc_value, traceback):
|
||||||
if exc_type is not None: # If an exception has been raised
|
if exc_type is not None: # If an exception has been raised
|
||||||
logger.error(
|
logger.error(f"Session rollback because of exception: {exc_type.__name__} {exc_value}")
|
||||||
f"Session rollback because of exception: {exc_type.__name__} {exc_value}"
|
|
||||||
)
|
|
||||||
self._session.rollback()
|
self._session.rollback()
|
||||||
else:
|
else:
|
||||||
self._session.commit()
|
self._session.commit()
|
||||||
|
|
@ -70,9 +65,7 @@ class DatabaseService(Service):
|
||||||
settings_service = get_settings_service()
|
settings_service = get_settings_service()
|
||||||
if settings_service.auth_settings.AUTO_LOGIN:
|
if settings_service.auth_settings.AUTO_LOGIN:
|
||||||
with Session(self.engine) as session:
|
with Session(self.engine) as session:
|
||||||
flows = session.exec(
|
flows = session.exec(select(models.Flow).where(models.Flow.user_id is None)).all()
|
||||||
select(models.Flow).where(models.Flow.user_id is None)
|
|
||||||
).all()
|
|
||||||
if flows:
|
if flows:
|
||||||
logger.debug("Migrating flows to default superuser")
|
logger.debug("Migrating flows to default superuser")
|
||||||
username = settings_service.auth_settings.SUPERUSER
|
username = settings_service.auth_settings.SUPERUSER
|
||||||
|
|
@ -102,9 +95,7 @@ class DatabaseService(Service):
|
||||||
expected_columns = list(model.model_fields.keys())
|
expected_columns = list(model.model_fields.keys())
|
||||||
|
|
||||||
try:
|
try:
|
||||||
available_columns = [
|
available_columns = [col["name"] for col in inspector.get_columns(table)]
|
||||||
col["name"] for col in inspector.get_columns(table)
|
|
||||||
]
|
|
||||||
except sa.exc.NoSuchTableError:
|
except sa.exc.NoSuchTableError:
|
||||||
logger.error(f"Missing table: {table}")
|
logger.error(f"Missing table: {table}")
|
||||||
return False
|
return False
|
||||||
|
|
@ -161,9 +152,7 @@ class DatabaseService(Service):
|
||||||
try:
|
try:
|
||||||
command.check(alembic_cfg)
|
command.check(alembic_cfg)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
if isinstance(
|
if isinstance(exc, (util.exc.CommandError, util.exc.AutogenerateDiffsDetected)):
|
||||||
exc, (util.exc.CommandError, util.exc.AutogenerateDiffsDetected)
|
|
||||||
):
|
|
||||||
command.upgrade(alembic_cfg, "head")
|
command.upgrade(alembic_cfg, "head")
|
||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
|
|
||||||
|
|
@ -199,10 +188,7 @@ class DatabaseService(Service):
|
||||||
# We will check that all models are in the database
|
# We will check that all models are in the database
|
||||||
# and that the database is up to date with all columns
|
# and that the database is up to date with all columns
|
||||||
sql_models = [models.Flow, models.User, models.ApiKey]
|
sql_models = [models.Flow, models.User, models.ApiKey]
|
||||||
return [
|
return [TableResults(sql_model.__tablename__, self.check_table(sql_model)) for sql_model in sql_models]
|
||||||
TableResults(sql_model.__tablename__, self.check_table(sql_model))
|
|
||||||
for sql_model in sql_models
|
|
||||||
]
|
|
||||||
|
|
||||||
def check_table(self, model):
|
def check_table(self, model):
|
||||||
results = []
|
results = []
|
||||||
|
|
@ -211,9 +197,7 @@ class DatabaseService(Service):
|
||||||
expected_columns = list(model.__fields__.keys())
|
expected_columns = list(model.__fields__.keys())
|
||||||
available_columns = []
|
available_columns = []
|
||||||
try:
|
try:
|
||||||
available_columns = [
|
available_columns = [col["name"] for col in inspector.get_columns(table_name)]
|
||||||
col["name"] for col in inspector.get_columns(table_name)
|
|
||||||
]
|
|
||||||
results.append(Result(name=table_name, type="table", success=True))
|
results.append(Result(name=table_name, type="table", success=True))
|
||||||
except sa.exc.NoSuchTableError:
|
except sa.exc.NoSuchTableError:
|
||||||
logger.error(f"Missing table: {table_name}")
|
logger.error(f"Missing table: {table_name}")
|
||||||
|
|
@ -244,9 +228,7 @@ class DatabaseService(Service):
|
||||||
try:
|
try:
|
||||||
table.create(self.engine, checkfirst=True)
|
table.create(self.engine, checkfirst=True)
|
||||||
except OperationalError as oe:
|
except OperationalError as oe:
|
||||||
logger.warning(
|
logger.warning(f"Table {table} already exists, skipping. Exception: {oe}")
|
||||||
f"Table {table} already exists, skipping. Exception: {oe}"
|
|
||||||
)
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error(f"Error creating table {table}: {exc}")
|
logger.error(f"Error creating table {table}: {exc}")
|
||||||
raise RuntimeError(f"Error creating table {table}") from exc
|
raise RuntimeError(f"Error creating table {table}") from exc
|
||||||
|
|
@ -258,9 +240,7 @@ class DatabaseService(Service):
|
||||||
if table not in table_names:
|
if table not in table_names:
|
||||||
logger.error("Something went wrong creating the database and tables.")
|
logger.error("Something went wrong creating the database and tables.")
|
||||||
logger.error("Please check your database settings.")
|
logger.error("Please check your database settings.")
|
||||||
raise RuntimeError(
|
raise RuntimeError("Something went wrong creating the database and tables.")
|
||||||
"Something went wrong creating the database and tables."
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.debug("Database and tables created successfully")
|
logger.debug("Database and tables created successfully")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
class TransactionModel(BaseModel):
|
class TransactionModel(BaseModel):
|
||||||
id: Optional[int] = Field(default=None, alias="id")
|
id: Optional[int] = Field(default=None, alias="id")
|
||||||
timestamp: Optional[datetime] = Field(
|
timestamp: Optional[datetime] = Field(default_factory=datetime.now, alias="timestamp")
|
||||||
default_factory=datetime.now, alias="timestamp"
|
|
||||||
)
|
|
||||||
source: str
|
source: str
|
||||||
target: str
|
target: str
|
||||||
target_args: dict
|
target_args: dict
|
||||||
|
|
@ -53,12 +51,8 @@ class MessageModel(BaseModel):
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_record(cls, record: "Record"):
|
def from_record(cls, record: "Record"):
|
||||||
# first check if the record has all the required fields
|
# first check if the record has all the required fields
|
||||||
if not record.data or (
|
if not record.data or ("sender" not in record.data and "sender_name" not in record.data):
|
||||||
"sender" not in record.data and "sender_name" not in record.data
|
raise ValueError("The record does not have the required fields 'sender' and 'sender_name' in the data.")
|
||||||
):
|
|
||||||
raise ValueError(
|
|
||||||
"The record does not have the required fields 'sender' and 'sender_name' in the data."
|
|
||||||
)
|
|
||||||
return cls(
|
return cls(
|
||||||
sender=record.data["sender"],
|
sender=record.data["sender"],
|
||||||
sender_name=record.data["sender_name"],
|
sender_name=record.data["sender_name"],
|
||||||
|
|
|
||||||
|
|
@ -58,12 +58,10 @@ class Settings(BaseSettings):
|
||||||
|
|
||||||
STORE: Optional[bool] = True
|
STORE: Optional[bool] = True
|
||||||
STORE_URL: Optional[str] = "https://api.langflow.store"
|
STORE_URL: Optional[str] = "https://api.langflow.store"
|
||||||
DOWNLOAD_WEBHOOK_URL: Optional[str] = (
|
DOWNLOAD_WEBHOOK_URL: Optional[
|
||||||
"https://api.langflow.store/flows/trigger/ec611a61-8460-4438-b187-a4f65e5559d4"
|
str
|
||||||
)
|
] = "https://api.langflow.store/flows/trigger/ec611a61-8460-4438-b187-a4f65e5559d4"
|
||||||
LIKE_WEBHOOK_URL: Optional[str] = (
|
LIKE_WEBHOOK_URL: Optional[str] = "https://api.langflow.store/flows/trigger/64275852-ec00-45c1-984e-3bff814732da"
|
||||||
"https://api.langflow.store/flows/trigger/64275852-ec00-45c1-984e-3bff814732da"
|
|
||||||
)
|
|
||||||
|
|
||||||
STORAGE_TYPE: str = "local"
|
STORAGE_TYPE: str = "local"
|
||||||
|
|
||||||
|
|
@ -95,9 +93,7 @@ class Settings(BaseSettings):
|
||||||
@validator("DATABASE_URL", pre=True)
|
@validator("DATABASE_URL", pre=True)
|
||||||
def set_database_url(cls, value, values):
|
def set_database_url(cls, value, values):
|
||||||
if not value:
|
if not value:
|
||||||
logger.debug(
|
logger.debug("No database_url provided, trying LANGFLOW_DATABASE_URL env variable")
|
||||||
"No database_url provided, trying LANGFLOW_DATABASE_URL env variable"
|
|
||||||
)
|
|
||||||
if langflow_database_url := os.getenv("LANGFLOW_DATABASE_URL"):
|
if langflow_database_url := os.getenv("LANGFLOW_DATABASE_URL"):
|
||||||
value = langflow_database_url
|
value = langflow_database_url
|
||||||
logger.debug("Using LANGFLOW_DATABASE_URL env variable.")
|
logger.debug("Using LANGFLOW_DATABASE_URL env variable.")
|
||||||
|
|
@ -107,9 +103,7 @@ class Settings(BaseSettings):
|
||||||
# so we need to migrate to the new format
|
# so we need to migrate to the new format
|
||||||
# if there is a database in that location
|
# if there is a database in that location
|
||||||
if not values["CONFIG_DIR"]:
|
if not values["CONFIG_DIR"]:
|
||||||
raise ValueError(
|
raise ValueError("CONFIG_DIR not set, please set it or provide a DATABASE_URL")
|
||||||
"CONFIG_DIR not set, please set it or provide a DATABASE_URL"
|
|
||||||
)
|
|
||||||
|
|
||||||
new_path = f"{values['CONFIG_DIR']}/langflow.db"
|
new_path = f"{values['CONFIG_DIR']}/langflow.db"
|
||||||
if Path("./langflow.db").exists():
|
if Path("./langflow.db").exists():
|
||||||
|
|
@ -133,22 +127,15 @@ class Settings(BaseSettings):
|
||||||
if os.getenv("LANGFLOW_COMPONENTS_PATH"):
|
if os.getenv("LANGFLOW_COMPONENTS_PATH"):
|
||||||
logger.debug("Adding LANGFLOW_COMPONENTS_PATH to components_path")
|
logger.debug("Adding LANGFLOW_COMPONENTS_PATH to components_path")
|
||||||
langflow_component_path = os.getenv("LANGFLOW_COMPONENTS_PATH")
|
langflow_component_path = os.getenv("LANGFLOW_COMPONENTS_PATH")
|
||||||
if (
|
if Path(langflow_component_path).exists() and langflow_component_path not in value:
|
||||||
Path(langflow_component_path).exists()
|
|
||||||
and langflow_component_path not in value
|
|
||||||
):
|
|
||||||
if isinstance(langflow_component_path, list):
|
if isinstance(langflow_component_path, list):
|
||||||
for path in langflow_component_path:
|
for path in langflow_component_path:
|
||||||
if path not in value:
|
if path not in value:
|
||||||
value.append(path)
|
value.append(path)
|
||||||
logger.debug(
|
logger.debug(f"Extending {langflow_component_path} to components_path")
|
||||||
f"Extending {langflow_component_path} to components_path"
|
|
||||||
)
|
|
||||||
elif langflow_component_path not in value:
|
elif langflow_component_path not in value:
|
||||||
value.append(langflow_component_path)
|
value.append(langflow_component_path)
|
||||||
logger.debug(
|
logger.debug(f"Appending {langflow_component_path} to components_path")
|
||||||
f"Appending {langflow_component_path} to components_path"
|
|
||||||
)
|
|
||||||
|
|
||||||
if not value:
|
if not value:
|
||||||
value = [BASE_COMPONENTS_PATH]
|
value = [BASE_COMPONENTS_PATH]
|
||||||
|
|
@ -160,9 +147,7 @@ class Settings(BaseSettings):
|
||||||
logger.debug(f"Components path: {value}")
|
logger.debug(f"Components path: {value}")
|
||||||
return value
|
return value
|
||||||
|
|
||||||
model_config = SettingsConfigDict(
|
model_config = SettingsConfigDict(validate_assignment=True, extra="ignore", env_prefix="LANGFLOW_")
|
||||||
validate_assignment=True, extra="ignore", env_prefix="LANGFLOW_"
|
|
||||||
)
|
|
||||||
|
|
||||||
# @model_validator()
|
# @model_validator()
|
||||||
# @classmethod
|
# @classmethod
|
||||||
|
|
|
||||||
|
|
@ -96,9 +96,7 @@ async def build_vertex(
|
||||||
)
|
)
|
||||||
|
|
||||||
# Emit the vertex build response
|
# Emit the vertex build response
|
||||||
response = VertexBuildResponse(
|
response = VertexBuildResponse(valid=valid, params=params, id=vertex.id, data=result_dict)
|
||||||
valid=valid, params=params, id=vertex.id, data=result_dict
|
|
||||||
)
|
|
||||||
await sio.emit("vertex_build", data=response.model_dump(), to=sid)
|
await sio.emit("vertex_build", data=response.model_dump(), to=sid)
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
|
|
||||||
|
|
@ -74,9 +74,7 @@ class TaskService(Service):
|
||||||
result = await result
|
result = await result
|
||||||
return task.id, result
|
return task.id, result
|
||||||
|
|
||||||
async def launch_task(
|
async def launch_task(self, task_func: Callable[..., Any], *args: Any, **kwargs: Any) -> Any:
|
||||||
self, task_func: Callable[..., Any], *args: Any, **kwargs: Any
|
|
||||||
) -> Any:
|
|
||||||
logger.debug(f"Launching task {task_func} with args {args} and kwargs {kwargs}")
|
logger.debug(f"Launching task {task_func} with args {args} and kwargs {kwargs}")
|
||||||
logger.debug(f"Using backend {self.backend}")
|
logger.debug(f"Using backend {self.backend}")
|
||||||
task = self.backend.launch_task(task_func, *args, **kwargs)
|
task = self.backend.launch_task(task_func, *args, **kwargs)
|
||||||
|
|
|
||||||
|
|
@ -92,16 +92,12 @@ def get_or_create_super_user(session: Session, username, password, is_default):
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
logger.debug(
|
logger.debug("User with superuser credentials exists but is not a superuser.")
|
||||||
"User with superuser credentials exists but is not a superuser."
|
|
||||||
)
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if user:
|
if user:
|
||||||
if verify_password(password, user.password):
|
if verify_password(password, user.password):
|
||||||
raise ValueError(
|
raise ValueError("User with superuser credentials exists but is not a superuser.")
|
||||||
"User with superuser credentials exists but is not a superuser."
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
raise ValueError("Incorrect superuser credentials")
|
raise ValueError("Incorrect superuser credentials")
|
||||||
|
|
||||||
|
|
@ -130,21 +126,15 @@ def setup_superuser(settings_service, session: Session):
|
||||||
username = settings_service.auth_settings.SUPERUSER
|
username = settings_service.auth_settings.SUPERUSER
|
||||||
password = settings_service.auth_settings.SUPERUSER_PASSWORD
|
password = settings_service.auth_settings.SUPERUSER_PASSWORD
|
||||||
|
|
||||||
is_default = (username == DEFAULT_SUPERUSER) and (
|
is_default = (username == DEFAULT_SUPERUSER) and (password == DEFAULT_SUPERUSER_PASSWORD)
|
||||||
password == DEFAULT_SUPERUSER_PASSWORD
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
user = get_or_create_super_user(
|
user = get_or_create_super_user(session=session, username=username, password=password, is_default=is_default)
|
||||||
session=session, username=username, password=password, is_default=is_default
|
|
||||||
)
|
|
||||||
if user is not None:
|
if user is not None:
|
||||||
logger.debug("Superuser created successfully.")
|
logger.debug("Superuser created successfully.")
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception(exc)
|
logger.exception(exc)
|
||||||
raise RuntimeError(
|
raise RuntimeError("Could not create superuser. Please create a superuser manually.") from exc
|
||||||
"Could not create superuser. Please create a superuser manually."
|
|
||||||
) from exc
|
|
||||||
finally:
|
finally:
|
||||||
settings_service.auth_settings.reset_credentials()
|
settings_service.auth_settings.reset_credentials()
|
||||||
|
|
||||||
|
|
@ -158,9 +148,7 @@ def teardown_superuser(settings_service, session):
|
||||||
|
|
||||||
if not settings_service.auth_settings.AUTO_LOGIN:
|
if not settings_service.auth_settings.AUTO_LOGIN:
|
||||||
try:
|
try:
|
||||||
logger.debug(
|
logger.debug("AUTO_LOGIN is set to False. Removing default superuser if exists.")
|
||||||
"AUTO_LOGIN is set to False. Removing default superuser if exists."
|
|
||||||
)
|
|
||||||
username = DEFAULT_SUPERUSER
|
username = DEFAULT_SUPERUSER
|
||||||
from langflow.services.database.models.user.model import User
|
from langflow.services.database.models.user.model import User
|
||||||
|
|
||||||
|
|
@ -210,9 +198,7 @@ def initialize_session_service():
|
||||||
|
|
||||||
initialize_settings_service()
|
initialize_settings_service()
|
||||||
|
|
||||||
service_manager.register_factory(
|
service_manager.register_factory(cache_factory.CacheServiceFactory(), dependencies=[ServiceType.SETTINGS_SERVICE])
|
||||||
cache_factory.CacheServiceFactory(), dependencies=[ServiceType.SETTINGS_SERVICE]
|
|
||||||
)
|
|
||||||
|
|
||||||
service_manager.register_factory(
|
service_manager.register_factory(
|
||||||
session_service_factory.SessionServiceFactory(),
|
session_service_factory.SessionServiceFactory(),
|
||||||
|
|
@ -229,9 +215,7 @@ def initialize_services(fix_migration: bool = False, socketio_server=None):
|
||||||
service_manager.register_factory(factory, dependencies=dependencies)
|
service_manager.register_factory(factory, dependencies=dependencies)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception(exc)
|
logger.exception(exc)
|
||||||
raise RuntimeError(
|
raise RuntimeError("Could not initialize services. Please check your settings.") from exc
|
||||||
"Could not initialize services. Please check your settings."
|
|
||||||
) from exc
|
|
||||||
|
|
||||||
# Test cache connection
|
# Test cache connection
|
||||||
service_manager.get(ServiceType.CACHE_SERVICE)
|
service_manager.get(ServiceType.CACHE_SERVICE)
|
||||||
|
|
@ -241,9 +225,7 @@ def initialize_services(fix_migration: bool = False, socketio_server=None):
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error(exc)
|
logger.error(exc)
|
||||||
raise exc
|
raise exc
|
||||||
setup_superuser(
|
setup_superuser(service_manager.get(ServiceType.SETTINGS_SERVICE), next(get_session()))
|
||||||
service_manager.get(ServiceType.SETTINGS_SERVICE), next(get_session())
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
get_db_service().migrate_flows_if_auto_login()
|
get_db_service().migrate_flows_if_auto_login()
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
|
|
||||||
|
|
@ -68,9 +68,7 @@ class TemplateField(BaseModel):
|
||||||
refresh: Optional[bool] = None
|
refresh: Optional[bool] = None
|
||||||
"""Specifies if the field should be refreshed. Defaults to False."""
|
"""Specifies if the field should be refreshed. Defaults to False."""
|
||||||
|
|
||||||
range_spec: Optional[RangeSpec] = Field(
|
range_spec: Optional[RangeSpec] = Field(default=None, serialization_alias="rangeSpec")
|
||||||
default=None, serialization_alias="rangeSpec"
|
|
||||||
)
|
|
||||||
"""Range specification for the field. Defaults to None."""
|
"""Range specification for the field. Defaults to None."""
|
||||||
|
|
||||||
title_case: bool = False
|
title_case: bool = False
|
||||||
|
|
@ -119,10 +117,6 @@ class TemplateField(BaseModel):
|
||||||
if not isinstance(value, list):
|
if not isinstance(value, list):
|
||||||
raise ValueError("file_types must be a list")
|
raise ValueError("file_types must be a list")
|
||||||
return [
|
return [
|
||||||
(
|
(f".{file_type}" if isinstance(file_type, str) and not file_type.startswith(".") else file_type)
|
||||||
f".{file_type}"
|
|
||||||
if isinstance(file_type, str) and not file_type.startswith(".")
|
|
||||||
else file_type
|
|
||||||
)
|
|
||||||
for file_type in value
|
for file_type in value
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -174,9 +174,7 @@ class FrontendNode(BaseModel):
|
||||||
return _type
|
return _type
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def handle_special_field(
|
def handle_special_field(field, key: str, _type: str, SPECIAL_FIELD_HANDLERS) -> str:
|
||||||
field, key: str, _type: str, SPECIAL_FIELD_HANDLERS
|
|
||||||
) -> str:
|
|
||||||
"""Handles special field by using the respective handler if present."""
|
"""Handles special field by using the respective handler if present."""
|
||||||
handler = SPECIAL_FIELD_HANDLERS.get(key)
|
handler = SPECIAL_FIELD_HANDLERS.get(key)
|
||||||
return handler(field) if handler else _type
|
return handler(field) if handler else _type
|
||||||
|
|
@ -187,11 +185,7 @@ class FrontendNode(BaseModel):
|
||||||
if "dict" in _type.lower() and field.name == "dict_":
|
if "dict" in _type.lower() and field.name == "dict_":
|
||||||
field.field_type = "file"
|
field.field_type = "file"
|
||||||
field.file_types = [".json", ".yaml", ".yml"]
|
field.file_types = [".json", ".yaml", ".yml"]
|
||||||
elif (
|
elif _type.startswith("Dict") or _type.startswith("Mapping") or _type.startswith("dict"):
|
||||||
_type.startswith("Dict")
|
|
||||||
or _type.startswith("Mapping")
|
|
||||||
or _type.startswith("dict")
|
|
||||||
):
|
|
||||||
field.field_type = "dict"
|
field.field_type = "dict"
|
||||||
return _type
|
return _type
|
||||||
|
|
||||||
|
|
@ -202,9 +196,7 @@ class FrontendNode(BaseModel):
|
||||||
field.value = value["default"]
|
field.value = value["default"]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def handle_specific_field_values(
|
def handle_specific_field_values(field: TemplateField, key: str, name: Optional[str] = None) -> None:
|
||||||
field: TemplateField, key: str, name: Optional[str] = None
|
|
||||||
) -> None:
|
|
||||||
"""Handles specific field values for certain fields."""
|
"""Handles specific field values for certain fields."""
|
||||||
if key == "headers":
|
if key == "headers":
|
||||||
field.value = """{"Authorization": "Bearer <token>"}"""
|
field.value = """{"Authorization": "Bearer <token>"}"""
|
||||||
|
|
@ -212,9 +204,7 @@ class FrontendNode(BaseModel):
|
||||||
FrontendNode._handle_api_key_specific_field_values(field, key, name)
|
FrontendNode._handle_api_key_specific_field_values(field, key, name)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _handle_model_specific_field_values(
|
def _handle_model_specific_field_values(field: TemplateField, key: str, name: Optional[str] = None) -> None:
|
||||||
field: TemplateField, key: str, name: Optional[str] = None
|
|
||||||
) -> None:
|
|
||||||
"""Handles specific field values related to models."""
|
"""Handles specific field values related to models."""
|
||||||
model_dict = {
|
model_dict = {
|
||||||
"OpenAI": constants.OPENAI_MODELS,
|
"OpenAI": constants.OPENAI_MODELS,
|
||||||
|
|
@ -227,9 +217,7 @@ class FrontendNode(BaseModel):
|
||||||
field.is_list = True
|
field.is_list = True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _handle_api_key_specific_field_values(
|
def _handle_api_key_specific_field_values(field: TemplateField, key: str, name: Optional[str] = None) -> None:
|
||||||
field: TemplateField, key: str, name: Optional[str] = None
|
|
||||||
) -> None:
|
|
||||||
"""Handles specific field values related to API keys."""
|
"""Handles specific field values related to API keys."""
|
||||||
if "api_key" in key and "OpenAI" in str(name):
|
if "api_key" in key and "OpenAI" in str(name):
|
||||||
field.display_name = "OpenAI API Key"
|
field.display_name = "OpenAI API Key"
|
||||||
|
|
@ -269,10 +257,7 @@ class FrontendNode(BaseModel):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def should_be_password(key: str, show: bool) -> bool:
|
def should_be_password(key: str, show: bool) -> bool:
|
||||||
"""Determines whether the field should be a password field."""
|
"""Determines whether the field should be a password field."""
|
||||||
return (
|
return any(text in key.lower() for text in {"password", "token", "api", "key"}) and show
|
||||||
any(text in key.lower() for text in {"password", "token", "api", "key"})
|
|
||||||
and show
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def should_be_multiline(key: str) -> bool:
|
def should_be_multiline(key: str) -> bool:
|
||||||
|
|
|
||||||
|
|
@ -80,9 +80,7 @@ class MemoryFrontendNode(FrontendNode):
|
||||||
field.show = True
|
field.show = True
|
||||||
field.advanced = False
|
field.advanced = False
|
||||||
field.value = ""
|
field.value = ""
|
||||||
field.info = (
|
field.info = INPUT_KEY_INFO if field.name == "input_key" else OUTPUT_KEY_INFO
|
||||||
INPUT_KEY_INFO if field.name == "input_key" else OUTPUT_KEY_INFO
|
|
||||||
)
|
|
||||||
|
|
||||||
if field.name == "memory_key":
|
if field.name == "memory_key":
|
||||||
field.value = "chat_history"
|
field.value = "chat_history"
|
||||||
|
|
|
||||||
|
|
@ -45,9 +45,7 @@ class Template(BaseModel):
|
||||||
"""Returns the field with the given name."""
|
"""Returns the field with the given name."""
|
||||||
field = next((field for field in self.fields if field.name == field_name), None)
|
field = next((field for field in self.fields if field.name == field_name), None)
|
||||||
if field is None:
|
if field is None:
|
||||||
raise ValueError(
|
raise ValueError(f"Field {field_name} not found in template {self.type_name}")
|
||||||
f"Field {field_name} not found in template {self.type_name}"
|
|
||||||
)
|
|
||||||
return field
|
return field
|
||||||
|
|
||||||
def update_field(self, field_name: str, field: TemplateField) -> None:
|
def update_field(self, field_name: str, field: TemplateField) -> None:
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,8 @@ def remove_ansi_escape_codes(text):
|
||||||
return re.sub(r"\x1b\[[0-9;]*[a-zA-Z]", "", text)
|
return re.sub(r"\x1b\[[0-9;]*[a-zA-Z]", "", text)
|
||||||
|
|
||||||
|
|
||||||
def build_template_from_function(
|
def build_template_from_function(name: str, type_to_loader_dict: Dict, add_function: bool = False):
|
||||||
name: str, type_to_loader_dict: Dict, add_function: bool = False
|
classes = [item.__annotations__["return"].__name__ for item in type_to_loader_dict.values()]
|
||||||
):
|
|
||||||
classes = [
|
|
||||||
item.__annotations__["return"].__name__ for item in type_to_loader_dict.values()
|
|
||||||
]
|
|
||||||
|
|
||||||
# Raise error if name is not in chains
|
# Raise error if name is not in chains
|
||||||
if name not in classes:
|
if name not in classes:
|
||||||
|
|
@ -41,20 +37,16 @@ def build_template_from_function(
|
||||||
for name_, value_ in value.__repr_args__():
|
for name_, value_ in value.__repr_args__():
|
||||||
if name_ == "default_factory":
|
if name_ == "default_factory":
|
||||||
try:
|
try:
|
||||||
variables[class_field_items]["default"] = (
|
variables[class_field_items]["default"] = get_default_factory(
|
||||||
get_default_factory(
|
|
||||||
module=_class.__base__.__module__, function=value_
|
module=_class.__base__.__module__, function=value_
|
||||||
)
|
)
|
||||||
)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
variables[class_field_items]["default"] = None
|
variables[class_field_items]["default"] = None
|
||||||
elif name_ not in ["name"]:
|
elif name_ not in ["name"]:
|
||||||
variables[class_field_items][name_] = value_
|
variables[class_field_items][name_] = value_
|
||||||
|
|
||||||
variables[class_field_items]["placeholder"] = (
|
variables[class_field_items]["placeholder"] = (
|
||||||
docs.params[class_field_items]
|
docs.params[class_field_items] if class_field_items in docs.params else ""
|
||||||
if class_field_items in docs.params
|
|
||||||
else ""
|
|
||||||
)
|
)
|
||||||
# Adding function to base classes to allow
|
# Adding function to base classes to allow
|
||||||
# the output to be a function
|
# the output to be a function
|
||||||
|
|
@ -69,9 +61,7 @@ def build_template_from_function(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def build_template_from_class(
|
def build_template_from_class(name: str, type_to_cls_dict: Dict, add_function: bool = False):
|
||||||
name: str, type_to_cls_dict: Dict, add_function: bool = False
|
|
||||||
):
|
|
||||||
classes = [item.__name__ for item in type_to_cls_dict.values()]
|
classes = [item.__name__ for item in type_to_cls_dict.values()]
|
||||||
|
|
||||||
# Raise error if name is not in chains
|
# Raise error if name is not in chains
|
||||||
|
|
@ -95,21 +85,17 @@ def build_template_from_class(
|
||||||
for name_, value_ in value.__repr_args__():
|
for name_, value_ in value.__repr_args__():
|
||||||
if name_ == "default_factory":
|
if name_ == "default_factory":
|
||||||
try:
|
try:
|
||||||
variables[class_field_items]["default"] = (
|
variables[class_field_items]["default"] = get_default_factory(
|
||||||
get_default_factory(
|
|
||||||
module=_class.__base__.__module__,
|
module=_class.__base__.__module__,
|
||||||
function=value_,
|
function=value_,
|
||||||
)
|
)
|
||||||
)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
variables[class_field_items]["default"] = None
|
variables[class_field_items]["default"] = None
|
||||||
elif name_ not in ["name"]:
|
elif name_ not in ["name"]:
|
||||||
variables[class_field_items][name_] = value_
|
variables[class_field_items][name_] = value_
|
||||||
|
|
||||||
variables[class_field_items]["placeholder"] = (
|
variables[class_field_items]["placeholder"] = (
|
||||||
docs.params[class_field_items]
|
docs.params[class_field_items] if class_field_items in docs.params else ""
|
||||||
if class_field_items in docs.params
|
|
||||||
else ""
|
|
||||||
)
|
)
|
||||||
base_classes = get_base_classes(_class)
|
base_classes = get_base_classes(_class)
|
||||||
# Adding function to base classes to allow
|
# Adding function to base classes to allow
|
||||||
|
|
@ -141,9 +127,7 @@ def build_template_from_method(
|
||||||
|
|
||||||
# Check if the method exists in this class
|
# Check if the method exists in this class
|
||||||
if not hasattr(_class, method_name):
|
if not hasattr(_class, method_name):
|
||||||
raise ValueError(
|
raise ValueError(f"Method {method_name} not found in class {class_name}")
|
||||||
f"Method {method_name} not found in class {class_name}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Get the method
|
# Get the method
|
||||||
method = getattr(_class, method_name)
|
method = getattr(_class, method_name)
|
||||||
|
|
@ -162,14 +146,8 @@ def build_template_from_method(
|
||||||
"_type": _type,
|
"_type": _type,
|
||||||
**{
|
**{
|
||||||
name: {
|
name: {
|
||||||
"default": (
|
"default": (param.default if param.default != param.empty else None),
|
||||||
param.default if param.default != param.empty else None
|
"type": (param.annotation if param.annotation != param.empty else None),
|
||||||
),
|
|
||||||
"type": (
|
|
||||||
param.annotation
|
|
||||||
if param.annotation != param.empty
|
|
||||||
else None
|
|
||||||
),
|
|
||||||
"required": param.default == param.empty,
|
"required": param.default == param.empty,
|
||||||
}
|
}
|
||||||
for name, param in params.items()
|
for name, param in params.items()
|
||||||
|
|
@ -256,9 +234,7 @@ def sync_to_async(func):
|
||||||
return async_wrapper
|
return async_wrapper
|
||||||
|
|
||||||
|
|
||||||
def format_dict(
|
def format_dict(dictionary: Dict[str, Any], class_name: Optional[str] = None) -> Dict[str, Any]:
|
||||||
dictionary: Dict[str, Any], class_name: Optional[str] = None
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""
|
"""
|
||||||
Formats a dictionary by removing certain keys and modifying the
|
Formats a dictionary by removing certain keys and modifying the
|
||||||
values of other keys.
|
values of other keys.
|
||||||
|
|
@ -344,9 +320,7 @@ def check_list_type(_type: str, value: Dict[str, Any]) -> str:
|
||||||
The modified type string.
|
The modified type string.
|
||||||
"""
|
"""
|
||||||
if any(list_type in _type for list_type in ["List", "Sequence", "Set"]):
|
if any(list_type in _type for list_type in ["List", "Sequence", "Set"]):
|
||||||
_type = (
|
_type = _type.replace("List[", "").replace("Sequence[", "").replace("Set[", "")[:-1]
|
||||||
_type.replace("List[", "").replace("Sequence[", "").replace("Set[", "")[:-1]
|
|
||||||
)
|
|
||||||
value["list"] = True
|
value["list"] = True
|
||||||
else:
|
else:
|
||||||
value["list"] = False
|
value["list"] = False
|
||||||
|
|
@ -449,9 +423,7 @@ def set_headers_value(value: Dict[str, Any]) -> None:
|
||||||
value["value"] = """{"Authorization": "Bearer <token>"}"""
|
value["value"] = """{"Authorization": "Bearer <token>"}"""
|
||||||
|
|
||||||
|
|
||||||
def add_options_to_field(
|
def add_options_to_field(value: Dict[str, Any], class_name: Optional[str], key: str) -> None:
|
||||||
value: Dict[str, Any], class_name: Optional[str], key: str
|
|
||||||
) -> None:
|
|
||||||
"""
|
"""
|
||||||
Adds options to the field based on the class name and key.
|
Adds options to the field based on the class name and key.
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,7 @@ def validate_code(code):
|
||||||
# Evaluate the function definition
|
# Evaluate the function definition
|
||||||
for node in tree.body:
|
for node in tree.body:
|
||||||
if isinstance(node, ast.FunctionDef):
|
if isinstance(node, ast.FunctionDef):
|
||||||
code_obj = compile(
|
code_obj = compile(ast.Module(body=[node], type_ignores=[]), "<string>", "exec")
|
||||||
ast.Module(body=[node], type_ignores=[]), "<string>", "exec"
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
exec(code_obj)
|
exec(code_obj)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -89,23 +87,15 @@ def execute_function(code, function_name, *args, **kwargs):
|
||||||
exec_globals,
|
exec_globals,
|
||||||
locals(),
|
locals(),
|
||||||
)
|
)
|
||||||
exec_globals[alias.asname or alias.name] = importlib.import_module(
|
exec_globals[alias.asname or alias.name] = importlib.import_module(alias.name)
|
||||||
alias.name
|
|
||||||
)
|
|
||||||
except ModuleNotFoundError as e:
|
except ModuleNotFoundError as e:
|
||||||
raise ModuleNotFoundError(
|
raise ModuleNotFoundError(f"Module {alias.name} not found. Please install it and try again.") from e
|
||||||
f"Module {alias.name} not found. Please install it and try again."
|
|
||||||
) from e
|
|
||||||
|
|
||||||
function_code = next(
|
function_code = next(
|
||||||
node
|
node for node in module.body if isinstance(node, ast.FunctionDef) and node.name == function_name
|
||||||
for node in module.body
|
|
||||||
if isinstance(node, ast.FunctionDef) and node.name == function_name
|
|
||||||
)
|
)
|
||||||
function_code.parent = None
|
function_code.parent = None
|
||||||
code_obj = compile(
|
code_obj = compile(ast.Module(body=[function_code], type_ignores=[]), "<string>", "exec")
|
||||||
ast.Module(body=[function_code], type_ignores=[]), "<string>", "exec"
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
exec(code_obj, exec_globals, locals())
|
exec(code_obj, exec_globals, locals())
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
|
@ -132,23 +122,15 @@ def create_function(code, function_name):
|
||||||
if isinstance(node, ast.Import):
|
if isinstance(node, ast.Import):
|
||||||
for alias in node.names:
|
for alias in node.names:
|
||||||
try:
|
try:
|
||||||
exec_globals[alias.asname or alias.name] = importlib.import_module(
|
exec_globals[alias.asname or alias.name] = importlib.import_module(alias.name)
|
||||||
alias.name
|
|
||||||
)
|
|
||||||
except ModuleNotFoundError as e:
|
except ModuleNotFoundError as e:
|
||||||
raise ModuleNotFoundError(
|
raise ModuleNotFoundError(f"Module {alias.name} not found. Please install it and try again.") from e
|
||||||
f"Module {alias.name} not found. Please install it and try again."
|
|
||||||
) from e
|
|
||||||
|
|
||||||
function_code = next(
|
function_code = next(
|
||||||
node
|
node for node in module.body if isinstance(node, ast.FunctionDef) and node.name == function_name
|
||||||
for node in module.body
|
|
||||||
if isinstance(node, ast.FunctionDef) and node.name == function_name
|
|
||||||
)
|
)
|
||||||
function_code.parent = None
|
function_code.parent = None
|
||||||
code_obj = compile(
|
code_obj = compile(ast.Module(body=[function_code], type_ignores=[]), "<string>", "exec")
|
||||||
ast.Module(body=[function_code], type_ignores=[]), "<string>", "exec"
|
|
||||||
)
|
|
||||||
with contextlib.suppress(Exception):
|
with contextlib.suppress(Exception):
|
||||||
exec(code_obj, exec_globals, locals())
|
exec(code_obj, exec_globals, locals())
|
||||||
exec_globals[function_name] = locals()[function_name]
|
exec_globals[function_name] = locals()[function_name]
|
||||||
|
|
@ -210,13 +192,9 @@ def prepare_global_scope(code, module):
|
||||||
if isinstance(node, ast.Import):
|
if isinstance(node, ast.Import):
|
||||||
for alias in node.names:
|
for alias in node.names:
|
||||||
try:
|
try:
|
||||||
exec_globals[alias.asname or alias.name] = importlib.import_module(
|
exec_globals[alias.asname or alias.name] = importlib.import_module(alias.name)
|
||||||
alias.name
|
|
||||||
)
|
|
||||||
except ModuleNotFoundError as e:
|
except ModuleNotFoundError as e:
|
||||||
raise ModuleNotFoundError(
|
raise ModuleNotFoundError(f"Module {alias.name} not found. Please install it and try again.") from e
|
||||||
f"Module {alias.name} not found. Please install it and try again."
|
|
||||||
) from e
|
|
||||||
elif isinstance(node, ast.ImportFrom) and node.module is not None:
|
elif isinstance(node, ast.ImportFrom) and node.module is not None:
|
||||||
try:
|
try:
|
||||||
imported_module = importlib.import_module(node.module)
|
imported_module = importlib.import_module(node.module)
|
||||||
|
|
@ -237,11 +215,7 @@ def extract_class_code(module, class_name):
|
||||||
:param class_name: Name of the class to extract
|
:param class_name: Name of the class to extract
|
||||||
:return: AST node of the specified class
|
:return: AST node of the specified class
|
||||||
"""
|
"""
|
||||||
class_code = next(
|
class_code = next(node for node in module.body if isinstance(node, ast.ClassDef) and node.name == class_name)
|
||||||
node
|
|
||||||
for node in module.body
|
|
||||||
if isinstance(node, ast.ClassDef) and node.name == class_name
|
|
||||||
)
|
|
||||||
|
|
||||||
class_code.parent = None
|
class_code.parent = None
|
||||||
return class_code
|
return class_code
|
||||||
|
|
@ -254,9 +228,7 @@ def compile_class_code(class_code):
|
||||||
:param class_code: AST node of the class
|
:param class_code: AST node of the class
|
||||||
:return: Compiled code object of the class
|
:return: Compiled code object of the class
|
||||||
"""
|
"""
|
||||||
code_obj = compile(
|
code_obj = compile(ast.Module(body=[class_code], type_ignores=[]), "<string>", "exec")
|
||||||
ast.Module(body=[class_code], type_ignores=[]), "<string>", "exec"
|
|
||||||
)
|
|
||||||
return code_obj
|
return code_obj
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -300,9 +272,7 @@ def get_default_imports(code_string):
|
||||||
langflow_imports = list(CUSTOM_COMPONENT_SUPPORTED_TYPES.keys())
|
langflow_imports = list(CUSTOM_COMPONENT_SUPPORTED_TYPES.keys())
|
||||||
necessary_imports = find_names_in_code(code_string, langflow_imports)
|
necessary_imports = find_names_in_code(code_string, langflow_imports)
|
||||||
langflow_module = importlib.import_module("langflow.field_typing")
|
langflow_module = importlib.import_module("langflow.field_typing")
|
||||||
default_imports.update(
|
default_imports.update({name: getattr(langflow_module, name) for name in necessary_imports})
|
||||||
{name: getattr(langflow_module, name) for name in necessary_imports}
|
|
||||||
)
|
|
||||||
|
|
||||||
return default_imports
|
return default_imports
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue