Merge branch 'feature/store' of github.com:logspace-ai/langflow into feature/store
This commit is contained in:
commit
7a15fb7a0d
123 changed files with 1841 additions and 2729 deletions
5
Makefile
5
Makefile
|
|
@ -30,13 +30,12 @@ else
|
||||||
endif
|
endif
|
||||||
|
|
||||||
format:
|
format:
|
||||||
poetry run black .
|
poetry run ruff . --fix
|
||||||
poetry run ruff . --fix --exclude src/backend/langflow/alembic
|
poetry run ruff format .
|
||||||
cd src/frontend && npm run format
|
cd src/frontend && npm run format
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
poetry run mypy src/backend/langflow
|
poetry run mypy src/backend/langflow
|
||||||
poetry run black . --check
|
|
||||||
poetry run ruff . --fix
|
poetry run ruff . --fix
|
||||||
|
|
||||||
install_frontend:
|
install_frontend:
|
||||||
|
|
|
||||||
2295
poetry.lock
generated
2295
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -96,10 +96,9 @@ qianfan = "0.0.5"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
types-redis = "^4.6.0.5"
|
types-redis = "^4.6.0.5"
|
||||||
black = "^23.1.0"
|
|
||||||
ipykernel = "^6.21.2"
|
ipykernel = "^6.21.2"
|
||||||
mypy = "^1.1.1"
|
mypy = "^1.1.1"
|
||||||
ruff = "^0.0.254"
|
ruff = "^0.1.5"
|
||||||
httpx = "*"
|
httpx = "*"
|
||||||
pytest = "^7.2.2"
|
pytest = "^7.2.2"
|
||||||
types-requests = "^2.28.11"
|
types-requests = "^2.28.11"
|
||||||
|
|
@ -136,6 +135,7 @@ markers = ["async_test"]
|
||||||
|
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
|
exclude = ["src/backend/langflow/alembic/*"]
|
||||||
line-length = 120
|
line-length = 120
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
|
|
|
||||||
|
|
@ -98,12 +98,8 @@ def update_settings(
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def run(
|
def run(
|
||||||
host: str = typer.Option(
|
host: str = typer.Option("127.0.0.1", help="Host to bind the server to.", envvar="LANGFLOW_HOST"),
|
||||||
"127.0.0.1", help="Host to bind the server to.", envvar="LANGFLOW_HOST"
|
workers: int = typer.Option(1, help="Number of worker processes.", envvar="LANGFLOW_WORKERS"),
|
||||||
),
|
|
||||||
workers: int = typer.Option(
|
|
||||||
1, help="Number of worker processes.", envvar="LANGFLOW_WORKERS"
|
|
||||||
),
|
|
||||||
timeout: int = typer.Option(300, help="Worker timeout in seconds."),
|
timeout: int = typer.Option(300, help="Worker timeout in seconds."),
|
||||||
port: int = typer.Option(7860, help="Port to listen on.", envvar="LANGFLOW_PORT"),
|
port: int = typer.Option(7860, help="Port to listen on.", envvar="LANGFLOW_PORT"),
|
||||||
components_path: Optional[Path] = typer.Option(
|
components_path: Optional[Path] = typer.Option(
|
||||||
|
|
@ -111,19 +107,11 @@ def run(
|
||||||
help="Path to the directory containing custom components.",
|
help="Path to the directory containing custom components.",
|
||||||
envvar="LANGFLOW_COMPONENTS_PATH",
|
envvar="LANGFLOW_COMPONENTS_PATH",
|
||||||
),
|
),
|
||||||
config: str = typer.Option(
|
config: str = typer.Option(Path(__file__).parent / "config.yaml", help="Path to the configuration file."),
|
||||||
Path(__file__).parent / "config.yaml", help="Path to the configuration file."
|
|
||||||
),
|
|
||||||
# .env file param
|
# .env file param
|
||||||
env_file: Path = typer.Option(
|
env_file: Path = typer.Option(None, help="Path to the .env file containing environment variables."),
|
||||||
None, help="Path to the .env file containing environment variables."
|
log_level: str = typer.Option("critical", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL"),
|
||||||
),
|
log_file: Path = typer.Option("logs/langflow.log", help="Path to the log file.", envvar="LANGFLOW_LOG_FILE"),
|
||||||
log_level: str = typer.Option(
|
|
||||||
"critical", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL"
|
|
||||||
),
|
|
||||||
log_file: Path = typer.Option(
|
|
||||||
"logs/langflow.log", help="Path to the log file.", envvar="LANGFLOW_LOG_FILE"
|
|
||||||
),
|
|
||||||
cache: Optional[str] = typer.Option(
|
cache: Optional[str] = typer.Option(
|
||||||
envvar="LANGFLOW_LANGCHAIN_CACHE",
|
envvar="LANGFLOW_LANGCHAIN_CACHE",
|
||||||
help="Type of cache to use. (InMemoryCache, SQLiteCache)",
|
help="Type of cache to use. (InMemoryCache, SQLiteCache)",
|
||||||
|
|
@ -202,9 +190,7 @@ def run(
|
||||||
|
|
||||||
|
|
||||||
def run_on_mac_or_linux(host, port, log_level, options, app, open_browser=True):
|
def run_on_mac_or_linux(host, port, log_level, options, app, open_browser=True):
|
||||||
webapp_process = Process(
|
webapp_process = Process(target=run_langflow, args=(host, port, log_level, options, app))
|
||||||
target=run_langflow, args=(host, port, log_level, options, app)
|
|
||||||
)
|
|
||||||
webapp_process.start()
|
webapp_process.start()
|
||||||
status_code = 0
|
status_code = 0
|
||||||
while status_code != 200:
|
while status_code != 200:
|
||||||
|
|
@ -280,9 +266,7 @@ def print_banner(host, port):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create a panel with the title and the info text, and a border around it
|
# Create a panel with the title and the info text, and a border around it
|
||||||
panel = Panel(
|
panel = Panel(f"{title}\n{info_text}", box=box.ROUNDED, border_style="blue", expand=False)
|
||||||
f"{title}\n{info_text}", box=box.ROUNDED, border_style="blue", expand=False
|
|
||||||
)
|
|
||||||
|
|
||||||
# Print the banner with a separator line before and after
|
# Print the banner with a separator line before and after
|
||||||
rprint(panel)
|
rprint(panel)
|
||||||
|
|
@ -314,12 +298,8 @@ def run_langflow(host, port, log_level, options, app):
|
||||||
@app.command()
|
@app.command()
|
||||||
def superuser(
|
def superuser(
|
||||||
username: str = typer.Option(..., prompt=True, help="Username for the superuser."),
|
username: str = typer.Option(..., prompt=True, help="Username for the superuser."),
|
||||||
password: str = typer.Option(
|
password: str = typer.Option(..., prompt=True, hide_input=True, help="Password for the superuser."),
|
||||||
..., prompt=True, hide_input=True, help="Password for the superuser."
|
log_level: str = typer.Option("critical", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL"),
|
||||||
),
|
|
||||||
log_level: str = typer.Option(
|
|
||||||
"critical", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL"
|
|
||||||
),
|
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Create a superuser.
|
Create a superuser.
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,7 @@ API_WORDS = ["api", "key", "token"]
|
||||||
|
|
||||||
|
|
||||||
def has_api_terms(word: str):
|
def has_api_terms(word: str):
|
||||||
return "api" in word and (
|
return "api" in word and ("key" in word or ("token" in word and "tokens" not in word))
|
||||||
"key" in word or ("token" in word and "tokens" not in word)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def remove_api_keys(flow: dict):
|
def remove_api_keys(flow: dict):
|
||||||
|
|
@ -14,11 +12,7 @@ def remove_api_keys(flow: dict):
|
||||||
node_data = node.get("data").get("node")
|
node_data = node.get("data").get("node")
|
||||||
template = node_data.get("template")
|
template = node_data.get("template")
|
||||||
for value in template.values():
|
for value in template.values():
|
||||||
if (
|
if isinstance(value, dict) and has_api_terms(value["name"]) and value.get("password"):
|
||||||
isinstance(value, dict)
|
|
||||||
and has_api_terms(value["name"])
|
|
||||||
and value.get("password")
|
|
||||||
):
|
|
||||||
value["value"] = None
|
value["value"] = None
|
||||||
|
|
||||||
return flow
|
return flow
|
||||||
|
|
@ -39,9 +33,7 @@ def build_input_keys_response(langchain_object, artifacts):
|
||||||
input_keys_response["input_keys"][key] = value
|
input_keys_response["input_keys"][key] = value
|
||||||
# If the object has memory, that memory will have a memory_variables attribute
|
# If the object has memory, that memory will have a memory_variables attribute
|
||||||
# memory variables should be removed from the input keys
|
# memory variables should be removed from the input keys
|
||||||
if hasattr(langchain_object, "memory") and hasattr(
|
if hasattr(langchain_object, "memory") and hasattr(langchain_object.memory, "memory_variables"):
|
||||||
langchain_object.memory, "memory_variables"
|
|
||||||
):
|
|
||||||
# Remove memory variables from input keys
|
# Remove memory variables from input keys
|
||||||
input_keys_response["input_keys"] = {
|
input_keys_response["input_keys"] = {
|
||||||
key: value
|
key: value
|
||||||
|
|
@ -51,9 +43,7 @@ def build_input_keys_response(langchain_object, artifacts):
|
||||||
# Add memory variables to memory_keys
|
# Add memory variables to memory_keys
|
||||||
input_keys_response["memory_keys"] = langchain_object.memory.memory_variables
|
input_keys_response["memory_keys"] = langchain_object.memory.memory_variables
|
||||||
|
|
||||||
if hasattr(langchain_object, "prompt") and hasattr(
|
if hasattr(langchain_object, "prompt") and hasattr(langchain_object.prompt, "template"):
|
||||||
langchain_object.prompt, "template"
|
|
||||||
):
|
|
||||||
input_keys_response["template"] = langchain_object.prompt.template
|
input_keys_response["template"] = langchain_object.prompt.template
|
||||||
|
|
||||||
return input_keys_response
|
return input_keys_response
|
||||||
|
|
|
||||||
|
|
@ -79,9 +79,7 @@ def save_store_api_key(
|
||||||
try:
|
try:
|
||||||
api_key = api_key_request.api_key
|
api_key = api_key_request.api_key
|
||||||
# Encrypt the API key
|
# Encrypt the API key
|
||||||
encrypted = auth_utils.encrypt_api_key(
|
encrypted = auth_utils.encrypt_api_key(api_key, settings_service=settings_service)
|
||||||
api_key, settings_service=settings_service
|
|
||||||
)
|
|
||||||
current_user.store_api_key = encrypted
|
current_user.store_api_key = encrypted
|
||||||
db.commit()
|
db.commit()
|
||||||
return {"detail": "API Key saved"}
|
return {"detail": "API Key saved"}
|
||||||
|
|
|
||||||
|
|
@ -79,9 +79,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)
|
||||||
|
|
@ -132,9 +130,7 @@ def check_input_variables(input_variables: list):
|
||||||
return input_variables
|
return input_variables
|
||||||
|
|
||||||
|
|
||||||
def build_error_message(
|
def build_error_message(input_variables, invalid_chars, wrong_variables, fixed_variables, empty_variables):
|
||||||
input_variables, invalid_chars, wrong_variables, fixed_variables, empty_variables
|
|
||||||
):
|
|
||||||
input_variables_str = ", ".join([f"'{var}'" for var in input_variables])
|
input_variables_str = ", ".join([f"'{var}'" for var in input_variables])
|
||||||
error_string = f"Invalid input variables: {input_variables_str}. "
|
error_string = f"Invalid input variables: {input_variables_str}. "
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,7 @@ class AsyncStreamingLLMCallbackHandler(AsyncCallbackHandler):
|
||||||
resp = ChatResponse(message=token, type="stream", intermediate_steps="")
|
resp = ChatResponse(message=token, type="stream", intermediate_steps="")
|
||||||
await self.websocket.send_json(resp.dict())
|
await self.websocket.send_json(resp.dict())
|
||||||
|
|
||||||
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="",
|
||||||
|
|
|
||||||
|
|
@ -37,13 +37,9 @@ async def chat(
|
||||||
await websocket.accept()
|
await websocket.accept()
|
||||||
user = await get_current_user(token, db)
|
user = await get_current_user(token, db)
|
||||||
if not user:
|
if not user:
|
||||||
await websocket.close(
|
await websocket.close(code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized")
|
||||||
code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized"
|
|
||||||
)
|
|
||||||
if not user.is_active:
|
if not user.is_active:
|
||||||
await websocket.close(
|
await websocket.close(code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized")
|
||||||
code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized"
|
|
||||||
)
|
|
||||||
|
|
||||||
if client_id in chat_service.cache_service:
|
if client_id in chat_service.cache_service:
|
||||||
await chat_service.handle_websocket(client_id, websocket)
|
await chat_service.handle_websocket(client_id, websocket)
|
||||||
|
|
@ -59,9 +55,7 @@ async def chat(
|
||||||
logger.error(f"Error in chat websocket: {exc}")
|
logger.error(f"Error in chat websocket: {exc}")
|
||||||
messsage = exc.detail if isinstance(exc, HTTPException) else str(exc)
|
messsage = exc.detail if isinstance(exc, HTTPException) else str(exc)
|
||||||
if "Could not validate credentials" in str(exc):
|
if "Could not validate credentials" in str(exc):
|
||||||
await websocket.close(
|
await websocket.close(code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized")
|
||||||
code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized"
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
await websocket.close(code=status.WS_1011_INTERNAL_ERROR, reason=messsage)
|
await websocket.close(code=status.WS_1011_INTERNAL_ERROR, reason=messsage)
|
||||||
|
|
||||||
|
|
@ -103,15 +97,10 @@ async def init_build(
|
||||||
|
|
||||||
|
|
||||||
@router.get("/build/{flow_id}/status", response_model=BuiltResponse)
|
@router.get("/build/{flow_id}/status", response_model=BuiltResponse)
|
||||||
async def build_status(
|
async def build_status(flow_id: str, cache_service: "BaseCacheService" = Depends(get_cache_service)):
|
||||||
flow_id: str, cache_service: "BaseCacheService" = Depends(get_cache_service)
|
|
||||||
):
|
|
||||||
"""Check the flow_id is in the cache_service."""
|
"""Check the flow_id is in the cache_service."""
|
||||||
try:
|
try:
|
||||||
built = (
|
built = flow_id in cache_service and cache_service[flow_id]["status"] == BuildStatus.SUCCESS
|
||||||
flow_id in cache_service
|
|
||||||
and cache_service[flow_id]["status"] == BuildStatus.SUCCESS
|
|
||||||
)
|
|
||||||
|
|
||||||
return BuiltResponse(
|
return BuiltResponse(
|
||||||
built=built,
|
built=built,
|
||||||
|
|
@ -173,9 +162,7 @@ async def stream_build(
|
||||||
params = vertex._built_object_repr()
|
params = vertex._built_object_repr()
|
||||||
valid = True
|
valid = True
|
||||||
logger.debug(f"Building node {str(vertex.vertex_type)}")
|
logger.debug(f"Building node {str(vertex.vertex_type)}")
|
||||||
logger.debug(
|
logger.debug(f"Output: {params[:100]}{'...' if len(params) > 100 else ''}")
|
||||||
f"Output: {params[:100]}{'...' if len(params) > 100 else ''}"
|
|
||||||
)
|
|
||||||
if vertex.artifacts:
|
if vertex.artifacts:
|
||||||
# The artifacts will be prompt variables
|
# The artifacts will be prompt variables
|
||||||
# passed to build_input_keys_response
|
# passed to build_input_keys_response
|
||||||
|
|
@ -187,9 +174,7 @@ async def stream_build(
|
||||||
valid = False
|
valid = False
|
||||||
update_build_status(cache_service, flow_id, BuildStatus.FAILURE)
|
update_build_status(cache_service, flow_id, BuildStatus.FAILURE)
|
||||||
|
|
||||||
vertex_id = (
|
vertex_id = vertex.parent_node_id if vertex.parent_is_top_level else vertex.id
|
||||||
vertex.parent_node_id if vertex.parent_is_top_level else vertex.id
|
|
||||||
)
|
|
||||||
if vertex_id in graph.top_level_nodes:
|
if vertex_id in graph.top_level_nodes:
|
||||||
response = {
|
response = {
|
||||||
"valid": valid,
|
"valid": valid,
|
||||||
|
|
@ -203,9 +188,7 @@ async def stream_build(
|
||||||
langchain_object = graph.build()
|
langchain_object = graph.build()
|
||||||
# Now we need to check the input_keys to send them to the client
|
# Now we need to check the input_keys to send them to the client
|
||||||
if hasattr(langchain_object, "input_keys"):
|
if hasattr(langchain_object, "input_keys"):
|
||||||
input_keys_response = build_input_keys_response(
|
input_keys_response = build_input_keys_response(langchain_object, artifacts)
|
||||||
langchain_object, artifacts
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
input_keys_response = {
|
input_keys_response = {
|
||||||
"input_keys": None,
|
"input_keys": None,
|
||||||
|
|
|
||||||
|
|
@ -92,12 +92,7 @@ async def process(
|
||||||
)
|
)
|
||||||
|
|
||||||
# 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 = (
|
flow = session.query(Flow).filter(Flow.id == flow_id).filter(Flow.user_id == api_key_user.id).first()
|
||||||
session.query(Flow)
|
|
||||||
.filter(Flow.id == flow_id)
|
|
||||||
.filter(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")
|
||||||
|
|
||||||
|
|
@ -111,9 +106,7 @@ async def process(
|
||||||
logger.error(f"Error processing tweaks: {exc}")
|
logger.error(f"Error processing tweaks: {exc}")
|
||||||
if sync:
|
if sync:
|
||||||
task_id, result = await task_service.launch_and_await_task(
|
task_id, result = await task_service.launch_and_await_task(
|
||||||
process_graph_cached_task
|
process_graph_cached_task if task_service.use_celery else process_graph_cached,
|
||||||
if task_service.use_celery
|
|
||||||
else process_graph_cached,
|
|
||||||
graph_data,
|
graph_data,
|
||||||
inputs,
|
inputs,
|
||||||
clear_cache,
|
clear_cache,
|
||||||
|
|
@ -133,13 +126,9 @@ async def process(
|
||||||
)
|
)
|
||||||
if session_id is None:
|
if session_id is None:
|
||||||
# Generate a session ID
|
# Generate a session ID
|
||||||
session_id = get_session_service().generate_key(
|
session_id = get_session_service().generate_key(session_id=session_id, data_graph=graph_data)
|
||||||
session_id=session_id, data_graph=graph_data
|
|
||||||
)
|
|
||||||
task_id, task = await task_service.launch_task(
|
task_id, task = await task_service.launch_task(
|
||||||
process_graph_cached_task
|
process_graph_cached_task if task_service.use_celery else process_graph_cached,
|
||||||
if task_service.use_celery
|
|
||||||
else process_graph_cached,
|
|
||||||
graph_data,
|
graph_data,
|
||||||
inputs,
|
inputs,
|
||||||
clear_cache,
|
clear_cache,
|
||||||
|
|
@ -162,18 +151,12 @@ async def process(
|
||||||
# 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
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Log stack trace
|
# Log stack trace
|
||||||
logger.exception(e)
|
logger.exception(e)
|
||||||
|
|
|
||||||
|
|
@ -64,12 +64,7 @@ def read_flow(
|
||||||
current_user: User = Depends(get_current_active_user),
|
current_user: User = Depends(get_current_active_user),
|
||||||
):
|
):
|
||||||
"""Read a flow."""
|
"""Read a flow."""
|
||||||
if user_flow := (
|
if user_flow := (session.query(Flow).filter(Flow.id == flow_id).filter(Flow.user_id == current_user.id).first()):
|
||||||
session.query(Flow)
|
|
||||||
.filter(Flow.id == flow_id)
|
|
||||||
.filter(Flow.user_id == current_user.id)
|
|
||||||
.first()
|
|
||||||
):
|
|
||||||
return user_flow
|
return user_flow
|
||||||
else:
|
else:
|
||||||
raise HTTPException(status_code=404, detail="Flow not found")
|
raise HTTPException(status_code=404, detail="Flow not found")
|
||||||
|
|
|
||||||
|
|
@ -44,9 +44,7 @@ async def login_to_get_access_token(
|
||||||
|
|
||||||
|
|
||||||
@router.get("/auto_login")
|
@router.get("/auto_login")
|
||||||
async def auto_login(
|
async def auto_login(db: Session = Depends(get_session), settings_service=Depends(get_settings_service)):
|
||||||
db: Session = Depends(get_session), settings_service=Depends(get_settings_service)
|
|
||||||
):
|
|
||||||
if settings_service.auth_settings.AUTO_LOGIN:
|
if settings_service.auth_settings.AUTO_LOGIN:
|
||||||
return create_user_longterm_token(db)
|
return create_user_longterm_token(db)
|
||||||
|
|
||||||
|
|
@ -60,9 +58,7 @@ async def auto_login(
|
||||||
|
|
||||||
|
|
||||||
@router.post("/refresh")
|
@router.post("/refresh")
|
||||||
async def refresh_token(
|
async def refresh_token(token: str, current_user: Session = Depends(get_current_active_user)):
|
||||||
token: str, current_user: Session = Depends(get_current_active_user)
|
|
||||||
):
|
|
||||||
if token:
|
if token:
|
||||||
return create_refresh_token(token)
|
return create_refresh_token(token)
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -149,9 +149,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):
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,25 @@
|
||||||
from typing import List, Optional
|
from datetime import datetime
|
||||||
|
from typing import Annotated, Any, Dict, List, Optional, Union
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
|
from httpx import HTTPStatusError
|
||||||
|
|
||||||
from langflow.services.auth import utils as auth_utils
|
from langflow.services.auth import utils as auth_utils
|
||||||
from langflow.services.database.models.user.user import User
|
from langflow.services.database.models.user.user import User
|
||||||
from langflow.services.deps import (
|
from langflow.services.deps import get_settings_service, get_store_service
|
||||||
get_store_service,
|
|
||||||
get_settings_service,
|
|
||||||
)
|
|
||||||
from langflow.services.store.schema import (
|
from langflow.services.store.schema import (
|
||||||
ComponentResponse,
|
ComponentResponse,
|
||||||
DownloadComponentResponse,
|
DownloadComponentResponse,
|
||||||
|
ListComponentResponse,
|
||||||
ListComponentResponseModel,
|
ListComponentResponseModel,
|
||||||
StoreComponentCreate,
|
StoreComponentCreate,
|
||||||
TagResponse,
|
TagResponse,
|
||||||
UsersLikesResponse,
|
UsersLikesResponse,
|
||||||
)
|
)
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from langflow.services.store.service import StoreService, user_data_context
|
from langflow.services.store.service import StoreService, user_data_context
|
||||||
from langflow.services.store.utils import update_components_with_user_data
|
from langflow.services.store.utils import update_components_with_user_data
|
||||||
|
|
||||||
|
|
||||||
router = APIRouter(prefix="/store", tags=["Components Store"])
|
router = APIRouter(prefix="/store", tags=["Components Store"])
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -30,9 +28,7 @@ def get_user_store_api_key(
|
||||||
settings_service=Depends(get_settings_service),
|
settings_service=Depends(get_settings_service),
|
||||||
):
|
):
|
||||||
if not user.store_api_key:
|
if not user.store_api_key:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=400, detail="You must have a store API key set.")
|
||||||
status_code=400, detail="You must have a store API key set."
|
|
||||||
)
|
|
||||||
decrypted = auth_utils.decrypt_api_key(user.store_api_key, settings_service)
|
decrypted = auth_utils.decrypt_api_key(user.store_api_key, settings_service)
|
||||||
return decrypted
|
return decrypted
|
||||||
|
|
||||||
|
|
@ -79,78 +75,74 @@ def create_component(
|
||||||
|
|
||||||
@router.get("/components/", response_model=ListComponentResponseModel)
|
@router.get("/components/", response_model=ListComponentResponseModel)
|
||||||
def get_components(
|
def get_components(
|
||||||
search: Optional[str] = Query(None),
|
search: Annotated[Optional[str], Query()] = None,
|
||||||
status: Optional[str] = Query(None),
|
status: Annotated[Optional[str], Query()] = None,
|
||||||
is_component: Optional[bool] = Query(None),
|
is_component: Annotated[Optional[bool], Query()] = None,
|
||||||
tags: Optional[List[str]] = Query(None),
|
tags: Annotated[Optional[list[str]], Query()] = None,
|
||||||
sort: Optional[List[str]] = Query(None),
|
sort: Annotated[Union[list[str], None], Query()] = None,
|
||||||
filter_by_user: bool = Query(False),
|
filter_by_user: Annotated[bool, Query()] = False,
|
||||||
page: int = 1,
|
page: int = 1,
|
||||||
limit: int = 10,
|
limit: int = 10,
|
||||||
store_service: StoreService = Depends(get_store_service),
|
store_service: StoreService = Depends(get_store_service),
|
||||||
store_api_Key: Optional[str] = Depends(get_optional_user_store_api_key),
|
store_api_Key: Optional[str] = Depends(get_optional_user_store_api_key),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
with user_data_context(store_api_Key, store_service):
|
with user_data_context(api_key=store_api_Key, store_service=store_service):
|
||||||
|
filter_conditions: List[Dict[str, Any]] = []
|
||||||
|
result: List[ListComponentResponse] = []
|
||||||
authorized = False
|
authorized = False
|
||||||
result = store_service.query_components(
|
|
||||||
api_key=store_api_Key,
|
|
||||||
page=page,
|
|
||||||
limit=limit,
|
|
||||||
filter_by_user=filter_by_user,
|
|
||||||
is_component=is_component,
|
|
||||||
search=search,
|
|
||||||
status=status,
|
|
||||||
tags=tags,
|
|
||||||
sort=sort,
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
comp_count = store_service.count_components(
|
result, filter_conditions = store_service.query_components(
|
||||||
api_key=store_api_Key,
|
api_key=store_api_Key,
|
||||||
|
page=page,
|
||||||
|
limit=limit,
|
||||||
filter_by_user=filter_by_user,
|
filter_by_user=filter_by_user,
|
||||||
is_component=is_component,
|
is_component=is_component,
|
||||||
|
search=search,
|
||||||
|
status=status,
|
||||||
|
tags=tags,
|
||||||
|
sort=sort,
|
||||||
)
|
)
|
||||||
except Exception:
|
except HTTPStatusError as exc:
|
||||||
#! This should be removed once we fix the bug
|
if exc.response.status_code == 403:
|
||||||
comp_count = 0
|
raise ValueError("You are not authorized to access this public resource")
|
||||||
|
try:
|
||||||
|
if result:
|
||||||
|
if len(result) >= limit:
|
||||||
|
comp_count = store_service.count_components(
|
||||||
|
api_key=store_api_Key,
|
||||||
|
filter_by_user=filter_by_user,
|
||||||
|
filter_conditions=filter_conditions,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
comp_count = len(result)
|
||||||
|
else:
|
||||||
|
comp_count = 0
|
||||||
|
except HTTPStatusError as exc:
|
||||||
|
if exc.response.status_code == 403:
|
||||||
|
raise ValueError("You are not authorized to access this public resource")
|
||||||
|
|
||||||
if store_api_Key:
|
if store_api_Key and result:
|
||||||
# Now, from the result, we need to get the components
|
# Now, from the result, we need to get the components
|
||||||
# the user likes and set the liked_by_user to True
|
# the user likes and set the liked_by_user to True
|
||||||
try:
|
try:
|
||||||
updated_result = update_components_with_user_data(
|
updated_result = update_components_with_user_data(result, store_service, store_api_Key)
|
||||||
result, store_service, store_api_Key
|
|
||||||
)
|
|
||||||
authorized = True
|
authorized = True
|
||||||
result = updated_result
|
result = updated_result
|
||||||
except Exception:
|
except Exception:
|
||||||
# If we get an error here, it means the user is not authorized
|
# If we get an error here, it means the user is not authorized
|
||||||
authorized = False
|
authorized = False
|
||||||
return ListComponentResponseModel(
|
return ListComponentResponseModel(results=result, authorized=authorized, count=comp_count)
|
||||||
results=result, authorized=authorized, count=comp_count
|
|
||||||
)
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
if isinstance(exc, HTTPStatusError):
|
||||||
|
if exc.response.status_code == 403:
|
||||||
|
raise HTTPException(status_code=403, detail="Forbidden")
|
||||||
|
elif isinstance(exc, ValueError):
|
||||||
|
raise HTTPException(status_code=403, detail=str(exc))
|
||||||
|
|
||||||
raise HTTPException(status_code=500, detail=str(exc))
|
raise HTTPException(status_code=500, detail=str(exc))
|
||||||
|
|
||||||
|
|
||||||
@router.get("/components/count", response_model=dict)
|
|
||||||
def count_components(
|
|
||||||
filter_by_user: bool = Query(False),
|
|
||||||
store_service: StoreService = Depends(get_store_service),
|
|
||||||
store_api_Key: str = Depends(get_optional_user_store_api_key),
|
|
||||||
is_component: Optional[bool] = Query(None),
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
result = store_service.count_components(
|
|
||||||
api_key=store_api_Key,
|
|
||||||
filter_by_user=filter_by_user,
|
|
||||||
is_component=is_component,
|
|
||||||
)
|
|
||||||
return {"count": result}
|
|
||||||
except Exception as exc:
|
|
||||||
raise HTTPException(status_code=400, detail=str(exc))
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/components/{component_id}", response_model=DownloadComponentResponse)
|
@router.get("/components/{component_id}", response_model=DownloadComponentResponse)
|
||||||
def read_component(
|
def read_component(
|
||||||
component_id: UUID,
|
component_id: UUID,
|
||||||
|
|
@ -235,9 +227,7 @@ def like_component(
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
result = store_service.like_component(store_api_Key, component_id)
|
result = store_service.like_component(store_api_Key, component_id)
|
||||||
likes_count = store_service.get_component_likes_count(
|
likes_count = store_service.get_component_likes_count(store_api_Key, component_id)
|
||||||
store_api_Key, component_id
|
|
||||||
)
|
|
||||||
|
|
||||||
return UsersLikesResponse(likes_count=likes_count, liked_by_user=result)
|
return UsersLikesResponse(likes_count=likes_count, liked_by_user=result)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
|
|
||||||
|
|
@ -46,9 +46,7 @@ def add_user(
|
||||||
session.refresh(new_user)
|
session.refresh(new_user)
|
||||||
except IntegrityError as e:
|
except IntegrityError as e:
|
||||||
session.rollback()
|
session.rollback()
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=400, detail="This username is unavailable.") from e
|
||||||
status_code=400, detail="This username is unavailable."
|
|
||||||
) from e
|
|
||||||
|
|
||||||
return new_user
|
return new_user
|
||||||
|
|
||||||
|
|
@ -96,14 +94,10 @@ def patch_user(
|
||||||
Update an existing user's data.
|
Update an existing user's data.
|
||||||
"""
|
"""
|
||||||
if not user.is_superuser and user.id != user_id:
|
if not user.is_superuser and user.id != user_id:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=403, detail="You don't have the permission to update this user")
|
||||||
status_code=403, detail="You don't have the permission to update this user"
|
|
||||||
)
|
|
||||||
if user_update.password:
|
if user_update.password:
|
||||||
if not user.is_superuser:
|
if not user.is_superuser:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=400, detail="You can't change your password here")
|
||||||
status_code=400, detail="You can't change your password here"
|
|
||||||
)
|
|
||||||
user_update.password = get_password_hash(user_update.password)
|
user_update.password = get_password_hash(user_update.password)
|
||||||
|
|
||||||
if user_db := get_user_by_id(session, user_id):
|
if user_db := get_user_by_id(session, user_id):
|
||||||
|
|
@ -123,16 +117,12 @@ def reset_password(
|
||||||
Reset a user's password.
|
Reset a user's password.
|
||||||
"""
|
"""
|
||||||
if user_id != user.id:
|
if user_id != user.id:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=400, detail="You can't change another user's password")
|
||||||
status_code=400, detail="You can't change another user's password"
|
|
||||||
)
|
|
||||||
|
|
||||||
if not user:
|
if not user:
|
||||||
raise HTTPException(status_code=404, detail="User not found")
|
raise HTTPException(status_code=404, detail="User not found")
|
||||||
if verify_password(user_update.password, user.password):
|
if verify_password(user_update.password, user.password):
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=400, detail="You can't use your current password")
|
||||||
status_code=400, detail="You can't use your current password"
|
|
||||||
)
|
|
||||||
new_password = get_password_hash(user_update.password)
|
new_password = get_password_hash(user_update.password)
|
||||||
user.password = new_password
|
user.password = new_password
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
@ -151,13 +141,9 @@ def delete_user(
|
||||||
Delete a user from the database.
|
Delete a user from the database.
|
||||||
"""
|
"""
|
||||||
if current_user.id == user_id:
|
if current_user.id == user_id:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=400, detail="You can't delete your own user account")
|
||||||
status_code=400, detail="You can't delete your own user account"
|
|
||||||
)
|
|
||||||
elif not current_user.is_superuser:
|
elif not current_user.is_superuser:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=403, detail="You don't have the permission to delete this user")
|
||||||
status_code=403, detail="You don't have the permission to delete this user"
|
|
||||||
)
|
|
||||||
|
|
||||||
user_db = session.query(User).filter(User.id == user_id).first()
|
user_db = session.query(User).filter(User.id == user_id).first()
|
||||||
if not user_db:
|
if not user_db:
|
||||||
|
|
|
||||||
|
|
@ -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].copy()
|
||||||
prompt_request.name
|
|
||||||
].copy()
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
old_custom_fields = []
|
old_custom_fields = []
|
||||||
prompt_request.frontend_node.custom_fields[prompt_request.name] = []
|
prompt_request.frontend_node.custom_fields[prompt_request.name] = []
|
||||||
|
|
@ -92,40 +83,26 @@ def add_new_variables_to_template(input_variables, prompt_request):
|
||||||
)
|
)
|
||||||
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)
|
||||||
|
|
@ -137,6 +114,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
|
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,9 @@ class ConversationalAgent(CustomComponent):
|
||||||
extra_prompt_messages=[MessagesPlaceholder(variable_name=memory_key)],
|
extra_prompt_messages=[MessagesPlaceholder(variable_name=memory_key)],
|
||||||
)
|
)
|
||||||
agent = OpenAIFunctionsAgent(
|
agent = OpenAIFunctionsAgent(
|
||||||
llm=llm, tools=tools, prompt=prompt # type: ignore
|
llm=llm,
|
||||||
|
tools=tools,
|
||||||
|
prompt=prompt, # type: ignore
|
||||||
)
|
)
|
||||||
return AgentExecutor(
|
return AgentExecutor(
|
||||||
agent=agent,
|
agent=agent,
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,7 @@ class PromptRunner(CustomComponent):
|
||||||
"code": {"show": False},
|
"code": {"show": False},
|
||||||
}
|
}
|
||||||
|
|
||||||
def build(
|
def build(self, llm: BaseLLM, prompt: PromptTemplate, inputs: dict = {}) -> Document:
|
||||||
self, llm: BaseLLM, prompt: PromptTemplate, inputs: dict = {}
|
|
||||||
) -> Document:
|
|
||||||
chain = prompt | llm
|
chain = prompt | llm
|
||||||
# The input is an empty dict because the prompt is already filled
|
# The input is an empty dict because the prompt is already filled
|
||||||
result = chain.invoke(input=inputs)
|
result = chain.invoke(input=inputs)
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,7 @@ class MetalRetrieverComponent(CustomComponent):
|
||||||
"code": {"show": False},
|
"code": {"show": False},
|
||||||
}
|
}
|
||||||
|
|
||||||
def build(
|
def build(self, api_key: str, client_id: str, index_id: str, params: Optional[dict] = None) -> BaseRetriever:
|
||||||
self, api_key: str, client_id: str, index_id: str, params: Optional[dict] = None
|
|
||||||
) -> BaseRetriever:
|
|
||||||
try:
|
try:
|
||||||
metal = Metal(api_key=api_key, client_id=client_id, index_id=index_id)
|
metal = Metal(api_key=api_key, client_id=client_id, index_id=index_id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,7 @@ from langchain.agents.agent_toolkits.base import BaseToolkit
|
||||||
class MetaphorToolkit(CustomComponent):
|
class MetaphorToolkit(CustomComponent):
|
||||||
display_name: str = "Metaphor"
|
display_name: str = "Metaphor"
|
||||||
description: str = "Metaphor Toolkit"
|
description: str = "Metaphor Toolkit"
|
||||||
documentation = (
|
documentation = "https://python.langchain.com/docs/integrations/tools/metaphor_search"
|
||||||
"https://python.langchain.com/docs/integrations/tools/metaphor_search"
|
|
||||||
)
|
|
||||||
beta = True
|
beta = True
|
||||||
# api key should be password = True
|
# api key should be password = True
|
||||||
field_config = {
|
field_config = {
|
||||||
|
|
@ -33,9 +31,7 @@ class MetaphorToolkit(CustomComponent):
|
||||||
@tool
|
@tool
|
||||||
def search(query: str):
|
def search(query: str):
|
||||||
"""Call search engine with a query."""
|
"""Call search engine with a query."""
|
||||||
return client.search(
|
return client.search(query, use_autoprompt=use_autoprompt, num_results=search_num_results)
|
||||||
query, use_autoprompt=use_autoprompt, num_results=search_num_results
|
|
||||||
)
|
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
def get_contents(ids: List[str]):
|
def get_contents(ids: List[str]):
|
||||||
|
|
|
||||||
|
|
@ -30,9 +30,7 @@ class GetRequest(CustomComponent):
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_document(
|
def get_document(self, session: requests.Session, url: str, headers: Optional[dict], timeout: int) -> Document:
|
||||||
self, session: requests.Session, url: str, headers: Optional[dict], timeout: int
|
|
||||||
) -> Document:
|
|
||||||
try:
|
try:
|
||||||
response = session.get(url, headers=headers, timeout=int(timeout))
|
response = session.get(url, headers=headers, timeout=int(timeout))
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,7 @@ class JSONDocumentBuilder(CustomComponent):
|
||||||
description: str = "Build a Document containing a JSON object using a key and another Document page content."
|
description: str = "Build a Document containing a JSON object using a key and another Document page content."
|
||||||
output_types: list[str] = ["Document"]
|
output_types: list[str] = ["Document"]
|
||||||
beta = True
|
beta = True
|
||||||
documentation: str = (
|
documentation: str = "https://docs.langflow.org/components/utilities#json-document-builder"
|
||||||
"https://docs.langflow.org/components/utilities#json-document-builder"
|
|
||||||
)
|
|
||||||
|
|
||||||
field_config = {
|
field_config = {
|
||||||
"key": {"display_name": "Key"},
|
"key": {"display_name": "Key"},
|
||||||
|
|
@ -38,18 +36,11 @@ class JSONDocumentBuilder(CustomComponent):
|
||||||
documents = None
|
documents = None
|
||||||
if isinstance(document, list):
|
if isinstance(document, list):
|
||||||
documents = [
|
documents = [
|
||||||
Document(
|
Document(page_content=orjson_dumps({key: doc.page_content}, indent_2=False)) for doc in document
|
||||||
page_content=orjson_dumps({key: doc.page_content}, indent_2=False)
|
|
||||||
)
|
|
||||||
for doc in document
|
|
||||||
]
|
]
|
||||||
elif isinstance(document, Document):
|
elif isinstance(document, Document):
|
||||||
documents = Document(
|
documents = Document(page_content=orjson_dumps({key: document.page_content}, indent_2=False))
|
||||||
page_content=orjson_dumps({key: document.page_content}, indent_2=False)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
raise TypeError(
|
raise TypeError(f"Expected Document or list of Documents, got {type(document)}")
|
||||||
f"Expected Document or list of Documents, got {type(document)}"
|
|
||||||
)
|
|
||||||
self.repr_value = documents
|
self.repr_value = documents
|
||||||
return documents
|
return documents
|
||||||
|
|
|
||||||
|
|
@ -65,16 +65,12 @@ class PostRequest(CustomComponent):
|
||||||
|
|
||||||
if not isinstance(document, list) and isinstance(document, Document):
|
if not isinstance(document, list) and isinstance(document, Document):
|
||||||
documents: list[Document] = [document]
|
documents: list[Document] = [document]
|
||||||
elif isinstance(document, list) and all(
|
elif isinstance(document, list) and all(isinstance(doc, Document) for doc in document):
|
||||||
isinstance(doc, Document) for doc in document
|
|
||||||
):
|
|
||||||
documents = document
|
documents = document
|
||||||
else:
|
else:
|
||||||
raise ValueError("document must be a Document or a list of Documents")
|
raise ValueError("document must be a Document or a list of Documents")
|
||||||
|
|
||||||
with requests.Session() as session:
|
with requests.Session() as session:
|
||||||
documents = [
|
documents = [self.post_document(session, doc, url, headers) for doc in documents]
|
||||||
self.post_document(session, doc, url, headers) for doc in documents
|
|
||||||
]
|
|
||||||
self.repr_value = documents
|
self.repr_value = documents
|
||||||
return documents
|
return documents
|
||||||
|
|
|
||||||
|
|
@ -39,9 +39,7 @@ class UpdateRequest(CustomComponent):
|
||||||
) -> Document:
|
) -> Document:
|
||||||
try:
|
try:
|
||||||
if method == "PATCH":
|
if method == "PATCH":
|
||||||
response = session.patch(
|
response = session.patch(url, headers=headers, data=document.page_content)
|
||||||
url, headers=headers, data=document.page_content
|
|
||||||
)
|
|
||||||
elif method == "PUT":
|
elif method == "PUT":
|
||||||
response = session.put(url, headers=headers, data=document.page_content)
|
response = session.put(url, headers=headers, data=document.page_content)
|
||||||
else:
|
else:
|
||||||
|
|
@ -78,17 +76,12 @@ class UpdateRequest(CustomComponent):
|
||||||
|
|
||||||
if not isinstance(document, list) and isinstance(document, Document):
|
if not isinstance(document, list) and isinstance(document, Document):
|
||||||
documents: list[Document] = [document]
|
documents: list[Document] = [document]
|
||||||
elif isinstance(document, list) and all(
|
elif isinstance(document, list) and all(isinstance(doc, Document) for doc in document):
|
||||||
isinstance(doc, Document) for doc in document
|
|
||||||
):
|
|
||||||
documents = document
|
documents = document
|
||||||
else:
|
else:
|
||||||
raise ValueError("document must be a Document or a list of Documents")
|
raise ValueError("document must be a Document or a list of Documents")
|
||||||
|
|
||||||
with requests.Session() as session:
|
with requests.Session() as session:
|
||||||
documents = [
|
documents = [self.update_document(session, doc, url, headers, method) for doc in documents]
|
||||||
self.update_document(session, doc, url, headers, method)
|
|
||||||
for doc in documents
|
|
||||||
]
|
|
||||||
self.repr_value = documents
|
self.repr_value = documents
|
||||||
return documents
|
return documents
|
||||||
|
|
|
||||||
|
|
@ -86,8 +86,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,
|
||||||
|
|
@ -104,6 +103,4 @@ class ChromaComponent(CustomComponent):
|
||||||
client_settings=chroma_settings,
|
client_settings=chroma_settings,
|
||||||
)
|
)
|
||||||
|
|
||||||
return Chroma(
|
return Chroma(persist_directory=persist_directory, client_settings=chroma_settings)
|
||||||
persist_directory=persist_directory, client_settings=chroma_settings
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,7 @@ from langchain.schema import BaseRetriever
|
||||||
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
|
||||||
# api key should be password = True
|
# api key should be password = True
|
||||||
field_config = {
|
field_config = {
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,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.")
|
||||||
|
|
||||||
|
|
@ -18,9 +16,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.")
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -49,23 +45,17 @@ class Edge:
|
||||||
|
|
||||||
def validate_handles(self) -> None:
|
def validate_handles(self) -> 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 {self.source.vertex_type} and {self.target.vertex_type} "
|
f"Edge between {self.source.vertex_type} and {self.target.vertex_type} " f"has invalid handles"
|
||||||
f"has invalid handles"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def __setstate__(self, state):
|
def __setstate__(self, state):
|
||||||
|
|
@ -87,11 +77,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(
|
||||||
|
|
@ -103,8 +89,7 @@ class Edge:
|
||||||
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 {self.source.vertex_type} and {self.target.vertex_type} "
|
f"Edge between {self.source.vertex_type} and {self.target.vertex_type} " f"has no matched type"
|
||||||
f"has no matched type"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
|
|
@ -117,8 +102,4 @@ class Edge:
|
||||||
return hash(self.__repr__())
|
return hash(self.__repr__())
|
||||||
|
|
||||||
def __eq__(self, __value: object) -> bool:
|
def __eq__(self, __value: object) -> bool:
|
||||||
return (
|
return self.__repr__() == __value.__repr__() if isinstance(__value, Edge) else False
|
||||||
self.__repr__() == __value.__repr__()
|
|
||||||
if isinstance(__value, Edge)
|
|
||||||
else False
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -104,9 +104,7 @@ class Graph:
|
||||||
return
|
return
|
||||||
for node in self.nodes:
|
for node in self.nodes:
|
||||||
if not self._validate_node(node):
|
if not self._validate_node(node):
|
||||||
raise ValueError(
|
raise ValueError(f"{node.vertex_type} is not connected to any other components")
|
||||||
f"{node.vertex_type} is not connected to any other components"
|
|
||||||
)
|
|
||||||
|
|
||||||
def _validate_node(self, node: Vertex) -> bool:
|
def _validate_node(self, node: Vertex) -> bool:
|
||||||
"""Validates a node."""
|
"""Validates a node."""
|
||||||
|
|
@ -119,9 +117,7 @@ class Graph:
|
||||||
|
|
||||||
def get_nodes_with_target(self, node: Vertex) -> List[Vertex]:
|
def get_nodes_with_target(self, node: Vertex) -> List[Vertex]:
|
||||||
"""Returns the nodes connected to a node."""
|
"""Returns the nodes connected to a node."""
|
||||||
connected_nodes: List[Vertex] = [
|
connected_nodes: List[Vertex] = [edge.source for edge in self.edges if edge.target == node]
|
||||||
edge.source for edge in self.edges if edge.target == node
|
|
||||||
]
|
|
||||||
return connected_nodes
|
return connected_nodes
|
||||||
|
|
||||||
def build(self) -> Chain:
|
def build(self) -> Chain:
|
||||||
|
|
@ -149,9 +145,7 @@ class Graph:
|
||||||
def dfs(node):
|
def dfs(node):
|
||||||
if state[node] == 1:
|
if state[node] == 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[node] == 0:
|
if state[node] == 0:
|
||||||
state[node] = 1
|
state[node] = 1
|
||||||
for edge in node.edges:
|
for edge in node.edges:
|
||||||
|
|
@ -245,7 +239,5 @@ class Graph:
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
node_ids = [node.id for node in self.nodes]
|
node_ids = [node.id for node in self.nodes]
|
||||||
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: {node_ids}\nConnections:\n{edges_repr}"
|
return f"Graph:\nNodes: {node_ids}\nConnections:\n{edges_repr}"
|
||||||
|
|
|
||||||
|
|
@ -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()},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,23 +28,14 @@ def ungroup_node(group_node_data, base_flow):
|
||||||
g_edges = flow["data"]["edges"]
|
g_edges = flow["data"]["edges"]
|
||||||
|
|
||||||
# Redirect edges to the correct proxy node
|
# Redirect edges to the correct proxy node
|
||||||
updated_edges = get_updated_edges(
|
updated_edges = get_updated_edges(base_flow, g_nodes, g_edges, group_node_data["id"])
|
||||||
base_flow, g_nodes, g_edges, group_node_data["id"]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Update template values
|
# Update template values
|
||||||
update_template(template, g_nodes)
|
update_template(template, g_nodes)
|
||||||
|
|
||||||
nodes = [
|
nodes = [n for n in base_flow["nodes"] if n["id"] != group_node_data["id"]] + g_nodes
|
||||||
n for n in base_flow["nodes"] if n["id"] != group_node_data["id"]
|
|
||||||
] + g_nodes
|
|
||||||
edges = (
|
edges = (
|
||||||
[
|
[e for e in base_flow["edges"] if e["target"] != group_node_data["id"] and e["source"] != group_node_data["id"]]
|
||||||
e
|
|
||||||
for e in base_flow["edges"]
|
|
||||||
if e["target"] != group_node_data["id"]
|
|
||||||
and e["source"] != group_node_data["id"]
|
|
||||||
]
|
|
||||||
+ g_edges
|
+ g_edges
|
||||||
+ updated_edges
|
+ updated_edges
|
||||||
)
|
)
|
||||||
|
|
@ -66,11 +57,7 @@ def process_flow(flow_object):
|
||||||
if node_id in processed_nodes:
|
if node_id in processed_nodes:
|
||||||
return
|
return
|
||||||
|
|
||||||
if (
|
if node.get("data") and node["data"].get("node") and node["data"]["node"].get("flow"):
|
||||||
node.get("data")
|
|
||||||
and node["data"].get("node")
|
|
||||||
and node["data"]["node"].get("flow")
|
|
||||||
):
|
|
||||||
process_flow(node["data"]["node"]["flow"]["data"])
|
process_flow(node["data"]["node"]["flow"]["data"])
|
||||||
new_nodes = ungroup_node(node["data"], cloned_flow)
|
new_nodes = ungroup_node(node["data"], cloned_flow)
|
||||||
# Add new nodes to the queue for future processing
|
# Add new nodes to the queue for future processing
|
||||||
|
|
@ -108,26 +95,16 @@ def update_template(template, g_nodes):
|
||||||
if node_index != -1:
|
if node_index != -1:
|
||||||
display_name = None
|
display_name = None
|
||||||
show = g_nodes[node_index]["data"]["node"]["template"][field]["show"]
|
show = g_nodes[node_index]["data"]["node"]["template"][field]["show"]
|
||||||
advanced = g_nodes[node_index]["data"]["node"]["template"][field][
|
advanced = g_nodes[node_index]["data"]["node"]["template"][field]["advanced"]
|
||||||
"advanced"
|
|
||||||
]
|
|
||||||
if "display_name" in g_nodes[node_index]["data"]["node"]["template"][field]:
|
if "display_name" in g_nodes[node_index]["data"]["node"]["template"][field]:
|
||||||
display_name = g_nodes[node_index]["data"]["node"]["template"][field][
|
display_name = g_nodes[node_index]["data"]["node"]["template"][field]["display_name"]
|
||||||
"display_name"
|
|
||||||
]
|
|
||||||
else:
|
else:
|
||||||
display_name = g_nodes[node_index]["data"]["node"]["template"][field][
|
display_name = g_nodes[node_index]["data"]["node"]["template"][field]["name"]
|
||||||
"name"
|
|
||||||
]
|
|
||||||
|
|
||||||
g_nodes[node_index]["data"]["node"]["template"][field] = value
|
g_nodes[node_index]["data"]["node"]["template"][field] = value
|
||||||
g_nodes[node_index]["data"]["node"]["template"][field]["show"] = show
|
g_nodes[node_index]["data"]["node"]["template"][field]["show"] = show
|
||||||
g_nodes[node_index]["data"]["node"]["template"][field][
|
g_nodes[node_index]["data"]["node"]["template"][field]["advanced"] = advanced
|
||||||
"advanced"
|
g_nodes[node_index]["data"]["node"]["template"][field]["display_name"] = display_name
|
||||||
] = advanced
|
|
||||||
g_nodes[node_index]["data"]["node"]["template"][field][
|
|
||||||
"display_name"
|
|
||||||
] = display_name
|
|
||||||
|
|
||||||
|
|
||||||
def update_target_handle(new_edge, g_nodes, group_node_id):
|
def update_target_handle(new_edge, g_nodes, group_node_id):
|
||||||
|
|
|
||||||
|
|
@ -51,9 +51,7 @@ class Vertex:
|
||||||
self.params.pop(target_param, None)
|
self.params.pop(target_param, None)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if target_param in self.params and not is_basic_type(
|
if target_param in self.params and not is_basic_type(self.params[target_param]):
|
||||||
self.params[target_param]
|
|
||||||
):
|
|
||||||
# edge.source.params = {}
|
# edge.source.params = {}
|
||||||
edge.source._build_params()
|
edge.source._build_params()
|
||||||
edge.source._built_object = UnbuiltObject()
|
edge.source._built_object = UnbuiltObject()
|
||||||
|
|
@ -99,29 +97,17 @@ class Vertex:
|
||||||
def _parse_data(self) -> None:
|
def _parse_data(self) -> None:
|
||||||
self.data = self._data["data"]
|
self.data = self._data["data"]
|
||||||
self.output = self.data["node"]["base_classes"]
|
self.output = self.data["node"]["base_classes"]
|
||||||
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.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"]
|
||||||
|
|
@ -160,11 +146,7 @@ class Vertex:
|
||||||
# and use that as the value for the param
|
# and use that as the value for the param
|
||||||
# If the type is "str", then we need to get the value of the "value" key
|
# If the type is "str", then we need to get the value of the "value" key
|
||||||
# and use that as the value for the param
|
# and use that as the value for the param
|
||||||
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 = self.params.copy() if self.params else {}
|
params = self.params.copy() if self.params else {}
|
||||||
|
|
||||||
for edge in self.edges:
|
for edge in self.edges:
|
||||||
|
|
@ -209,11 +191,7 @@ class Vertex:
|
||||||
# before passing it to the build method
|
# before passing it to the build method
|
||||||
_value = value.get("value")
|
_value = value.get("value")
|
||||||
if isinstance(_value, list):
|
if isinstance(_value, 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(_value, dict):
|
elif isinstance(_value, dict):
|
||||||
params[key] = _value
|
params[key] = _value
|
||||||
elif value.get("type") == "int" and value.get("value") is not None:
|
elif value.get("type") == "int" and value.get("value") is not None:
|
||||||
|
|
@ -304,9 +282,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
|
||||||
|
|
||||||
def _build_list_of_nodes_and_update_params(
|
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.
|
||||||
"""
|
"""
|
||||||
|
|
@ -358,9 +334,7 @@ class Vertex:
|
||||||
self._update_built_object_and_artifacts(result)
|
self._update_built_object_and_artifacts(result)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception(exc)
|
logger.exception(exc)
|
||||||
raise ValueError(
|
raise ValueError(f"Error building node {self.vertex_type}(ID:{self.id}): {str(exc)}") from exc
|
||||||
f"Error building node {self.vertex_type}(ID:{self.id}): {str(exc)}"
|
|
||||||
) from exc
|
|
||||||
|
|
||||||
def _update_built_object_and_artifacts(self, result):
|
def _update_built_object_and_artifacts(self, result):
|
||||||
"""
|
"""
|
||||||
|
|
@ -408,8 +382,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 😵💫"
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -107,11 +107,9 @@ class DocumentLoaderVertex(Vertex):
|
||||||
# show how many documents are in the list?
|
# show how many documents are in the list?
|
||||||
|
|
||||||
if self._built_object:
|
if self._built_object:
|
||||||
avg_length = sum(
|
avg_length = sum(len(doc.page_content) for doc in self._built_object if hasattr(doc, "page_content")) / len(
|
||||||
len(doc.page_content)
|
self._built_object
|
||||||
for doc in self._built_object
|
)
|
||||||
if hasattr(doc, "page_content")
|
|
||||||
) / len(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)}
|
||||||
Documents: {self._built_object[:3]}..."""
|
Documents: {self._built_object[:3]}..."""
|
||||||
|
|
@ -184,9 +182,7 @@ class TextSplitterVertex(Vertex):
|
||||||
# show how many documents are in the list?
|
# show how many documents are in the list?
|
||||||
|
|
||||||
if self._built_object:
|
if self._built_object:
|
||||||
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]}..."""
|
||||||
|
|
@ -205,6 +201,8 @@ class ChainVertex(Vertex):
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
if not self._built or force:
|
if not self._built or force:
|
||||||
|
# Temporarily remove the code from the params
|
||||||
|
self.params.pop("code", None)
|
||||||
# Check if the chain requires a PromptVertex
|
# Check if the chain requires a PromptVertex
|
||||||
for key, value in self.params.items():
|
for key, value in self.params.items():
|
||||||
if isinstance(value, PromptVertex):
|
if isinstance(value, PromptVertex):
|
||||||
|
|
@ -230,27 +228,18 @@ class PromptVertex(Vertex):
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
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"]
|
||||||
|
|
@ -260,9 +249,7 @@ 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)
|
||||||
|
|
||||||
|
|
@ -270,11 +257,7 @@ class PromptVertex(Vertex):
|
||||||
return self._built_object
|
return self._built_object
|
||||||
|
|
||||||
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()
|
||||||
# We'll build the prompt with the artifacts
|
# We'll build the prompt with the artifacts
|
||||||
# to show the user what the prompt looks like
|
# to show the user what the prompt looks like
|
||||||
|
|
@ -284,9 +267,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
|
||||||
|
|
@ -294,11 +275,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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,9 +42,7 @@ class AgentCreator(LangChainTypeCreator):
|
||||||
add_function=True,
|
add_function=True,
|
||||||
method_name=self.from_method_nodes[name],
|
method_name=self.from_method_nodes[name],
|
||||||
)
|
)
|
||||||
return build_template_from_class(
|
return build_template_from_class(name, self.type_to_loader_dict, add_function=True)
|
||||||
name, self.type_to_loader_dict, add_function=True
|
|
||||||
)
|
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise ValueError("Agent not found") from exc
|
raise ValueError("Agent not found") from exc
|
||||||
except AttributeError as exc:
|
except AttributeError as exc:
|
||||||
|
|
@ -56,15 +54,8 @@ class AgentCreator(LangChainTypeCreator):
|
||||||
names = []
|
names = []
|
||||||
settings_service = get_settings_service()
|
settings_service = get_settings_service()
|
||||||
for _, agent in self.type_to_loader_dict.items():
|
for _, agent in self.type_to_loader_dict.items():
|
||||||
agent_name = (
|
agent_name = agent.function_name() if hasattr(agent, "function_name") else agent.__name__
|
||||||
agent.function_name()
|
if agent_name in settings_service.settings.AGENTS or settings_service.settings.DEV:
|
||||||
if hasattr(agent, "function_name")
|
|
||||||
else agent.__name__
|
|
||||||
)
|
|
||||||
if (
|
|
||||||
agent_name in settings_service.settings.AGENTS
|
|
||||||
or settings_service.settings.DEV
|
|
||||||
):
|
|
||||||
names.append(agent_name)
|
names.append(agent_name)
|
||||||
return names
|
return names
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,8 @@ class JsonAgent(CustomAgentExecutor):
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
)
|
)
|
||||||
agent = ZeroShotAgent(
|
agent = ZeroShotAgent(
|
||||||
llm_chain=llm_chain, allowed_tools=tool_names # type: ignore
|
llm_chain=llm_chain,
|
||||||
|
allowed_tools=tool_names, # type: ignore
|
||||||
)
|
)
|
||||||
return cls.from_agent_and_tools(agent=agent, tools=tools, verbose=True)
|
return cls.from_agent_and_tools(agent=agent, tools=tools, verbose=True)
|
||||||
|
|
||||||
|
|
@ -90,11 +91,7 @@ class CSVAgent(CustomAgentExecutor):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_toolkit_and_llm(
|
def from_toolkit_and_llm(
|
||||||
cls,
|
cls, path: str, llm: BaseLanguageModel, pandas_kwargs: Optional[dict] = None, **kwargs: Any
|
||||||
path: str,
|
|
||||||
llm: BaseLanguageModel,
|
|
||||||
pandas_kwargs: Optional[dict] = None,
|
|
||||||
**kwargs: Any
|
|
||||||
):
|
):
|
||||||
import pandas as pd # type: ignore
|
import pandas as pd # type: ignore
|
||||||
|
|
||||||
|
|
@ -115,7 +112,9 @@ class CSVAgent(CustomAgentExecutor):
|
||||||
)
|
)
|
||||||
tool_names = {tool.name for tool in tools}
|
tool_names = {tool.name for tool in tools}
|
||||||
agent = ZeroShotAgent(
|
agent = ZeroShotAgent(
|
||||||
llm_chain=llm_chain, allowed_tools=tool_names, **kwargs # type: ignore
|
llm_chain=llm_chain,
|
||||||
|
allowed_tools=tool_names,
|
||||||
|
**kwargs, # type: ignore
|
||||||
)
|
)
|
||||||
|
|
||||||
return cls.from_agent_and_tools(agent=agent, tools=tools, verbose=True)
|
return cls.from_agent_and_tools(agent=agent, tools=tools, verbose=True)
|
||||||
|
|
@ -139,9 +138,7 @@ class VectorStoreAgent(CustomAgentExecutor):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_toolkit_and_llm(
|
def from_toolkit_and_llm(cls, llm: BaseLanguageModel, vectorstoreinfo: VectorStoreInfo, **kwargs: Any):
|
||||||
cls, llm: BaseLanguageModel, vectorstoreinfo: VectorStoreInfo, **kwargs: Any
|
|
||||||
):
|
|
||||||
"""Construct a vectorstore agent from an LLM and tools."""
|
"""Construct a vectorstore agent from an LLM and tools."""
|
||||||
|
|
||||||
toolkit = VectorStoreToolkit(vectorstore_info=vectorstoreinfo, llm=llm)
|
toolkit = VectorStoreToolkit(vectorstore_info=vectorstoreinfo, llm=llm)
|
||||||
|
|
@ -154,11 +151,11 @@ class VectorStoreAgent(CustomAgentExecutor):
|
||||||
)
|
)
|
||||||
tool_names = {tool.name for tool in tools}
|
tool_names = {tool.name for tool in tools}
|
||||||
agent = ZeroShotAgent(
|
agent = ZeroShotAgent(
|
||||||
llm_chain=llm_chain, allowed_tools=tool_names, **kwargs # type: ignore
|
llm_chain=llm_chain,
|
||||||
)
|
allowed_tools=tool_names,
|
||||||
return AgentExecutor.from_agent_and_tools(
|
**kwargs, # type: ignore
|
||||||
agent=agent, tools=tools, verbose=True, handle_parsing_errors=True
|
|
||||||
)
|
)
|
||||||
|
return AgentExecutor.from_agent_and_tools(agent=agent, tools=tools, verbose=True, handle_parsing_errors=True)
|
||||||
|
|
||||||
def run(self, *args, **kwargs):
|
def run(self, *args, **kwargs):
|
||||||
return super().run(*args, **kwargs)
|
return super().run(*args, **kwargs)
|
||||||
|
|
@ -179,9 +176,7 @@ class SQLAgent(CustomAgentExecutor):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_toolkit_and_llm(
|
def from_toolkit_and_llm(cls, llm: BaseLanguageModel, database_uri: str, **kwargs: Any):
|
||||||
cls, llm: BaseLanguageModel, database_uri: str, **kwargs: Any
|
|
||||||
):
|
|
||||||
"""Construct an SQL agent from an LLM and tools."""
|
"""Construct an SQL agent from an LLM and tools."""
|
||||||
db = SQLDatabase.from_uri(database_uri)
|
db = SQLDatabase.from_uri(database_uri)
|
||||||
toolkit = SQLDatabaseToolkit(db=db, llm=llm)
|
toolkit = SQLDatabaseToolkit(db=db, llm=llm)
|
||||||
|
|
@ -199,9 +194,7 @@ class SQLAgent(CustomAgentExecutor):
|
||||||
|
|
||||||
llmchain = LLMChain(
|
llmchain = LLMChain(
|
||||||
llm=llm,
|
llm=llm,
|
||||||
prompt=PromptTemplate(
|
prompt=PromptTemplate(template=QUERY_CHECKER, input_variables=["query", "dialect"]),
|
||||||
template=QUERY_CHECKER, input_variables=["query", "dialect"]
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
tools = [
|
tools = [
|
||||||
|
|
@ -224,7 +217,9 @@ class SQLAgent(CustomAgentExecutor):
|
||||||
)
|
)
|
||||||
tool_names = {tool.name for tool in tools} # type: ignore
|
tool_names = {tool.name for tool in tools} # type: ignore
|
||||||
agent = ZeroShotAgent(
|
agent = ZeroShotAgent(
|
||||||
llm_chain=llm_chain, allowed_tools=tool_names, **kwargs # type: ignore
|
llm_chain=llm_chain,
|
||||||
|
allowed_tools=tool_names,
|
||||||
|
**kwargs, # type: ignore
|
||||||
)
|
)
|
||||||
return AgentExecutor.from_agent_and_tools(
|
return AgentExecutor.from_agent_and_tools(
|
||||||
agent=agent,
|
agent=agent,
|
||||||
|
|
@ -255,10 +250,7 @@ class VectorStoreRouterAgent(CustomAgentExecutor):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_toolkit_and_llm(
|
def from_toolkit_and_llm(
|
||||||
cls,
|
cls, llm: BaseLanguageModel, vectorstoreroutertoolkit: VectorStoreRouterToolkit, **kwargs: Any
|
||||||
llm: BaseLanguageModel,
|
|
||||||
vectorstoreroutertoolkit: VectorStoreRouterToolkit,
|
|
||||||
**kwargs: Any
|
|
||||||
):
|
):
|
||||||
"""Construct a vector store router agent from an LLM and tools."""
|
"""Construct a vector store router agent from an LLM and tools."""
|
||||||
|
|
||||||
|
|
@ -274,11 +266,11 @@ class VectorStoreRouterAgent(CustomAgentExecutor):
|
||||||
)
|
)
|
||||||
tool_names = {tool.name for tool in tools}
|
tool_names = {tool.name for tool in tools}
|
||||||
agent = ZeroShotAgent(
|
agent = ZeroShotAgent(
|
||||||
llm_chain=llm_chain, allowed_tools=tool_names, **kwargs # type: ignore
|
llm_chain=llm_chain,
|
||||||
)
|
allowed_tools=tool_names,
|
||||||
return AgentExecutor.from_agent_and_tools(
|
**kwargs, # type: ignore
|
||||||
agent=agent, tools=tools, verbose=True, handle_parsing_errors=True
|
|
||||||
)
|
)
|
||||||
|
return AgentExecutor.from_agent_and_tools(agent=agent, tools=tools, verbose=True, handle_parsing_errors=True)
|
||||||
|
|
||||||
def run(self, *args, **kwargs):
|
def run(self, *args, **kwargs):
|
||||||
return super().run(*args, **kwargs)
|
return super().run(*args, **kwargs)
|
||||||
|
|
|
||||||
|
|
@ -30,13 +30,8 @@ class LangChainTypeCreator(BaseModel, ABC):
|
||||||
settings_service = get_settings_service()
|
settings_service = get_settings_service()
|
||||||
if self.name_docs_dict is None:
|
if self.name_docs_dict is None:
|
||||||
try:
|
try:
|
||||||
type_settings = getattr(
|
type_settings = getattr(settings_service.settings, self.type_name.upper())
|
||||||
settings_service.settings, self.type_name.upper()
|
self.name_docs_dict = {name: value_dict["documentation"] for name, value_dict in type_settings.items()}
|
||||||
)
|
|
||||||
self.name_docs_dict = {
|
|
||||||
name: value_dict["documentation"]
|
|
||||||
for name, value_dict in type_settings.items()
|
|
||||||
}
|
|
||||||
except AttributeError as exc:
|
except AttributeError as exc:
|
||||||
logger.error(f"Error getting settings for {self.type_name}: {exc}")
|
logger.error(f"Error getting settings for {self.type_name}: {exc}")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,7 @@ class ChainCreator(LangChainTypeCreator):
|
||||||
if self.type_dict is None:
|
if self.type_dict is None:
|
||||||
settings_service = get_settings_service()
|
settings_service = get_settings_service()
|
||||||
self.type_dict: dict[str, Any] = {
|
self.type_dict: dict[str, Any] = {
|
||||||
chain_name: import_class(f"langchain.chains.{chain_name}")
|
chain_name: import_class(f"langchain.chains.{chain_name}") for chain_name in chains.__all__
|
||||||
for chain_name in chains.__all__
|
|
||||||
}
|
}
|
||||||
from langflow.interface.chains.custom import CUSTOM_CHAINS
|
from langflow.interface.chains.custom import CUSTOM_CHAINS
|
||||||
|
|
||||||
|
|
@ -45,8 +44,7 @@ class ChainCreator(LangChainTypeCreator):
|
||||||
self.type_dict = {
|
self.type_dict = {
|
||||||
name: chain
|
name: chain
|
||||||
for name, chain in self.type_dict.items()
|
for name, chain in self.type_dict.items()
|
||||||
if name in settings_service.settings.CHAINS
|
if name in settings_service.settings.CHAINS or settings_service.settings.DEV
|
||||||
or settings_service.settings.DEV
|
|
||||||
}
|
}
|
||||||
return self.type_dict
|
return self.type_dict
|
||||||
|
|
||||||
|
|
@ -61,9 +59,7 @@ class ChainCreator(LangChainTypeCreator):
|
||||||
method_name=self.from_method_nodes[name],
|
method_name=self.from_method_nodes[name],
|
||||||
add_function=True,
|
add_function=True,
|
||||||
)
|
)
|
||||||
return build_template_from_class(
|
return build_template_from_class(name, self.type_to_loader_dict, add_function=True)
|
||||||
name, self.type_to_loader_dict, add_function=True
|
|
||||||
)
|
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise ValueError(f"Chain {name} not found: {exc}") from exc
|
raise ValueError(f"Chain {name} not found: {exc}") from exc
|
||||||
except AttributeError as exc:
|
except AttributeError as exc:
|
||||||
|
|
@ -73,11 +69,7 @@ class ChainCreator(LangChainTypeCreator):
|
||||||
def to_list(self) -> List[str]:
|
def to_list(self) -> List[str]:
|
||||||
names = []
|
names = []
|
||||||
for _, chain in self.type_to_loader_dict.items():
|
for _, chain in self.type_to_loader_dict.items():
|
||||||
chain_name = (
|
chain_name = chain.function_name() if hasattr(chain, "function_name") else chain.__name__
|
||||||
chain.function_name()
|
|
||||||
if hasattr(chain, "function_name")
|
|
||||||
else chain.__name__
|
|
||||||
)
|
|
||||||
names.append(chain_name)
|
names.append(chain_name)
|
||||||
return names
|
return names
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,9 +41,7 @@ class BaseCustomConversationChain(ConversationChain):
|
||||||
values["template"] = values["template"].format(**format_dict)
|
values["template"] = values["template"].format(**format_dict)
|
||||||
|
|
||||||
values["template"] = values["template"]
|
values["template"] = values["template"]
|
||||||
values["input_variables"] = extract_input_variables_from_prompt(
|
values["input_variables"] = extract_input_variables_from_prompt(values["template"])
|
||||||
values["template"]
|
|
||||||
)
|
|
||||||
values["prompt"].template = values["template"]
|
values["prompt"].template = values["template"]
|
||||||
values["prompt"].input_variables = values["input_variables"]
|
values["prompt"].input_variables = values["input_variables"]
|
||||||
return values
|
return values
|
||||||
|
|
@ -54,9 +52,7 @@ class SeriesCharacterChain(BaseCustomConversationChain):
|
||||||
|
|
||||||
character: str
|
character: str
|
||||||
series: str
|
series: str
|
||||||
template: Optional[
|
template: Optional[str] = """I want you to act like {character} from {series}.
|
||||||
str
|
|
||||||
] = """I want you to act like {character} from {series}.
|
|
||||||
I want you to respond and answer like {character}. do not write any explanations. only answer like {character}.
|
I want you to respond and answer like {character}. do not write any explanations. only answer like {character}.
|
||||||
You must know all of the knowledge of {character}.
|
You must know all of the knowledge of {character}.
|
||||||
Current conversation:
|
Current conversation:
|
||||||
|
|
@ -71,9 +67,7 @@ Human: {input}
|
||||||
class MidJourneyPromptChain(BaseCustomConversationChain):
|
class MidJourneyPromptChain(BaseCustomConversationChain):
|
||||||
"""MidJourneyPromptChain is a chain you can use to generate new MidJourney prompts."""
|
"""MidJourneyPromptChain is a chain you can use to generate new MidJourney prompts."""
|
||||||
|
|
||||||
template: Optional[
|
template: Optional[str] = """I want you to act as a prompt generator for Midjourney's artificial intelligence program.
|
||||||
str
|
|
||||||
] = """I want you to act as a prompt generator for Midjourney's artificial intelligence program.
|
|
||||||
Your job is to provide detailed and creative descriptions that will inspire unique and interesting images from the AI.
|
Your job is to provide detailed and creative descriptions that will inspire unique and interesting images from the AI.
|
||||||
Keep in mind that the AI is capable of understanding a wide range of language and can interpret abstract concepts, so feel free to be as imaginative and descriptive as possible.
|
Keep in mind that the AI is capable of understanding a wide range of language and can interpret abstract concepts, so feel free to be as imaginative and descriptive as possible.
|
||||||
For example, you could describe a scene from a futuristic city, or a surreal landscape filled with strange creatures.
|
For example, you could describe a scene from a futuristic city, or a surreal landscape filled with strange creatures.
|
||||||
|
|
@ -87,9 +81,7 @@ class MidJourneyPromptChain(BaseCustomConversationChain):
|
||||||
|
|
||||||
|
|
||||||
class TimeTravelGuideChain(BaseCustomConversationChain):
|
class TimeTravelGuideChain(BaseCustomConversationChain):
|
||||||
template: Optional[
|
template: Optional[str] = """I want you to act as my time travel guide. You are helpful and creative. I will provide you with the historical period or future time I want to visit and you will suggest the best events, sights, or people to experience. Provide the suggestions and any necessary information.
|
||||||
str
|
|
||||||
] = """I want you to act as my time travel guide. You are helpful and creative. I will provide you with the historical period or future time I want to visit and you will suggest the best events, sights, or people to experience. Provide the suggestions and any necessary information.
|
|
||||||
Current conversation:
|
Current conversation:
|
||||||
{history}
|
{history}
|
||||||
Human: {input}
|
Human: {input}
|
||||||
|
|
|
||||||
|
|
@ -127,22 +127,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]]:
|
||||||
|
|
@ -160,17 +152,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]]:
|
||||||
|
|
@ -254,9 +240,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)
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,7 @@ class ComponentFunctionEntrypointNameNullError(HTTPException):
|
||||||
|
|
||||||
class Component(BaseModel):
|
class Component(BaseModel):
|
||||||
ERROR_CODE_NULL = "Python code must be provided."
|
ERROR_CODE_NULL = "Python code must be provided."
|
||||||
ERROR_FUNCTION_ENTRYPOINT_NAME_NULL = (
|
ERROR_FUNCTION_ENTRYPOINT_NAME_NULL = "The name of the entrypoint function must be provided."
|
||||||
"The name of the entrypoint function must be provided."
|
|
||||||
)
|
|
||||||
|
|
||||||
code: Optional[str]
|
code: Optional[str]
|
||||||
function_entrypoint_name = "build"
|
function_entrypoint_name = "build"
|
||||||
|
|
|
||||||
|
|
@ -53,9 +53,9 @@ class CustomComponent(Component, extra=Extra.allow):
|
||||||
reader = DirectoryReader("", False)
|
reader = DirectoryReader("", False)
|
||||||
|
|
||||||
for type_hint in TYPE_HINT_LIST:
|
for type_hint in TYPE_HINT_LIST:
|
||||||
if reader._is_type_hint_used_in_args(
|
if reader._is_type_hint_used_in_args(type_hint, code) and not reader._is_type_hint_imported(
|
||||||
type_hint, code
|
type_hint, code
|
||||||
) and not reader._is_type_hint_imported(type_hint, code):
|
):
|
||||||
error_detail = {
|
error_detail = {
|
||||||
"error": "Type hint Error",
|
"error": "Type hint Error",
|
||||||
"traceback": f"Type hint '{type_hint}' is used but not imported in the code.",
|
"traceback": f"Type hint '{type_hint}' is used but not imported in the code.",
|
||||||
|
|
@ -74,20 +74,14 @@ class CustomComponent(Component, extra=Extra.allow):
|
||||||
return ""
|
return ""
|
||||||
tree = self.get_code_tree(self.code)
|
tree = self.get_code_tree(self.code)
|
||||||
|
|
||||||
component_classes = [
|
component_classes = [cls for cls in tree["classes"] if self.code_class_base_inheritance in cls["bases"]]
|
||||||
cls
|
|
||||||
for cls in 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
|
|
||||||
]
|
]
|
||||||
|
|
||||||
if not build_methods:
|
if not build_methods:
|
||||||
|
|
@ -103,8 +97,7 @@ class CustomComponent(Component, extra=Extra.allow):
|
||||||
detail={
|
detail={
|
||||||
"error": "Type hint Error",
|
"error": "Type hint Error",
|
||||||
"traceback": (
|
"traceback": (
|
||||||
"Prompt type is not supported in the build method."
|
"Prompt type is not supported in the build method." " Try using PromptTemplate instead."
|
||||||
" Try using PromptTemplate instead."
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -119,20 +112,14 @@ class CustomComponent(Component, extra=Extra.allow):
|
||||||
return []
|
return []
|
||||||
tree = self.get_code_tree(self.code)
|
tree = self.get_code_tree(self.code)
|
||||||
|
|
||||||
component_classes = [
|
component_classes = [cls for cls in tree["classes"] if self.code_class_base_inheritance in cls["bases"]]
|
||||||
cls
|
|
||||||
for cls in 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
|
|
||||||
]
|
]
|
||||||
|
|
||||||
if not build_methods:
|
if not build_methods:
|
||||||
|
|
@ -230,11 +217,7 @@ class CustomComponent(Component, extra=Extra.allow):
|
||||||
if flow_id:
|
if flow_id:
|
||||||
flow = session.query(Flow).get(flow_id)
|
flow = session.query(Flow).get(flow_id)
|
||||||
elif flow_name:
|
elif flow_name:
|
||||||
flow = (
|
flow = (session.query(Flow).filter(Flow.name == flow_name).filter(Flow.user_id == self.user_id)).first()
|
||||||
session.query(Flow)
|
|
||||||
.filter(Flow.name == flow_name)
|
|
||||||
.filter(Flow.user_id == self.user_id)
|
|
||||||
).first()
|
|
||||||
else:
|
else:
|
||||||
raise ValueError("Either flow_name or flow_id must be provided")
|
raise ValueError("Either flow_name or flow_id must be provided")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -76,9 +76,7 @@ class DirectoryReader:
|
||||||
for menu in data["menu"]
|
for menu in data["menu"]
|
||||||
]
|
]
|
||||||
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):
|
||||||
|
|
@ -111,9 +109,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 = []
|
||||||
for root, _, files in os.walk(safe_path):
|
for root, _, files in os.walk(safe_path):
|
||||||
|
|
@ -158,9 +154,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
|
||||||
|
|
@ -178,16 +172,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
|
||||||
|
|
@ -208,9 +200,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.",
|
||||||
|
|
@ -226,9 +218,7 @@ 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))
|
||||||
|
|
@ -248,9 +238,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
|
||||||
|
|
||||||
|
|
@ -266,7 +254,5 @@ class DirectoryReader:
|
||||||
logger.debug(f"Component info: {component_info}")
|
logger.debug(f"Component info: {component_info}")
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -46,34 +46,26 @@ toolkit_type_to_cls_dict: dict[str, Any] = {
|
||||||
|
|
||||||
# Memories
|
# Memories
|
||||||
memory_type_to_cls_dict: dict[str, Any] = {
|
memory_type_to_cls_dict: dict[str, Any] = {
|
||||||
memory_name: import_class(f"langchain.memory.{memory_name}")
|
memory_name: import_class(f"langchain.memory.{memory_name}") for memory_name in memory.__all__
|
||||||
for memory_name in memory.__all__
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Wrappers
|
# Wrappers
|
||||||
wrapper_type_to_cls_dict: dict[str, Any] = {
|
wrapper_type_to_cls_dict: dict[str, Any] = {wrapper.__name__: wrapper for wrapper in [requests.RequestsWrapper]}
|
||||||
wrapper.__name__: wrapper for wrapper in [requests.RequestsWrapper]
|
|
||||||
}
|
|
||||||
|
|
||||||
# Embeddings
|
# Embeddings
|
||||||
embedding_type_to_cls_dict: dict[str, Any] = {
|
embedding_type_to_cls_dict: dict[str, Any] = {
|
||||||
embedding_name: import_class(f"langchain.embeddings.{embedding_name}")
|
embedding_name: import_class(f"langchain.embeddings.{embedding_name}") for embedding_name in embeddings.__all__
|
||||||
for embedding_name in embeddings.__all__
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# Document Loaders
|
# Document Loaders
|
||||||
documentloaders_type_to_cls_dict: dict[str, Any] = {
|
documentloaders_type_to_cls_dict: dict[str, Any] = {
|
||||||
documentloader_name: import_class(
|
documentloader_name: import_class(f"langchain.document_loaders.{documentloader_name}")
|
||||||
f"langchain.document_loaders.{documentloader_name}"
|
|
||||||
)
|
|
||||||
for documentloader_name in document_loaders.__all__
|
for documentloader_name in document_loaders.__all__
|
||||||
}
|
}
|
||||||
|
|
||||||
# Text Splitters
|
# Text Splitters
|
||||||
textsplitter_type_to_cls_dict: dict[str, Any] = dict(
|
textsplitter_type_to_cls_dict: dict[str, Any] = dict(inspect.getmembers(text_splitter, inspect.isclass))
|
||||||
inspect.getmembers(text_splitter, inspect.isclass)
|
|
||||||
)
|
|
||||||
|
|
||||||
# merge CUSTOM_AGENTS and CUSTOM_CHAINS
|
# merge CUSTOM_AGENTS and CUSTOM_CHAINS
|
||||||
CUSTOM_NODES = {**CUSTOM_AGENTS, **CUSTOM_CHAINS} # type: ignore
|
CUSTOM_NODES = {**CUSTOM_AGENTS, **CUSTOM_CHAINS} # type: ignore
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,7 @@ class DocumentLoaderCreator(LangChainTypeCreator):
|
||||||
return [
|
return [
|
||||||
documentloader.__name__
|
documentloader.__name__
|
||||||
for documentloader in self.type_to_loader_dict.values()
|
for documentloader in self.type_to_loader_dict.values()
|
||||||
if documentloader.__name__ in settings_service.settings.DOCUMENTLOADERS
|
if documentloader.__name__ in settings_service.settings.DOCUMENTLOADERS or settings_service.settings.DEV
|
||||||
or settings_service.settings.DEV
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,7 @@ class EmbeddingCreator(LangChainTypeCreator):
|
||||||
return [
|
return [
|
||||||
embedding.__name__
|
embedding.__name__
|
||||||
for embedding in self.type_to_loader_dict.values()
|
for embedding in self.type_to_loader_dict.values()
|
||||||
if embedding.__name__ in settings_service.settings.EMBEDDINGS
|
if embedding.__name__ in settings_service.settings.EMBEDDINGS or settings_service.settings.DEV
|
||||||
or settings_service.settings.DEV
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -104,10 +104,7 @@ def import_prompt(prompt: str) -> Type[PromptTemplate]:
|
||||||
|
|
||||||
def import_wrapper(wrapper: str) -> Any:
|
def import_wrapper(wrapper: str) -> Any:
|
||||||
"""Import wrapper from wrapper name"""
|
"""Import wrapper from wrapper name"""
|
||||||
if (
|
if isinstance(wrapper_creator.type_dict, dict) and wrapper in wrapper_creator.type_dict:
|
||||||
isinstance(wrapper_creator.type_dict, dict)
|
|
||||||
and wrapper in wrapper_creator.type_dict
|
|
||||||
):
|
|
||||||
return wrapper_creator.type_dict.get(wrapper)
|
return wrapper_creator.type_dict.get(wrapper)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@ def initialize_vertexai(class_object, params):
|
||||||
if credentials_path := params.get("credentials"):
|
if credentials_path := params.get("credentials"):
|
||||||
from google.oauth2 import service_account # type: ignore
|
from google.oauth2 import service_account # type: ignore
|
||||||
|
|
||||||
credentials_object = service_account.Credentials.from_service_account_file(
|
credentials_object = service_account.Credentials.from_service_account_file(filename=credentials_path)
|
||||||
filename=credentials_path
|
|
||||||
)
|
|
||||||
params["credentials"] = credentials_object
|
params["credentials"] = credentials_object
|
||||||
return class_object(**params)
|
return class_object(**params)
|
||||||
|
|
|
||||||
|
|
@ -44,15 +44,10 @@ def build_vertex_in_params(params: Dict) -> Dict:
|
||||||
from langflow.graph.vertex.base import Vertex
|
from langflow.graph.vertex.base import Vertex
|
||||||
|
|
||||||
# If any of the values in params is a Vertex, we will build it
|
# If any of the values in params is a Vertex, we will build it
|
||||||
return {
|
return {key: value.build() if isinstance(value, Vertex) else value for key, value in params.items()}
|
||||||
key: value.build() if isinstance(value, Vertex) else value
|
|
||||||
for key, value in params.items()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def instantiate_class(
|
def instantiate_class(node_type: str, base_type: str, params: Dict, user_id=None) -> Any:
|
||||||
node_type: str, base_type: str, params: Dict, user_id=None
|
|
||||||
) -> Any:
|
|
||||||
"""Instantiate class from module type and key, and params"""
|
"""Instantiate class from module type and key, and params"""
|
||||||
params = convert_params_to_sets(params)
|
params = convert_params_to_sets(params)
|
||||||
params = convert_kwargs(params)
|
params = convert_kwargs(params)
|
||||||
|
|
@ -64,9 +59,7 @@ def instantiate_class(
|
||||||
return custom_node(**params)
|
return custom_node(**params)
|
||||||
logger.debug(f"Instantiating {node_type} of type {base_type}")
|
logger.debug(f"Instantiating {node_type} of type {base_type}")
|
||||||
class_object = import_by_type(_type=base_type, name=node_type)
|
class_object = import_by_type(_type=base_type, name=node_type)
|
||||||
return instantiate_based_on_type(
|
return instantiate_based_on_type(class_object, base_type, node_type, params, user_id=user_id)
|
||||||
class_object, base_type, node_type, params, user_id=user_id
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def convert_params_to_sets(params):
|
def convert_params_to_sets(params):
|
||||||
|
|
@ -194,9 +187,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."
|
||||||
|
|
@ -235,9 +226,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)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -290,11 +279,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.__fields__}
|
||||||
key: value
|
|
||||||
for key, value in params.items()
|
|
||||||
if key in class_object.__fields__
|
|
||||||
}
|
|
||||||
return class_object(**params)
|
return class_object(**params)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -304,9 +289,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:
|
||||||
|
|
@ -321,9 +304,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
|
||||||
|
|
@ -332,17 +313,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):
|
||||||
|
|
@ -375,16 +352,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
|
||||||
|
|
@ -411,8 +384,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
|
||||||
|
|
@ -426,9 +398,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
|
||||||
|
|
@ -456,10 +426,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)
|
||||||
|
|
|
||||||
|
|
@ -30,9 +30,7 @@ def check_tools_in_params(params: Dict):
|
||||||
|
|
||||||
|
|
||||||
def instantiate_from_template(class_object, params: Dict):
|
def instantiate_from_template(class_object, params: Dict):
|
||||||
from_template_params = {
|
from_template_params = {"template": params.pop("prompt", params.pop("template", ""))}
|
||||||
"template": params.pop("prompt", params.pop("template", ""))
|
|
||||||
}
|
|
||||||
if not from_template_params.get("template"):
|
if not from_template_params.get("template"):
|
||||||
raise ValueError("Prompt template is required")
|
raise ValueError("Prompt template is required")
|
||||||
return class_object.from_template(**from_template_params)
|
return class_object.from_template(**from_template_params)
|
||||||
|
|
@ -48,9 +46,7 @@ def handle_format_kwargs(prompt, params: Dict):
|
||||||
|
|
||||||
def handle_partial_variables(prompt, format_kwargs: Dict):
|
def handle_partial_variables(prompt, format_kwargs: Dict):
|
||||||
partial_variables = format_kwargs.copy()
|
partial_variables = format_kwargs.copy()
|
||||||
partial_variables = {
|
partial_variables = {key: value for key, value in partial_variables.items() if value}
|
||||||
key: value for key, value in partial_variables.items() if value
|
|
||||||
}
|
|
||||||
# Remove handle_keys otherwise LangChain raises an error
|
# Remove handle_keys otherwise LangChain raises an error
|
||||||
partial_variables.pop("handle_keys", None)
|
partial_variables.pop("handle_keys", None)
|
||||||
if partial_variables and hasattr(prompt, "partial"):
|
if partial_variables and hasattr(prompt, "partial"):
|
||||||
|
|
@ -62,9 +58,7 @@ def handle_variable(params: Dict, input_variable: str, format_kwargs: Dict):
|
||||||
variable = params[input_variable]
|
variable = params[input_variable]
|
||||||
if isinstance(variable, str):
|
if isinstance(variable, str):
|
||||||
format_kwargs[input_variable] = variable
|
format_kwargs[input_variable] = variable
|
||||||
elif isinstance(variable, BaseOutputParser) and hasattr(
|
elif isinstance(variable, BaseOutputParser) and hasattr(variable, "get_format_instructions"):
|
||||||
variable, "get_format_instructions"
|
|
||||||
):
|
|
||||||
format_kwargs[input_variable] = variable.get_format_instructions()
|
format_kwargs[input_variable] = variable.get_format_instructions()
|
||||||
elif is_instance_of_list_or_document(variable):
|
elif is_instance_of_list_or_document(variable):
|
||||||
format_kwargs = format_document(variable, input_variable, format_kwargs)
|
format_kwargs = format_document(variable, input_variable, format_kwargs)
|
||||||
|
|
@ -107,8 +101,7 @@ def try_to_load_json(content):
|
||||||
|
|
||||||
def needs_handle_keys(variable):
|
def needs_handle_keys(variable):
|
||||||
return is_instance_of_list_or_document(variable) or (
|
return is_instance_of_list_or_document(variable) or (
|
||||||
isinstance(variable, BaseOutputParser)
|
isinstance(variable, BaseOutputParser) and hasattr(variable, "get_format_instructions")
|
||||||
and hasattr(variable, "get_format_instructions")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,7 @@ import orjson
|
||||||
def docs_in_params(params: dict) -> bool:
|
def docs_in_params(params: dict) -> bool:
|
||||||
"""Check if params has documents OR texts and one of them is not an empty list,
|
"""Check if params has documents OR texts and one of them is not an empty list,
|
||||||
If any of them is not an empty list, return True, else return False"""
|
If any of them is not an empty list, return True, else return False"""
|
||||||
return ("documents" in params and params["documents"]) or (
|
return ("documents" in params and params["documents"]) or ("texts" in params and params["texts"])
|
||||||
"texts" in params and params["texts"]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def initialize_mongodb(class_object: Type[MongoDBAtlasVectorSearch], params: dict):
|
def initialize_mongodb(class_object: Type[MongoDBAtlasVectorSearch], params: dict):
|
||||||
|
|
@ -31,9 +29,7 @@ def initialize_mongodb(class_object: Type[MongoDBAtlasVectorSearch], params: dic
|
||||||
from pymongo import MongoClient
|
from pymongo import MongoClient
|
||||||
import certifi
|
import certifi
|
||||||
|
|
||||||
client: MongoClient = MongoClient(
|
client: MongoClient = MongoClient(MONGODB_ATLAS_CLUSTER_URI, tlsCAFile=certifi.where())
|
||||||
MONGODB_ATLAS_CLUSTER_URI, tlsCAFile=certifi.where()
|
|
||||||
)
|
|
||||||
db_name = params.pop("db_name", None)
|
db_name = params.pop("db_name", None)
|
||||||
collection_name = params.pop("collection_name", None)
|
collection_name = params.pop("collection_name", None)
|
||||||
if not db_name or not collection_name:
|
if not db_name or not collection_name:
|
||||||
|
|
@ -141,9 +137,7 @@ def initialize_pinecone(class_object: Type[Pinecone], params: dict):
|
||||||
pinecone_env = os.getenv("PINECONE_ENV")
|
pinecone_env = os.getenv("PINECONE_ENV")
|
||||||
|
|
||||||
if pinecone_api_key is None or pinecone_env is None:
|
if pinecone_api_key is None or pinecone_env is None:
|
||||||
raise ValueError(
|
raise ValueError("Pinecone API key and environment must be provided in the params")
|
||||||
"Pinecone API key and environment must be provided in the params"
|
|
||||||
)
|
|
||||||
|
|
||||||
# initialize pinecone
|
# initialize pinecone
|
||||||
pinecone.init(
|
pinecone.init(
|
||||||
|
|
@ -177,19 +171,13 @@ def initialize_chroma(class_object: Type[Chroma], params: dict):
|
||||||
import chromadb # type: ignore
|
import chromadb # type: ignore
|
||||||
|
|
||||||
settings_params = {
|
settings_params = {
|
||||||
key: params[key]
|
key: params[key] for key, value_ in params.items() if key.startswith("chroma_server_") and value_
|
||||||
for key, value_ in params.items()
|
|
||||||
if key.startswith("chroma_server_") and value_
|
|
||||||
}
|
}
|
||||||
chroma_settings = chromadb.config.Settings(**settings_params)
|
chroma_settings = chromadb.config.Settings(**settings_params)
|
||||||
params["client_settings"] = chroma_settings
|
params["client_settings"] = chroma_settings
|
||||||
else:
|
else:
|
||||||
# remove all chroma_server_ keys from params
|
# remove all chroma_server_ keys from params
|
||||||
params = {
|
params = {key: value for key, value in params.items() if not key.startswith("chroma_server_")}
|
||||||
key: value
|
|
||||||
for key, value in params.items()
|
|
||||||
if not key.startswith("chroma_server_")
|
|
||||||
}
|
|
||||||
|
|
||||||
persist = params.pop("persist", False)
|
persist = params.pop("persist", False)
|
||||||
if not docs_in_params(params):
|
if not docs_in_params(params):
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,7 @@ class LLMCreator(LangChainTypeCreator):
|
||||||
return [
|
return [
|
||||||
llm.__name__
|
llm.__name__
|
||||||
for llm in self.type_to_loader_dict.values()
|
for llm in self.type_to_loader_dict.values()
|
||||||
if llm.__name__ in settings_service.settings.LLMS
|
if llm.__name__ in settings_service.settings.LLMS or settings_service.settings.DEV
|
||||||
or settings_service.settings.DEV
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,8 +53,7 @@ class MemoryCreator(LangChainTypeCreator):
|
||||||
return [
|
return [
|
||||||
memory.__name__
|
memory.__name__
|
||||||
for memory in self.type_to_loader_dict.values()
|
for memory in self.type_to_loader_dict.values()
|
||||||
if memory.__name__ in settings_service.settings.MEMORIES
|
if memory.__name__ in settings_service.settings.MEMORIES or settings_service.settings.DEV
|
||||||
or settings_service.settings.DEV
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,17 +26,14 @@ class OutputParserCreator(LangChainTypeCreator):
|
||||||
if self.type_dict is None:
|
if self.type_dict is None:
|
||||||
settings_service = get_settings_service()
|
settings_service = get_settings_service()
|
||||||
self.type_dict = {
|
self.type_dict = {
|
||||||
output_parser_name: import_class(
|
output_parser_name: import_class(f"langchain.output_parsers.{output_parser_name}")
|
||||||
f"langchain.output_parsers.{output_parser_name}"
|
|
||||||
)
|
|
||||||
# if output_parser_name is not lower case it is a class
|
# if output_parser_name is not lower case it is a class
|
||||||
for output_parser_name in output_parsers.__all__
|
for output_parser_name in output_parsers.__all__
|
||||||
}
|
}
|
||||||
self.type_dict = {
|
self.type_dict = {
|
||||||
name: output_parser
|
name: output_parser
|
||||||
for name, output_parser in self.type_dict.items()
|
for name, output_parser in self.type_dict.items()
|
||||||
if name in settings_service.settings.OUTPUT_PARSERS
|
if name in settings_service.settings.OUTPUT_PARSERS or settings_service.settings.DEV
|
||||||
or settings_service.settings.DEV
|
|
||||||
}
|
}
|
||||||
return self.type_dict
|
return self.type_dict
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,7 @@ class PromptCreator(LangChainTypeCreator):
|
||||||
self.type_dict = {
|
self.type_dict = {
|
||||||
name: prompt
|
name: prompt
|
||||||
for name, prompt in self.type_dict.items()
|
for name, prompt in self.type_dict.items()
|
||||||
if name in settings_service.settings.PROMPTS
|
if name in settings_service.settings.PROMPTS or settings_service.settings.DEV
|
||||||
or settings_service.settings.DEV
|
|
||||||
}
|
}
|
||||||
return self.type_dict
|
return self.type_dict
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,17 +42,13 @@ class BaseCustomPrompt(PromptTemplate):
|
||||||
values["template"] = values["template"].format(**format_dict)
|
values["template"] = values["template"].format(**format_dict)
|
||||||
|
|
||||||
values["template"] = values["template"]
|
values["template"] = values["template"]
|
||||||
values["input_variables"] = extract_input_variables_from_prompt(
|
values["input_variables"] = extract_input_variables_from_prompt(values["template"])
|
||||||
values["template"]
|
|
||||||
)
|
|
||||||
return values
|
return values
|
||||||
|
|
||||||
|
|
||||||
class SeriesCharacterPrompt(BaseCustomPrompt):
|
class SeriesCharacterPrompt(BaseCustomPrompt):
|
||||||
# Add a very descriptive description for the prompt generator
|
# Add a very descriptive description for the prompt generator
|
||||||
description: Optional[
|
description: Optional[str] = "A prompt that asks the AI to act like a character from a series."
|
||||||
str
|
|
||||||
] = "A prompt that asks the AI to act like a character from a series."
|
|
||||||
character: str
|
character: str
|
||||||
series: str
|
series: str
|
||||||
template: str = """I want you to act like {character} from {series}.
|
template: str = """I want you to act like {character} from {series}.
|
||||||
|
|
@ -68,6 +64,4 @@ Human: {input}
|
||||||
input_variables: List[str] = ["character", "series"]
|
input_variables: List[str] = ["character", "series"]
|
||||||
|
|
||||||
|
|
||||||
CUSTOM_PROMPTS: Dict[str, Type[BaseCustomPrompt]] = {
|
CUSTOM_PROMPTS: Dict[str, Type[BaseCustomPrompt]] = {"SeriesCharacterPrompt": SeriesCharacterPrompt}
|
||||||
"SeriesCharacterPrompt": SeriesCharacterPrompt
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -39,9 +39,7 @@ class RetrieverCreator(LangChainTypeCreator):
|
||||||
method_name=self.from_method_nodes[name],
|
method_name=self.from_method_nodes[name],
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return build_template_from_class(
|
return build_template_from_class(name, type_to_cls_dict=self.type_to_loader_dict)
|
||||||
name, type_to_cls_dict=self.type_to_loader_dict
|
|
||||||
)
|
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise ValueError(f"Retriever {name} not found") from exc
|
raise ValueError(f"Retriever {name} not found") from exc
|
||||||
except AttributeError as exc:
|
except AttributeError as exc:
|
||||||
|
|
@ -53,8 +51,7 @@ class RetrieverCreator(LangChainTypeCreator):
|
||||||
return [
|
return [
|
||||||
retriever
|
retriever
|
||||||
for retriever in self.type_to_loader_dict.keys()
|
for retriever in self.type_to_loader_dict.keys()
|
||||||
if retriever in settings_service.settings.RETRIEVERS
|
if retriever in settings_service.settings.RETRIEVERS or settings_service.settings.DEV
|
||||||
or settings_service.settings.DEV
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,7 @@ class TextSplitterCreator(LangChainTypeCreator):
|
||||||
return [
|
return [
|
||||||
textsplitter.__name__
|
textsplitter.__name__
|
||||||
for textsplitter in self.type_to_loader_dict.values()
|
for textsplitter in self.type_to_loader_dict.values()
|
||||||
if textsplitter.__name__ in settings_service.settings.TEXTSPLITTERS
|
if textsplitter.__name__ in settings_service.settings.TEXTSPLITTERS or settings_service.settings.DEV
|
||||||
or settings_service.settings.DEV
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,13 +32,10 @@ class ToolkitCreator(LangChainTypeCreator):
|
||||||
if self.type_dict is None:
|
if self.type_dict is None:
|
||||||
settings_service = get_settings_service()
|
settings_service = get_settings_service()
|
||||||
self.type_dict = {
|
self.type_dict = {
|
||||||
toolkit_name: import_class(
|
toolkit_name: import_class(f"langchain.agents.agent_toolkits.{toolkit_name}")
|
||||||
f"langchain.agents.agent_toolkits.{toolkit_name}"
|
|
||||||
)
|
|
||||||
# if toolkit_name is not lower case it is a class
|
# if toolkit_name is not lower case it is a class
|
||||||
for toolkit_name in agent_toolkits.__all__
|
for toolkit_name in agent_toolkits.__all__
|
||||||
if not toolkit_name.islower()
|
if not toolkit_name.islower() and toolkit_name in settings_service.settings.TOOLKITS
|
||||||
and toolkit_name in settings_service.settings.TOOLKITS
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.type_dict
|
return self.type_dict
|
||||||
|
|
@ -61,9 +58,7 @@ class ToolkitCreator(LangChainTypeCreator):
|
||||||
|
|
||||||
def get_create_function(self, name: str) -> Callable:
|
def get_create_function(self, name: str) -> Callable:
|
||||||
if loader_name := self.create_functions.get(name):
|
if loader_name := self.create_functions.get(name):
|
||||||
return import_module(
|
return import_module(f"from langchain.agents.agent_toolkits import {loader_name[0]}")
|
||||||
f"from langchain.agents.agent_toolkits import {loader_name[0]}"
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
raise ValueError("Toolkit not found")
|
raise ValueError("Toolkit not found")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,7 @@ TOOL_INPUTS = {
|
||||||
placeholder="",
|
placeholder="",
|
||||||
value="",
|
value="",
|
||||||
),
|
),
|
||||||
"llm": TemplateField(
|
"llm": TemplateField(field_type="BaseLanguageModel", required=True, is_list=False, show=True),
|
||||||
field_type="BaseLanguageModel", required=True, is_list=False, show=True
|
|
||||||
),
|
|
||||||
"func": TemplateField(
|
"func": TemplateField(
|
||||||
field_type="function",
|
field_type="function",
|
||||||
required=True,
|
required=True,
|
||||||
|
|
@ -76,10 +74,7 @@ class ToolCreator(LangChainTypeCreator):
|
||||||
|
|
||||||
tool_name = tool_params.get("name") or tool
|
tool_name = tool_params.get("name") or tool
|
||||||
|
|
||||||
if (
|
if tool_name in settings_service.settings.TOOLS or settings_service.settings.DEV:
|
||||||
tool_name in settings_service.settings.TOOLS
|
|
||||||
or settings_service.settings.DEV
|
|
||||||
):
|
|
||||||
if tool_name == "JsonSpec":
|
if tool_name == "JsonSpec":
|
||||||
tool_params["path"] = tool_params.pop("dict_") # type: ignore
|
tool_params["path"] = tool_params.pop("dict_") # type: ignore
|
||||||
all_tools[tool_name] = {
|
all_tools[tool_name] = {
|
||||||
|
|
|
||||||
|
|
@ -21,16 +21,12 @@ def get_func_tool_params(func, **kwargs) -> Union[Dict, None]:
|
||||||
for keyword in tool.keywords:
|
for keyword in tool.keywords:
|
||||||
if keyword.arg == "name":
|
if keyword.arg == "name":
|
||||||
try:
|
try:
|
||||||
tool_params["name"] = ast.literal_eval(
|
tool_params["name"] = ast.literal_eval(keyword.value)
|
||||||
keyword.value
|
|
||||||
)
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
break
|
break
|
||||||
elif keyword.arg == "description":
|
elif keyword.arg == "description":
|
||||||
try:
|
try:
|
||||||
tool_params["description"] = ast.literal_eval(
|
tool_params["description"] = ast.literal_eval(keyword.value)
|
||||||
keyword.value
|
|
||||||
)
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
@ -43,9 +39,7 @@ def get_func_tool_params(func, **kwargs) -> Union[Dict, None]:
|
||||||
else:
|
else:
|
||||||
# get the class object from the return statement
|
# get the class object from the return statement
|
||||||
try:
|
try:
|
||||||
class_obj = eval(
|
class_obj = eval(compile(ast.Expression(tool), "<string>", "eval"))
|
||||||
compile(ast.Expression(tool), "<string>", "eval")
|
|
||||||
)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -114,14 +114,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."
|
|
||||||
)
|
|
||||||
field_config.pop("name", None)
|
field_config.pop("name", None)
|
||||||
|
|
||||||
required = field_config.pop("required", field_required)
|
required = field_config.pop("required", field_required)
|
||||||
|
|
@ -185,9 +181,7 @@ def extract_type_from_optional(field_type):
|
||||||
def build_frontend_node(custom_component: CustomComponent):
|
def build_frontend_node(custom_component: CustomComponent):
|
||||||
"""Build a frontend node for a custom component"""
|
"""Build a frontend node for a custom component"""
|
||||||
try:
|
try:
|
||||||
return (
|
return CustomComponentFrontendNode().to_dict().get(type(custom_component).__name__)
|
||||||
CustomComponentFrontendNode().to_dict().get(type(custom_component).__name__)
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error(f"Error while building base frontend node: {exc}")
|
logger.error(f"Error while building base frontend node: {exc}")
|
||||||
|
|
@ -236,9 +230,7 @@ def add_extra_fields(frontend_node, field_config, function_args):
|
||||||
if "name" not in extra_field or extra_field["name"] == "self":
|
if "name" not in extra_field or extra_field["name"] == "self":
|
||||||
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.get(field_name, {})
|
config = field_config.get(field_name, {})
|
||||||
frontend_node = add_new_custom_field(
|
frontend_node = add_new_custom_field(
|
||||||
frontend_node,
|
frontend_node,
|
||||||
|
|
@ -273,8 +265,7 @@ def add_base_classes(frontend_node, return_types: List[str]):
|
||||||
status_code=400,
|
status_code=400,
|
||||||
detail={
|
detail={
|
||||||
"error": (
|
"error": (
|
||||||
"Invalid return type should be one of: "
|
"Invalid return type should be one of: " f"{list(CUSTOM_COMPONENT_SUPPORTED_TYPES.keys())}"
|
||||||
f"{list(CUSTOM_COMPONENT_SUPPORTED_TYPES.keys())}"
|
|
||||||
),
|
),
|
||||||
"traceback": traceback.format_exc(),
|
"traceback": traceback.format_exc(),
|
||||||
},
|
},
|
||||||
|
|
@ -296,8 +287,7 @@ def add_output_types(frontend_node, return_types: List[str]):
|
||||||
status_code=400,
|
status_code=400,
|
||||||
detail={
|
detail={
|
||||||
"error": (
|
"error": (
|
||||||
"Invalid return type should be one of: "
|
"Invalid return type should be one of: " f"{list(CUSTOM_COMPONENT_SUPPORTED_TYPES.keys())}"
|
||||||
f"{list(CUSTOM_COMPONENT_SUPPORTED_TYPES.keys())}"
|
|
||||||
),
|
),
|
||||||
"traceback": traceback.format_exc(),
|
"traceback": traceback.format_exc(),
|
||||||
},
|
},
|
||||||
|
|
@ -325,16 +315,10 @@ def build_langchain_template_custom_component(custom_component: CustomComponent)
|
||||||
|
|
||||||
add_extra_fields(frontend_node, field_config, entrypoint_args)
|
add_extra_fields(frontend_node, field_config, entrypoint_args)
|
||||||
logger.debug("Added extra fields")
|
logger.debug("Added extra fields")
|
||||||
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", {})
|
|
||||||
)
|
|
||||||
logger.debug("Added code field")
|
logger.debug("Added code field")
|
||||||
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
|
|
||||||
)
|
|
||||||
logger.debug("Added base classes")
|
logger.debug("Added base classes")
|
||||||
return frontend_node
|
return frontend_node
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
|
@ -343,9 +327,7 @@ def build_langchain_template_custom_component(custom_component: CustomComponent)
|
||||||
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
|
||||||
|
|
@ -377,9 +359,7 @@ def build_valid_menu(valid_components):
|
||||||
valid_menu[menu_name] = {}
|
valid_menu[menu_name] = {}
|
||||||
|
|
||||||
for component in menu_item["components"]:
|
for component in menu_item["components"]:
|
||||||
logger.debug(
|
logger.debug(f"Building component: {component.get('name'), component.get('output_types')}")
|
||||||
f"Building component: {component.get('name'), component.get('output_types')}"
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
component_name = component["name"]
|
component_name = component["name"]
|
||||||
component_code = component["code"]
|
component_code = component["code"]
|
||||||
|
|
@ -388,9 +368,7 @@ def build_valid_menu(valid_components):
|
||||||
component_extractor = CustomComponent(code=component_code)
|
component_extractor = CustomComponent(code=component_code)
|
||||||
component_extractor.is_check_valid()
|
component_extractor.is_check_valid()
|
||||||
|
|
||||||
component_template = build_langchain_template_custom_component(
|
component_template = build_langchain_template_custom_component(component_extractor)
|
||||||
component_extractor
|
|
||||||
)
|
|
||||||
component_template["output_types"] = component_output_types
|
component_template["output_types"] = component_output_types
|
||||||
if len(component_output_types) == 1:
|
if len(component_output_types) == 1:
|
||||||
component_name = component_output_types[0]
|
component_name = component_output_types[0]
|
||||||
|
|
@ -398,9 +376,7 @@ def build_valid_menu(valid_components):
|
||||||
file_name = component.get("file").split(".")[0]
|
file_name = component.get("file").split(".")[0]
|
||||||
if "_" in file_name:
|
if "_" in file_name:
|
||||||
# turn .py file into camelcase
|
# turn .py file into camelcase
|
||||||
component_name = "".join(
|
component_name = "".join([word.capitalize() for word in file_name.split("_")])
|
||||||
[word.capitalize() for word in file_name.split("_")]
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
component_name = file_name
|
component_name = file_name
|
||||||
|
|
||||||
|
|
@ -409,9 +385,7 @@ def build_valid_menu(valid_components):
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error(f"Error loading Component: {component['output_types']}")
|
logger.error(f"Error loading Component: {component['output_types']}")
|
||||||
logger.exception(
|
logger.exception(f"Error while building custom component {component_output_types}: {exc}")
|
||||||
f"Error while building custom component {component_output_types}: {exc}"
|
|
||||||
)
|
|
||||||
|
|
||||||
return valid_menu
|
return valid_menu
|
||||||
|
|
||||||
|
|
@ -449,20 +423,14 @@ def build_invalid_menu(invalid_components):
|
||||||
logger.debug(f"Added {component_name} to invalid menu to {menu_name}")
|
logger.debug(f"Added {component_name} to invalid menu to {menu_name}")
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception(
|
logger.exception(f"Error while creating custom component [{component_name}]: {str(exc)}")
|
||||||
f"Error while creating custom component [{component_name}]: {str(exc)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
return invalid_menu
|
return invalid_menu
|
||||||
|
|
||||||
|
|
||||||
def merge_nested_dicts_with_renaming(dict1, dict2):
|
def merge_nested_dicts_with_renaming(dict1, dict2):
|
||||||
for key, value in dict2.items():
|
for key, value in dict2.items():
|
||||||
if (
|
if key in dict1 and isinstance(value, dict) and isinstance(dict1.get(key), dict):
|
||||||
key in dict1
|
|
||||||
and isinstance(value, dict)
|
|
||||||
and isinstance(dict1.get(key), dict)
|
|
||||||
):
|
|
||||||
for sub_key, sub_value in value.items():
|
for sub_key, sub_value in value.items():
|
||||||
if sub_key in dict1[key]:
|
if sub_key in dict1[key]:
|
||||||
new_key = get_new_key(dict1[key], sub_key)
|
new_key = get_new_key(dict1[key], sub_key)
|
||||||
|
|
@ -479,9 +447,7 @@ def build_langchain_custom_component_list_from_path(path: str):
|
||||||
file_list = load_files_from_path(path)
|
file_list = load_files_from_path(path)
|
||||||
reader = DirectoryReader(path, False)
|
reader = DirectoryReader(path, False)
|
||||||
|
|
||||||
valid_components, invalid_components = build_and_validate_all_files(
|
valid_components, invalid_components = build_and_validate_all_files(reader, file_list)
|
||||||
reader, file_list
|
|
||||||
)
|
|
||||||
|
|
||||||
valid_menu = build_valid_menu(valid_components)
|
valid_menu = build_valid_menu(valid_components)
|
||||||
invalid_menu = build_invalid_menu(invalid_components)
|
invalid_menu = build_invalid_menu(invalid_components)
|
||||||
|
|
@ -495,18 +461,14 @@ def get_all_types_dict(settings_service):
|
||||||
# need to merge all the keys into one dict
|
# need to merge all the keys into one dict
|
||||||
custom_components_from_file: dict[str, Any] = {}
|
custom_components_from_file: dict[str, Any] = {}
|
||||||
if settings_service.settings.COMPONENTS_PATH:
|
if settings_service.settings.COMPONENTS_PATH:
|
||||||
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_component_dicts = []
|
custom_component_dicts = []
|
||||||
processed_paths = []
|
processed_paths = []
|
||||||
for path in settings_service.settings.COMPONENTS_PATH:
|
for path in settings_service.settings.COMPONENTS_PATH:
|
||||||
if str(path) in processed_paths:
|
if str(path) in processed_paths:
|
||||||
continue
|
continue
|
||||||
custom_component_dict = build_langchain_custom_component_list_from_path(
|
custom_component_dict = build_langchain_custom_component_list_from_path(str(path))
|
||||||
str(path)
|
|
||||||
)
|
|
||||||
custom_component_dicts.append(custom_component_dict)
|
custom_component_dicts.append(custom_component_dict)
|
||||||
processed_paths.append(str(path))
|
processed_paths.append(str(path))
|
||||||
|
|
||||||
|
|
@ -516,16 +478,12 @@ def get_all_types_dict(settings_service):
|
||||||
if not custom_component_dict:
|
if not custom_component_dict:
|
||||||
continue
|
continue
|
||||||
category = list(custom_component_dict.keys())[0]
|
category = list(custom_component_dict.keys())[0]
|
||||||
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
|
||||||
)
|
)
|
||||||
|
|
||||||
return merge_nested_dicts_with_renaming(
|
return merge_nested_dicts_with_renaming(native_components, custom_components_from_file)
|
||||||
native_components, custom_components_from_file
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def merge_nested_dicts(dict1, dict2):
|
def merge_nested_dicts(dict1, dict2):
|
||||||
|
|
|
||||||
|
|
@ -29,16 +29,14 @@ class UtilityCreator(LangChainTypeCreator):
|
||||||
if self.type_dict is None:
|
if self.type_dict is None:
|
||||||
settings_service = get_settings_service()
|
settings_service = get_settings_service()
|
||||||
self.type_dict = {
|
self.type_dict = {
|
||||||
utility_name: import_class(f"langchain.utilities.{utility_name}")
|
utility_name: import_class(f"langchain.utilities.{utility_name}") for utility_name in utilities.__all__
|
||||||
for utility_name in utilities.__all__
|
|
||||||
}
|
}
|
||||||
self.type_dict["SQLDatabase"] = utilities.SQLDatabase
|
self.type_dict["SQLDatabase"] = utilities.SQLDatabase
|
||||||
# Filter according to settings.utilities
|
# Filter according to settings.utilities
|
||||||
self.type_dict = {
|
self.type_dict = {
|
||||||
name: utility
|
name: utility
|
||||||
for name, utility in self.type_dict.items()
|
for name, utility in self.type_dict.items()
|
||||||
if name in settings_service.settings.UTILITIES
|
if name in settings_service.settings.UTILITIES or settings_service.settings.DEV
|
||||||
or settings_service.settings.DEV
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.type_dict
|
return self.type_dict
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
@ -79,9 +77,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__}")
|
||||||
langchain.llm_cache = cache_class()
|
langchain.llm_cache = cache_class()
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,7 @@ class VectorstoreCreator(LangChainTypeCreator):
|
||||||
def type_to_loader_dict(self) -> Dict:
|
def type_to_loader_dict(self) -> Dict:
|
||||||
if self.type_dict is None:
|
if self.type_dict is None:
|
||||||
self.type_dict: dict[str, Any] = {
|
self.type_dict: dict[str, Any] = {
|
||||||
vectorstore_name: import_class(
|
vectorstore_name: import_class(f"langchain.vectorstores.{vectorstore_name}")
|
||||||
f"langchain.vectorstores.{vectorstore_name}"
|
|
||||||
)
|
|
||||||
for vectorstore_name in vectorstores.__all__
|
for vectorstore_name in vectorstores.__all__
|
||||||
}
|
}
|
||||||
return self.type_dict
|
return self.type_dict
|
||||||
|
|
@ -48,8 +46,7 @@ class VectorstoreCreator(LangChainTypeCreator):
|
||||||
return [
|
return [
|
||||||
vectorstore
|
vectorstore
|
||||||
for vectorstore in self.type_to_loader_dict.keys()
|
for vectorstore in self.type_to_loader_dict.keys()
|
||||||
if vectorstore in settings_service.settings.VECTORSTORES
|
if vectorstore in settings_service.settings.VECTORSTORES or settings_service.settings.DEV
|
||||||
or settings_service.settings.DEV
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,7 @@ class WrapperCreator(LangChainTypeCreator):
|
||||||
def type_to_loader_dict(self) -> Dict:
|
def type_to_loader_dict(self) -> Dict:
|
||||||
if self.type_dict is None:
|
if self.type_dict is None:
|
||||||
self.type_dict = {
|
self.type_dict = {
|
||||||
wrapper.__name__: wrapper
|
wrapper.__name__: wrapper for wrapper in [requests.TextRequestsWrapper, sql_database.SQLDatabase]
|
||||||
for wrapper in [requests.TextRequestsWrapper, sql_database.SQLDatabase]
|
|
||||||
}
|
}
|
||||||
return self.type_dict
|
return self.type_dict
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,16 @@
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from fastapi import FastAPI
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
|
from fastapi import FastAPI, Request
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from fastapi.responses import FileResponse
|
from fastapi.responses import FileResponse
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
|
||||||
from langflow.api import router
|
from langflow.api import router
|
||||||
|
|
||||||
|
|
||||||
from langflow.interface.utils import setup_llm_caching
|
from langflow.interface.utils import setup_llm_caching
|
||||||
from langflow.services.utils import initialize_services
|
|
||||||
from langflow.services.plugins.langfuse import LangfuseInstance
|
from langflow.services.plugins.langfuse import LangfuseInstance
|
||||||
from langflow.services.utils import (
|
from langflow.services.utils import initialize_services, teardown_services
|
||||||
teardown_services,
|
|
||||||
)
|
|
||||||
from langflow.utils.logger import configure
|
from langflow.utils.logger import configure
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -23,7 +20,6 @@ def create_app():
|
||||||
configure()
|
configure()
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
origins = ["*"]
|
origins = ["*"]
|
||||||
|
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
|
|
@ -34,6 +30,16 @@ def create_app():
|
||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@app.middleware("http")
|
||||||
|
async def flatten_query_string_lists(request: Request, call_next):
|
||||||
|
flattened = []
|
||||||
|
for key, value in request.query_params.multi_items():
|
||||||
|
flattened.extend((key, entry) for entry in value.split(","))
|
||||||
|
|
||||||
|
request.scope["query_string"] = urlencode(flattened, doseq=True).encode("utf-8")
|
||||||
|
|
||||||
|
return await call_next(request)
|
||||||
|
|
||||||
@app.get("/health")
|
@app.get("/health")
|
||||||
def health():
|
def health():
|
||||||
return {"status": "ok"}
|
return {"status": "ok"}
|
||||||
|
|
@ -78,9 +84,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:
|
||||||
|
|
@ -96,6 +100,7 @@ def setup_app(
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
|
||||||
from langflow.__main__ import get_number_of_workers
|
from langflow.__main__ import get_number_of_workers
|
||||||
|
|
||||||
configure()
|
configure()
|
||||||
|
|
|
||||||
|
|
@ -34,9 +34,7 @@ def get_langfuse_callback(trace_id):
|
||||||
if langfuse := LangfuseInstance.get():
|
if langfuse := LangfuseInstance.get():
|
||||||
logger.debug("Langfuse credentials found")
|
logger.debug("Langfuse credentials found")
|
||||||
try:
|
try:
|
||||||
trace = langfuse.trace(
|
trace = langfuse.trace(CreateTrace(name="langflow-" + trace_id, id=trace_id))
|
||||||
CreateTrace(name="langflow-" + trace_id, id=trace_id)
|
|
||||||
)
|
|
||||||
return trace.getNewHandler()
|
return trace.getNewHandler()
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error(f"Error initializing langfuse callback: {exc}")
|
logger.error(f"Error initializing langfuse callback: {exc}")
|
||||||
|
|
@ -44,9 +42,7 @@ def get_langfuse_callback(trace_id):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def flush_langfuse_callback_if_present(
|
def flush_langfuse_callback_if_present(callbacks: List[Union[BaseCallbackHandler, "CallbackHandler"]]):
|
||||||
callbacks: List[Union[BaseCallbackHandler, "CallbackHandler"]]
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
If langfuse callback is present, run callback.langfuse.flush()
|
If langfuse callback is present, run callback.langfuse.flush()
|
||||||
"""
|
"""
|
||||||
|
|
@ -88,15 +84,9 @@ async def get_result_and_steps(langchain_object, inputs: Union[dict, str], **kwa
|
||||||
# if langfuse callback is present, run callback.langfuse.flush()
|
# if langfuse callback is present, run callback.langfuse.flush()
|
||||||
flush_langfuse_callback_if_present(callbacks)
|
flush_langfuse_callback_if_present(callbacks)
|
||||||
|
|
||||||
intermediate_steps = (
|
intermediate_steps = output.get("intermediate_steps", []) if isinstance(output, dict) else []
|
||||||
output.get("intermediate_steps", []) if isinstance(output, dict) else []
|
|
||||||
)
|
|
||||||
|
|
||||||
result = (
|
result = output.get(langchain_object.output_keys[0]) if isinstance(output, dict) else output
|
||||||
output.get(langchain_object.output_keys[0])
|
|
||||||
if isinstance(output, dict)
|
|
||||||
else output
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
thought = format_actions(intermediate_steps) if intermediate_steps else ""
|
thought = format_actions(intermediate_steps) if intermediate_steps else ""
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
|
|
||||||
|
|
@ -112,9 +112,7 @@ def load_langchain_object(
|
||||||
logger.debug("Loaded LangChain object")
|
logger.debug("Loaded LangChain object")
|
||||||
|
|
||||||
if langchain_object is None:
|
if langchain_object is None:
|
||||||
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."
|
|
||||||
)
|
|
||||||
|
|
||||||
return langchain_object, artifacts, session_id
|
return langchain_object, artifacts, session_id
|
||||||
|
|
||||||
|
|
@ -164,9 +162,7 @@ async def process_graph_cached(
|
||||||
if clear_cache:
|
if clear_cache:
|
||||||
session_service.clear_session(session_id)
|
session_service.clear_session(session_id)
|
||||||
if session_id is None:
|
if session_id is None:
|
||||||
session_id = session_service.generate_key(
|
session_id = session_service.generate_key(session_id=session_id, data_graph=data_graph)
|
||||||
session_id=session_id, data_graph=data_graph
|
|
||||||
)
|
|
||||||
# Load the graph using SessionService
|
# Load the graph using SessionService
|
||||||
graph, artifacts = session_service.load_session(session_id, data_graph)
|
graph, artifacts = session_service.load_session(session_id, data_graph)
|
||||||
built_object = graph.build()
|
built_object = graph.build()
|
||||||
|
|
@ -179,9 +175,7 @@ async def process_graph_cached(
|
||||||
return Result(result=result, session_id=session_id)
|
return Result(result=result, session_id=session_id)
|
||||||
|
|
||||||
|
|
||||||
def load_flow_from_json(
|
def load_flow_from_json(flow: Union[Path, str, dict], tweaks: Optional[dict] = None, build=True):
|
||||||
flow: Union[Path, str, dict], tweaks: Optional[dict] = None, build=True
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Load flow from a JSON file or a JSON object.
|
Load flow from a JSON file or a JSON object.
|
||||||
|
|
||||||
|
|
@ -198,9 +192,7 @@ def load_flow_from_json(
|
||||||
elif isinstance(flow, dict):
|
elif isinstance(flow, dict):
|
||||||
flow_graph = flow
|
flow_graph = flow
|
||||||
else:
|
else:
|
||||||
raise TypeError(
|
raise TypeError("Input must be either a file path (str) or a JSON object (dict)")
|
||||||
"Input must be either a file path (str) or a JSON object (dict)"
|
|
||||||
)
|
|
||||||
|
|
||||||
graph_data = flow_graph["data"]
|
graph_data = flow_graph["data"]
|
||||||
if tweaks is not None:
|
if tweaks is not None:
|
||||||
|
|
@ -226,18 +218,14 @@ def load_flow_from_json(
|
||||||
return graph
|
return graph
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
|
@ -246,9 +234,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():
|
||||||
|
|
@ -257,9 +243,7 @@ def apply_tweaks(node: Dict[str, Any], node_tweaks: Dict[str, Any]) -> None:
|
||||||
template_data[tweak_name][key] = tweak_value
|
template_data[tweak_name][key] = 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.
|
||||||
|
|
||||||
|
|
@ -280,8 +264,6 @@ def process_tweaks(
|
||||||
if node_tweaks := tweaks.get(node_id):
|
if node_tweaks := tweaks.get(node_id):
|
||||||
apply_tweaks(node, node_tweaks)
|
apply_tweaks(node, node_tweaks)
|
||||||
else:
|
else:
|
||||||
logger.warning(
|
logger.warning("Each node should be a dictionary with an 'id' key of type str")
|
||||||
"Each node should be a dictionary with an 'id' key of type str"
|
|
||||||
)
|
|
||||||
|
|
||||||
return graph_data
|
return graph_data
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,7 @@ class LangflowApplication(BaseApplication):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
def load_config(self):
|
def load_config(self):
|
||||||
config = {
|
config = {key: value for key, value in self.options.items() if key in self.cfg.settings and value is not None}
|
||||||
key: value
|
|
||||||
for key, value in self.options.items()
|
|
||||||
if key in self.cfg.settings and value is not None
|
|
||||||
}
|
|
||||||
for key, value in config.items():
|
for key, value in config.items():
|
||||||
self.cfg.set(key.lower(), value)
|
self.cfg.set(key.lower(), value)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,8 @@ oauth2_login = OAuth2PasswordBearer(tokenUrl="api/v1/login")
|
||||||
|
|
||||||
API_KEY_NAME = "x-api-key"
|
API_KEY_NAME = "x-api-key"
|
||||||
|
|
||||||
api_key_query = APIKeyQuery(
|
api_key_query = APIKeyQuery(name=API_KEY_NAME, scheme_name="API key query", auto_error=False)
|
||||||
name=API_KEY_NAME, scheme_name="API key query", auto_error=False
|
api_key_header = APIKeyHeader(name=API_KEY_NAME, scheme_name="API key header", auto_error=False)
|
||||||
)
|
|
||||||
api_key_header = APIKeyHeader(
|
|
||||||
name=API_KEY_NAME, scheme_name="API key header", auto_error=False
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# Source: https://github.com/mrtolkien/fastapi_simple_security/blob/master/fastapi_simple_security/security_api_key.py
|
# Source: https://github.com/mrtolkien/fastapi_simple_security/blob/master/fastapi_simple_security/security_api_key.py
|
||||||
|
|
@ -118,23 +114,17 @@ def get_current_active_user(current_user: Annotated[User, Depends(get_current_us
|
||||||
return current_user
|
return current_user
|
||||||
|
|
||||||
|
|
||||||
def get_current_active_superuser(
|
def get_current_active_superuser(current_user: Annotated[User, Depends(get_current_user)]) -> User:
|
||||||
current_user: Annotated[User, Depends(get_current_user)]
|
|
||||||
) -> User:
|
|
||||||
if not current_user.is_active:
|
if not current_user.is_active:
|
||||||
raise HTTPException(status_code=401, detail="Inactive user")
|
raise HTTPException(status_code=401, detail="Inactive user")
|
||||||
if not current_user.is_superuser:
|
if not current_user.is_superuser:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=400, detail="The user doesn't have enough privileges")
|
||||||
status_code=400, detail="The user doesn't have enough privileges"
|
|
||||||
)
|
|
||||||
return current_user
|
return current_user
|
||||||
|
|
||||||
|
|
||||||
def verify_password(plain_password, hashed_password):
|
def verify_password(plain_password, hashed_password):
|
||||||
settings_service = get_settings_service()
|
settings_service = get_settings_service()
|
||||||
return settings_service.auth_settings.pwd_context.verify(
|
return settings_service.auth_settings.pwd_context.verify(plain_password, hashed_password)
|
||||||
plain_password, hashed_password
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_password_hash(password):
|
def get_password_hash(password):
|
||||||
|
|
@ -223,22 +213,16 @@ def get_user_id_from_token(token: str) -> UUID:
|
||||||
return UUID(int=0)
|
return UUID(int=0)
|
||||||
|
|
||||||
|
|
||||||
def create_user_tokens(
|
def create_user_tokens(user_id: UUID, db: Session = Depends(get_session), update_last_login: bool = False) -> dict:
|
||||||
user_id: UUID, db: Session = Depends(get_session), update_last_login: bool = False
|
|
||||||
) -> dict:
|
|
||||||
settings_service = get_settings_service()
|
settings_service = get_settings_service()
|
||||||
|
|
||||||
access_token_expires = timedelta(
|
access_token_expires = timedelta(minutes=settings_service.auth_settings.ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||||
minutes=settings_service.auth_settings.ACCESS_TOKEN_EXPIRE_MINUTES
|
|
||||||
)
|
|
||||||
access_token = create_token(
|
access_token = create_token(
|
||||||
data={"sub": str(user_id)},
|
data={"sub": str(user_id)},
|
||||||
expires_delta=access_token_expires,
|
expires_delta=access_token_expires,
|
||||||
)
|
)
|
||||||
|
|
||||||
refresh_token_expires = timedelta(
|
refresh_token_expires = timedelta(minutes=settings_service.auth_settings.REFRESH_TOKEN_EXPIRE_MINUTES)
|
||||||
minutes=settings_service.auth_settings.REFRESH_TOKEN_EXPIRE_MINUTES
|
|
||||||
)
|
|
||||||
refresh_token = create_token(
|
refresh_token = create_token(
|
||||||
data={"sub": str(user_id), "type": "rf"},
|
data={"sub": str(user_id), "type": "rf"},
|
||||||
expires_delta=refresh_token_expires,
|
expires_delta=refresh_token_expires,
|
||||||
|
|
@ -268,9 +252,7 @@ def create_refresh_token(refresh_token: str, db: Session = Depends(get_session))
|
||||||
token_type: str = payload.get("type") # type: ignore
|
token_type: str = payload.get("type") # type: ignore
|
||||||
|
|
||||||
if user_id is None or token_type is None:
|
if user_id is None or token_type is None:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid refresh token")
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid refresh token"
|
|
||||||
)
|
|
||||||
|
|
||||||
return create_user_tokens(user_id, db)
|
return create_user_tokens(user_id, db)
|
||||||
|
|
||||||
|
|
@ -281,9 +263,7 @@ def create_refresh_token(refresh_token: str, db: Session = Depends(get_session))
|
||||||
) from e
|
) from e
|
||||||
|
|
||||||
|
|
||||||
def authenticate_user(
|
def authenticate_user(username: str, password: str, db: Session = Depends(get_session)) -> Optional[User]:
|
||||||
username: str, password: str, db: Session = Depends(get_session)
|
|
||||||
) -> Optional[User]:
|
|
||||||
user = get_user_by_username(db, username)
|
user = get_user_by_username(db, username)
|
||||||
|
|
||||||
if not user:
|
if not user:
|
||||||
|
|
@ -318,9 +298,7 @@ def encrypt_api_key(api_key: str, settings_service=Depends(get_settings_service)
|
||||||
return encrypted_key
|
return encrypted_key
|
||||||
|
|
||||||
|
|
||||||
def decrypt_api_key(
|
def decrypt_api_key(encrypted_api_key: str, settings_service=Depends(get_settings_service)):
|
||||||
encrypted_api_key: str, settings_service=Depends(get_settings_service)
|
|
||||||
):
|
|
||||||
fernet = get_fernet(settings_service)
|
fernet = get_fernet(settings_service)
|
||||||
# Two-way decryption
|
# Two-way decryption
|
||||||
if isinstance(encrypted_api_key, str):
|
if isinstance(encrypted_api_key, str):
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,7 @@ class CacheServiceFactory(ServiceFactory):
|
||||||
if redis_cache.is_connected():
|
if redis_cache.is_connected():
|
||||||
logger.debug("Redis cache is connected")
|
logger.debug("Redis cache is connected")
|
||||||
return redis_cache
|
return redis_cache
|
||||||
logger.warning(
|
logger.warning("Redis cache is not connected, falling back to in-memory cache")
|
||||||
"Redis cache is not connected, falling back to in-memory cache"
|
|
||||||
)
|
|
||||||
return InMemoryCache()
|
return InMemoryCache()
|
||||||
|
|
||||||
elif settings_service.settings.CACHE_TYPE == "memory":
|
elif settings_service.settings.CACHE_TYPE == "memory":
|
||||||
|
|
|
||||||
21
src/backend/langflow/services/cache/service.py
vendored
21
src/backend/langflow/services/cache/service.py
vendored
|
|
@ -68,10 +68,7 @@ class InMemoryCache(BaseCacheService, Service):
|
||||||
Retrieve an item from the cache without acquiring the lock.
|
Retrieve an item from the cache without acquiring the lock.
|
||||||
"""
|
"""
|
||||||
if item := self._cache.get(key):
|
if item := self._cache.get(key):
|
||||||
if (
|
if self.expiration_time is None or time.time() - item["time"] < self.expiration_time:
|
||||||
self.expiration_time is None
|
|
||||||
or time.time() - item["time"] < self.expiration_time
|
|
||||||
):
|
|
||||||
# Move the key to the end to make it recently used
|
# Move the key to the end to make it recently used
|
||||||
self._cache.move_to_end(key)
|
self._cache.move_to_end(key)
|
||||||
# Check if the value is pickled
|
# Check if the value is pickled
|
||||||
|
|
@ -118,11 +115,7 @@ class InMemoryCache(BaseCacheService, Service):
|
||||||
"""
|
"""
|
||||||
with self._lock:
|
with self._lock:
|
||||||
existing_value = self._get_without_lock(key)
|
existing_value = self._get_without_lock(key)
|
||||||
if (
|
if existing_value is not None and isinstance(existing_value, dict) and isinstance(value, dict):
|
||||||
existing_value is not None
|
|
||||||
and isinstance(existing_value, dict)
|
|
||||||
and isinstance(value, dict)
|
|
||||||
):
|
|
||||||
existing_value.update(value)
|
existing_value.update(value)
|
||||||
value = existing_value
|
value = existing_value
|
||||||
|
|
||||||
|
|
@ -276,9 +269,7 @@ class RedisCache(BaseCacheService, Service):
|
||||||
if not result:
|
if not result:
|
||||||
raise ValueError("RedisCache could not set the value.")
|
raise ValueError("RedisCache could not set the value.")
|
||||||
except TypeError as exc:
|
except TypeError as exc:
|
||||||
raise TypeError(
|
raise TypeError("RedisCache only accepts values that can be pickled. ") from exc
|
||||||
"RedisCache only accepts values that can be pickled. "
|
|
||||||
) from exc
|
|
||||||
|
|
||||||
def upsert(self, key, value):
|
def upsert(self, key, value):
|
||||||
"""
|
"""
|
||||||
|
|
@ -290,11 +281,7 @@ class RedisCache(BaseCacheService, Service):
|
||||||
value: The value to insert or update.
|
value: The value to insert or update.
|
||||||
"""
|
"""
|
||||||
existing_value = self.get(key)
|
existing_value = self.get(key)
|
||||||
if (
|
if existing_value is not None and isinstance(existing_value, dict) and isinstance(value, dict):
|
||||||
existing_value is not None
|
|
||||||
and isinstance(existing_value, dict)
|
|
||||||
and isinstance(value, dict)
|
|
||||||
):
|
|
||||||
existing_value.update(value)
|
existing_value.update(value)
|
||||||
value = existing_value
|
value = existing_value
|
||||||
|
|
||||||
|
|
|
||||||
4
src/backend/langflow/services/cache/utils.py
vendored
4
src/backend/langflow/services/cache/utils.py
vendored
|
|
@ -83,9 +83,7 @@ def clear_old_cache_files(max_cache_size: int = 3):
|
||||||
cache_files = list(cache_dir.glob("*.dill"))
|
cache_files = list(cache_dir.glob("*.dill"))
|
||||||
|
|
||||||
if len(cache_files) > max_cache_size:
|
if len(cache_files) > max_cache_size:
|
||||||
cache_files_sorted_by_mtime = sorted(
|
cache_files_sorted_by_mtime = sorted(cache_files, key=lambda x: x.stat().st_mtime, reverse=True)
|
||||||
cache_files, key=lambda x: x.stat().st_mtime, reverse=True
|
|
||||||
)
|
|
||||||
|
|
||||||
for cache_file in cache_files_sorted_by_mtime[max_cache_size:]:
|
for cache_file in cache_files_sorted_by_mtime[max_cache_size:]:
|
||||||
with contextlib.suppress(OSError):
|
with contextlib.suppress(OSError):
|
||||||
|
|
|
||||||
|
|
@ -59,9 +59,7 @@ class ChatService(Service):
|
||||||
"""Send the last chat message to the client."""
|
"""Send the last chat message to the client."""
|
||||||
client_id = self.chat_cache.current_client_id
|
client_id = self.chat_cache.current_client_id
|
||||||
if client_id in self.active_connections:
|
if client_id in self.active_connections:
|
||||||
chat_response = self.chat_history.get_history(
|
chat_response = self.chat_history.get_history(client_id, filter_messages=False)[-1]
|
||||||
client_id, filter_messages=False
|
|
||||||
)[-1]
|
|
||||||
if chat_response.is_bot:
|
if chat_response.is_bot:
|
||||||
# Process FileResponse
|
# Process FileResponse
|
||||||
if isinstance(chat_response, FileResponse):
|
if isinstance(chat_response, FileResponse):
|
||||||
|
|
@ -88,9 +86,7 @@ class ChatService(Service):
|
||||||
data_type=self.last_cached_object_dict["type"],
|
data_type=self.last_cached_object_dict["type"],
|
||||||
)
|
)
|
||||||
|
|
||||||
self.chat_history.add_message(
|
self.chat_history.add_message(self.chat_cache.current_client_id, chat_response)
|
||||||
self.chat_cache.current_client_id, chat_response
|
|
||||||
)
|
|
||||||
|
|
||||||
async def connect(self, client_id: str, websocket: WebSocket):
|
async def connect(self, client_id: str, websocket: WebSocket):
|
||||||
self.active_connections[client_id] = websocket
|
self.active_connections[client_id] = websocket
|
||||||
|
|
@ -121,9 +117,7 @@ class ChatService(Service):
|
||||||
if "after sending" in str(exc):
|
if "after sending" in str(exc):
|
||||||
logger.error(f"Error closing connection: {exc}")
|
logger.error(f"Error closing connection: {exc}")
|
||||||
|
|
||||||
async def process_message(
|
async def process_message(self, client_id: str, payload: Dict, langchain_object: Any):
|
||||||
self, client_id: str, payload: Dict, langchain_object: Any
|
|
||||||
):
|
|
||||||
# Process the graph data and chat message
|
# Process the graph data and chat message
|
||||||
chat_inputs = payload.pop("inputs", {})
|
chat_inputs = payload.pop("inputs", {})
|
||||||
chatkey = payload.pop("chatKey", None)
|
chatkey = payload.pop("chatKey", None)
|
||||||
|
|
@ -211,15 +205,11 @@ class ChatService(Service):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
with self.chat_cache.set_client_id(client_id):
|
with self.chat_cache.set_client_id(client_id):
|
||||||
if langchain_object := self.cache_service.get(client_id).get(
|
if langchain_object := self.cache_service.get(client_id).get("result"):
|
||||||
"result"
|
|
||||||
):
|
|
||||||
await self.process_message(client_id, payload, langchain_object)
|
await self.process_message(client_id, payload, langchain_object)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise RuntimeError(
|
raise RuntimeError(f"Could not find a build result for client_id {client_id}")
|
||||||
f"Could not find a build result for client_id {client_id}"
|
|
||||||
)
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
# Handle any exceptions that might occur
|
# Handle any exceptions that might occur
|
||||||
logger.exception(f"Error handling websocket: {exc}")
|
logger.exception(f"Error handling websocket: {exc}")
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,7 @@ async def process_graph(
|
||||||
|
|
||||||
if langchain_object is None:
|
if langchain_object 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:
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,7 @@ def get_api_keys(session: Session, user_id: UUID) -> List[ApiKeyRead]:
|
||||||
return [ApiKeyRead.from_orm(api_key) for api_key in api_keys]
|
return [ApiKeyRead.from_orm(api_key) for api_key in api_keys]
|
||||||
|
|
||||||
|
|
||||||
def create_api_key(
|
def create_api_key(session: Session, api_key_create: ApiKeyCreate, user_id: UUID) -> UnmaskedApiKeyRead:
|
||||||
session: Session, api_key_create: ApiKeyCreate, user_id: UUID
|
|
||||||
) -> UnmaskedApiKeyRead:
|
|
||||||
# Generate a random API key with 32 bytes of randomness
|
# Generate a random API key with 32 bytes of randomness
|
||||||
generated_api_key = f"sk-{secrets.token_urlsafe(32)}"
|
generated_api_key = f"sk-{secrets.token_urlsafe(32)}"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,7 @@ def get_user_by_id(db: Session, id: UUID) -> Union[User, None]:
|
||||||
return db.query(User).filter(User.id == id).first()
|
return db.query(User).filter(User.id == id).first()
|
||||||
|
|
||||||
|
|
||||||
def update_user(
|
def update_user(user_db: Optional[User], user: UserUpdate, db: Session = Depends(get_session)) -> User:
|
||||||
user_db: Optional[User], user: UserUpdate, db: Session = Depends(get_session)
|
|
||||||
) -> User:
|
|
||||||
if not user_db:
|
if not user_db:
|
||||||
raise HTTPException(status_code=404, detail="User not found")
|
raise HTTPException(status_code=404, detail="User not found")
|
||||||
|
|
||||||
|
|
@ -37,9 +35,7 @@ def update_user(
|
||||||
changed = True
|
changed = True
|
||||||
|
|
||||||
if not changed:
|
if not changed:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_304_NOT_MODIFIED, detail="Nothing to update")
|
||||||
status_code=status.HTTP_304_NOT_MODIFIED, detail="Nothing to update"
|
|
||||||
)
|
|
||||||
|
|
||||||
user_db.updated_at = datetime.now(timezone.utc)
|
user_db.updated_at = datetime.now(timezone.utc)
|
||||||
flag_modified(user_db, "updated_at")
|
flag_modified(user_db, "updated_at")
|
||||||
|
|
|
||||||
|
|
@ -34,10 +34,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 = {}
|
||||||
|
|
@ -49,9 +46,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()
|
||||||
|
|
@ -99,9 +94,7 @@ class DatabaseService(Service):
|
||||||
expected_columns = list(model.__fields__.keys())
|
expected_columns = list(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
|
||||||
|
|
@ -153,9 +146,7 @@ class DatabaseService(Service):
|
||||||
try:
|
try:
|
||||||
command.check(alembic_cfg)
|
command.check(alembic_cfg)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
if isinstance(exc, util.exc.CommandError) or isinstance(
|
if isinstance(exc, util.exc.CommandError) or isinstance(exc, util.exc.AutogenerateDiffsDetected):
|
||||||
exc, util.exc.AutogenerateDiffsDetected
|
|
||||||
):
|
|
||||||
command.upgrade(alembic_cfg, "head")
|
command.upgrade(alembic_cfg, "head")
|
||||||
|
|
||||||
# We should check the schema health after running migrations
|
# We should check the schema health after running migrations
|
||||||
|
|
@ -174,10 +165,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 = []
|
||||||
|
|
@ -185,9 +173,7 @@ class DatabaseService(Service):
|
||||||
table_name = model.__tablename__
|
table_name = model.__tablename__
|
||||||
expected_columns = list(model.__fields__.keys())
|
expected_columns = list(model.__fields__.keys())
|
||||||
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}")
|
||||||
|
|
@ -218,9 +204,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
|
||||||
|
|
@ -232,9 +216,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")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,7 @@ def initialize_database():
|
||||||
logger.debug("Initializing database")
|
logger.debug("Initializing database")
|
||||||
from langflow.services import service_manager, ServiceType
|
from langflow.services import service_manager, ServiceType
|
||||||
|
|
||||||
database_service: "DatabaseService" = service_manager.get(
|
database_service: "DatabaseService" = service_manager.get(ServiceType.DATABASE_SERVICE)
|
||||||
ServiceType.DATABASE_SERVICE
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
database_service.create_db_and_tables()
|
database_service.create_db_and_tables()
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
|
@ -41,9 +39,7 @@ def initialize_database():
|
||||||
# This means there's wrong revision in the DB
|
# This means there's wrong revision in the DB
|
||||||
# We need to delete the alembic_version table
|
# We need to delete the alembic_version table
|
||||||
# and run the migrations again
|
# and run the migrations again
|
||||||
logger.warning(
|
logger.warning("Wrong revision in DB, deleting alembic_version table and running migrations again")
|
||||||
"Wrong revision in DB, deleting alembic_version table and running migrations again"
|
|
||||||
)
|
|
||||||
with session_getter(database_service) as session:
|
with session_getter(database_service) as session:
|
||||||
session.execute("DROP TABLE alembic_version")
|
session.execute("DROP TABLE alembic_version")
|
||||||
database_service.run_migrations()
|
database_service.run_migrations()
|
||||||
|
|
|
||||||
|
|
@ -53,15 +53,10 @@ class ServiceManager:
|
||||||
self._create_service(dependency)
|
self._create_service(dependency)
|
||||||
|
|
||||||
# Collect the dependent services
|
# Collect the dependent services
|
||||||
dependent_services = {
|
dependent_services = {dep.value: self.services[dep] for dep in self.dependencies.get(service_name, [])}
|
||||||
dep.value: self.services[dep]
|
|
||||||
for dep in self.dependencies.get(service_name, [])
|
|
||||||
}
|
|
||||||
|
|
||||||
# Create the actual service
|
# Create the actual service
|
||||||
self.services[service_name] = self.factories[service_name].create(
|
self.services[service_name] = self.factories[service_name].create(**dependent_services)
|
||||||
**dependent_services
|
|
||||||
)
|
|
||||||
self.services[service_name].set_ready()
|
self.services[service_name].set_ready()
|
||||||
|
|
||||||
def _validate_service_creation(self, service_name: ServiceType):
|
def _validate_service_creation(self, service_name: ServiceType):
|
||||||
|
|
@ -69,9 +64,7 @@ class ServiceManager:
|
||||||
Validate whether the service can be created.
|
Validate whether the service can be created.
|
||||||
"""
|
"""
|
||||||
if service_name not in self.factories:
|
if service_name not in self.factories:
|
||||||
raise ValueError(
|
raise ValueError(f"No factory registered for the service class '{service_name.name}'")
|
||||||
f"No factory registered for the service class '{service_name.name}'"
|
|
||||||
)
|
|
||||||
|
|
||||||
def update(self, service_name: ServiceType):
|
def update(self, service_name: ServiceType):
|
||||||
"""
|
"""
|
||||||
|
|
@ -144,9 +137,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(),
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,7 @@ class LangfuseInstance:
|
||||||
|
|
||||||
settings_manager = get_settings_service()
|
settings_manager = get_settings_service()
|
||||||
|
|
||||||
if (
|
if settings_manager.settings.LANGFUSE_PUBLIC_KEY and settings_manager.settings.LANGFUSE_SECRET_KEY:
|
||||||
settings_manager.settings.LANGFUSE_PUBLIC_KEY
|
|
||||||
and settings_manager.settings.LANGFUSE_SECRET_KEY
|
|
||||||
):
|
|
||||||
logger.debug("Langfuse credentials found")
|
logger.debug("Langfuse credentials found")
|
||||||
cls._instance = Langfuse(
|
cls._instance = Langfuse(
|
||||||
public_key=settings_manager.settings.LANGFUSE_PUBLIC_KEY,
|
public_key=settings_manager.settings.LANGFUSE_PUBLIC_KEY,
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,4 @@ import string
|
||||||
|
|
||||||
|
|
||||||
def session_id_generator(size=6):
|
def session_id_generator(size=6):
|
||||||
return "".join(
|
return "".join(random.SystemRandom().choices(string.ascii_uppercase + string.digits, k=size))
|
||||||
random.SystemRandom().choices(string.ascii_uppercase + string.digits, k=size)
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,7 @@ class AuthSettings(BaseSettings):
|
||||||
REFRESH_TOKEN_EXPIRE_MINUTES: int = 60 * 12
|
REFRESH_TOKEN_EXPIRE_MINUTES: int = 60 * 12
|
||||||
|
|
||||||
# API Key to execute /process endpoint
|
# API Key to execute /process endpoint
|
||||||
API_KEY_SECRET_KEY: Optional[
|
API_KEY_SECRET_KEY: Optional[str] = "b82818e0ad4ff76615c5721ee21004b07d84cd9b87ba4d9cb42374da134b841a"
|
||||||
str
|
|
||||||
] = "b82818e0ad4ff76615c5721ee21004b07d84cd9b87ba4d9cb42374da134b841a"
|
|
||||||
API_KEY_ALGORITHM: str = "HS256"
|
API_KEY_ALGORITHM: str = "HS256"
|
||||||
API_V1_STR: str = "/api/v1"
|
API_V1_STR: str = "/api/v1"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -83,9 +83,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.")
|
||||||
|
|
@ -95,9 +93,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():
|
||||||
|
|
@ -121,22 +117,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]
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,4 @@ class SettingsServiceFactory(ServiceFactory):
|
||||||
def create(self):
|
def create(self):
|
||||||
# Here you would have logic to create and configure a SettingsService
|
# Here you would have logic to create and configure a SettingsService
|
||||||
langflow_dir = Path(__file__).parent.parent.parent
|
langflow_dir = Path(__file__).parent.parent.parent
|
||||||
return SettingsService.load_settings_from_yaml(
|
return SettingsService.load_settings_from_yaml(str(langflow_dir / "config.yaml"))
|
||||||
str(langflow_dir / "config.yaml")
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -30,9 +30,7 @@ class SettingsService(Service):
|
||||||
for key in settings_dict:
|
for key in settings_dict:
|
||||||
if key not in Settings.__fields__.keys():
|
if key not in Settings.__fields__.keys():
|
||||||
raise KeyError(f"Key {key} not found in settings")
|
raise KeyError(f"Key {key} not found in settings")
|
||||||
logger.debug(
|
logger.debug(f"Loading {len(settings_dict[key])} {key} from {file_path}")
|
||||||
f"Loading {len(settings_dict[key])} {key} from {file_path}"
|
|
||||||
)
|
|
||||||
|
|
||||||
settings = Settings(**settings_dict)
|
settings = Settings(**settings_dict)
|
||||||
if not settings.CONFIG_DIR:
|
if not settings.CONFIG_DIR:
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,7 @@ def set_secure_permissions(file_path):
|
||||||
import win32security
|
import win32security
|
||||||
|
|
||||||
user, domain, _ = win32security.LookupAccountName("", win32api.GetUserName())
|
user, domain, _ = win32security.LookupAccountName("", win32api.GetUserName())
|
||||||
sd = win32security.GetFileSecurity(
|
sd = win32security.GetFileSecurity(file_path, win32security.DACL_SECURITY_INFORMATION)
|
||||||
file_path, win32security.DACL_SECURITY_INFORMATION
|
|
||||||
)
|
|
||||||
dacl = win32security.ACL()
|
dacl = win32security.ACL()
|
||||||
|
|
||||||
# Set the new DACL for the file: read and write access for the owner, no access for everyone else
|
# Set the new DACL for the file: read and write access for the owner, no access for everyone else
|
||||||
|
|
@ -26,9 +24,7 @@ def set_secure_permissions(file_path):
|
||||||
user,
|
user,
|
||||||
)
|
)
|
||||||
sd.SetSecurityDescriptorDacl(1, dacl, 0)
|
sd.SetSecurityDescriptorDacl(1, dacl, 0)
|
||||||
win32security.SetFileSecurity(
|
win32security.SetFileSecurity(file_path, win32security.DACL_SECURITY_INFORMATION, sd)
|
||||||
file_path, win32security.DACL_SECURITY_INFORMATION, sd
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
print("Unsupported OS")
|
print("Unsupported OS")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pydantic import BaseModel, validator
|
from typing import List, Optional
|
||||||
from typing import Optional, List
|
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
|
from pydantic import BaseModel, validator
|
||||||
|
|
||||||
|
|
||||||
class TagResponse(BaseModel):
|
class TagResponse(BaseModel):
|
||||||
id: UUID
|
id: UUID
|
||||||
|
|
@ -62,9 +63,7 @@ class ListComponentResponse(BaseModel):
|
||||||
if all(["id" in tag and "name" in tag for tag in v]):
|
if all(["id" in tag and "name" in tag for tag in v]):
|
||||||
return v
|
return v
|
||||||
else:
|
else:
|
||||||
return [
|
return [TagResponse(**tag.get("tags_id")) for tag in v if tag.get("tags_id")]
|
||||||
TagResponse(**tag.get("tags_id")) for tag in v if tag.get("tags_id")
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class ListComponentResponseModel(BaseModel):
|
class ListComponentResponseModel(BaseModel):
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
from datetime import datetime
|
|
||||||
import json
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
from langflow.services.base import Service
|
|
||||||
from typing import TYPE_CHECKING, List, Dict, Any, Optional, Union
|
|
||||||
import httpx
|
|
||||||
|
|
||||||
from httpx import HTTPError
|
import httpx
|
||||||
|
from httpx import HTTPError, HTTPStatusError
|
||||||
|
|
||||||
|
from langflow.services.base import Service
|
||||||
from langflow.services.store.schema import (
|
from langflow.services.store.schema import (
|
||||||
ComponentResponse,
|
ComponentResponse,
|
||||||
DownloadComponentResponse,
|
DownloadComponentResponse,
|
||||||
|
|
@ -16,22 +17,23 @@ from langflow.services.store.utils import process_tags_for_post
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from langflow.services.settings.service import SettingsService
|
from langflow.services.settings.service import SettingsService
|
||||||
|
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from contextvars import ContextVar
|
from contextvars import ContextVar
|
||||||
|
|
||||||
user_data_var: ContextVar[Optional[Dict[str, Any]]] = ContextVar(
|
user_data_var: ContextVar[Optional[Dict[str, Any]]] = ContextVar("user_data", default=None)
|
||||||
"user_data", default=None
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def user_data_context(api_key: str, store_service: "StoreService"):
|
def user_data_context(store_service: "StoreService", api_key: Optional[str] = None):
|
||||||
# Fetch and set user data to the context variable
|
# Fetch and set user data to the context variable
|
||||||
if api_key:
|
if api_key:
|
||||||
user_data = store_service._get(
|
try:
|
||||||
f"{store_service.base_url}/users/me", api_key, params={"fields": "id"}
|
user_data = store_service._get(f"{store_service.base_url}/users/me", api_key, params={"fields": "id"})
|
||||||
)
|
user_data_var.set(user_data)
|
||||||
user_data_var.set(user_data)
|
except HTTPStatusError as exc:
|
||||||
|
if exc.response.status_code == 403:
|
||||||
|
raise ValueError("Invalid API key")
|
||||||
try:
|
try:
|
||||||
yield
|
yield
|
||||||
finally:
|
finally:
|
||||||
|
|
@ -71,9 +73,7 @@ class StoreService(Service):
|
||||||
# will make a property return that data
|
# will make a property return that data
|
||||||
# Without making the request multiple times
|
# Without making the request multiple times
|
||||||
|
|
||||||
def _get(
|
def _get(self, url: str, api_key: str, params: Dict[str, Any] = None) -> List[Dict[str, Any]]:
|
||||||
self, url: str, api_key: str, params: Dict[str, Any] = None
|
|
||||||
) -> List[Dict[str, Any]]:
|
|
||||||
"""Utility method to perform GET requests."""
|
"""Utility method to perform GET requests."""
|
||||||
if api_key:
|
if api_key:
|
||||||
headers = {"Authorization": f"Bearer {api_key}"}
|
headers = {"Authorization": f"Bearer {api_key}"}
|
||||||
|
|
@ -93,9 +93,7 @@ class StoreService(Service):
|
||||||
# For now we are calling it just for testing
|
# For now we are calling it just for testing
|
||||||
try:
|
try:
|
||||||
headers = {"Authorization": f"Bearer {api_key}"}
|
headers = {"Authorization": f"Bearer {api_key}"}
|
||||||
response = httpx.post(
|
response = httpx.post(webhook_url, headers=headers, json={"component_id": str(component_id)})
|
||||||
webhook_url, headers=headers, json={"component_id": str(component_id)}
|
|
||||||
)
|
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return response.json()
|
return response.json()
|
||||||
except HTTPError as exc:
|
except HTTPError as exc:
|
||||||
|
|
@ -138,8 +136,10 @@ class StoreService(Service):
|
||||||
filter_conditions.append({"is_component": {"_eq": is_component}})
|
filter_conditions.append({"is_component": {"_eq": is_component}})
|
||||||
|
|
||||||
if tags:
|
if tags:
|
||||||
# params["filter[tags][_in]"] = ",".join(tags)
|
tags_filter = {"tags": {"_and": []}}
|
||||||
filter_conditions.append({"tags": {"tags_id": {"name": {"_in": tags}}}})
|
for tag in tags:
|
||||||
|
tags_filter["tags"]["_and"].append({"_some": {"tags_id": {"name": {"_eq": tag}}}})
|
||||||
|
filter_conditions.append(tags_filter)
|
||||||
|
|
||||||
if date_from:
|
if date_from:
|
||||||
# params["filter[date_updated][_gte]"] = date_from.isoformat()
|
# params["filter[date_updated][_gte]"] = date_from.isoformat()
|
||||||
|
|
@ -156,13 +156,7 @@ class StoreService(Service):
|
||||||
params["fields"] = ",".join(fields)
|
params["fields"] = ",".join(fields)
|
||||||
|
|
||||||
if filter_by_user:
|
if filter_by_user:
|
||||||
params["deep"] = json.dumps(
|
params["deep"] = json.dumps({"components": {"_filter": {"user_created": {"token": {"_eq": api_key}}}}})
|
||||||
{
|
|
||||||
"components": {
|
|
||||||
"_filter": {"user_created": {"token": {"_eq": api_key}}}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
# params["filter"] = json.dumps({"status": {"_eq": "public"}})
|
# params["filter"] = json.dumps({"status": {"_eq": "public"}})
|
||||||
filter_conditions.append({"status": {"_in": ["public", "Public"]}})
|
filter_conditions.append({"status": {"_in": ["public", "Public"]}})
|
||||||
|
|
@ -177,29 +171,20 @@ class StoreService(Service):
|
||||||
self,
|
self,
|
||||||
api_key: Optional[str] = None,
|
api_key: Optional[str] = None,
|
||||||
filter_by_user: bool = False,
|
filter_by_user: bool = False,
|
||||||
is_component: Optional[bool] = None,
|
filter_conditions: Optional[List[Dict[str, Any]]] = None,
|
||||||
) -> int:
|
) -> int:
|
||||||
params = {"aggregate": json.dumps({"count": "*"})}
|
params = {"aggregate": json.dumps({"count": "*"})}
|
||||||
filter_conditions = []
|
filter_conditions = [] if filter_conditions is None else filter_conditions
|
||||||
if filter_by_user:
|
if filter_by_user:
|
||||||
params["deep"] = json.dumps(
|
params["deep"] = json.dumps({"components": {"_filter": {"user_created": {"token": {"_eq": api_key}}}}})
|
||||||
{
|
|
||||||
"components": {
|
|
||||||
"_filter": {"user_created": {"token": {"_eq": api_key}}}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
filter_conditions.append({"status": {"_in": ["public", "Public"]}})
|
filter_conditions.append({"status": {"_in": ["public", "Public"]}})
|
||||||
|
|
||||||
if is_component is not None:
|
|
||||||
filter_conditions.append({"is_component": {"_eq": is_component}})
|
|
||||||
|
|
||||||
if filter_conditions:
|
if filter_conditions:
|
||||||
params["filter"] = json.dumps({"_and": filter_conditions})
|
params["filter"] = json.dumps({"_and": filter_conditions})
|
||||||
|
|
||||||
results = self._get(self.components_url, api_key, params)
|
results = self._get(self.components_url, api_key, params)
|
||||||
return results[0].get("count", 0)
|
return int(results[0].get("count", 0))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def build_search_filter_conditions(query: str):
|
def build_search_filter_conditions(query: str):
|
||||||
|
|
@ -213,7 +198,7 @@ class StoreService(Service):
|
||||||
|
|
||||||
def query_components(
|
def query_components(
|
||||||
self,
|
self,
|
||||||
api_key: str,
|
api_key: Optional[str] = None,
|
||||||
search: Optional[str] = None,
|
search: Optional[str] = None,
|
||||||
status: Optional[str] = None,
|
status: Optional[str] = None,
|
||||||
tags: Optional[List[str]] = None,
|
tags: Optional[List[str]] = None,
|
||||||
|
|
@ -223,7 +208,7 @@ class StoreService(Service):
|
||||||
fields: Optional[List[str]] = None,
|
fields: Optional[List[str]] = None,
|
||||||
is_component: Optional[bool] = None,
|
is_component: Optional[bool] = None,
|
||||||
filter_by_user: bool = False,
|
filter_by_user: bool = False,
|
||||||
) -> Union[List[ListComponentResponse], List[Dict[str, int]]]:
|
) -> Tuple[List[ListComponentResponse], List[Dict[str, Any]]]:
|
||||||
params = {"page": page, "limit": limit}
|
params = {"page": page, "limit": limit}
|
||||||
# ?aggregate[count]=likes
|
# ?aggregate[count]=likes
|
||||||
params["fields"] = ",".join(fields) if fields else ",".join(self.default_fields)
|
params["fields"] = ",".join(fields) if fields else ",".join(self.default_fields)
|
||||||
|
|
@ -241,7 +226,10 @@ class StoreService(Service):
|
||||||
filter_conditions.append({"status": {"_eq": status}})
|
filter_conditions.append({"status": {"_eq": status}})
|
||||||
|
|
||||||
if tags:
|
if tags:
|
||||||
filter_conditions.append({"tags": {"tags_id": {"name": {"_in": tags}}}})
|
tags_filter = {"tags": {"_and": []}}
|
||||||
|
for tag in tags:
|
||||||
|
tags_filter["tags"]["_and"].append({"_some": {"tags_id": {"name": {"_eq": tag}}}})
|
||||||
|
filter_conditions.append(tags_filter)
|
||||||
|
|
||||||
if is_component is not None:
|
if is_component is not None:
|
||||||
filter_conditions.append({"is_component": {"_eq": is_component}})
|
filter_conditions.append({"is_component": {"_eq": is_component}})
|
||||||
|
|
@ -257,6 +245,8 @@ class StoreService(Service):
|
||||||
if filter_by_user and api_key:
|
if filter_by_user and api_key:
|
||||||
user_data = user_data_var.get()
|
user_data = user_data_var.get()
|
||||||
# params["filter"] = json.dumps({"user_created": {"_eq": user_data["id"]}})
|
# params["filter"] = json.dumps({"user_created": {"_eq": user_data["id"]}})
|
||||||
|
if not user_data:
|
||||||
|
raise ValueError("No user data")
|
||||||
filter_conditions.append({"user_created": {"_eq": user_data["id"]}})
|
filter_conditions.append({"user_created": {"_eq": user_data["id"]}})
|
||||||
else:
|
else:
|
||||||
filter_conditions.append({"status": {"_in": ["public", "Public"]}})
|
filter_conditions.append({"status": {"_in": ["public", "Public"]}})
|
||||||
|
|
@ -270,15 +260,15 @@ class StoreService(Service):
|
||||||
# for component in results_objects:
|
# for component in results_objects:
|
||||||
# if component.tags:
|
# if component.tags:
|
||||||
# component.tags = [tags_id.tags_id for tags_id in component.tags]
|
# component.tags = [tags_id.tags_id for tags_id in component.tags]
|
||||||
return results_objects
|
return results_objects, filter_conditions
|
||||||
|
|
||||||
def get_liked_by_user_components(
|
def get_liked_by_user_components(self, component_ids: List[UUID], api_key: str) -> List[UUID]:
|
||||||
self, component_ids: List[UUID], api_key: str
|
|
||||||
) -> List[UUID]:
|
|
||||||
# Get fields id
|
# Get fields id
|
||||||
# filter should be "id is in component_ids AND liked_by directus_users_id token is api_key"
|
# filter should be "id is in component_ids AND liked_by directus_users_id token is api_key"
|
||||||
# return the ids
|
# return the ids
|
||||||
user_data = user_data_var.get()
|
user_data = user_data_var.get()
|
||||||
|
if not user_data:
|
||||||
|
raise ValueError("No user data")
|
||||||
params = {
|
params = {
|
||||||
"fields": "id",
|
"fields": "id",
|
||||||
"filter": json.dumps(
|
"filter": json.dumps(
|
||||||
|
|
@ -294,10 +284,10 @@ class StoreService(Service):
|
||||||
return [result["id"] for result in results]
|
return [result["id"] for result in results]
|
||||||
|
|
||||||
# Which of the components is parent of the user's components
|
# Which of the components is parent of the user's components
|
||||||
def get_components_in_users_collection(
|
def get_components_in_users_collection(self, component_ids: List[UUID], api_key: str):
|
||||||
self, component_ids: List[UUID], api_key: str
|
|
||||||
):
|
|
||||||
user_data = user_data_var.get()
|
user_data = user_data_var.get()
|
||||||
|
if not user_data:
|
||||||
|
raise ValueError("No user data")
|
||||||
params = {
|
params = {
|
||||||
"fields": "id",
|
"fields": "id",
|
||||||
"filter": json.dumps(
|
"filter": json.dumps(
|
||||||
|
|
@ -314,18 +304,14 @@ class StoreService(Service):
|
||||||
|
|
||||||
def download(self, api_key: str, component_id: str) -> DownloadComponentResponse:
|
def download(self, api_key: str, component_id: str) -> DownloadComponentResponse:
|
||||||
url = f"{self.components_url}/{component_id}"
|
url = f"{self.components_url}/{component_id}"
|
||||||
params = {
|
params = {"fields": ",".join(["id", "name", "description", "data", "is_component"])}
|
||||||
"fields": ",".join(["id", "name", "description", "data", "is_component"])
|
|
||||||
}
|
|
||||||
|
|
||||||
component = self._get(url, api_key, params)
|
component = self._get(url, api_key, params)
|
||||||
self.call_webhook(api_key, self.download_webhook_url, component_id)
|
self.call_webhook(api_key, self.download_webhook_url, component_id)
|
||||||
|
|
||||||
return DownloadComponentResponse(**component)
|
return DownloadComponentResponse(**component)
|
||||||
|
|
||||||
def upload(
|
def upload(self, api_key: str, component_data: StoreComponentCreate) -> ComponentResponse:
|
||||||
self, api_key: str, component_data: StoreComponentCreate
|
|
||||||
) -> ComponentResponse:
|
|
||||||
headers = {"Authorization": f"Bearer {api_key}"}
|
headers = {"Authorization": f"Bearer {api_key}"}
|
||||||
component_dict = component_data.dict(exclude_unset=True)
|
component_dict = component_data.dict(exclude_unset=True)
|
||||||
# Parent is a UUID, but the store expects a string
|
# Parent is a UUID, but the store expects a string
|
||||||
|
|
@ -335,9 +321,7 @@ class StoreService(Service):
|
||||||
|
|
||||||
component_dict = process_tags_for_post(component_dict)
|
component_dict = process_tags_for_post(component_dict)
|
||||||
try:
|
try:
|
||||||
response = httpx.post(
|
response = httpx.post(self.components_url, headers=headers, json=component_dict)
|
||||||
self.components_url, headers=headers, json=component_dict
|
|
||||||
)
|
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
component = response.json()["data"]
|
component = response.json()["data"]
|
||||||
return ComponentResponse(**component)
|
return ComponentResponse(**component)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING, List
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from langflow.services.store.schema import ListComponentResponse
|
from langflow.services.store.schema import ListComponentResponse
|
||||||
|
|
@ -14,7 +13,7 @@ def process_tags_for_post(component_dict):
|
||||||
|
|
||||||
|
|
||||||
def update_components_with_user_data(
|
def update_components_with_user_data(
|
||||||
components: "ListComponentResponse",
|
components: List["ListComponentResponse"],
|
||||||
store_service: "StoreService",
|
store_service: "StoreService",
|
||||||
store_api_Key: str,
|
store_api_Key: str,
|
||||||
):
|
):
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,7 @@ class CeleryBackend(TaskBackend):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.celery_app = celery_app
|
self.celery_app = celery_app
|
||||||
|
|
||||||
def launch_task(
|
def launch_task(self, task_func: Callable[..., Any], *args: Any, **kwargs: Any) -> tuple[str, AsyncResult]:
|
||||||
self, task_func: Callable[..., Any], *args: Any, **kwargs: Any
|
|
||||||
) -> tuple[str, AsyncResult]:
|
|
||||||
# I need to type the delay method to make it easier
|
# I need to type the delay method to make it easier
|
||||||
from celery import Task # type: ignore
|
from celery import Task # type: ignore
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,9 +63,7 @@ class TaskService(Service):
|
||||||
result = task.get()
|
result = task.get()
|
||||||
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)
|
||||||
|
|
|
||||||
|
|
@ -71,16 +71,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")
|
||||||
|
|
||||||
|
|
@ -109,21 +105,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()
|
||||||
|
|
||||||
|
|
@ -137,9 +127,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.user import User
|
from langflow.services.database.models.user.user import User
|
||||||
|
|
||||||
|
|
@ -187,9 +175,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(),
|
||||||
|
|
@ -206,17 +192,13 @@ def initialize_services():
|
||||||
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)
|
||||||
# Setup the superuser
|
# Setup the superuser
|
||||||
initialize_database()
|
initialize_database()
|
||||||
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:
|
||||||
|
|
|
||||||
|
|
@ -67,11 +67,7 @@ class FrontendNode(BaseModel):
|
||||||
|
|
||||||
def process_base_classes(self) -> None:
|
def process_base_classes(self) -> None:
|
||||||
"""Removes unwanted base classes from the list of base classes."""
|
"""Removes unwanted base classes from the list of base classes."""
|
||||||
self.base_classes = [
|
self.base_classes = [base_class for base_class in self.base_classes if base_class not in CLASSES_TO_REMOVE]
|
||||||
base_class
|
|
||||||
for base_class in self.base_classes
|
|
||||||
if base_class not in CLASSES_TO_REMOVE
|
|
||||||
]
|
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
def to_dict(self) -> dict:
|
||||||
"""Returns a dict representation of the frontend node."""
|
"""Returns a dict representation of the frontend node."""
|
||||||
|
|
@ -130,9 +126,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
|
||||||
|
|
@ -144,11 +138,7 @@ class FrontendNode(BaseModel):
|
||||||
field.field_type = "file"
|
field.field_type = "file"
|
||||||
field.suffixes = [".json", ".yaml", ".yml"]
|
field.suffixes = [".json", ".yaml", ".yml"]
|
||||||
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
|
||||||
|
|
||||||
|
|
@ -159,9 +149,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>"}"""
|
||||||
|
|
@ -169,9 +157,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,
|
||||||
|
|
@ -184,9 +170,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"
|
||||||
|
|
@ -225,10 +209,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:
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,9 @@ class SeriesCharacterChainNode(FrontendNode):
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
description: str = "SeriesCharacterChain is a chain you can use to have a conversation with a character from a series." # noqa
|
description: str = (
|
||||||
|
"SeriesCharacterChain is a chain you can use to have a conversation with a character from a series." # noqa
|
||||||
|
)
|
||||||
base_classes: list[str] = [
|
base_classes: list[str] = [
|
||||||
"LLMChain",
|
"LLMChain",
|
||||||
"BaseCustomChain",
|
"BaseCustomChain",
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,7 @@ from langflow.template.field.base import TemplateField
|
||||||
from langflow.template.frontend_node.base import FrontendNode
|
from langflow.template.frontend_node.base import FrontendNode
|
||||||
|
|
||||||
|
|
||||||
def build_file_field(
|
def build_file_field(suffixes: list, fileTypes: list, name: str = "file_path") -> TemplateField:
|
||||||
suffixes: list, fileTypes: list, name: str = "file_path"
|
|
||||||
) -> TemplateField:
|
|
||||||
"""Build a template field for a document loader."""
|
"""Build a template field for a document loader."""
|
||||||
return TemplateField(
|
return TemplateField(
|
||||||
field_type="file",
|
field_type="file",
|
||||||
|
|
@ -27,32 +25,22 @@ class DocumentLoaderFrontNode(FrontendNode):
|
||||||
"AirbyteJSONLoader": build_file_field(suffixes=[".json"], fileTypes=["json"]),
|
"AirbyteJSONLoader": build_file_field(suffixes=[".json"], fileTypes=["json"]),
|
||||||
"CoNLLULoader": build_file_field(suffixes=[".csv"], fileTypes=["csv"]),
|
"CoNLLULoader": build_file_field(suffixes=[".csv"], fileTypes=["csv"]),
|
||||||
"CSVLoader": build_file_field(suffixes=[".csv"], fileTypes=["csv"]),
|
"CSVLoader": build_file_field(suffixes=[".csv"], fileTypes=["csv"]),
|
||||||
"UnstructuredEmailLoader": build_file_field(
|
"UnstructuredEmailLoader": build_file_field(suffixes=[".eml"], fileTypes=["eml"]),
|
||||||
suffixes=[".eml"], fileTypes=["eml"]
|
|
||||||
),
|
|
||||||
"EverNoteLoader": build_file_field(suffixes=[".xml"], fileTypes=["xml"]),
|
"EverNoteLoader": build_file_field(suffixes=[".xml"], fileTypes=["xml"]),
|
||||||
"FacebookChatLoader": build_file_field(suffixes=[".json"], fileTypes=["json"]),
|
"FacebookChatLoader": build_file_field(suffixes=[".json"], fileTypes=["json"]),
|
||||||
"BSHTMLLoader": build_file_field(suffixes=[".html"], fileTypes=["html"]),
|
"BSHTMLLoader": build_file_field(suffixes=[".html"], fileTypes=["html"]),
|
||||||
"UnstructuredHTMLLoader": build_file_field(
|
"UnstructuredHTMLLoader": build_file_field(suffixes=[".html"], fileTypes=["html"]),
|
||||||
suffixes=[".html"], fileTypes=["html"]
|
|
||||||
),
|
|
||||||
"UnstructuredImageLoader": build_file_field(
|
"UnstructuredImageLoader": build_file_field(
|
||||||
suffixes=[".jpg", ".jpeg", ".png", ".gif", ".bmp"],
|
suffixes=[".jpg", ".jpeg", ".png", ".gif", ".bmp"],
|
||||||
fileTypes=["jpg", "jpeg", "png", "gif", "bmp"],
|
fileTypes=["jpg", "jpeg", "png", "gif", "bmp"],
|
||||||
),
|
),
|
||||||
"UnstructuredMarkdownLoader": build_file_field(
|
"UnstructuredMarkdownLoader": build_file_field(suffixes=[".md"], fileTypes=["md"]),
|
||||||
suffixes=[".md"], fileTypes=["md"]
|
|
||||||
),
|
|
||||||
"PyPDFLoader": build_file_field(suffixes=[".pdf"], fileTypes=["pdf"]),
|
"PyPDFLoader": build_file_field(suffixes=[".pdf"], fileTypes=["pdf"]),
|
||||||
"UnstructuredPowerPointLoader": build_file_field(
|
"UnstructuredPowerPointLoader": build_file_field(suffixes=[".pptx", ".ppt"], fileTypes=["pptx", "ppt"]),
|
||||||
suffixes=[".pptx", ".ppt"], fileTypes=["pptx", "ppt"]
|
|
||||||
),
|
|
||||||
"SRTLoader": build_file_field(suffixes=[".srt"], fileTypes=["srt"]),
|
"SRTLoader": build_file_field(suffixes=[".srt"], fileTypes=["srt"]),
|
||||||
"TelegramChatLoader": build_file_field(suffixes=[".json"], fileTypes=["json"]),
|
"TelegramChatLoader": build_file_field(suffixes=[".json"], fileTypes=["json"]),
|
||||||
"TextLoader": build_file_field(suffixes=[".txt"], fileTypes=["txt"]),
|
"TextLoader": build_file_field(suffixes=[".txt"], fileTypes=["txt"]),
|
||||||
"UnstructuredWordDocumentLoader": build_file_field(
|
"UnstructuredWordDocumentLoader": build_file_field(suffixes=[".docx", ".doc"], fileTypes=["docx", "doc"]),
|
||||||
suffixes=[".docx", ".doc"], fileTypes=["docx", "doc"]
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def add_extra_fields(self) -> None:
|
def add_extra_fields(self) -> None:
|
||||||
|
|
|
||||||
|
|
@ -70,9 +70,7 @@ class EmbeddingFrontendNode(FrontendNode):
|
||||||
field.advanced = True
|
field.advanced = True
|
||||||
split_name = field.name.split("_")
|
split_name = field.name.split("_")
|
||||||
title_name = " ".join([s.capitalize() for s in split_name])
|
title_name = " ".join([s.capitalize() for s in split_name])
|
||||||
field.display_name = title_name.replace("Openai", "OpenAI").replace(
|
field.display_name = title_name.replace("Openai", "OpenAI").replace("Api", "API")
|
||||||
"Api", "API"
|
|
||||||
)
|
|
||||||
|
|
||||||
if "api_key" in field.name:
|
if "api_key" in field.name:
|
||||||
field.password = True
|
field.password = True
|
||||||
|
|
|
||||||
|
|
@ -112,10 +112,7 @@ class PasswordFieldFormatter(FieldFormatter):
|
||||||
def format(self, field: TemplateField, name: Optional[str] = None) -> None:
|
def format(self, field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
key = field.name
|
key = field.name
|
||||||
show = field.show
|
show = field.show
|
||||||
if (
|
if 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
|
|
||||||
):
|
|
||||||
field.password = True
|
field.password = True
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -157,9 +154,5 @@ class DictCodeFileFormatter(FieldFormatter):
|
||||||
field.field_type = "file"
|
field.field_type = "file"
|
||||||
field.suffixes = [".json", ".yaml", ".yml"]
|
field.suffixes = [".json", ".yaml", ".yml"]
|
||||||
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"
|
||||||
|
|
|
||||||
|
|
@ -54,9 +54,9 @@ class LLMFrontendNode(FrontendNode):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def format_openai_field(field: TemplateField):
|
def format_openai_field(field: TemplateField):
|
||||||
if "openai" in field.name.lower():
|
if "openai" in field.name.lower():
|
||||||
field.display_name = (
|
field.display_name = (field.name.title().replace("Openai", "OpenAI").replace("_", " ")).replace(
|
||||||
field.name.title().replace("Openai", "OpenAI").replace("_", " ")
|
"Api", "API"
|
||||||
).replace("Api", "API")
|
)
|
||||||
|
|
||||||
if "key" not in field.name.lower() and "token" not in field.name.lower():
|
if "key" not in field.name.lower() and "token" not in field.name.lower():
|
||||||
field.password = False
|
field.password = False
|
||||||
|
|
@ -109,10 +109,7 @@ class LLMFrontendNode(FrontendNode):
|
||||||
if field.name in SHOW_FIELDS:
|
if field.name in SHOW_FIELDS:
|
||||||
field.show = True
|
field.show = True
|
||||||
|
|
||||||
if "api" in field.name and (
|
if "api" in field.name and ("key" in field.name or ("token" in field.name and "tokens" not in field.name)):
|
||||||
"key" in field.name
|
|
||||||
or ("token" in field.name and "tokens" not in field.name)
|
|
||||||
):
|
|
||||||
field.password = True
|
field.password = True
|
||||||
field.show = True
|
field.show = True
|
||||||
# Required should be False to support
|
# Required should be False to support
|
||||||
|
|
|
||||||
|
|
@ -76,9 +76,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"
|
||||||
|
|
|
||||||
|
|
@ -36,10 +36,7 @@ class PromptFrontendNode(FrontendNode):
|
||||||
field.field_type = "prompt"
|
field.field_type = "prompt"
|
||||||
field.advanced = False
|
field.advanced = False
|
||||||
|
|
||||||
if (
|
if "Union" in field.field_type and "BaseMessagePromptTemplate" in field.field_type:
|
||||||
"Union" in field.field_type
|
|
||||||
and "BaseMessagePromptTemplate" in field.field_type
|
|
||||||
):
|
|
||||||
field.field_type = "BaseMessagePromptTemplate"
|
field.field_type = "BaseMessagePromptTemplate"
|
||||||
|
|
||||||
# All prompt fields should be password=False
|
# All prompt fields should be password=False
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue