mirror of
https://github.com/langflow-ai/langflow.git
synced 2026-07-24 04:47:23 +08:00
Merge branch 'main' into docs-publish-v0
This commit is contained in:
1
.github/changes-filter.yaml
vendored
1
.github/changes-filter.yaml
vendored
@ -25,6 +25,7 @@ starter-projects:
|
||||
- "src/backend/base/langflow/components/**"
|
||||
- "src/backend/base/langflow/services/**"
|
||||
- "src/backend/base/langflow/custom/**"
|
||||
- "src/backend/base/langflow/api/v1/chat.py"
|
||||
- "src/frontend/src/pages/MainPage/**"
|
||||
- "src/frontend/src/utils/reactflowUtils.ts"
|
||||
- "src/frontend/tests/extended/features/**"
|
||||
|
||||
1
.github/workflows/codeflash.yml
vendored
1
.github/workflows/codeflash.yml
vendored
@ -29,6 +29,7 @@ jobs:
|
||||
- run: uv sync --extra dev
|
||||
- name: Run Codeflash Optimizer
|
||||
working-directory: ./src/backend/base
|
||||
continue-on-error: true
|
||||
run: uv run codeflash
|
||||
- name: Minimize uv cache
|
||||
run: uv cache prune --ci
|
||||
|
||||
48
Makefile
48
Makefile
@ -463,3 +463,51 @@ alembic-check: ## check migration status
|
||||
alembic-stamp: ## stamp the database with a specific revision
|
||||
@echo 'Stamping the database with revision $(revision)'
|
||||
cd src/backend/base/langflow/ && uv run alembic stamp $(revision)
|
||||
|
||||
######################
|
||||
# LOAD TESTING
|
||||
######################
|
||||
|
||||
# Default values for locust configuration
|
||||
locust_users ?= 10
|
||||
locust_spawn_rate ?= 1
|
||||
locust_host ?= http://localhost:7860
|
||||
locust_headless ?= true
|
||||
locust_time ?= 300s
|
||||
locust_api_key ?= your-api-key
|
||||
locust_flow_id ?= your-flow-id
|
||||
locust_file ?= src/backend/tests/locust/locustfile.py
|
||||
locust_min_wait ?= 2000
|
||||
locust_max_wait ?= 5000
|
||||
locust_request_timeout ?= 30.0
|
||||
|
||||
locust: ## run locust load tests (options: locust_users=10 locust_spawn_rate=1 locust_host=http://localhost:7860 locust_headless=true locust_time=300s locust_api_key=your-api-key locust_flow_id=your-flow-id locust_file=src/backend/tests/locust/locustfile.py locust_min_wait=2000 locust_max_wait=5000 locust_request_timeout=30.0)
|
||||
@if [ ! -f "$(locust_file)" ]; then \
|
||||
echo "$(RED)Error: Locustfile not found at $(locust_file)$(NC)"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@echo "Starting Locust with $(locust_users) users, spawn rate of $(locust_spawn_rate)"
|
||||
@echo "Testing host: $(locust_host)"
|
||||
@echo "Using locustfile: $(locust_file)"
|
||||
@export API_KEY=$(locust_api_key) && \
|
||||
export FLOW_ID=$(locust_flow_id) && \
|
||||
export LANGFLOW_HOST=$(locust_host) && \
|
||||
export MIN_WAIT=$(locust_min_wait) && \
|
||||
export MAX_WAIT=$(locust_max_wait) && \
|
||||
export REQUEST_TIMEOUT=$(locust_request_timeout) && \
|
||||
cd $$(dirname "$(locust_file)") && \
|
||||
if [ "$(locust_headless)" = "true" ]; then \
|
||||
uv run locust \
|
||||
--headless \
|
||||
-u $(locust_users) \
|
||||
-r $(locust_spawn_rate) \
|
||||
--run-time $(locust_time) \
|
||||
--host $(locust_host) \
|
||||
-f $$(basename "$(locust_file)"); \
|
||||
else \
|
||||
uv run locust \
|
||||
-u $(locust_users) \
|
||||
-r $(locust_spawn_rate) \
|
||||
--host $(locust_host) \
|
||||
-f $$(basename "$(locust_file)"); \
|
||||
fi
|
||||
|
||||
@ -31,6 +31,7 @@
|
||||
<a href="./README.ja.md"><img alt="README en Japonés" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
|
||||
<a href="./README.KR.md"><img alt="README en Coreano" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
|
||||
<a href="./README.FR.md"><img alt="README en Francès" src="https://img.shields.io/badge/Français-d9d9d9"></a>
|
||||
<a href="./README.RU.md"><img alt="README en Russian" src="https://img.shields.io/badge/Русский"></a>
|
||||
</div>
|
||||
|
||||
<p align="center">
|
||||
|
||||
@ -38,6 +38,7 @@
|
||||
<a href="./README.ja.md"><img alt="README in Japanese" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
|
||||
<a href="./README.KR.md"><img alt="README in KOREAN" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
|
||||
<a href="./README.FR.md"><img alt="README in French" src="https://img.shields.io/badge/Français-d9d9d9"></a>
|
||||
<a href="./README.RU.md"><img alt="README in Russian" src="https://img.shields.io/badge/Русский"></a>
|
||||
</div>
|
||||
|
||||
<p align="center">
|
||||
|
||||
@ -33,6 +33,7 @@
|
||||
<a href="./README.ja.md"><img alt="README em Japonês" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
|
||||
<a href="./README.KR.md"><img alt="README em Coreano" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
|
||||
<a href="./README.FR.md"><img alt="README em Francês" src="https://img.shields.io/badge/Français-d9d9d9"></a>
|
||||
<a href="./README.RU.md"><img alt="README in Russian" src="https://img.shields.io/badge/Русский"></a>
|
||||
</div>
|
||||
|
||||
<p align="center">
|
||||
|
||||
78
README.RU.md
Normal file
78
README.RU.md
Normal file
@ -0,0 +1,78 @@
|
||||
<!-- markdownlint-disable MD030 -->
|
||||
|
||||

|
||||
|
||||
<p align="center" style="font-size: 12px;">
|
||||
Langflow — это инструмент для создания приложений с низким уровнем кода для RAG и многоагентных ИИ-приложений. Он основан на Python и не зависит от конкретных моделей, API или баз данных.
|
||||
</p>
|
||||
|
||||
<p align="center" style="font-size: 12px;">
|
||||
<a href="https://docs.langflow.org" style="text-decoration: underline;">Документация</a> -
|
||||
<a href="https://astra.datastax.com/signup?type=langflow" style="text-decoration: underline;">Бесплатный облачный сервис</a> -
|
||||
<a href="https://docs.langflow.org/get-started-installation" style="text-decoration: underline;">Самостоятельное развертывание</a>
|
||||
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
<a href="./README.md"><img alt="README на английском" src="https://img.shields.io/badge/English-d9d9d9"></a>
|
||||
<a href="./README.PT.md"><img alt="README на португальском" src="https://img.shields.io/badge/Portuguese-d9d9d9"></a>
|
||||
<a href="./README.ES.md"><img alt="README на испанском" src="https://img.shields.io/badge/Spanish-d9d9d9"></a>
|
||||
<a href="./README.zh_CN.md"><img alt="README на упрощенном китайском" src="https://img.shields.io/badge/简体中文-d9d9d9"></a>
|
||||
<a href="./README.ja.md"><img alt="README на японском" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
|
||||
<a href="./README.KR.md"><img alt="README на корейском" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
|
||||
<a href="./README.FR.md"><img alt="README на французском" src="https://img.shields.io/badge/Français-d9d9d9"></a>
|
||||
<a href="./README.RU.md"><img alt="README на русском" src="https://img.shields.io/badge/Русский-d9d9d9"></a>
|
||||
</div>
|
||||
|
||||
## ✨ Основные возможности
|
||||
|
||||
1. **Основан на Python** и не зависит от моделей, API, источников данных или баз данных.
|
||||
2. **Визуальная IDE** для построения и тестирования рабочих процессов методом drag-and-drop.
|
||||
3. **Песочница** для мгновенного тестирования и итерации процессов с поэтапным контролем.
|
||||
4. **Оркестрация многоагентных систем** с управлением диалогами и извлечением информации.
|
||||
5. **Бесплатный облачный сервис**, позволяющий начать работу за считанные минуты без настройки.
|
||||
6. **Публикация в виде API** или экспорт в виде Python-приложения.
|
||||
7. **Наблюдаемость** с интеграцией LangSmith, LangFuse или LangWatch.
|
||||
8. **Безопасность и масштабируемость уровня предприятия** с бесплатным облачным сервисом DataStax Langflow.
|
||||
9. **Настройка рабочих процессов** или создание потоков исключительно на Python.
|
||||
10. **Интеграция с экосистемами** через повторно используемые компоненты для любых моделей, API или баз данных.
|
||||
|
||||

|
||||
|
||||
## 📦 Быстрый старт
|
||||
|
||||
- **Установка через uv (рекомендуется)** (Python 3.10–3.12):
|
||||
|
||||
```shell
|
||||
uv pip install langflow
|
||||
```
|
||||
|
||||
- **Установка через pip** (Python 3.10–3.12):
|
||||
|
||||
```shell
|
||||
pip install langflow
|
||||
```
|
||||
|
||||
- **Облако:** DataStax Langflow — это управляемая среда без необходимости настройки. [Зарегистрируйтесь бесплатно.](https://astra.datastax.com/signup?type=langflow)
|
||||
- **Самостоятельное развертывание:** Запустите Langflow в своей среде. [Установите Langflow](https://docs.langflow.org/get-started-installation), чтобы запустить локальный сервер Langflow, а затем воспользуйтесь [руководством по быстрому старту](https://docs.langflow.org/get-started-quickstart) для создания и выполнения потока.
|
||||
- **Hugging Face:** [Клонируйте пространство по этой ссылке](https://huggingface.co/spaces/Langflow/Langflow?duplicate=true), чтобы создать рабочее пространство Langflow.
|
||||
|
||||
[](https://www.youtube.com/watch?v=kinngWhaUKM)
|
||||
|
||||
## ⭐ Будьте в курсе обновлений
|
||||
|
||||
Добавьте Langflow в избранное на GitHub, чтобы мгновенно узнавать о новых релизах.
|
||||
|
||||

|
||||
|
||||
## 👋 Внесите свой вклад
|
||||
|
||||
Мы приветствуем вклад разработчиков любого уровня. Если хотите помочь в развитии проекта, ознакомьтесь с нашими [руководящими принципами для участников](./CONTRIBUTING.md) и сделайте Langflow еще доступнее.
|
||||
|
||||
---
|
||||
|
||||
[](https://star-history.com/#langflow-ai/langflow&Date)
|
||||
|
||||
## ❤️ Соавторы
|
||||
|
||||
[](https://github.com/langflow-ai/langflow/graphs/contributors)
|
||||
@ -38,6 +38,7 @@
|
||||
<a href="./README.ja.md"><img alt="README in Japanese" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
|
||||
<a href="./README.KR.md"><img alt="README in KOREAN" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
|
||||
<a href="./README.FR.md"><img alt="README in French" src="https://img.shields.io/badge/Français-d9d9d9"></a>
|
||||
<a href="./README.RU.md"><img alt="README in Russian" src="https://img.shields.io/badge/Русский"></a>
|
||||
</div>
|
||||
|
||||
<p align="center">
|
||||
|
||||
@ -21,6 +21,7 @@
|
||||
<a href="./README.ja.md"><img alt="README in Japanese" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
|
||||
<a href="./README.KR.md"><img alt="README in KOREAN" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
|
||||
<a href="./README.FR.md"><img alt="README in French" src="https://img.shields.io/badge/Français-d9d9d9"></a>
|
||||
<a href="./README.RU.md"><img alt="README in Russian" src="https://img.shields.io/badge/Русский"></a>
|
||||
</div>
|
||||
|
||||
## ✨ Core features
|
||||
|
||||
@ -33,6 +33,7 @@
|
||||
<a href="./README.ja.md"><img alt="README in Japanese" src="https://img.shields.io/badge/日本語-d9d9d9"></a>
|
||||
<a href="./README.KR.md"><img alt="README in KOREAN" src="https://img.shields.io/badge/한국어-d9d9d9"></a>
|
||||
<a href="./README.FR.md"><img alt="README in French" src="https://img.shields.io/badge/Français-d9d9d9"></a>
|
||||
<a href="./README.RU.md"><img alt="README in Russian" src="https://img.shields.io/badge/Русский"></a>
|
||||
</div>
|
||||
|
||||
<p align="center">
|
||||
|
||||
@ -191,6 +191,7 @@ dev-dependencies = [
|
||||
"types-aiofiles>=24.1.0.20240626",
|
||||
"codeflash>=0.8.4",
|
||||
"hypothesis>=6.123.17",
|
||||
"locust>=2.32.9",
|
||||
]
|
||||
|
||||
|
||||
|
||||
428
src/backend/base/langflow/api/build.py
Normal file
428
src/backend/base/langflow/api/build.py
Normal file
@ -0,0 +1,428 @@
|
||||
import asyncio
|
||||
import json
|
||||
import time
|
||||
import traceback
|
||||
import uuid
|
||||
from collections.abc import AsyncIterator
|
||||
|
||||
from fastapi import BackgroundTasks, HTTPException
|
||||
from fastapi.responses import JSONResponse
|
||||
from loguru import logger
|
||||
from sqlmodel import select
|
||||
|
||||
from langflow.api.disconnect import DisconnectHandlerStreamingResponse
|
||||
from langflow.api.utils import (
|
||||
CurrentActiveUser,
|
||||
build_graph_from_data,
|
||||
build_graph_from_db,
|
||||
format_elapsed_time,
|
||||
format_exception_message,
|
||||
get_top_level_vertices,
|
||||
parse_exception,
|
||||
)
|
||||
from langflow.api.v1.schemas import (
|
||||
FlowDataRequest,
|
||||
InputValueRequest,
|
||||
ResultDataResponse,
|
||||
VertexBuildResponse,
|
||||
)
|
||||
from langflow.events.event_manager import EventManager
|
||||
from langflow.exceptions.component import ComponentBuildError
|
||||
from langflow.graph.graph.base import Graph
|
||||
from langflow.graph.utils import log_vertex_build
|
||||
from langflow.schema.message import ErrorMessage
|
||||
from langflow.schema.schema import OutputValue
|
||||
from langflow.services.database.models.flow import Flow
|
||||
from langflow.services.deps import get_chat_service, get_telemetry_service, session_scope
|
||||
from langflow.services.job_queue.service import JobQueueService
|
||||
from langflow.services.telemetry.schema import ComponentPayload, PlaygroundPayload
|
||||
|
||||
|
||||
async def start_flow_build(
|
||||
*,
|
||||
flow_id: uuid.UUID,
|
||||
background_tasks: BackgroundTasks,
|
||||
inputs: InputValueRequest | None,
|
||||
data: FlowDataRequest | None,
|
||||
files: list[str] | None,
|
||||
stop_component_id: str | None,
|
||||
start_component_id: str | None,
|
||||
log_builds: bool,
|
||||
current_user: CurrentActiveUser,
|
||||
queue_service: JobQueueService,
|
||||
) -> str:
|
||||
"""Start the flow build process by setting up the queue and starting the build task.
|
||||
|
||||
Returns:
|
||||
the job_id.
|
||||
"""
|
||||
job_id = str(uuid.uuid4())
|
||||
try:
|
||||
_, event_manager = queue_service.create_queue(job_id)
|
||||
task_coro = generate_flow_events(
|
||||
flow_id=flow_id,
|
||||
background_tasks=background_tasks,
|
||||
event_manager=event_manager,
|
||||
inputs=inputs,
|
||||
data=data,
|
||||
files=files,
|
||||
stop_component_id=stop_component_id,
|
||||
start_component_id=start_component_id,
|
||||
log_builds=log_builds,
|
||||
current_user=current_user,
|
||||
)
|
||||
queue_service.start_job(job_id, task_coro)
|
||||
except Exception as e:
|
||||
logger.exception("Failed to create queue and start task")
|
||||
raise HTTPException(status_code=500, detail=str(e)) from e
|
||||
return job_id
|
||||
|
||||
|
||||
async def get_flow_events_response(
|
||||
*,
|
||||
job_id: str,
|
||||
queue_service: JobQueueService,
|
||||
stream: bool = True,
|
||||
):
|
||||
"""Get events for a specific build job, either as a stream or single event."""
|
||||
try:
|
||||
main_queue, event_manager, event_task = queue_service.get_queue_data(job_id)
|
||||
if stream:
|
||||
if event_task is None:
|
||||
raise HTTPException(status_code=404, detail="No event task found for job")
|
||||
return await create_flow_response(
|
||||
queue=main_queue,
|
||||
event_manager=event_manager,
|
||||
event_task=event_task,
|
||||
)
|
||||
|
||||
# Polling mode - get exactly one event
|
||||
_, value, _ = await main_queue.get()
|
||||
if value is None:
|
||||
# End of stream, trigger end event
|
||||
if event_task is not None:
|
||||
event_task.cancel()
|
||||
event_manager.on_end(data={})
|
||||
|
||||
return JSONResponse({"event": value.decode("utf-8") if value else None})
|
||||
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
|
||||
async def create_flow_response(
|
||||
queue: asyncio.Queue,
|
||||
event_manager: EventManager,
|
||||
event_task: asyncio.Task,
|
||||
) -> DisconnectHandlerStreamingResponse:
|
||||
"""Create a streaming response for the flow build process."""
|
||||
|
||||
async def consume_and_yield() -> AsyncIterator[str]:
|
||||
while True:
|
||||
try:
|
||||
event_id, value, put_time = await queue.get()
|
||||
if value is None:
|
||||
break
|
||||
get_time = time.time()
|
||||
yield value.decode("utf-8")
|
||||
logger.debug(f"Event {event_id} consumed in {get_time - put_time:.4f}s")
|
||||
except Exception as exc: # noqa: BLE001
|
||||
logger.exception(f"Error consuming event: {exc}")
|
||||
break
|
||||
|
||||
def on_disconnect() -> None:
|
||||
logger.debug("Client disconnected, closing tasks")
|
||||
event_task.cancel()
|
||||
event_manager.on_end(data={})
|
||||
|
||||
return DisconnectHandlerStreamingResponse(
|
||||
consume_and_yield(),
|
||||
media_type="application/x-ndjson",
|
||||
on_disconnect=on_disconnect,
|
||||
)
|
||||
|
||||
|
||||
async def generate_flow_events(
|
||||
*,
|
||||
flow_id: uuid.UUID,
|
||||
background_tasks: BackgroundTasks,
|
||||
event_manager: EventManager,
|
||||
inputs: InputValueRequest | None,
|
||||
data: FlowDataRequest | None,
|
||||
files: list[str] | None,
|
||||
stop_component_id: str | None,
|
||||
start_component_id: str | None,
|
||||
log_builds: bool,
|
||||
current_user: CurrentActiveUser,
|
||||
) -> None:
|
||||
"""Generate events for flow building process.
|
||||
|
||||
This function handles the core flow building logic and generates appropriate events:
|
||||
- Building and validating the graph
|
||||
- Processing vertices
|
||||
- Handling errors and cleanup
|
||||
"""
|
||||
chat_service = get_chat_service()
|
||||
telemetry_service = get_telemetry_service()
|
||||
if not inputs:
|
||||
inputs = InputValueRequest(session=str(flow_id))
|
||||
|
||||
async def build_graph_and_get_order() -> tuple[list[str], list[str], Graph]:
|
||||
start_time = time.perf_counter()
|
||||
components_count = 0
|
||||
graph = None
|
||||
try:
|
||||
flow_id_str = str(flow_id)
|
||||
# Create a fresh session for database operations
|
||||
async with session_scope() as fresh_session:
|
||||
graph = await create_graph(fresh_session, flow_id_str)
|
||||
|
||||
graph.validate_stream()
|
||||
first_layer = sort_vertices(graph)
|
||||
|
||||
if inputs is not None and getattr(inputs, "session", None) is not None:
|
||||
graph.session_id = inputs.session
|
||||
|
||||
for vertex_id in first_layer:
|
||||
graph.run_manager.add_to_vertices_being_run(vertex_id)
|
||||
|
||||
# Now vertices is a list of lists
|
||||
# We need to get the id of each vertex
|
||||
# and return the same structure but only with the ids
|
||||
components_count = len(graph.vertices)
|
||||
vertices_to_run = list(graph.vertices_to_run.union(get_top_level_vertices(graph, graph.vertices_to_run)))
|
||||
|
||||
await chat_service.set_cache(flow_id_str, graph)
|
||||
await log_telemetry(start_time, components_count, success=True)
|
||||
|
||||
except Exception as exc:
|
||||
await log_telemetry(start_time, components_count, success=False, error_message=str(exc))
|
||||
|
||||
if "stream or streaming set to True" in str(exc):
|
||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||
logger.exception("Error checking build status")
|
||||
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
||||
return first_layer, vertices_to_run, graph
|
||||
|
||||
async def log_telemetry(
|
||||
start_time: float, components_count: int, *, success: bool, error_message: str | None = None
|
||||
):
|
||||
background_tasks.add_task(
|
||||
telemetry_service.log_package_playground,
|
||||
PlaygroundPayload(
|
||||
playground_seconds=int(time.perf_counter() - start_time),
|
||||
playground_component_count=components_count,
|
||||
playground_success=success,
|
||||
playground_error_message=str(error_message) if error_message else "",
|
||||
),
|
||||
)
|
||||
|
||||
async def create_graph(fresh_session, flow_id_str: str) -> Graph:
|
||||
if not data:
|
||||
return await build_graph_from_db(flow_id=flow_id, session=fresh_session, chat_service=chat_service)
|
||||
|
||||
result = await fresh_session.exec(select(Flow.name).where(Flow.id == flow_id))
|
||||
flow_name = result.first()
|
||||
|
||||
return await build_graph_from_data(
|
||||
flow_id=flow_id_str,
|
||||
payload=data.model_dump(),
|
||||
user_id=str(current_user.id),
|
||||
flow_name=flow_name,
|
||||
)
|
||||
|
||||
def sort_vertices(graph: Graph) -> list[str]:
|
||||
try:
|
||||
return graph.sort_vertices(stop_component_id, start_component_id)
|
||||
except Exception: # noqa: BLE001
|
||||
logger.exception("Error sorting vertices")
|
||||
return graph.sort_vertices()
|
||||
|
||||
async def _build_vertex(vertex_id: str, graph: Graph, event_manager: EventManager) -> VertexBuildResponse:
|
||||
flow_id_str = str(flow_id)
|
||||
next_runnable_vertices = []
|
||||
top_level_vertices = []
|
||||
start_time = time.perf_counter()
|
||||
error_message = None
|
||||
try:
|
||||
vertex = graph.get_vertex(vertex_id)
|
||||
try:
|
||||
lock = chat_service.async_cache_locks[flow_id_str]
|
||||
vertex_build_result = await graph.build_vertex(
|
||||
vertex_id=vertex_id,
|
||||
user_id=str(current_user.id),
|
||||
inputs_dict=inputs.model_dump() if inputs else {},
|
||||
files=files,
|
||||
get_cache=chat_service.get_cache,
|
||||
set_cache=chat_service.set_cache,
|
||||
event_manager=event_manager,
|
||||
)
|
||||
result_dict = vertex_build_result.result_dict
|
||||
params = vertex_build_result.params
|
||||
valid = vertex_build_result.valid
|
||||
artifacts = vertex_build_result.artifacts
|
||||
next_runnable_vertices = await graph.get_next_runnable_vertices(lock, vertex=vertex, cache=False)
|
||||
top_level_vertices = graph.get_top_level_vertices(next_runnable_vertices)
|
||||
|
||||
result_data_response = ResultDataResponse.model_validate(result_dict, from_attributes=True)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
if isinstance(exc, ComponentBuildError):
|
||||
params = exc.message
|
||||
tb = exc.formatted_traceback
|
||||
else:
|
||||
tb = traceback.format_exc()
|
||||
logger.exception("Error building Component")
|
||||
params = format_exception_message(exc)
|
||||
message = {"errorMessage": params, "stackTrace": tb}
|
||||
valid = False
|
||||
error_message = params
|
||||
output_label = vertex.outputs[0]["name"] if vertex.outputs else "output"
|
||||
outputs = {output_label: OutputValue(message=message, type="error")}
|
||||
result_data_response = ResultDataResponse(results={}, outputs=outputs)
|
||||
artifacts = {}
|
||||
background_tasks.add_task(graph.end_all_traces, error=exc)
|
||||
|
||||
result_data_response.message = artifacts
|
||||
|
||||
# Log the vertex build
|
||||
if not vertex.will_stream and log_builds:
|
||||
background_tasks.add_task(
|
||||
log_vertex_build,
|
||||
flow_id=flow_id_str,
|
||||
vertex_id=vertex_id,
|
||||
valid=valid,
|
||||
params=params,
|
||||
data=result_data_response,
|
||||
artifacts=artifacts,
|
||||
)
|
||||
else:
|
||||
await chat_service.set_cache(flow_id_str, graph)
|
||||
|
||||
timedelta = time.perf_counter() - start_time
|
||||
duration = format_elapsed_time(timedelta)
|
||||
result_data_response.duration = duration
|
||||
result_data_response.timedelta = timedelta
|
||||
vertex.add_build_time(timedelta)
|
||||
inactivated_vertices = list(graph.inactivated_vertices)
|
||||
graph.reset_inactivated_vertices()
|
||||
graph.reset_activated_vertices()
|
||||
# graph.stop_vertex tells us if the user asked
|
||||
# to stop the build of the graph at a certain vertex
|
||||
# if it is in next_vertices_ids, we need to remove other
|
||||
# vertices from next_vertices_ids
|
||||
if graph.stop_vertex and graph.stop_vertex in next_runnable_vertices:
|
||||
next_runnable_vertices = [graph.stop_vertex]
|
||||
|
||||
if not graph.run_manager.vertices_being_run and not next_runnable_vertices:
|
||||
background_tasks.add_task(graph.end_all_traces)
|
||||
|
||||
build_response = VertexBuildResponse(
|
||||
inactivated_vertices=list(set(inactivated_vertices)),
|
||||
next_vertices_ids=list(set(next_runnable_vertices)),
|
||||
top_level_vertices=list(set(top_level_vertices)),
|
||||
valid=valid,
|
||||
params=params,
|
||||
id=vertex.id,
|
||||
data=result_data_response,
|
||||
)
|
||||
background_tasks.add_task(
|
||||
telemetry_service.log_package_component,
|
||||
ComponentPayload(
|
||||
component_name=vertex_id.split("-")[0],
|
||||
component_seconds=int(time.perf_counter() - start_time),
|
||||
component_success=valid,
|
||||
component_error_message=error_message,
|
||||
),
|
||||
)
|
||||
except Exception as exc:
|
||||
background_tasks.add_task(
|
||||
telemetry_service.log_package_component,
|
||||
ComponentPayload(
|
||||
component_name=vertex_id.split("-")[0],
|
||||
component_seconds=int(time.perf_counter() - start_time),
|
||||
component_success=False,
|
||||
component_error_message=str(exc),
|
||||
),
|
||||
)
|
||||
logger.exception("Error building Component")
|
||||
message = parse_exception(exc)
|
||||
raise HTTPException(status_code=500, detail=message) from exc
|
||||
|
||||
return build_response
|
||||
|
||||
async def build_vertices(
|
||||
vertex_id: str,
|
||||
graph: Graph,
|
||||
event_manager: EventManager,
|
||||
) -> None:
|
||||
"""Build vertices and handle their events.
|
||||
|
||||
Args:
|
||||
vertex_id: The ID of the vertex to build
|
||||
graph: The graph instance
|
||||
event_manager: Manager for handling events
|
||||
"""
|
||||
try:
|
||||
vertex_build_response: VertexBuildResponse = await _build_vertex(vertex_id, graph, event_manager)
|
||||
except asyncio.CancelledError as exc:
|
||||
logger.exception(exc)
|
||||
raise
|
||||
|
||||
# send built event or error event
|
||||
try:
|
||||
vertex_build_response_json = vertex_build_response.model_dump_json()
|
||||
build_data = json.loads(vertex_build_response_json)
|
||||
except Exception as exc:
|
||||
msg = f"Error serializing vertex build response: {exc}"
|
||||
raise ValueError(msg) from exc
|
||||
|
||||
event_manager.on_end_vertex(data={"build_data": build_data})
|
||||
|
||||
if vertex_build_response.valid and vertex_build_response.next_vertices_ids:
|
||||
tasks = []
|
||||
for next_vertex_id in vertex_build_response.next_vertices_ids:
|
||||
task = asyncio.create_task(
|
||||
build_vertices(
|
||||
next_vertex_id,
|
||||
graph,
|
||||
event_manager,
|
||||
)
|
||||
)
|
||||
tasks.append(task)
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
try:
|
||||
ids, vertices_to_run, graph = await build_graph_and_get_order()
|
||||
except Exception as e:
|
||||
error_message = ErrorMessage(
|
||||
flow_id=flow_id,
|
||||
exception=e,
|
||||
)
|
||||
event_manager.on_error(data=error_message.data)
|
||||
raise
|
||||
|
||||
event_manager.on_vertices_sorted(data={"ids": ids, "to_run": vertices_to_run})
|
||||
|
||||
tasks = []
|
||||
for vertex_id in ids:
|
||||
task = asyncio.create_task(build_vertices(vertex_id, graph, event_manager))
|
||||
tasks.append(task)
|
||||
try:
|
||||
await asyncio.gather(*tasks)
|
||||
except asyncio.CancelledError:
|
||||
background_tasks.add_task(graph.end_all_traces)
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error building vertices: {e}")
|
||||
custom_component = graph.get_vertex(vertex_id).custom_component
|
||||
trace_name = getattr(custom_component, "trace_name", None)
|
||||
error_message = ErrorMessage(
|
||||
flow_id=flow_id,
|
||||
exception=e,
|
||||
session_id=graph.session_id,
|
||||
trace_name=trace_name,
|
||||
)
|
||||
event_manager.on_error(data=error_message.data)
|
||||
raise
|
||||
event_manager.on_end(data={})
|
||||
await event_manager.queue.put((None, None, time.time()))
|
||||
31
src/backend/base/langflow/api/disconnect.py
Normal file
31
src/backend/base/langflow/api/disconnect.py
Normal file
@ -0,0 +1,31 @@
|
||||
import asyncio
|
||||
import typing
|
||||
|
||||
from fastapi.responses import StreamingResponse
|
||||
from starlette.background import BackgroundTask
|
||||
from starlette.responses import ContentStream
|
||||
from starlette.types import Receive
|
||||
|
||||
|
||||
class DisconnectHandlerStreamingResponse(StreamingResponse):
|
||||
def __init__(
|
||||
self,
|
||||
content: ContentStream,
|
||||
status_code: int = 200,
|
||||
headers: typing.Mapping[str, str] | None = None,
|
||||
media_type: str | None = None,
|
||||
background: BackgroundTask | None = None,
|
||||
on_disconnect: typing.Callable | None = None,
|
||||
):
|
||||
super().__init__(content, status_code, headers, media_type, background)
|
||||
self.on_disconnect = on_disconnect
|
||||
|
||||
async def listen_for_disconnect(self, receive: Receive) -> None:
|
||||
while True:
|
||||
message = await receive()
|
||||
if message["type"] == "http.disconnect":
|
||||
if self.on_disconnect:
|
||||
coro = self.on_disconnect()
|
||||
if asyncio.iscoroutine(coro):
|
||||
await coro
|
||||
break
|
||||
29
src/backend/base/langflow/api/limited_background_tasks.py
Normal file
29
src/backend/base/langflow/api/limited_background_tasks.py
Normal file
@ -0,0 +1,29 @@
|
||||
from fastapi import BackgroundTasks
|
||||
|
||||
from langflow.graph.utils import log_vertex_build
|
||||
from langflow.services.deps import get_settings_service
|
||||
|
||||
|
||||
class LimitVertexBuildBackgroundTasks(BackgroundTasks):
|
||||
"""A subclass of FastAPI BackgroundTasks that limits the number of tasks added per vertex_id.
|
||||
|
||||
If more than max_vertex_builds_per_vertex tasks are added for a given vertex_id,
|
||||
the oldest task is removed so that only the most recent remain.
|
||||
This only applies to log_vertex_build tasks.
|
||||
"""
|
||||
|
||||
def add_task(self, func, *args, **kwargs):
|
||||
# Only apply limiting logic to log_vertex_build tasks
|
||||
if func == log_vertex_build:
|
||||
vertex_id = kwargs.get("vertex_id")
|
||||
if vertex_id is not None:
|
||||
# Filter tasks that are log_vertex_build calls with the same vertex_id
|
||||
relevant_tasks = [
|
||||
t for t in self.tasks if t.func == log_vertex_build and t.kwargs.get("vertex_id") == vertex_id
|
||||
]
|
||||
if len(relevant_tasks) >= get_settings_service().settings.max_vertex_builds_per_vertex:
|
||||
# Remove the oldest task for this vertex_id
|
||||
oldest_task = relevant_tasks[0]
|
||||
self.tasks.remove(oldest_task)
|
||||
|
||||
super().add_task(func, *args, **kwargs)
|
||||
@ -14,6 +14,7 @@ from langflow.graph.graph.base import Graph
|
||||
from langflow.services.auth.utils import get_current_active_user
|
||||
from langflow.services.database.models import User
|
||||
from langflow.services.database.models.flow import Flow
|
||||
from langflow.services.database.models.message import MessageTable
|
||||
from langflow.services.database.models.transactions.model import TransactionTable
|
||||
from langflow.services.database.models.vertex_builds.model import VertexBuildTable
|
||||
from langflow.services.deps import get_session, session_scope
|
||||
@ -281,16 +282,16 @@ def parse_value(value: Any, input_type: str) -> Any:
|
||||
|
||||
async def cascade_delete_flow(session: AsyncSession, flow_id: uuid.UUID) -> None:
|
||||
try:
|
||||
await session.exec(delete(TransactionTable).where(TransactionTable.flow_id == flow_id))
|
||||
await session.exec(delete(VertexBuildTable).where(VertexBuildTable.flow_id == flow_id))
|
||||
# TODO: Verify if deleting messages is safe in terms of session id relevance
|
||||
# If we delete messages directly, rather than setting flow_id to null,
|
||||
# it might cause unexpected behaviors because the session id could still be
|
||||
# used elsewhere to search for these messages.
|
||||
# await session.exec(delete(MessageTable).where(MessageTable.flow_id == flow_id))
|
||||
await session.exec(delete(MessageTable).where(MessageTable.flow_id == flow_id))
|
||||
await session.exec(delete(TransactionTable).where(TransactionTable.flow_id == flow_id))
|
||||
await session.exec(delete(VertexBuildTable).where(VertexBuildTable.flow_id == flow_id))
|
||||
await session.exec(delete(Flow).where(Flow.id == flow_id))
|
||||
except Exception as e:
|
||||
msg = f"Unable to cascade delete flow: ${flow_id}"
|
||||
msg = f"Unable to cascade delete flow: {flow_id}"
|
||||
raise RuntimeError(msg, e) from e
|
||||
|
||||
|
||||
|
||||
@ -1,26 +1,23 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import time
|
||||
import traceback
|
||||
import typing
|
||||
import uuid
|
||||
from typing import TYPE_CHECKING, Annotated
|
||||
|
||||
from fastapi import APIRouter, BackgroundTasks, Body, HTTPException
|
||||
from fastapi import APIRouter, BackgroundTasks, Body, Depends, HTTPException
|
||||
from fastapi.responses import StreamingResponse
|
||||
from loguru import logger
|
||||
from sqlmodel import select
|
||||
from starlette.background import BackgroundTask
|
||||
from starlette.responses import ContentStream
|
||||
from starlette.types import Receive
|
||||
|
||||
from langflow.api.build import (
|
||||
get_flow_events_response,
|
||||
start_flow_build,
|
||||
)
|
||||
from langflow.api.limited_background_tasks import LimitVertexBuildBackgroundTasks
|
||||
from langflow.api.utils import (
|
||||
CurrentActiveUser,
|
||||
DbSession,
|
||||
build_and_cache_graph_from_data,
|
||||
build_graph_from_data,
|
||||
build_graph_from_db,
|
||||
format_elapsed_time,
|
||||
format_exception_message,
|
||||
@ -35,16 +32,21 @@ from langflow.api.v1.schemas import (
|
||||
VertexBuildResponse,
|
||||
VerticesOrderResponse,
|
||||
)
|
||||
from langflow.events.event_manager import EventManager, create_default_event_manager
|
||||
from langflow.exceptions.component import ComponentBuildError
|
||||
from langflow.graph.graph.base import Graph
|
||||
from langflow.graph.utils import log_vertex_build
|
||||
from langflow.schema.message import ErrorMessage
|
||||
from langflow.schema.schema import OutputValue
|
||||
from langflow.services.cache.utils import CacheMiss
|
||||
from langflow.services.chat.service import ChatService
|
||||
from langflow.services.database.models.flow.model import Flow
|
||||
from langflow.services.deps import get_chat_service, get_session, get_telemetry_service, session_scope
|
||||
from langflow.services.deps import (
|
||||
get_chat_service,
|
||||
get_queue_service,
|
||||
get_session,
|
||||
get_telemetry_service,
|
||||
session_scope,
|
||||
)
|
||||
from langflow.services.job_queue.service import JobQueueService
|
||||
from langflow.services.telemetry.schema import ComponentPayload, PlaygroundPayload
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -53,22 +55,6 @@ if TYPE_CHECKING:
|
||||
router = APIRouter(tags=["Chat"])
|
||||
|
||||
|
||||
async def try_running_celery_task(vertex, user_id):
|
||||
# Try running the task in celery
|
||||
# and set the task_id to the local vertex
|
||||
# if it fails, run the task locally
|
||||
try:
|
||||
from langflow.worker import build_vertex
|
||||
|
||||
task = build_vertex.delay(vertex)
|
||||
vertex.task_id = task.id
|
||||
except Exception: # noqa: BLE001
|
||||
logger.opt(exception=True).debug("Error running task in celery")
|
||||
vertex.task_id = None
|
||||
await vertex.build(user_id=user_id)
|
||||
return vertex
|
||||
|
||||
|
||||
@router.post("/build/{flow_id}/vertices", deprecated=True)
|
||||
async def retrieve_vertices_order(
|
||||
*,
|
||||
@ -143,331 +129,52 @@ async def retrieve_vertices_order(
|
||||
@router.post("/build/{flow_id}/flow")
|
||||
async def build_flow(
|
||||
*,
|
||||
background_tasks: BackgroundTasks,
|
||||
flow_id: uuid.UUID,
|
||||
background_tasks: LimitVertexBuildBackgroundTasks,
|
||||
inputs: Annotated[InputValueRequest | None, Body(embed=True)] = None,
|
||||
data: Annotated[FlowDataRequest | None, Body(embed=True)] = None,
|
||||
files: list[str] | None = None,
|
||||
stop_component_id: str | None = None,
|
||||
start_component_id: str | None = None,
|
||||
log_builds: bool | None = True,
|
||||
log_builds: bool = True,
|
||||
current_user: CurrentActiveUser,
|
||||
queue_service: Annotated[JobQueueService, Depends(get_queue_service)],
|
||||
):
|
||||
chat_service = get_chat_service()
|
||||
telemetry_service = get_telemetry_service()
|
||||
if not inputs:
|
||||
inputs = InputValueRequest(session=str(flow_id))
|
||||
"""Build and process a flow, returning a job ID for event polling."""
|
||||
# First verify the flow exists
|
||||
async with session_scope() as session:
|
||||
flow = await session.get(Flow, flow_id)
|
||||
if not flow:
|
||||
raise HTTPException(status_code=404, detail=f"Flow with id {flow_id} not found")
|
||||
|
||||
async def build_graph_and_get_order() -> tuple[list[str], list[str], Graph]:
|
||||
start_time = time.perf_counter()
|
||||
components_count = 0
|
||||
graph = None
|
||||
try:
|
||||
flow_id_str = str(flow_id)
|
||||
# Create a fresh session for database operations
|
||||
async with session_scope() as fresh_session:
|
||||
graph = await create_graph(fresh_session, flow_id_str)
|
||||
|
||||
graph.validate_stream()
|
||||
first_layer = sort_vertices(graph)
|
||||
|
||||
if inputs is not None and hasattr(inputs, "session") and inputs.session is not None:
|
||||
graph.session_id = inputs.session
|
||||
|
||||
for vertex_id in first_layer:
|
||||
graph.run_manager.add_to_vertices_being_run(vertex_id)
|
||||
|
||||
# Now vertices is a list of lists
|
||||
# We need to get the id of each vertex
|
||||
# and return the same structure but only with the ids
|
||||
components_count = len(graph.vertices)
|
||||
vertices_to_run = list(graph.vertices_to_run.union(get_top_level_vertices(graph, graph.vertices_to_run)))
|
||||
|
||||
await chat_service.set_cache(flow_id_str, graph)
|
||||
await log_telemetry(start_time, components_count, success=True)
|
||||
|
||||
except Exception as exc:
|
||||
await log_telemetry(start_time, components_count, success=False, error_message=str(exc))
|
||||
|
||||
if "stream or streaming set to True" in str(exc):
|
||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||
logger.exception("Error checking build status")
|
||||
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
||||
return first_layer, vertices_to_run, graph
|
||||
|
||||
async def log_telemetry(
|
||||
start_time: float, components_count: int, *, success: bool, error_message: str | None = None
|
||||
):
|
||||
background_tasks.add_task(
|
||||
telemetry_service.log_package_playground,
|
||||
PlaygroundPayload(
|
||||
playground_seconds=int(time.perf_counter() - start_time),
|
||||
playground_component_count=components_count,
|
||||
playground_success=success,
|
||||
playground_error_message=str(error_message) if error_message else "",
|
||||
),
|
||||
)
|
||||
|
||||
async def create_graph(fresh_session, flow_id_str: str) -> Graph:
|
||||
if not data:
|
||||
return await build_graph_from_db(flow_id=flow_id, session=fresh_session, chat_service=chat_service)
|
||||
|
||||
result = await fresh_session.exec(select(Flow.name).where(Flow.id == flow_id))
|
||||
flow_name = result.first()
|
||||
|
||||
return await build_graph_from_data(
|
||||
flow_id=flow_id_str,
|
||||
payload=data.model_dump(),
|
||||
user_id=str(current_user.id),
|
||||
flow_name=flow_name,
|
||||
)
|
||||
|
||||
def sort_vertices(graph: Graph) -> list[str]:
|
||||
try:
|
||||
return graph.sort_vertices(stop_component_id, start_component_id)
|
||||
except Exception: # noqa: BLE001
|
||||
logger.exception("Error sorting vertices")
|
||||
return graph.sort_vertices()
|
||||
|
||||
async def _build_vertex(vertex_id: str, graph: Graph, event_manager: EventManager) -> VertexBuildResponse:
|
||||
flow_id_str = str(flow_id)
|
||||
next_runnable_vertices = []
|
||||
top_level_vertices = []
|
||||
start_time = time.perf_counter()
|
||||
error_message = None
|
||||
try:
|
||||
vertex = graph.get_vertex(vertex_id)
|
||||
try:
|
||||
lock = chat_service.async_cache_locks[flow_id_str]
|
||||
vertex_build_result = await graph.build_vertex(
|
||||
vertex_id=vertex_id,
|
||||
user_id=str(current_user.id),
|
||||
inputs_dict=inputs.model_dump() if inputs else {},
|
||||
files=files,
|
||||
get_cache=chat_service.get_cache,
|
||||
set_cache=chat_service.set_cache,
|
||||
event_manager=event_manager,
|
||||
)
|
||||
result_dict = vertex_build_result.result_dict
|
||||
params = vertex_build_result.params
|
||||
valid = vertex_build_result.valid
|
||||
artifacts = vertex_build_result.artifacts
|
||||
next_runnable_vertices = await graph.get_next_runnable_vertices(lock, vertex=vertex, cache=False)
|
||||
top_level_vertices = graph.get_top_level_vertices(next_runnable_vertices)
|
||||
|
||||
result_data_response = ResultDataResponse.model_validate(result_dict, from_attributes=True)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
if isinstance(exc, ComponentBuildError):
|
||||
params = exc.message
|
||||
tb = exc.formatted_traceback
|
||||
else:
|
||||
tb = traceback.format_exc()
|
||||
logger.exception("Error building Component")
|
||||
params = format_exception_message(exc)
|
||||
message = {"errorMessage": params, "stackTrace": tb}
|
||||
valid = False
|
||||
error_message = params
|
||||
output_label = vertex.outputs[0]["name"] if vertex.outputs else "output"
|
||||
outputs = {output_label: OutputValue(message=message, type="error")}
|
||||
result_data_response = ResultDataResponse(results={}, outputs=outputs)
|
||||
artifacts = {}
|
||||
background_tasks.add_task(graph.end_all_traces, error=exc)
|
||||
|
||||
result_data_response.message = artifacts
|
||||
|
||||
# Log the vertex build
|
||||
if not vertex.will_stream and log_builds:
|
||||
background_tasks.add_task(
|
||||
log_vertex_build,
|
||||
flow_id=flow_id_str,
|
||||
vertex_id=vertex_id,
|
||||
valid=valid,
|
||||
params=params,
|
||||
data=result_data_response,
|
||||
artifacts=artifacts,
|
||||
)
|
||||
else:
|
||||
await chat_service.set_cache(flow_id_str, graph)
|
||||
|
||||
timedelta = time.perf_counter() - start_time
|
||||
duration = format_elapsed_time(timedelta)
|
||||
result_data_response.duration = duration
|
||||
result_data_response.timedelta = timedelta
|
||||
vertex.add_build_time(timedelta)
|
||||
inactivated_vertices = list(graph.inactivated_vertices)
|
||||
graph.reset_inactivated_vertices()
|
||||
graph.reset_activated_vertices()
|
||||
# graph.stop_vertex tells us if the user asked
|
||||
# to stop the build of the graph at a certain vertex
|
||||
# if it is in next_vertices_ids, we need to remove other
|
||||
# vertices from next_vertices_ids
|
||||
if graph.stop_vertex and graph.stop_vertex in next_runnable_vertices:
|
||||
next_runnable_vertices = [graph.stop_vertex]
|
||||
|
||||
if not graph.run_manager.vertices_being_run and not next_runnable_vertices:
|
||||
background_tasks.add_task(graph.end_all_traces)
|
||||
|
||||
build_response = VertexBuildResponse(
|
||||
inactivated_vertices=list(set(inactivated_vertices)),
|
||||
next_vertices_ids=list(set(next_runnable_vertices)),
|
||||
top_level_vertices=list(set(top_level_vertices)),
|
||||
valid=valid,
|
||||
params=params,
|
||||
id=vertex.id,
|
||||
data=result_data_response,
|
||||
)
|
||||
background_tasks.add_task(
|
||||
telemetry_service.log_package_component,
|
||||
ComponentPayload(
|
||||
component_name=vertex_id.split("-")[0],
|
||||
component_seconds=int(time.perf_counter() - start_time),
|
||||
component_success=valid,
|
||||
component_error_message=error_message,
|
||||
),
|
||||
)
|
||||
except Exception as exc:
|
||||
background_tasks.add_task(
|
||||
telemetry_service.log_package_component,
|
||||
ComponentPayload(
|
||||
component_name=vertex_id.split("-")[0],
|
||||
component_seconds=int(time.perf_counter() - start_time),
|
||||
component_success=False,
|
||||
component_error_message=str(exc),
|
||||
),
|
||||
)
|
||||
logger.exception("Error building Component")
|
||||
message = parse_exception(exc)
|
||||
raise HTTPException(status_code=500, detail=message) from exc
|
||||
|
||||
return build_response
|
||||
|
||||
async def build_vertices(
|
||||
vertex_id: str,
|
||||
graph: Graph,
|
||||
client_consumed_queue: asyncio.Queue,
|
||||
event_manager: EventManager,
|
||||
) -> None:
|
||||
build_task = asyncio.create_task(_build_vertex(vertex_id, graph, event_manager))
|
||||
try:
|
||||
await build_task
|
||||
vertex_build_response: VertexBuildResponse = build_task.result()
|
||||
except asyncio.CancelledError as exc:
|
||||
logger.exception(exc)
|
||||
build_task.cancel()
|
||||
return
|
||||
|
||||
# send built event or error event
|
||||
try:
|
||||
vertex_build_response_json = vertex_build_response.model_dump_json()
|
||||
build_data = json.loads(vertex_build_response_json)
|
||||
except Exception as exc:
|
||||
msg = f"Error serializing vertex build response: {exc}"
|
||||
raise ValueError(msg) from exc
|
||||
event_manager.on_end_vertex(data={"build_data": build_data})
|
||||
await client_consumed_queue.get()
|
||||
if vertex_build_response.valid and vertex_build_response.next_vertices_ids:
|
||||
tasks = []
|
||||
for next_vertex_id in vertex_build_response.next_vertices_ids:
|
||||
task = asyncio.create_task(build_vertices(next_vertex_id, graph, client_consumed_queue, event_manager))
|
||||
tasks.append(task)
|
||||
try:
|
||||
await asyncio.gather(*tasks)
|
||||
except asyncio.CancelledError:
|
||||
for task in tasks:
|
||||
task.cancel()
|
||||
return
|
||||
|
||||
async def event_generator(event_manager: EventManager, client_consumed_queue: asyncio.Queue) -> None:
|
||||
try:
|
||||
ids, vertices_to_run, graph = await build_graph_and_get_order()
|
||||
except Exception as e:
|
||||
error_message = ErrorMessage(
|
||||
flow_id=flow_id,
|
||||
exception=e,
|
||||
)
|
||||
event_manager.on_error(data=error_message.data)
|
||||
raise
|
||||
event_manager.on_vertices_sorted(data={"ids": ids, "to_run": vertices_to_run})
|
||||
await client_consumed_queue.get()
|
||||
tasks = []
|
||||
for vertex_id in ids:
|
||||
task = asyncio.create_task(build_vertices(vertex_id, graph, client_consumed_queue, event_manager))
|
||||
tasks.append(task)
|
||||
try:
|
||||
await asyncio.gather(*tasks)
|
||||
except asyncio.CancelledError:
|
||||
background_tasks.add_task(graph.end_all_traces)
|
||||
for task in tasks:
|
||||
task.cancel()
|
||||
return
|
||||
except Exception as e:
|
||||
logger.error(f"Error building vertices: {e}")
|
||||
custom_component = graph.get_vertex(vertex_id).custom_component
|
||||
trace_name = getattr(custom_component, "trace_name", None)
|
||||
error_message = ErrorMessage(
|
||||
flow_id=flow_id,
|
||||
exception=e,
|
||||
session_id=graph.session_id,
|
||||
trace_name=trace_name,
|
||||
)
|
||||
event_manager.on_error(data=error_message.data)
|
||||
raise
|
||||
event_manager.on_end(data={})
|
||||
await event_manager.queue.put((None, None, time.time))
|
||||
|
||||
async def consume_and_yield(queue: asyncio.Queue, client_consumed_queue: asyncio.Queue) -> typing.AsyncGenerator:
|
||||
while True:
|
||||
event_id, value, put_time = await queue.get()
|
||||
if value is None:
|
||||
break
|
||||
get_time = time.time()
|
||||
yield value
|
||||
get_time_yield = time.time()
|
||||
client_consumed_queue.put_nowait(event_id)
|
||||
logger.debug(
|
||||
f"consumed event {event_id} "
|
||||
f"(time in queue, {get_time - put_time:.4f}, "
|
||||
f"client {get_time_yield - get_time:.4f})"
|
||||
)
|
||||
|
||||
asyncio_queue: asyncio.Queue = asyncio.Queue()
|
||||
asyncio_queue_client_consumed: asyncio.Queue = asyncio.Queue()
|
||||
event_manager = create_default_event_manager(queue=asyncio_queue)
|
||||
main_task = asyncio.create_task(event_generator(event_manager, asyncio_queue_client_consumed))
|
||||
|
||||
def on_disconnect() -> None:
|
||||
logger.debug("Client disconnected, closing tasks")
|
||||
main_task.cancel()
|
||||
|
||||
return DisconnectHandlerStreamingResponse(
|
||||
consume_and_yield(asyncio_queue, asyncio_queue_client_consumed),
|
||||
media_type="application/x-ndjson",
|
||||
on_disconnect=on_disconnect,
|
||||
job_id = await start_flow_build(
|
||||
flow_id=flow_id,
|
||||
background_tasks=background_tasks,
|
||||
inputs=inputs,
|
||||
data=data,
|
||||
files=files,
|
||||
stop_component_id=stop_component_id,
|
||||
start_component_id=start_component_id,
|
||||
log_builds=log_builds,
|
||||
current_user=current_user,
|
||||
queue_service=queue_service,
|
||||
)
|
||||
return {"job_id": job_id}
|
||||
|
||||
|
||||
class DisconnectHandlerStreamingResponse(StreamingResponse):
|
||||
def __init__(
|
||||
self,
|
||||
content: ContentStream,
|
||||
status_code: int = 200,
|
||||
headers: typing.Mapping[str, str] | None = None,
|
||||
media_type: str | None = None,
|
||||
background: BackgroundTask | None = None,
|
||||
on_disconnect: typing.Callable | None = None,
|
||||
):
|
||||
super().__init__(content, status_code, headers, media_type, background)
|
||||
self.on_disconnect = on_disconnect
|
||||
|
||||
async def listen_for_disconnect(self, receive: Receive) -> None:
|
||||
while True:
|
||||
message = await receive()
|
||||
if message["type"] == "http.disconnect":
|
||||
if self.on_disconnect:
|
||||
coro = self.on_disconnect()
|
||||
if asyncio.iscoroutine(coro):
|
||||
await coro
|
||||
break
|
||||
@router.get("/build/{job_id}/events")
|
||||
async def get_build_events(
|
||||
job_id: str,
|
||||
queue_service: Annotated[JobQueueService, Depends(get_queue_service)],
|
||||
*,
|
||||
stream: bool = True,
|
||||
):
|
||||
"""Get events for a specific build job."""
|
||||
return await get_flow_events_response(
|
||||
job_id=job_id,
|
||||
queue_service=queue_service,
|
||||
stream=stream,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/build/{flow_id}/vertices/{vertex_id}", deprecated=True)
|
||||
|
||||
@ -44,7 +44,7 @@ from langflow.services.database.models.flow import Flow
|
||||
from langflow.services.database.models.flow.model import FlowRead
|
||||
from langflow.services.database.models.flow.utils import get_all_webhook_components_in_flow
|
||||
from langflow.services.database.models.user.model import User, UserRead
|
||||
from langflow.services.deps import get_session_service, get_settings_service, get_task_service, get_telemetry_service
|
||||
from langflow.services.deps import get_session_service, get_settings_service, get_telemetry_service
|
||||
from langflow.services.settings.feature_flags import FEATURE_FLAGS
|
||||
from langflow.services.telemetry.schema import RunPayload
|
||||
from langflow.utils.version import get_version_info
|
||||
@ -599,29 +599,16 @@ async def process() -> None:
|
||||
)
|
||||
|
||||
|
||||
@router.get("/task/{task_id}")
|
||||
async def get_task_status(task_id: str) -> TaskStatusResponse:
|
||||
task_service = get_task_service()
|
||||
task = task_service.get_task(task_id)
|
||||
result = None
|
||||
if task is None:
|
||||
raise HTTPException(status_code=404, detail="Task not found")
|
||||
if task.ready():
|
||||
result = task.result
|
||||
# If result isinstance of Exception, can we get the traceback?
|
||||
if isinstance(result, Exception):
|
||||
logger.exception(task.traceback)
|
||||
@router.get("/task/{_task_id}", deprecated=True)
|
||||
async def get_task_status(_task_id: str) -> TaskStatusResponse:
|
||||
"""Get the status of a task by ID (Deprecated).
|
||||
|
||||
if isinstance(result, dict) and "result" in result:
|
||||
result = result["result"]
|
||||
elif hasattr(result, "result"):
|
||||
result = result.result
|
||||
|
||||
if task.status == "FAILURE":
|
||||
result = str(task.result)
|
||||
logger.error(f"Task {task_id} failed: {task.traceback}")
|
||||
|
||||
return TaskStatusResponse(status=task.status, result=result)
|
||||
This endpoint is deprecated and will be removed in a future version.
|
||||
"""
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="The /task endpoint is deprecated and will be removed in a future version. Please use /run instead.",
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
|
||||
@ -3,7 +3,6 @@ import base64
|
||||
import json
|
||||
import logging
|
||||
import traceback
|
||||
from contextlib import suppress
|
||||
from contextvars import ContextVar
|
||||
from typing import Annotated
|
||||
from urllib.parse import quote, unquote, urlparse
|
||||
@ -266,8 +265,9 @@ async def handle_call_tool(
|
||||
return collected_results
|
||||
finally:
|
||||
progress_task.cancel()
|
||||
with suppress(asyncio.CancelledError):
|
||||
await progress_task
|
||||
await asyncio.wait([progress_task])
|
||||
if not progress_task.cancelled() and (exc := progress_task.exception()) is not None:
|
||||
raise exc
|
||||
except Exception as e:
|
||||
msg = f"Error in async session: {e}"
|
||||
logger.exception(msg)
|
||||
@ -333,6 +333,7 @@ async def handle_sse(request: Request, current_user: Annotated[User, Depends(get
|
||||
logger.info("Client disconnected from SSE connection")
|
||||
except asyncio.CancelledError:
|
||||
logger.info("SSE connection was cancelled")
|
||||
raise
|
||||
except Exception as e:
|
||||
msg = f"Error in MCP: {e!s}"
|
||||
logger.exception(msg)
|
||||
|
||||
@ -35,6 +35,7 @@ async def get_vertex_builds(flow_id: Annotated[UUID, Query()], session: DbSessio
|
||||
async def delete_vertex_builds(flow_id: Annotated[UUID, Query()], session: DbSession) -> None:
|
||||
try:
|
||||
await delete_vertex_builds_by_flow_id(session, flow_id)
|
||||
await session.commit()
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e)) from e
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
from datetime import datetime, timezone
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from typing import Any, Literal
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_serializer, field_validator, model_serializer
|
||||
@ -376,3 +376,4 @@ class ConfigResponse(BaseModel):
|
||||
auto_saving_interval: int
|
||||
health_check_max_retries: int
|
||||
max_file_size_upload: int
|
||||
event_delivery: Literal["polling", "streaming"]
|
||||
|
||||
@ -169,8 +169,9 @@ class LCAgentComponent(Component):
|
||||
cast("SendMessageFunctionType", self.send_message),
|
||||
)
|
||||
except ExceptionWithMessageError as e:
|
||||
msg_id = e.agent_message.id
|
||||
await delete_message(id_=msg_id)
|
||||
if hasattr(e, "agent_message") and hasattr(e.agent_message, "id"):
|
||||
msg_id = e.agent_message.id
|
||||
await delete_message(id_=msg_id)
|
||||
await self._send_message_event(e.agent_message, category="remove_message")
|
||||
logger.error(f"ExceptionWithMessageError: {e}")
|
||||
raise
|
||||
|
||||
@ -73,7 +73,7 @@ class HuggingFaceInferenceAPIEmbeddingsComponent(LCEmbeddingsModel):
|
||||
|
||||
def get_api_url(self) -> str:
|
||||
if "huggingface" in self.inference_endpoint.lower():
|
||||
return f"{self.inference_endpoint}{self.model_name}"
|
||||
return f"{self.inference_endpoint}"
|
||||
return self.inference_endpoint
|
||||
|
||||
@retry(stop=stop_after_attempt(3), wait=wait_fixed(2))
|
||||
|
||||
@ -26,6 +26,7 @@ class FirecrawlCrawlApi(Component):
|
||||
display_name="URL",
|
||||
required=True,
|
||||
info="The URL to scrape.",
|
||||
tool_mode=True,
|
||||
),
|
||||
IntInput(
|
||||
name="timeout",
|
||||
|
||||
@ -30,6 +30,7 @@ class FirecrawlScrapeApi(Component):
|
||||
display_name="URL",
|
||||
required=True,
|
||||
info="The URL to scrape.",
|
||||
tool_mode=True,
|
||||
),
|
||||
IntInput(
|
||||
name="timeout",
|
||||
|
||||
@ -1,9 +1,18 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from langflow.custom import Component
|
||||
from langflow.io import DataFrameInput, HandleInput, MultilineInput, Output, StrInput
|
||||
from langflow.io import (
|
||||
BoolInput,
|
||||
DataFrameInput,
|
||||
HandleInput,
|
||||
MessageTextInput,
|
||||
MultilineInput,
|
||||
Output,
|
||||
)
|
||||
from langflow.schema import DataFrame
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -14,8 +23,8 @@ class BatchRunComponent(Component):
|
||||
display_name = "Batch Run"
|
||||
description = (
|
||||
"Runs a language model over each row of a DataFrame's text column and returns a new "
|
||||
"DataFrame with two columns: 'text_input' (the original text) and 'model_response' "
|
||||
"containing the model's response."
|
||||
"DataFrame with three columns: '**text_input**' (the original text), "
|
||||
"'**model_response**' (the model's response),and '**batch_index**' (the processing order)."
|
||||
)
|
||||
icon = "List"
|
||||
beta = True
|
||||
@ -26,6 +35,7 @@ class BatchRunComponent(Component):
|
||||
display_name="Language Model",
|
||||
info="Connect the 'Language Model' output from your LLM component here.",
|
||||
input_types=["LanguageModel"],
|
||||
required=True,
|
||||
),
|
||||
MultilineInput(
|
||||
name="system_message",
|
||||
@ -37,12 +47,23 @@ class BatchRunComponent(Component):
|
||||
name="df",
|
||||
display_name="DataFrame",
|
||||
info="The DataFrame whose column (specified by 'column_name') we'll treat as text messages.",
|
||||
required=True,
|
||||
),
|
||||
StrInput(
|
||||
MessageTextInput(
|
||||
name="column_name",
|
||||
display_name="Column Name",
|
||||
info="The name of the DataFrame column to treat as text messages. Default='text'.",
|
||||
value="text",
|
||||
required=True,
|
||||
advanced=True,
|
||||
),
|
||||
BoolInput(
|
||||
name="enable_metadata",
|
||||
display_name="Enable Metadata",
|
||||
info="If True, add metadata to the output DataFrame.",
|
||||
value=True,
|
||||
required=False,
|
||||
advanced=True,
|
||||
),
|
||||
]
|
||||
|
||||
@ -51,51 +72,123 @@ class BatchRunComponent(Component):
|
||||
display_name="Batch Results",
|
||||
name="batch_results",
|
||||
method="run_batch",
|
||||
info="A DataFrame with two columns: 'text_input' and 'model_response'.",
|
||||
info="A DataFrame with columns: 'text_input', 'model_response', 'batch_index', and 'metadata'.",
|
||||
),
|
||||
]
|
||||
|
||||
async def run_batch(self) -> DataFrame:
|
||||
"""For each row in df[column_name], combine that text with system_message, then invoke the model asynchronously.
|
||||
def _create_base_row(self, text_input: str = "", model_response: str = "", batch_index: int = -1) -> dict[str, Any]:
|
||||
"""Create a base row with optional metadata."""
|
||||
return {
|
||||
"text_input": text_input,
|
||||
"model_response": model_response,
|
||||
"batch_index": batch_index,
|
||||
}
|
||||
|
||||
Returns a new DataFrame of the same length, with columns 'text_input' and 'model_response'.
|
||||
def _add_metadata(
|
||||
self, row: dict[str, Any], *, success: bool = True, system_msg: str = "", error: str | None = None
|
||||
) -> None:
|
||||
"""Add metadata to a row if enabled."""
|
||||
if not self.enable_metadata:
|
||||
return
|
||||
|
||||
if success:
|
||||
row["metadata"] = {
|
||||
"has_system_message": bool(system_msg),
|
||||
"input_length": len(row["text_input"]),
|
||||
"response_length": len(row["model_response"]),
|
||||
"processing_status": "success",
|
||||
}
|
||||
else:
|
||||
row["metadata"] = {
|
||||
"error": error,
|
||||
"processing_status": "failed",
|
||||
}
|
||||
|
||||
async def run_batch(self) -> DataFrame:
|
||||
"""Process each row in df[column_name] with the language model asynchronously.
|
||||
|
||||
Returns:
|
||||
DataFrame: A new DataFrame containing:
|
||||
- text_input: The original input text
|
||||
- model_response: The model's response
|
||||
- batch_index: The processing order
|
||||
- metadata: Additional processing information
|
||||
|
||||
Raises:
|
||||
ValueError: If the specified column is not found in the DataFrame
|
||||
TypeError: If the model is not compatible or input types are wrong
|
||||
"""
|
||||
model: Runnable = self.model
|
||||
system_msg = self.system_message or ""
|
||||
df: DataFrame = self.df
|
||||
col_name = self.column_name or "text"
|
||||
|
||||
# Validate inputs first
|
||||
if not isinstance(df, DataFrame):
|
||||
msg = f"Expected DataFrame input, got {type(df)}"
|
||||
raise TypeError(msg)
|
||||
|
||||
if col_name not in df.columns:
|
||||
msg = f"Column '{col_name}' not found in the DataFrame."
|
||||
msg = f"Column '{col_name}' not found in the DataFrame. Available columns: {', '.join(df.columns)}"
|
||||
raise ValueError(msg)
|
||||
|
||||
# Convert the specified column to a list of strings
|
||||
user_texts = df[col_name].astype(str).tolist()
|
||||
try:
|
||||
# Convert the specified column to a list of strings
|
||||
user_texts = df[col_name].astype(str).tolist()
|
||||
total_rows = len(user_texts)
|
||||
|
||||
# Prepare the batch of conversations
|
||||
conversations = [
|
||||
[{"role": "system", "content": system_msg}, {"role": "user", "content": text}]
|
||||
if system_msg
|
||||
else [{"role": "user", "content": text}]
|
||||
for text in user_texts
|
||||
]
|
||||
model = model.with_config(
|
||||
{
|
||||
"run_name": self.display_name,
|
||||
"project_name": self.get_project_name(),
|
||||
"callbacks": self.get_langchain_callbacks(),
|
||||
}
|
||||
)
|
||||
logger.info(f"Processing {total_rows} rows with batch run")
|
||||
|
||||
responses = await model.abatch(conversations)
|
||||
# Prepare the batch of conversations
|
||||
conversations = [
|
||||
[{"role": "system", "content": system_msg}, {"role": "user", "content": text}]
|
||||
if system_msg
|
||||
else [{"role": "user", "content": text}]
|
||||
for text in user_texts
|
||||
]
|
||||
|
||||
# Build the final data, each row has 'text_input' + 'model_response'
|
||||
rows = []
|
||||
for original_text, response in zip(user_texts, responses, strict=False):
|
||||
resp_text = response.content if hasattr(response, "content") else str(response)
|
||||
# Configure the model with project info and callbacks
|
||||
model = model.with_config(
|
||||
{
|
||||
"run_name": self.display_name,
|
||||
"project_name": self.get_project_name(),
|
||||
"callbacks": self.get_langchain_callbacks(),
|
||||
}
|
||||
)
|
||||
|
||||
row = {"text_input": original_text, "model_response": resp_text}
|
||||
rows.append(row)
|
||||
# Process batches and track progress
|
||||
responses_with_idx = [
|
||||
(idx, response)
|
||||
for idx, response in zip(
|
||||
range(len(conversations)), await model.abatch(list(conversations)), strict=True
|
||||
)
|
||||
]
|
||||
|
||||
# Convert to a new DataFrame
|
||||
return DataFrame(rows) # Langflow DataFrame from a list of dicts
|
||||
# Sort by index to maintain order
|
||||
responses_with_idx.sort(key=lambda x: x[0])
|
||||
|
||||
# Build the final data with enhanced metadata
|
||||
rows: list[dict[str, Any]] = []
|
||||
for idx, response in responses_with_idx:
|
||||
resp_text = response.content if hasattr(response, "content") else str(response)
|
||||
row = self._create_base_row(
|
||||
text_input=user_texts[idx],
|
||||
model_response=resp_text,
|
||||
batch_index=idx,
|
||||
)
|
||||
self._add_metadata(row, success=True, system_msg=system_msg)
|
||||
rows.append(row)
|
||||
|
||||
# Log progress
|
||||
if (idx + 1) % max(1, total_rows // 10) == 0:
|
||||
logger.info(f"Processed {idx + 1}/{total_rows} rows")
|
||||
|
||||
logger.info("Batch processing completed successfully")
|
||||
return DataFrame(rows)
|
||||
|
||||
except (KeyError, AttributeError) as e:
|
||||
# Handle data structure and attribute access errors
|
||||
logger.error(f"Data processing error: {e!s}")
|
||||
error_row = self._create_base_row()
|
||||
self._add_metadata(error_row, success=False, error=str(e))
|
||||
return DataFrame([error_row])
|
||||
|
||||
@ -61,10 +61,10 @@ class MessageStoreComponent(Component):
|
||||
self.memory.session_id = message.session_id
|
||||
lc_message = message.to_lc_message()
|
||||
await self.memory.aadd_messages([lc_message])
|
||||
stored_message = await self.memory.aget_messages()
|
||||
stored_message = [Message.from_lc_message(m) for m in stored_message]
|
||||
stored_messages = await self.memory.aget_messages()
|
||||
stored_messages = [Message.from_lc_message(m) for m in stored_messages]
|
||||
if message.sender:
|
||||
stored_message = [m for m in stored_message if m.sender == message.sender]
|
||||
stored_messages = [m for m in stored_messages if m.sender == message.sender]
|
||||
else:
|
||||
await astore_message(message, flow_id=self.graph.flow_id)
|
||||
stored_messages = await aget_messages(
|
||||
|
||||
3
src/backend/base/langflow/components/olivya/__init__.py
Normal file
3
src/backend/base/langflow/components/olivya/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
from .olivya import OlivyaComponent
|
||||
|
||||
__all__ = ["OlivyaComponent"]
|
||||
116
src/backend/base/langflow/components/olivya/olivya.py
Normal file
116
src/backend/base/langflow/components/olivya/olivya.py
Normal file
@ -0,0 +1,116 @@
|
||||
import json
|
||||
|
||||
import httpx
|
||||
from loguru import logger
|
||||
|
||||
from langflow.custom import Component
|
||||
from langflow.io import MessageTextInput, Output
|
||||
from langflow.schema import Data
|
||||
|
||||
|
||||
class OlivyaComponent(Component):
|
||||
display_name = "Place Call"
|
||||
description = "A component to create an outbound call request from Olivya's platform."
|
||||
documentation: str = "http://docs.langflow.org/components/olivya"
|
||||
icon = "Olivya"
|
||||
name = "OlivyaComponent"
|
||||
|
||||
inputs = [
|
||||
MessageTextInput(
|
||||
name="api_key",
|
||||
display_name="API Key",
|
||||
info="Your API key for authentication",
|
||||
value="",
|
||||
required=True,
|
||||
),
|
||||
MessageTextInput(
|
||||
name="from_number",
|
||||
display_name="From Number",
|
||||
info="The Agent's phone number",
|
||||
value="",
|
||||
required=True,
|
||||
),
|
||||
MessageTextInput(
|
||||
name="to_number",
|
||||
display_name="To Number",
|
||||
info="The recipient's phone number",
|
||||
value="",
|
||||
required=True,
|
||||
),
|
||||
MessageTextInput(
|
||||
name="first_message",
|
||||
display_name="First Message",
|
||||
info="The Agent's introductory message",
|
||||
value="",
|
||||
required=False,
|
||||
tool_mode=True,
|
||||
),
|
||||
MessageTextInput(
|
||||
name="system_prompt",
|
||||
display_name="System Prompt",
|
||||
info="The system prompt to guide the interaction",
|
||||
value="",
|
||||
required=False,
|
||||
),
|
||||
MessageTextInput(
|
||||
name="conversation_history",
|
||||
display_name="Conversation History",
|
||||
info="The summary of the conversation",
|
||||
value="",
|
||||
required=False,
|
||||
tool_mode=True,
|
||||
),
|
||||
]
|
||||
|
||||
outputs = [
|
||||
Output(display_name="Output", name="output", method="build_output"),
|
||||
]
|
||||
|
||||
async def build_output(self) -> Data:
|
||||
try:
|
||||
payload = {
|
||||
"variables": {
|
||||
"first_message": self.first_message.strip() if self.first_message else None,
|
||||
"system_prompt": self.system_prompt.strip() if self.system_prompt else None,
|
||||
"conversation_history": self.conversation_history.strip() if self.conversation_history else None,
|
||||
},
|
||||
"from_number": self.from_number.strip(),
|
||||
"to_number": self.to_number.strip(),
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Authorization": self.api_key.strip(),
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
logger.info("Sending POST request with payload: %s", payload)
|
||||
|
||||
# Send the POST request with a timeout
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.post(
|
||||
"https://phone.olivya.io/create_zap_call",
|
||||
headers=headers,
|
||||
json=payload,
|
||||
timeout=10.0,
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
# Parse and return the successful response
|
||||
response_data = response.json()
|
||||
logger.info("Request successful: %s", response_data)
|
||||
|
||||
except httpx.HTTPStatusError as http_err:
|
||||
logger.exception("HTTP error occurred")
|
||||
response_data = {"error": f"HTTP error occurred: {http_err}", "response_text": response.text}
|
||||
except httpx.RequestError as req_err:
|
||||
logger.exception("Request failed")
|
||||
response_data = {"error": f"Request failed: {req_err}"}
|
||||
except json.JSONDecodeError as json_err:
|
||||
logger.exception("Response parsing failed")
|
||||
response_data = {"error": f"Response parsing failed: {json_err}", "raw_response": response.text}
|
||||
except Exception as e: # noqa: BLE001
|
||||
logger.exception("An unexpected error occurred")
|
||||
response_data = {"error": f"An unexpected error occurred: {e!s}"}
|
||||
|
||||
# Return the response as part of the output
|
||||
return Data(value=response_data)
|
||||
@ -0,0 +1,68 @@
|
||||
from langflow.custom import Component
|
||||
from langflow.io import DataInput, Output
|
||||
from langflow.schema import Data, DataFrame
|
||||
|
||||
|
||||
class DataToDataFrameComponent(Component):
|
||||
display_name = "Data → DataFrame"
|
||||
description = (
|
||||
"Converts one or multiple Data objects into a DataFrame. "
|
||||
"Each Data object corresponds to one row. Fields from `.data` become columns, "
|
||||
"and the `.text` (if present) is placed in a 'text' column."
|
||||
)
|
||||
icon = "table"
|
||||
name = "DataToDataFrame"
|
||||
|
||||
inputs = [
|
||||
DataInput(
|
||||
name="data_list",
|
||||
display_name="Data or Data List",
|
||||
info="One or multiple Data objects to transform into a DataFrame.",
|
||||
is_list=True,
|
||||
),
|
||||
]
|
||||
|
||||
outputs = [
|
||||
Output(
|
||||
display_name="DataFrame",
|
||||
name="dataframe",
|
||||
method="build_dataframe",
|
||||
info="A DataFrame built from each Data object's fields plus a 'text' column.",
|
||||
),
|
||||
]
|
||||
|
||||
def build_dataframe(self) -> DataFrame:
|
||||
"""Builds a DataFrame from Data objects by combining their fields.
|
||||
|
||||
For each Data object:
|
||||
- Merge item.data (dictionary) as columns
|
||||
- If item.text is present, add 'text' column
|
||||
|
||||
Returns a DataFrame with one row per Data object.
|
||||
"""
|
||||
data_input = self.data_list
|
||||
|
||||
# If user passed a single Data, it might come in as a single object rather than a list
|
||||
if not isinstance(data_input, list):
|
||||
data_input = [data_input]
|
||||
|
||||
rows = []
|
||||
for item in data_input:
|
||||
if not isinstance(item, Data):
|
||||
msg = f"Expected Data objects, got {type(item)} instead."
|
||||
raise TypeError(msg)
|
||||
|
||||
# Start with a copy of item.data or an empty dict
|
||||
row_dict = dict(item.data) if item.data else {}
|
||||
|
||||
# If the Data object has text, store it under 'text' col
|
||||
text_val = item.get_text()
|
||||
if text_val:
|
||||
row_dict["text"] = text_val
|
||||
|
||||
rows.append(row_dict)
|
||||
|
||||
# Build a DataFrame from these row dictionaries
|
||||
df_result = DataFrame(rows)
|
||||
self.status = df_result # store in self.status for logs
|
||||
return df_result
|
||||
@ -10,9 +10,18 @@ class ParseDataComponent(Component):
|
||||
description = "Convert Data objects into Messages using any {field_name} from input data."
|
||||
icon = "message-square"
|
||||
name = "ParseData"
|
||||
metadata = {
|
||||
"legacy_name": "Parse Data",
|
||||
}
|
||||
|
||||
inputs = [
|
||||
DataInput(name="data", display_name="Data", info="The data to convert to text.", is_list=True, required=True),
|
||||
DataInput(
|
||||
name="data",
|
||||
display_name="Data",
|
||||
info="The data to convert to text.",
|
||||
is_list=True,
|
||||
required=True,
|
||||
),
|
||||
MultilineInput(
|
||||
name="template",
|
||||
display_name="Template",
|
||||
|
||||
172
src/backend/base/langflow/components/processing/save_to_file.py
Normal file
172
src/backend/base/langflow/components/processing/save_to_file.py
Normal file
@ -0,0 +1,172 @@
|
||||
import json
|
||||
from collections.abc import AsyncIterator, Iterator
|
||||
from pathlib import Path
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from langflow.custom import Component
|
||||
from langflow.io import (
|
||||
DataFrameInput,
|
||||
DataInput,
|
||||
DropdownInput,
|
||||
MessageInput,
|
||||
Output,
|
||||
StrInput,
|
||||
)
|
||||
from langflow.schema import Data, DataFrame, Message
|
||||
|
||||
|
||||
class SaveToFileComponent(Component):
|
||||
display_name = "Save to File"
|
||||
description = "Save DataFrames, Data, or Messages to various file formats."
|
||||
icon = "save"
|
||||
name = "SaveToFile"
|
||||
|
||||
# File format options for different types
|
||||
DATA_FORMAT_CHOICES = ["csv", "excel", "json", "markdown"]
|
||||
MESSAGE_FORMAT_CHOICES = ["txt", "json", "markdown"]
|
||||
|
||||
inputs = [
|
||||
DropdownInput(
|
||||
name="input_type",
|
||||
display_name="Input Type",
|
||||
options=["DataFrame", "Data", "Message"],
|
||||
info="Select the type of input to save.",
|
||||
value="DataFrame",
|
||||
real_time_refresh=True,
|
||||
),
|
||||
DataFrameInput(
|
||||
name="df",
|
||||
display_name="DataFrame",
|
||||
info="The DataFrame to save.",
|
||||
dynamic=True,
|
||||
show=True,
|
||||
),
|
||||
DataInput(
|
||||
name="data",
|
||||
display_name="Data",
|
||||
info="The Data object to save.",
|
||||
dynamic=True,
|
||||
show=False,
|
||||
),
|
||||
MessageInput(
|
||||
name="message",
|
||||
display_name="Message",
|
||||
info="The Message to save.",
|
||||
dynamic=True,
|
||||
show=False,
|
||||
),
|
||||
DropdownInput(
|
||||
name="file_format",
|
||||
display_name="File Format",
|
||||
options=DATA_FORMAT_CHOICES,
|
||||
info="Select the file format to save the input.",
|
||||
real_time_refresh=True,
|
||||
),
|
||||
StrInput(
|
||||
name="file_path",
|
||||
display_name="File Path (including filename)",
|
||||
info="The full file path (including filename and extension).",
|
||||
value="./output",
|
||||
),
|
||||
]
|
||||
|
||||
outputs = [
|
||||
Output(
|
||||
name="confirmation",
|
||||
display_name="Confirmation",
|
||||
method="save_to_file",
|
||||
info="Confirmation message after saving the file.",
|
||||
),
|
||||
]
|
||||
|
||||
def update_build_config(self, build_config, field_value, field_name=None):
|
||||
# Hide/show dynamic fields based on the selected input type
|
||||
if field_name == "input_type":
|
||||
build_config["df"]["show"] = field_value == "DataFrame"
|
||||
build_config["data"]["show"] = field_value == "Data"
|
||||
build_config["message"]["show"] = field_value == "Message"
|
||||
|
||||
if field_value in ["DataFrame", "Data"]:
|
||||
build_config["file_format"]["options"] = self.DATA_FORMAT_CHOICES
|
||||
elif field_value == "Message":
|
||||
build_config["file_format"]["options"] = self.MESSAGE_FORMAT_CHOICES
|
||||
|
||||
return build_config
|
||||
|
||||
def save_to_file(self) -> str:
|
||||
input_type = self.input_type
|
||||
file_format = self.file_format
|
||||
file_path = Path(self.file_path).expanduser()
|
||||
|
||||
# Ensure the directory exists
|
||||
if not file_path.parent.exists():
|
||||
file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if input_type == "DataFrame":
|
||||
dataframe = self.df
|
||||
return self._save_dataframe(dataframe, file_path, file_format)
|
||||
if input_type == "Data":
|
||||
data = self.data
|
||||
return self._save_data(data, file_path, file_format)
|
||||
if input_type == "Message":
|
||||
message = self.message
|
||||
return self._save_message(message, file_path, file_format)
|
||||
|
||||
error_msg = f"Unsupported input type: {input_type}"
|
||||
raise ValueError(error_msg)
|
||||
|
||||
def _save_dataframe(self, dataframe: DataFrame, path: Path, fmt: str) -> str:
|
||||
if fmt == "csv":
|
||||
dataframe.to_csv(path, index=False)
|
||||
elif fmt == "excel":
|
||||
dataframe.to_excel(path, index=False, engine="openpyxl")
|
||||
elif fmt == "json":
|
||||
dataframe.to_json(path, orient="records", indent=2)
|
||||
elif fmt == "markdown":
|
||||
path.write_text(dataframe.to_markdown(index=False), encoding="utf-8")
|
||||
else:
|
||||
error_msg = f"Unsupported DataFrame format: {fmt}"
|
||||
raise ValueError(error_msg)
|
||||
|
||||
return f"DataFrame saved successfully as '{path}'"
|
||||
|
||||
def _save_data(self, data: Data, path: Path, fmt: str) -> str:
|
||||
if fmt == "csv":
|
||||
pd.DataFrame(data.data).to_csv(path, index=False)
|
||||
elif fmt == "excel":
|
||||
pd.DataFrame(data.data).to_excel(path, index=False, engine="openpyxl")
|
||||
elif fmt == "json":
|
||||
path.write_text(json.dumps(data.data, indent=2), encoding="utf-8")
|
||||
elif fmt == "markdown":
|
||||
path.write_text(pd.DataFrame(data.data).to_markdown(index=False), encoding="utf-8")
|
||||
else:
|
||||
error_msg = f"Unsupported Data format: {fmt}"
|
||||
raise ValueError(error_msg)
|
||||
|
||||
return f"Data saved successfully as '{path}'"
|
||||
|
||||
def _save_message(self, message: Message, path: Path, fmt: str) -> str:
|
||||
if message.text is None:
|
||||
content = ""
|
||||
elif isinstance(message.text, AsyncIterator):
|
||||
# AsyncIterator needs to be handled differently
|
||||
error_msg = "AsyncIterator not supported"
|
||||
raise ValueError(error_msg)
|
||||
elif isinstance(message.text, Iterator):
|
||||
# Convert iterator to string
|
||||
content = " ".join(str(item) for item in message.text)
|
||||
else:
|
||||
content = str(message.text)
|
||||
|
||||
if fmt == "txt":
|
||||
path.write_text(content, encoding="utf-8")
|
||||
elif fmt == "json":
|
||||
path.write_text(json.dumps({"message": content}, indent=2), encoding="utf-8")
|
||||
elif fmt == "markdown":
|
||||
path.write_text(f"**Message:**\n\n{content}", encoding="utf-8")
|
||||
else:
|
||||
error_msg = f"Unsupported Message format: {fmt}"
|
||||
raise ValueError(error_msg)
|
||||
|
||||
return f"Message saved successfully as '{path}'"
|
||||
@ -1,8 +1,8 @@
|
||||
import os
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses import asdict, dataclass, field
|
||||
|
||||
from astrapy import AstraDBAdmin, DataAPIClient, Database
|
||||
from astrapy.info import CollectionDescriptor
|
||||
from langchain_astradb import AstraDBVectorStore, CollectionVectorServiceOptions
|
||||
|
||||
from langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store
|
||||
@ -36,22 +36,24 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent):
|
||||
default_factory=lambda: {
|
||||
"data": {
|
||||
"node": {
|
||||
"description": "Create a new database in Astra DB.",
|
||||
"display_name": "Create New Database",
|
||||
"name": "create_database",
|
||||
"description": "",
|
||||
"display_name": "Create new database",
|
||||
"field_order": ["new_database_name", "cloud_provider", "region"],
|
||||
"template": {
|
||||
"new_database_name": StrInput(
|
||||
name="new_database_name",
|
||||
display_name="New Database Name",
|
||||
display_name="Name",
|
||||
info="Name of the new database to create in Astra DB.",
|
||||
required=True,
|
||||
),
|
||||
"cloud_provider": DropdownInput(
|
||||
name="cloud_provider",
|
||||
display_name="Cloud Provider",
|
||||
display_name="Cloud provider",
|
||||
info="Cloud provider for the new database.",
|
||||
options=["Amazon Web Services", "Google Cloud Platform", "Microsoft Azure"],
|
||||
required=True,
|
||||
real_time_refresh=True,
|
||||
),
|
||||
"region": DropdownInput(
|
||||
name="region",
|
||||
@ -73,8 +75,9 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent):
|
||||
default_factory=lambda: {
|
||||
"data": {
|
||||
"node": {
|
||||
"description": "Create a new collection in Astra DB.",
|
||||
"display_name": "Create New Collection",
|
||||
"name": "create_collection",
|
||||
"description": "",
|
||||
"display_name": "Create new collection",
|
||||
"field_order": [
|
||||
"new_collection_name",
|
||||
"embedding_generation_provider",
|
||||
@ -83,23 +86,31 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent):
|
||||
"template": {
|
||||
"new_collection_name": StrInput(
|
||||
name="new_collection_name",
|
||||
display_name="New Collection Name",
|
||||
display_name="Name",
|
||||
info="Name of the new collection to create in Astra DB.",
|
||||
required=True,
|
||||
),
|
||||
"embedding_generation_provider": DropdownInput(
|
||||
name="embedding_generation_provider",
|
||||
display_name="Embedding Generation Provider",
|
||||
display_name="Embedding generation method",
|
||||
info="Provider to use for generating embeddings.",
|
||||
options=[],
|
||||
real_time_refresh=True,
|
||||
required=True,
|
||||
options=["Bring your own", "Nvidia"],
|
||||
),
|
||||
"embedding_generation_model": DropdownInput(
|
||||
name="embedding_generation_model",
|
||||
display_name="Embedding Generation Model",
|
||||
display_name="Embedding model",
|
||||
info="Model to use for generating embeddings.",
|
||||
options=[],
|
||||
required=True,
|
||||
options=[],
|
||||
),
|
||||
"dimension": IntInput(
|
||||
name="dimension",
|
||||
display_name="Dimensions (Required only for `Bring your own`)",
|
||||
info="Dimensions of the embeddings to generate.",
|
||||
required=False,
|
||||
value=1024,
|
||||
),
|
||||
},
|
||||
},
|
||||
@ -125,17 +136,18 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent):
|
||||
real_time_refresh=True,
|
||||
),
|
||||
DropdownInput(
|
||||
name="api_endpoint",
|
||||
name="database_name",
|
||||
display_name="Database",
|
||||
info="The Database / API Endpoint for the Astra DB instance.",
|
||||
info="The Database name for the Astra DB instance.",
|
||||
required=True,
|
||||
refresh_button=True,
|
||||
real_time_refresh=True,
|
||||
dialog_inputs=asdict(NewDatabaseInput()),
|
||||
combobox=True,
|
||||
),
|
||||
StrInput(
|
||||
name="d_api_endpoint",
|
||||
display_name="Database API Endpoint",
|
||||
name="api_endpoint",
|
||||
display_name="Astra DB API Endpoint",
|
||||
info="The API Endpoint for the Astra DB instance. Supercedes database selection.",
|
||||
advanced=True,
|
||||
),
|
||||
@ -146,8 +158,9 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent):
|
||||
required=True,
|
||||
refresh_button=True,
|
||||
real_time_refresh=True,
|
||||
# dialog_inputs=asdict(NewCollectionInput()),
|
||||
dialog_inputs=asdict(NewCollectionInput()),
|
||||
combobox=True,
|
||||
advanced=True,
|
||||
),
|
||||
StrInput(
|
||||
name="keyspace",
|
||||
@ -238,6 +251,7 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent):
|
||||
|
||||
@classmethod
|
||||
def map_cloud_providers(cls):
|
||||
# TODO: Programmatically fetch the regions for each cloud provider
|
||||
return {
|
||||
"Amazon Web Services": {
|
||||
"id": "aws",
|
||||
@ -254,54 +268,87 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent):
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def create_database_api(
|
||||
def get_vectorize_providers(cls, token: str, environment: str | None = None, api_endpoint: str | None = None):
|
||||
try:
|
||||
# Get the admin object
|
||||
admin = AstraDBAdmin(token=token, environment=environment)
|
||||
db_admin = admin.get_database_admin(api_endpoint=api_endpoint)
|
||||
|
||||
# Get the list of embedding providers
|
||||
embedding_providers = db_admin.find_embedding_providers().as_dict()
|
||||
|
||||
vectorize_providers_mapping = {}
|
||||
# Map the provider display name to the provider key and models
|
||||
for provider_key, provider_data in embedding_providers["embeddingProviders"].items():
|
||||
# Get the provider display name and models
|
||||
display_name = provider_data["displayName"]
|
||||
models = [model["name"] for model in provider_data["models"]]
|
||||
|
||||
# Build our mapping
|
||||
vectorize_providers_mapping[display_name] = [provider_key, models]
|
||||
|
||||
# Sort the resulting dictionary
|
||||
return defaultdict(list, dict(sorted(vectorize_providers_mapping.items())))
|
||||
except Exception as e:
|
||||
msg = f"Error fetching vectorize providers: {e}"
|
||||
raise ValueError(msg) from e
|
||||
|
||||
@classmethod
|
||||
async def create_database_api(
|
||||
cls,
|
||||
token: str,
|
||||
new_database_name: str,
|
||||
cloud_provider: str,
|
||||
region: str,
|
||||
token: str,
|
||||
environment: str | None = None,
|
||||
keyspace: str | None = None,
|
||||
):
|
||||
client = DataAPIClient(token=token)
|
||||
client = DataAPIClient(token=token, environment=environment)
|
||||
|
||||
# Get the admin object
|
||||
admin_client = client.get_admin(token=token)
|
||||
|
||||
# Call the create database function
|
||||
return admin_client.create_database(
|
||||
return await admin_client.async_create_database(
|
||||
name=new_database_name,
|
||||
cloud_provider=cloud_provider,
|
||||
cloud_provider=cls.map_cloud_providers()[cloud_provider]["id"],
|
||||
region=region,
|
||||
keyspace=keyspace,
|
||||
wait_until_active=False,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def create_collection_api(
|
||||
async def create_collection_api(
|
||||
cls,
|
||||
token: str,
|
||||
database_name: str,
|
||||
new_collection_name: str,
|
||||
token: str,
|
||||
api_endpoint: str,
|
||||
environment: str | None = None,
|
||||
keyspace: str | None = None,
|
||||
dimension: int | None = None,
|
||||
embedding_generation_provider: str | None = None,
|
||||
embedding_generation_model: str | None = None,
|
||||
):
|
||||
# Create the data API client
|
||||
client = DataAPIClient(token=token)
|
||||
api_endpoint = cls.get_api_endpoint_static(token=token, database_name=database_name)
|
||||
|
||||
# Get the database object
|
||||
database = client.get_database(api_endpoint=api_endpoint, token=token)
|
||||
database = client.get_async_database(api_endpoint=api_endpoint, token=token)
|
||||
|
||||
# Build vectorize options, if needed
|
||||
vectorize_options = None
|
||||
if not dimension:
|
||||
vectorize_options = CollectionVectorServiceOptions(
|
||||
provider=embedding_generation_provider,
|
||||
provider=cls.get_vectorize_providers(
|
||||
token=token, environment=environment, api_endpoint=api_endpoint
|
||||
).get(embedding_generation_provider, [None, []])[0],
|
||||
model_name=embedding_generation_model,
|
||||
authentication=None,
|
||||
parameters=None,
|
||||
)
|
||||
|
||||
# Create the collection
|
||||
return database.create_collection(
|
||||
return await database.create_collection(
|
||||
name=new_collection_name,
|
||||
keyspace=keyspace,
|
||||
dimension=dimension,
|
||||
service=vectorize_options,
|
||||
)
|
||||
@ -325,16 +372,28 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent):
|
||||
db_info_dict = {}
|
||||
for db in db_list:
|
||||
try:
|
||||
# Get the API endpoint for the database
|
||||
api_endpoint = f"https://{db.info.id}-{db.info.region}.apps.astra{env_string}.datastax.com"
|
||||
db_info_dict[db.info.name] = {
|
||||
"api_endpoint": api_endpoint,
|
||||
"collections": len(
|
||||
|
||||
# Get the number of collections
|
||||
try:
|
||||
num_collections = len(
|
||||
list(
|
||||
client.get_database(
|
||||
api_endpoint=api_endpoint, token=token, keyspace=db.info.keyspace
|
||||
).list_collection_names(keyspace=db.info.keyspace)
|
||||
)
|
||||
),
|
||||
)
|
||||
except Exception: # noqa: BLE001
|
||||
num_collections = 0
|
||||
if db.status != "PENDING":
|
||||
continue
|
||||
|
||||
# Add the database to the dictionary
|
||||
db_info_dict[db.info.name] = {
|
||||
"api_endpoint": api_endpoint,
|
||||
"collections": num_collections,
|
||||
"status": db.status if db.status != "ACTIVE" else None,
|
||||
}
|
||||
except Exception: # noqa: BLE001, S110
|
||||
pass
|
||||
@ -364,15 +423,20 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent):
|
||||
if not database_name:
|
||||
return None
|
||||
|
||||
# Otherwise, get the URL from the database list
|
||||
return cls.get_database_list_static(token=token, environment=environment).get(database_name).get("api_endpoint")
|
||||
# Grab the database object
|
||||
db = cls.get_database_list_static(token=token, environment=environment).get(database_name)
|
||||
if not db:
|
||||
return None
|
||||
|
||||
def get_api_endpoint(self, *, api_endpoint: str | None = None):
|
||||
# Otherwise, get the URL from the database list
|
||||
return db.get("api_endpoint")
|
||||
|
||||
def get_api_endpoint(self):
|
||||
return self.get_api_endpoint_static(
|
||||
token=self.token,
|
||||
environment=self.environment,
|
||||
api_endpoint=api_endpoint or self.d_api_endpoint,
|
||||
database_name=self.api_endpoint,
|
||||
api_endpoint=self.api_endpoint,
|
||||
database_name=self.database_name,
|
||||
)
|
||||
|
||||
def get_keyspace(self):
|
||||
@ -388,7 +452,7 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent):
|
||||
client = DataAPIClient(token=self.token, environment=self.environment)
|
||||
|
||||
return client.get_database(
|
||||
api_endpoint=self.get_api_endpoint(api_endpoint=api_endpoint),
|
||||
api_endpoint=api_endpoint or self.get_api_endpoint(),
|
||||
token=self.token,
|
||||
keyspace=self.get_keyspace(),
|
||||
)
|
||||
@ -415,40 +479,15 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent):
|
||||
|
||||
return None
|
||||
|
||||
def get_vectorize_providers(self):
|
||||
try:
|
||||
self.log("Dynamically updating list of Vectorize providers.")
|
||||
|
||||
# Get the admin object
|
||||
admin = AstraDBAdmin(token=self.token)
|
||||
db_admin = admin.get_database_admin(api_endpoint=self.get_api_endpoint())
|
||||
|
||||
# Get the list of embedding providers
|
||||
embedding_providers = db_admin.find_embedding_providers().as_dict()
|
||||
|
||||
vectorize_providers_mapping = {}
|
||||
# Map the provider display name to the provider key and models
|
||||
for provider_key, provider_data in embedding_providers["embeddingProviders"].items():
|
||||
display_name = provider_data["displayName"]
|
||||
models = [model["name"] for model in provider_data["models"]]
|
||||
|
||||
# TODO: https://astra.datastax.com/api/v2/graphql
|
||||
vectorize_providers_mapping[display_name] = [provider_key, models]
|
||||
|
||||
# Sort the resulting dictionary
|
||||
return defaultdict(list, dict(sorted(vectorize_providers_mapping.items())))
|
||||
except Exception as e: # noqa: BLE001
|
||||
self.log(f"Error fetching Vectorize providers: {e}")
|
||||
|
||||
return {}
|
||||
|
||||
def _initialize_database_options(self):
|
||||
try:
|
||||
return [
|
||||
{
|
||||
"name": name,
|
||||
"status": info["status"],
|
||||
"collections": info["collections"],
|
||||
"api_endpoint": info["api_endpoint"],
|
||||
"icon": "data",
|
||||
}
|
||||
for name, info in self.get_database_list().items()
|
||||
]
|
||||
@ -456,7 +495,35 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent):
|
||||
msg = f"Error fetching database options: {e}"
|
||||
raise ValueError(msg) from e
|
||||
|
||||
@classmethod
|
||||
def get_provider_icon(cls, collection: CollectionDescriptor | None = None, provider_name: str | None = None) -> str:
|
||||
# Get the provider name from the collection
|
||||
provider_name = provider_name or (
|
||||
collection.options.vector.service.provider
|
||||
if collection and collection.options and collection.options.vector and collection.options.vector.service
|
||||
else None
|
||||
)
|
||||
|
||||
# If there is no provider, use the vector store icon
|
||||
if not provider_name or provider_name == "bring your own":
|
||||
return "vectorstores"
|
||||
|
||||
# Special case for certain models
|
||||
# TODO: Add more icons
|
||||
if provider_name == "nvidia":
|
||||
return "NVIDIA"
|
||||
if provider_name == "openai":
|
||||
return "OpenAI"
|
||||
|
||||
# Title case on the provider for the icon if no special case
|
||||
return provider_name.title()
|
||||
|
||||
def _initialize_collection_options(self, api_endpoint: str | None = None):
|
||||
# Nothing to generate if we don't have an API endpoint yet
|
||||
api_endpoint = api_endpoint or self.get_api_endpoint()
|
||||
if not api_endpoint:
|
||||
return []
|
||||
|
||||
# Retrieve the database object
|
||||
database = self.get_database_object(api_endpoint=api_endpoint)
|
||||
|
||||
@ -471,7 +538,7 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent):
|
||||
"provider": (
|
||||
col.options.vector.service.provider if col.options.vector and col.options.vector.service else None
|
||||
),
|
||||
"icon": "",
|
||||
"icon": self.get_provider_icon(collection=col),
|
||||
"model": (
|
||||
col.options.vector.service.model_name if col.options.vector and col.options.vector.service else None
|
||||
),
|
||||
@ -479,9 +546,53 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent):
|
||||
for col in collection_list
|
||||
]
|
||||
|
||||
def reset_provider_options(self, build_config: dict):
|
||||
# Get the list of vectorize providers
|
||||
vectorize_providers = self.get_vectorize_providers(
|
||||
token=self.token,
|
||||
environment=self.environment,
|
||||
api_endpoint=build_config["api_endpoint"]["value"],
|
||||
)
|
||||
|
||||
# If the collection is set, allow user to see embedding options
|
||||
build_config["collection_name"]["dialog_inputs"]["fields"]["data"]["node"]["template"][
|
||||
"embedding_generation_provider"
|
||||
]["options"] = ["Bring your own", "Nvidia", *[key for key in vectorize_providers if key != "Nvidia"]]
|
||||
|
||||
# For all not Bring your own or Nvidia providers, add metadata saying configure in Astra DB Portal
|
||||
provider_options = build_config["collection_name"]["dialog_inputs"]["fields"]["data"]["node"]["template"][
|
||||
"embedding_generation_provider"
|
||||
]["options"]
|
||||
|
||||
# Go over each possible provider and add metadata to configure in Astra DB Portal
|
||||
for provider in provider_options:
|
||||
# Skip Bring your own and Nvidia, automatically configured
|
||||
if provider in ["Bring your own", "Nvidia"]:
|
||||
build_config["collection_name"]["dialog_inputs"]["fields"]["data"]["node"]["template"][
|
||||
"embedding_generation_provider"
|
||||
]["options_metadata"].append({"icon": self.get_provider_icon(provider_name=provider.lower())})
|
||||
continue
|
||||
|
||||
# Add metadata to configure in Astra DB Portal
|
||||
build_config["collection_name"]["dialog_inputs"]["fields"]["data"]["node"]["template"][
|
||||
"embedding_generation_provider"
|
||||
]["options_metadata"].append({" ": "Configure in Astra DB Portal"})
|
||||
|
||||
# And allow the user to see the models based on a selected provider
|
||||
embedding_provider = build_config["collection_name"]["dialog_inputs"]["fields"]["data"]["node"]["template"][
|
||||
"embedding_generation_provider"
|
||||
]["value"]
|
||||
|
||||
# Set the options for the embedding model based on the provider
|
||||
build_config["collection_name"]["dialog_inputs"]["fields"]["data"]["node"]["template"][
|
||||
"embedding_generation_model"
|
||||
]["options"] = vectorize_providers.get(embedding_provider, [[], []])[1]
|
||||
|
||||
return build_config
|
||||
|
||||
def reset_collection_list(self, build_config: dict):
|
||||
# Get the list of options we have based on the token provided
|
||||
collection_options = self._initialize_collection_options()
|
||||
collection_options = self._initialize_collection_options(api_endpoint=build_config["api_endpoint"]["value"])
|
||||
|
||||
# If we retrieved options based on the token, show the dropdown
|
||||
build_config["collection_name"]["options"] = [col["name"] for col in collection_options]
|
||||
@ -490,7 +601,11 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent):
|
||||
]
|
||||
|
||||
# Reset the selected collection
|
||||
build_config["collection_name"]["value"] = ""
|
||||
if build_config["collection_name"]["value"] not in build_config["collection_name"]["options"]:
|
||||
build_config["collection_name"]["value"] = ""
|
||||
|
||||
# If we have a database, collection name should not be advanced
|
||||
build_config["collection_name"]["advanced"] = not build_config["database_name"]["value"]
|
||||
|
||||
return build_config
|
||||
|
||||
@ -499,84 +614,171 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent):
|
||||
database_options = self._initialize_database_options()
|
||||
|
||||
# If we retrieved options based on the token, show the dropdown
|
||||
build_config["api_endpoint"]["options"] = [db["name"] for db in database_options]
|
||||
build_config["api_endpoint"]["options_metadata"] = [
|
||||
build_config["database_name"]["options"] = [db["name"] for db in database_options]
|
||||
build_config["database_name"]["options_metadata"] = [
|
||||
{k: v for k, v in db.items() if k not in ["name"]} for db in database_options
|
||||
]
|
||||
|
||||
# Reset the selected database
|
||||
build_config["api_endpoint"]["value"] = ""
|
||||
if build_config["database_name"]["value"] not in build_config["database_name"]["options"]:
|
||||
build_config["database_name"]["value"] = ""
|
||||
build_config["api_endpoint"]["value"] = ""
|
||||
build_config["collection_name"]["advanced"] = True
|
||||
|
||||
# If we have a token, database name should not be advanced
|
||||
build_config["database_name"]["advanced"] = not build_config["token"]["value"]
|
||||
|
||||
return build_config
|
||||
|
||||
def reset_build_config(self, build_config: dict):
|
||||
# Reset the list of databases we have based on the token provided
|
||||
build_config["api_endpoint"]["options"] = []
|
||||
build_config["api_endpoint"]["options_metadata"] = []
|
||||
build_config["database_name"]["options"] = []
|
||||
build_config["database_name"]["options_metadata"] = []
|
||||
build_config["database_name"]["value"] = ""
|
||||
build_config["database_name"]["advanced"] = True
|
||||
build_config["api_endpoint"]["value"] = ""
|
||||
build_config["api_endpoint"]["name"] = "Database"
|
||||
|
||||
# Reset the list of collections and metadata associated
|
||||
build_config["collection_name"]["options"] = []
|
||||
build_config["collection_name"]["options_metadata"] = []
|
||||
build_config["collection_name"]["value"] = ""
|
||||
build_config["collection_name"]["advanced"] = True
|
||||
|
||||
return build_config
|
||||
|
||||
def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):
|
||||
# When the component first executes, this is the update refresh call
|
||||
first_run = field_name == "collection_name" and not field_value and not build_config["api_endpoint"]["options"]
|
||||
async def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):
|
||||
# Callback for database creation
|
||||
if field_name == "database_name" and isinstance(field_value, dict) and "new_database_name" in field_value:
|
||||
try:
|
||||
await self.create_database_api(
|
||||
new_database_name=field_value["new_database_name"],
|
||||
token=self.token,
|
||||
keyspace=self.get_keyspace(),
|
||||
environment=self.environment,
|
||||
cloud_provider=field_value["cloud_provider"],
|
||||
region=field_value["region"],
|
||||
)
|
||||
except Exception as e:
|
||||
msg = f"Error creating database: {e}"
|
||||
raise ValueError(msg) from e
|
||||
|
||||
# If the token has not been provided, simply return
|
||||
# Add the new database to the list of options
|
||||
build_config["database_name"]["options"] = build_config["database_name"]["options"] + [
|
||||
field_value["new_database_name"]
|
||||
]
|
||||
build_config["database_name"]["options_metadata"] = build_config["database_name"]["options_metadata"] + [
|
||||
{"status": "PENDING"}
|
||||
]
|
||||
|
||||
return self.reset_collection_list(build_config)
|
||||
|
||||
# This is the callback required to update the list of regions for a cloud provider
|
||||
if field_name == "database_name" and isinstance(field_value, dict) and "new_database_name" not in field_value:
|
||||
cloud_provider = field_value["cloud_provider"]
|
||||
build_config["database_name"]["dialog_inputs"]["fields"]["data"]["node"]["template"]["region"][
|
||||
"options"
|
||||
] = self.map_cloud_providers()[cloud_provider]["regions"]
|
||||
|
||||
return build_config
|
||||
|
||||
# Callback for the creation of collections
|
||||
if field_name == "collection_name" and isinstance(field_value, dict) and "new_collection_name" in field_value:
|
||||
try:
|
||||
# Get the dimension if its a BYO provider
|
||||
dimension = (
|
||||
field_value["dimension"]
|
||||
if field_value["embedding_generation_provider"] == "Bring your own"
|
||||
else None
|
||||
)
|
||||
|
||||
# Create the collection
|
||||
await self.create_collection_api(
|
||||
new_collection_name=field_value["new_collection_name"],
|
||||
token=self.token,
|
||||
api_endpoint=build_config["api_endpoint"]["value"],
|
||||
environment=self.environment,
|
||||
keyspace=self.get_keyspace(),
|
||||
dimension=dimension,
|
||||
embedding_generation_provider=field_value["embedding_generation_provider"],
|
||||
embedding_generation_model=field_value["embedding_generation_model"],
|
||||
)
|
||||
except Exception as e:
|
||||
msg = f"Error creating collection: {e}"
|
||||
raise ValueError(msg) from e
|
||||
|
||||
# Add the new collection to the list of options
|
||||
build_config["collection_name"]["value"] = field_value["new_collection_name"]
|
||||
build_config["collection_name"]["options"].append(field_value["new_collection_name"])
|
||||
|
||||
# Get the provider and model for the new collection
|
||||
generation_provider = field_value["embedding_generation_provider"]
|
||||
provider = generation_provider if generation_provider != "Bring your own" else None
|
||||
generation_model = field_value["embedding_generation_model"]
|
||||
model = generation_model if generation_model else None
|
||||
|
||||
# Add the new collection to the list of options
|
||||
icon = "NVIDIA" if provider == "Nvidia" else "vectorstores"
|
||||
build_config["collection_name"]["options_metadata"] = build_config["collection_name"][
|
||||
"options_metadata"
|
||||
] + [{"records": 0, "provider": provider, "icon": icon, "model": model}]
|
||||
|
||||
return build_config
|
||||
|
||||
# Callback to update the model list based on the embedding provider
|
||||
if (
|
||||
field_name == "collection_name"
|
||||
and isinstance(field_value, dict)
|
||||
and "new_collection_name" not in field_value
|
||||
):
|
||||
return self.reset_provider_options(build_config)
|
||||
|
||||
# When the component first executes, this is the update refresh call
|
||||
first_run = field_name == "collection_name" and not field_value and not build_config["database_name"]["options"]
|
||||
|
||||
# If the token has not been provided, simply return the empty build config
|
||||
if not self.token:
|
||||
return self.reset_build_config(build_config)
|
||||
|
||||
# If this is the first execution of the component, reset and build database list
|
||||
if first_run or field_name in ["token", "environment"]:
|
||||
# Reset the build config to ensure we are starting fresh
|
||||
build_config = self.reset_build_config(build_config)
|
||||
build_config = self.reset_database_list(build_config)
|
||||
|
||||
# Get list of regions for a given cloud provider
|
||||
"""
|
||||
cloud_provider = (
|
||||
build_config["api_endpoint"]["dialog_inputs"]["fields"]["data"]["node"]["template"]["cloud_provider"][
|
||||
"value"
|
||||
]
|
||||
or "Amazon Web Services"
|
||||
)
|
||||
build_config["api_endpoint"]["dialog_inputs"]["fields"]["data"]["node"]["template"]["region"][
|
||||
"options"
|
||||
] = self.map_cloud_providers()[cloud_provider]["regions"]
|
||||
"""
|
||||
|
||||
return build_config
|
||||
return self.reset_database_list(build_config)
|
||||
|
||||
# Refresh the collection name options
|
||||
if field_name == "api_endpoint":
|
||||
if field_name == "database_name" and not isinstance(field_value, dict):
|
||||
# If missing, refresh the database options
|
||||
if not build_config["api_endpoint"]["options"] or not field_value:
|
||||
return self.update_build_config(build_config, field_value=self.token, field_name="token")
|
||||
if field_value not in build_config["database_name"]["options"]:
|
||||
build_config = await self.update_build_config(build_config, field_value=self.token, field_name="token")
|
||||
build_config["database_name"]["value"] = ""
|
||||
else:
|
||||
# Find the position of the selected database to align with metadata
|
||||
index_of_name = build_config["database_name"]["options"].index(field_value)
|
||||
|
||||
# Set the underlying api endpoint value of the database
|
||||
if field_value in build_config["api_endpoint"]["options"]:
|
||||
index_of_name = build_config["api_endpoint"]["options"].index(field_value)
|
||||
build_config["d_api_endpoint"]["value"] = build_config["api_endpoint"]["options_metadata"][
|
||||
# Initializing database condition
|
||||
pending = build_config["database_name"]["options_metadata"][index_of_name]["status"] == "PENDING"
|
||||
if pending:
|
||||
return self.update_build_config(build_config, field_value=self.token, field_name="token")
|
||||
|
||||
# Set the API endpoint based on the selected database
|
||||
build_config["api_endpoint"]["value"] = build_config["database_name"]["options_metadata"][
|
||||
index_of_name
|
||||
]["api_endpoint"]
|
||||
else:
|
||||
build_config["d_api_endpoint"]["value"] = ""
|
||||
|
||||
# Reset the provider options
|
||||
build_config = self.reset_provider_options(build_config)
|
||||
|
||||
# Reset the list of collections we have based on the token provided
|
||||
return self.reset_collection_list(build_config)
|
||||
|
||||
# Hide embedding model option if opriona_metadata provider is not null
|
||||
if field_name == "collection_name" and field_value:
|
||||
if field_name == "collection_name" and not isinstance(field_value, dict):
|
||||
# Assume we will be autodetecting the collection:
|
||||
build_config["autodetect_collection"]["value"] = True
|
||||
|
||||
# Reload the collection list
|
||||
build_config = self.reset_collection_list(build_config)
|
||||
|
||||
# Set the options for collection name to be the field value if its a new collection
|
||||
if field_value not in build_config["collection_name"]["options"]:
|
||||
if field_value and field_value not in build_config["collection_name"]["options"]:
|
||||
# Add the new collection to the list of options
|
||||
build_config["collection_name"]["options"].append(field_value)
|
||||
build_config["collection_name"]["options_metadata"].append(
|
||||
@ -598,36 +800,8 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent):
|
||||
build_config["embedding_model"]["advanced"] = False
|
||||
build_config["embedding_choice"]["value"] = "Embedding Model"
|
||||
|
||||
# For the final step, get the list of vectorize providers
|
||||
"""
|
||||
vectorize_providers = self.get_vectorize_providers()
|
||||
if not vectorize_providers:
|
||||
return build_config
|
||||
|
||||
# Allow the user to see the embedding provider options
|
||||
provider_options = build_config["collection_name"]["dialog_inputs"]["fields"]["data"]["node"]["template"][
|
||||
"embedding_generation_provider"
|
||||
]["options"]
|
||||
if not provider_options:
|
||||
# If the collection is set, allow user to see embedding options
|
||||
build_config["collection_name"]["dialog_inputs"]["fields"]["data"]["node"]["template"][
|
||||
"embedding_generation_provider"
|
||||
]["options"] = ["Bring your own", "Nvidia", *[key for key in vectorize_providers if key != "Nvidia"]]
|
||||
|
||||
# And allow the user to see the models based on a selected provider
|
||||
model_options = build_config["collection_name"]["dialog_inputs"]["fields"]["data"]["node"]["template"][
|
||||
"embedding_generation_model"
|
||||
]["options"]
|
||||
if not model_options:
|
||||
embedding_provider = build_config["collection_name"]["dialog_inputs"]["fields"]["data"]["node"]["template"][
|
||||
"embedding_generation_provider"
|
||||
]["value"]
|
||||
|
||||
build_config["collection_name"]["dialog_inputs"]["fields"]["data"]["node"]["template"][
|
||||
"embedding_generation_model"
|
||||
]["options"] = vectorize_providers.get(embedding_provider, [[], []])[1]
|
||||
"""
|
||||
|
||||
return build_config
|
||||
|
||||
@check_cached_vector_store
|
||||
@ -654,11 +828,11 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent):
|
||||
# Get Langflow version and platform information
|
||||
__version__ = get_version_info()["version"]
|
||||
langflow_prefix = ""
|
||||
if os.getenv("AWS_EXECUTION_ENV") == "AWS_ECS_FARGATE": # TODO: More precise way of detecting
|
||||
langflow_prefix = "ds-"
|
||||
# if os.getenv("AWS_EXECUTION_ENV") == "AWS_ECS_FARGATE": # TODO: More precise way of detecting
|
||||
# langflow_prefix = "ds-"
|
||||
|
||||
# Get the database object
|
||||
database = self.get_database_object(api_endpoint=self.d_api_endpoint)
|
||||
database = self.get_database_object()
|
||||
autodetect = self.collection_name in database.list_collection_names() and self.autodetect_collection
|
||||
|
||||
# Bundle up the auto-detect parameters
|
||||
@ -714,7 +888,7 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent):
|
||||
if documents and self.deletion_field:
|
||||
self.log(f"Deleting documents where {self.deletion_field}")
|
||||
try:
|
||||
database = self.get_database_object(api_endpoint=self.d_api_endpoint)
|
||||
database = self.get_database_object()
|
||||
collection = database.get_collection(self.collection_name, keyspace=database.keyspace)
|
||||
delete_values = list({doc.metadata[self.deletion_field] for doc in documents})
|
||||
self.log(f"Deleting documents where {self.deletion_field} matches {delete_values}.")
|
||||
|
||||
@ -981,9 +981,6 @@ class Component(CustomComponent):
|
||||
return {"repr": custom_repr, "raw": raw, "type": artifact_type}
|
||||
|
||||
def _process_raw_result(self, result):
|
||||
"""Process the raw result of the component."""
|
||||
if len(self.outputs) == 1:
|
||||
return self.status or self.extract_data(result)
|
||||
return self.extract_data(result)
|
||||
|
||||
def extract_data(self, result):
|
||||
|
||||
@ -1,21 +1,26 @@
|
||||
import asyncio
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
import json
|
||||
import time
|
||||
import uuid
|
||||
from functools import partial
|
||||
from typing import Literal
|
||||
from typing import TYPE_CHECKING, Literal
|
||||
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from loguru import logger
|
||||
from typing_extensions import Protocol
|
||||
|
||||
from langflow.schema.log import LoggableType
|
||||
from langflow.schema.playground_events import create_event_by_type
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import asyncio
|
||||
|
||||
from langflow.schema.log import LoggableType
|
||||
|
||||
|
||||
class EventCallback(Protocol):
|
||||
def __call__(self, *, manager: "EventManager", event_type: str, data: LoggableType): ...
|
||||
def __call__(self, *, manager: EventManager, event_type: str, data: LoggableType): ...
|
||||
|
||||
|
||||
class PartialEventCallback(Protocol):
|
||||
|
||||
@ -635,6 +635,15 @@ class Graph:
|
||||
raise ValueError(msg)
|
||||
return self._run_id
|
||||
|
||||
def set_tracing_session_id(self) -> None:
|
||||
"""Sets the ID of the current session.
|
||||
|
||||
Args:
|
||||
session_id (str): The session ID.
|
||||
"""
|
||||
if self.tracing_service:
|
||||
self.tracing_service.set_session_id(self._session_id)
|
||||
|
||||
def set_run_id(self, run_id: uuid.UUID | None = None) -> None:
|
||||
"""Sets the ID of the current run.
|
||||
|
||||
@ -647,6 +656,8 @@ class Graph:
|
||||
self._run_id = str(run_id)
|
||||
if self.tracing_service:
|
||||
self.tracing_service.set_run_id(run_id)
|
||||
if self._session_id and self.tracing_service is not None:
|
||||
self.tracing_service.set_session_id(self.session_id)
|
||||
|
||||
def set_run_name(self) -> None:
|
||||
# Given a flow name, flow_id
|
||||
|
||||
@ -135,10 +135,12 @@ async def log_transaction(
|
||||
flow_id=flow_id if isinstance(flow_id, UUID) else UUID(flow_id),
|
||||
)
|
||||
async with session_getter(get_db_service()) as session:
|
||||
inserted = await crud_log_transaction(session, transaction)
|
||||
logger.debug(f"Logged transaction: {inserted.id}")
|
||||
with session.no_autoflush:
|
||||
inserted = await crud_log_transaction(session, transaction)
|
||||
if inserted:
|
||||
logger.debug(f"Logged transaction: {inserted.id}")
|
||||
except Exception: # noqa: BLE001
|
||||
logger.exception("Error logging transaction")
|
||||
logger.error("Error logging transaction")
|
||||
|
||||
|
||||
async def log_vertex_build(
|
||||
|
||||
@ -8,16 +8,12 @@
|
||||
"dataType": "ChatInput",
|
||||
"id": "ChatInput-jFwUm",
|
||||
"name": "message",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
"id": "OpenAIModel-OcXkl",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -34,16 +30,12 @@
|
||||
"dataType": "Prompt",
|
||||
"id": "Prompt-3SM2g",
|
||||
"name": "prompt",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "system_message",
|
||||
"id": "OpenAIModel-OcXkl",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -60,16 +52,12 @@
|
||||
"dataType": "OpenAIModel",
|
||||
"id": "OpenAIModel-OcXkl",
|
||||
"name": "text_output",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
"id": "ChatOutput-gDYiJ",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -87,9 +75,7 @@
|
||||
"display_name": "Chat Input",
|
||||
"id": "ChatInput-jFwUm",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
@ -120,9 +106,7 @@
|
||||
"name": "message",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -135,9 +119,7 @@
|
||||
"display_name": "Background Color",
|
||||
"dynamic": false,
|
||||
"info": "The background color of the icon.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "background_color",
|
||||
@ -156,9 +138,7 @@
|
||||
"display_name": "Icon",
|
||||
"dynamic": false,
|
||||
"info": "The icon of the message.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "chat_icon",
|
||||
@ -255,10 +235,7 @@
|
||||
"dynamic": false,
|
||||
"info": "Type of sender.",
|
||||
"name": "sender",
|
||||
"options": [
|
||||
"Machine",
|
||||
"User"
|
||||
],
|
||||
"options": ["Machine", "User"],
|
||||
"placeholder": "",
|
||||
"required": false,
|
||||
"show": true,
|
||||
@ -272,9 +249,7 @@
|
||||
"display_name": "Sender Name",
|
||||
"dynamic": false,
|
||||
"info": "Name of the sender.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "sender_name",
|
||||
@ -292,9 +267,7 @@
|
||||
"display_name": "Session ID",
|
||||
"dynamic": false,
|
||||
"info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "session_id",
|
||||
@ -329,9 +302,7 @@
|
||||
"display_name": "Text Color",
|
||||
"dynamic": false,
|
||||
"info": "The text color of the name",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "text_color",
|
||||
@ -373,9 +344,7 @@
|
||||
"display_name": "Prompt",
|
||||
"id": "Prompt-3SM2g",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {
|
||||
@ -385,9 +354,7 @@
|
||||
"display_name": "Prompt",
|
||||
"documentation": "",
|
||||
"edited": false,
|
||||
"field_order": [
|
||||
"template"
|
||||
],
|
||||
"field_order": ["template"],
|
||||
"frozen": false,
|
||||
"icon": "prompts",
|
||||
"legacy": false,
|
||||
@ -402,9 +369,7 @@
|
||||
"name": "prompt",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -453,9 +418,7 @@
|
||||
"display_name": "Tool Placeholder",
|
||||
"dynamic": false,
|
||||
"info": "A placeholder input for tool mode.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "tool_placeholder",
|
||||
@ -570,9 +533,7 @@
|
||||
"data": {
|
||||
"id": "ChatOutput-gDYiJ",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
@ -605,9 +566,7 @@
|
||||
"name": "message",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -620,9 +579,7 @@
|
||||
"display_name": "Background Color",
|
||||
"dynamic": false,
|
||||
"info": "The background color of the icon.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "background_color",
|
||||
@ -642,9 +599,7 @@
|
||||
"display_name": "Icon",
|
||||
"dynamic": false,
|
||||
"info": "The icon of the message.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "chat_icon",
|
||||
@ -682,9 +637,7 @@
|
||||
"display_name": "Data Template",
|
||||
"dynamic": false,
|
||||
"info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "data_template",
|
||||
@ -704,9 +657,7 @@
|
||||
"display_name": "Text",
|
||||
"dynamic": false,
|
||||
"info": "Message to be passed as output.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "input_value",
|
||||
@ -727,10 +678,7 @@
|
||||
"dynamic": false,
|
||||
"info": "Type of sender.",
|
||||
"name": "sender",
|
||||
"options": [
|
||||
"Machine",
|
||||
"User"
|
||||
],
|
||||
"options": ["Machine", "User"],
|
||||
"placeholder": "",
|
||||
"required": false,
|
||||
"show": true,
|
||||
@ -746,9 +694,7 @@
|
||||
"display_name": "Sender Name",
|
||||
"dynamic": false,
|
||||
"info": "Name of the sender.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "sender_name",
|
||||
@ -768,9 +714,7 @@
|
||||
"display_name": "Session ID",
|
||||
"dynamic": false,
|
||||
"info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "session_id",
|
||||
@ -806,9 +750,7 @@
|
||||
"display_name": "Text Color",
|
||||
"dynamic": false,
|
||||
"info": "The text color of the name",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "text_color",
|
||||
@ -850,10 +792,7 @@
|
||||
"data": {
|
||||
"id": "OpenAIModel-OcXkl",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"LanguageModel",
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["LanguageModel", "Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
@ -892,9 +831,7 @@
|
||||
"required_inputs": [],
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
},
|
||||
{
|
||||
@ -903,14 +840,10 @@
|
||||
"display_name": "Language Model",
|
||||
"method": "build_model",
|
||||
"name": "model_output",
|
||||
"required_inputs": [
|
||||
"api_key"
|
||||
],
|
||||
"required_inputs": ["api_key"],
|
||||
"selected": "LanguageModel",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"LanguageModel"
|
||||
],
|
||||
"types": ["LanguageModel"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -923,10 +856,8 @@
|
||||
"display_name": "OpenAI API Key",
|
||||
"dynamic": false,
|
||||
"info": "The OpenAI API Key to use for the OpenAI model.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"load_from_db": false,
|
||||
"input_types": ["Message"],
|
||||
"load_from_db": true,
|
||||
"name": "api_key",
|
||||
"password": true,
|
||||
"placeholder": "",
|
||||
@ -934,7 +865,7 @@
|
||||
"show": true,
|
||||
"title_case": false,
|
||||
"type": "str",
|
||||
"value": ""
|
||||
"value": "OPENAI_API_KEY"
|
||||
},
|
||||
"code": {
|
||||
"advanced": true,
|
||||
@ -960,9 +891,7 @@
|
||||
"display_name": "Input",
|
||||
"dynamic": false,
|
||||
"info": "",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1144,9 +1073,7 @@
|
||||
"display_name": "System Message",
|
||||
"dynamic": false,
|
||||
"info": "System message to pass to the model.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1243,7 +1170,5 @@
|
||||
"is_component": false,
|
||||
"last_tested_version": "1.0.19.post2",
|
||||
"name": "Basic Prompting",
|
||||
"tags": [
|
||||
"chatbots"
|
||||
]
|
||||
}
|
||||
"tags": ["chatbots"]
|
||||
}
|
||||
|
||||
@ -9,17 +9,12 @@
|
||||
"dataType": "ParseData",
|
||||
"id": "ParseData-4Sckw",
|
||||
"name": "text",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "references",
|
||||
"id": "Prompt-65R68",
|
||||
"inputTypes": [
|
||||
"Message",
|
||||
"Text"
|
||||
],
|
||||
"inputTypes": ["Message", "Text"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -37,17 +32,12 @@
|
||||
"dataType": "TextInput",
|
||||
"id": "TextInput-t88FI",
|
||||
"name": "text",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "instructions",
|
||||
"id": "Prompt-65R68",
|
||||
"inputTypes": [
|
||||
"Message",
|
||||
"Text"
|
||||
],
|
||||
"inputTypes": ["Message", "Text"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -64,16 +54,12 @@
|
||||
"dataType": "Prompt",
|
||||
"id": "Prompt-65R68",
|
||||
"name": "prompt",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
"id": "OpenAIModel-MyAsQ",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -90,16 +76,12 @@
|
||||
"dataType": "OpenAIModel",
|
||||
"id": "OpenAIModel-MyAsQ",
|
||||
"name": "text_output",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
"id": "ChatOutput-BE4YI",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -116,16 +98,12 @@
|
||||
"dataType": "URL",
|
||||
"id": "URL-EPEnt",
|
||||
"name": "data",
|
||||
"output_types": [
|
||||
"Data"
|
||||
]
|
||||
"output_types": ["Data"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "data",
|
||||
"id": "ParseData-4Sckw",
|
||||
"inputTypes": [
|
||||
"Data"
|
||||
],
|
||||
"inputTypes": ["Data"],
|
||||
"type": "other"
|
||||
}
|
||||
},
|
||||
@ -143,9 +121,7 @@
|
||||
"display_name": "Parse Data",
|
||||
"id": "ParseData-4Sckw",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
@ -153,16 +129,14 @@
|
||||
"display_name": "Parse Data",
|
||||
"documentation": "",
|
||||
"edited": false,
|
||||
"field_order": [
|
||||
"data",
|
||||
"template",
|
||||
"sep"
|
||||
],
|
||||
"field_order": ["data", "template", "sep"],
|
||||
"frozen": false,
|
||||
"icon": "message-square",
|
||||
"legacy": false,
|
||||
"lf_version": "1.0.19.post2",
|
||||
"metadata": {},
|
||||
"metadata": {
|
||||
"legacy_name": "Parse Data"
|
||||
},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
{
|
||||
@ -173,9 +147,7 @@
|
||||
"name": "text",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
},
|
||||
{
|
||||
@ -186,9 +158,7 @@
|
||||
"name": "data_list",
|
||||
"selected": "Data",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Data"
|
||||
],
|
||||
"types": ["Data"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -211,16 +181,14 @@
|
||||
"show": true,
|
||||
"title_case": false,
|
||||
"type": "code",
|
||||
"value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text, data_to_text_list\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Data to Message\"\n description = \"Convert Data objects into Messages using any {field_name} from input data.\"\n icon = \"message-square\"\n name = \"ParseData\"\n\n inputs = [\n DataInput(name=\"data\", display_name=\"Data\", info=\"The data to convert to text.\", is_list=True, required=True),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n required=True,\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"text\",\n info=\"Data as a single Message, with each input Data separated by Separator\",\n method=\"parse_data\",\n ),\n Output(\n display_name=\"Data List\",\n name=\"data_list\",\n info=\"Data as a list of new Data, each having `text` formatted by Template\",\n method=\"parse_data_as_list\",\n ),\n ]\n\n def _clean_args(self) -> tuple[list[Data], str, str]:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n sep = self.sep\n return data, template, sep\n\n def parse_data(self) -> Message:\n data, template, sep = self._clean_args()\n result_string = data_to_text(template, data, sep)\n self.status = result_string\n return Message(text=result_string)\n\n def parse_data_as_list(self) -> list[Data]:\n data, template, _ = self._clean_args()\n text_list, data_list = data_to_text_list(template, data)\n for item, text in zip(data_list, text_list, strict=True):\n item.set_text(text)\n self.status = data_list\n return data_list\n"
|
||||
"value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text, data_to_text_list\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Data to Message\"\n description = \"Convert Data objects into Messages using any {field_name} from input data.\"\n icon = \"message-square\"\n name = \"ParseData\"\n metadata = {\n \"legacy_name\": \"Parse Data\",\n }\n\n inputs = [\n DataInput(\n name=\"data\",\n display_name=\"Data\",\n info=\"The data to convert to text.\",\n is_list=True,\n required=True,\n ),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n required=True,\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"text\",\n info=\"Data as a single Message, with each input Data separated by Separator\",\n method=\"parse_data\",\n ),\n Output(\n display_name=\"Data List\",\n name=\"data_list\",\n info=\"Data as a list of new Data, each having `text` formatted by Template\",\n method=\"parse_data_as_list\",\n ),\n ]\n\n def _clean_args(self) -> tuple[list[Data], str, str]:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n sep = self.sep\n return data, template, sep\n\n def parse_data(self) -> Message:\n data, template, sep = self._clean_args()\n result_string = data_to_text(template, data, sep)\n self.status = result_string\n return Message(text=result_string)\n\n def parse_data_as_list(self) -> list[Data]:\n data, template, _ = self._clean_args()\n text_list, data_list = data_to_text_list(template, data)\n for item, text in zip(data_list, text_list, strict=True):\n item.set_text(text)\n self.status = data_list\n return data_list\n"
|
||||
},
|
||||
"data": {
|
||||
"advanced": false,
|
||||
"display_name": "Data",
|
||||
"dynamic": false,
|
||||
"info": "The data to convert to text.",
|
||||
"input_types": [
|
||||
"Data"
|
||||
],
|
||||
"input_types": ["Data"],
|
||||
"list": true,
|
||||
"name": "data",
|
||||
"placeholder": "",
|
||||
@ -253,9 +221,7 @@
|
||||
"display_name": "Template",
|
||||
"dynamic": false,
|
||||
"info": "The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"multiline": true,
|
||||
@ -298,24 +264,17 @@
|
||||
"display_name": "Prompt",
|
||||
"id": "Prompt-65R68",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {
|
||||
"template": [
|
||||
"references",
|
||||
"instructions"
|
||||
]
|
||||
"template": ["references", "instructions"]
|
||||
},
|
||||
"description": "Create a prompt template with dynamic variables.",
|
||||
"display_name": "Prompt",
|
||||
"documentation": "",
|
||||
"edited": false,
|
||||
"field_order": [
|
||||
"template"
|
||||
],
|
||||
"field_order": ["template"],
|
||||
"frozen": false,
|
||||
"icon": "prompts",
|
||||
"legacy": false,
|
||||
@ -331,9 +290,7 @@
|
||||
"name": "prompt",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -366,10 +323,7 @@
|
||||
"fileTypes": [],
|
||||
"file_path": "",
|
||||
"info": "",
|
||||
"input_types": [
|
||||
"Message",
|
||||
"Text"
|
||||
],
|
||||
"input_types": ["Message", "Text"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"multiline": true,
|
||||
@ -390,10 +344,7 @@
|
||||
"fileTypes": [],
|
||||
"file_path": "",
|
||||
"info": "",
|
||||
"input_types": [
|
||||
"Message",
|
||||
"Text"
|
||||
],
|
||||
"input_types": ["Message", "Text"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"multiline": true,
|
||||
@ -428,9 +379,7 @@
|
||||
"display_name": "Tool Placeholder",
|
||||
"dynamic": false,
|
||||
"info": "A placeholder input for tool mode.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "tool_placeholder",
|
||||
@ -473,9 +422,7 @@
|
||||
"display_name": "Instructions",
|
||||
"id": "TextInput-t88FI",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
@ -483,9 +430,7 @@
|
||||
"display_name": "Instructions",
|
||||
"documentation": "",
|
||||
"edited": false,
|
||||
"field_order": [
|
||||
"input_value"
|
||||
],
|
||||
"field_order": ["input_value"],
|
||||
"frozen": false,
|
||||
"icon": "type",
|
||||
"legacy": false,
|
||||
@ -501,9 +446,7 @@
|
||||
"name": "text",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -534,9 +477,7 @@
|
||||
"display_name": "Text",
|
||||
"dynamic": false,
|
||||
"info": "Text to be passed as input.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"multiline": true,
|
||||
@ -579,9 +520,7 @@
|
||||
"display_name": "Chat Output",
|
||||
"id": "ChatOutput-BE4YI",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
@ -612,9 +551,7 @@
|
||||
"name": "message",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -627,9 +564,7 @@
|
||||
"display_name": "Background Color",
|
||||
"dynamic": false,
|
||||
"info": "The background color of the icon.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "background_color",
|
||||
@ -648,9 +583,7 @@
|
||||
"display_name": "Icon",
|
||||
"dynamic": false,
|
||||
"info": "The icon of the message.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "chat_icon",
|
||||
@ -686,9 +619,7 @@
|
||||
"display_name": "Data Template",
|
||||
"dynamic": false,
|
||||
"info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "data_template",
|
||||
@ -706,9 +637,7 @@
|
||||
"display_name": "Text",
|
||||
"dynamic": false,
|
||||
"info": "Message to be passed as output.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "input_value",
|
||||
@ -727,10 +656,7 @@
|
||||
"dynamic": false,
|
||||
"info": "Type of sender.",
|
||||
"name": "sender",
|
||||
"options": [
|
||||
"Machine",
|
||||
"User"
|
||||
],
|
||||
"options": ["Machine", "User"],
|
||||
"placeholder": "",
|
||||
"required": false,
|
||||
"show": true,
|
||||
@ -744,9 +670,7 @@
|
||||
"display_name": "Sender Name",
|
||||
"dynamic": false,
|
||||
"info": "Name of the sender.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "sender_name",
|
||||
@ -764,9 +688,7 @@
|
||||
"display_name": "Session ID",
|
||||
"dynamic": false,
|
||||
"info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "session_id",
|
||||
@ -801,9 +723,7 @@
|
||||
"display_name": "Text Color",
|
||||
"dynamic": false,
|
||||
"info": "The text color of the name",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "text_color",
|
||||
@ -952,10 +872,7 @@
|
||||
"data": {
|
||||
"id": "OpenAIModel-MyAsQ",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"LanguageModel",
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["LanguageModel", "Message"],
|
||||
"beta": false,
|
||||
"category": "models",
|
||||
"conditional_paths": [],
|
||||
@ -994,9 +911,7 @@
|
||||
"required_inputs": [],
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
},
|
||||
{
|
||||
@ -1005,14 +920,10 @@
|
||||
"display_name": "Language Model",
|
||||
"method": "build_model",
|
||||
"name": "model_output",
|
||||
"required_inputs": [
|
||||
"api_key"
|
||||
],
|
||||
"required_inputs": ["api_key"],
|
||||
"selected": "LanguageModel",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"LanguageModel"
|
||||
],
|
||||
"types": ["LanguageModel"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -1026,9 +937,7 @@
|
||||
"display_name": "OpenAI API Key",
|
||||
"dynamic": false,
|
||||
"info": "The OpenAI API Key to use for the OpenAI model.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"load_from_db": true,
|
||||
"name": "api_key",
|
||||
"password": true,
|
||||
@ -1037,7 +946,7 @@
|
||||
"show": true,
|
||||
"title_case": false,
|
||||
"type": "str",
|
||||
"value": ""
|
||||
"value": "OPENAI_API_KEY"
|
||||
},
|
||||
"code": {
|
||||
"advanced": true,
|
||||
@ -1063,9 +972,7 @@
|
||||
"display_name": "Input",
|
||||
"dynamic": false,
|
||||
"info": "",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1247,9 +1154,7 @@
|
||||
"display_name": "System Message",
|
||||
"dynamic": false,
|
||||
"info": "System message to pass to the model.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1334,11 +1239,7 @@
|
||||
"data": {
|
||||
"id": "URL-EPEnt",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Data",
|
||||
"DataFrame",
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Data", "DataFrame", "Message"],
|
||||
"beta": false,
|
||||
"category": "data",
|
||||
"conditional_paths": [],
|
||||
@ -1347,10 +1248,7 @@
|
||||
"display_name": "URL",
|
||||
"documentation": "",
|
||||
"edited": false,
|
||||
"field_order": [
|
||||
"urls",
|
||||
"format"
|
||||
],
|
||||
"field_order": ["urls", "format"],
|
||||
"frozen": false,
|
||||
"icon": "layout-template",
|
||||
"key": "URL",
|
||||
@ -1367,9 +1265,7 @@
|
||||
"name": "data",
|
||||
"selected": "Data",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Data"
|
||||
],
|
||||
"types": ["Data"],
|
||||
"value": "__UNDEFINED__"
|
||||
},
|
||||
{
|
||||
@ -1380,9 +1276,7 @@
|
||||
"name": "text",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
},
|
||||
{
|
||||
@ -1393,9 +1287,7 @@
|
||||
"name": "dataframe",
|
||||
"selected": "DataFrame",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"DataFrame"
|
||||
],
|
||||
"types": ["DataFrame"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -1430,10 +1322,7 @@
|
||||
"dynamic": false,
|
||||
"info": "Output Format. Use 'Text' to extract the text from the HTML or 'Raw HTML' for the raw HTML content.",
|
||||
"name": "format",
|
||||
"options": [
|
||||
"Text",
|
||||
"Raw HTML"
|
||||
],
|
||||
"options": ["Text", "Raw HTML"],
|
||||
"options_metadata": [],
|
||||
"placeholder": "",
|
||||
"required": false,
|
||||
@ -1450,9 +1339,7 @@
|
||||
"display_name": "URLs",
|
||||
"dynamic": false,
|
||||
"info": "",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": true,
|
||||
"list_add_label": "Add URL",
|
||||
"load_from_db": false,
|
||||
@ -1465,10 +1352,7 @@
|
||||
"trace_as_input": true,
|
||||
"trace_as_metadata": true,
|
||||
"type": "str",
|
||||
"value": [
|
||||
"https://langflow.org/",
|
||||
"https://docs.langflow.org/"
|
||||
]
|
||||
"value": ["https://langflow.org/", "https://docs.langflow.org/"]
|
||||
}
|
||||
},
|
||||
"tool_mode": false
|
||||
@ -1504,8 +1388,5 @@
|
||||
"is_component": false,
|
||||
"last_tested_version": "1.0.19.post2",
|
||||
"name": "Blog Writer",
|
||||
"tags": [
|
||||
"chatbots",
|
||||
"content-generation"
|
||||
]
|
||||
}
|
||||
"tags": ["chatbots", "content-generation"]
|
||||
}
|
||||
|
||||
@ -204,9 +204,7 @@
|
||||
"name": "message",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -490,9 +488,7 @@
|
||||
"name": "messages",
|
||||
"selected": "Data",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Data"
|
||||
],
|
||||
"types": ["Data"],
|
||||
"value": "__UNDEFINED__"
|
||||
},
|
||||
{
|
||||
@ -503,9 +499,7 @@
|
||||
"name": "messages_text",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -722,9 +716,7 @@
|
||||
"name": "prompt",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -950,9 +942,7 @@
|
||||
"name": "message",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -1267,9 +1257,7 @@
|
||||
"name": "data",
|
||||
"selected": "Data",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Data"
|
||||
],
|
||||
"types": ["Data"],
|
||||
"value": "__UNDEFINED__"
|
||||
},
|
||||
{
|
||||
@ -1280,9 +1268,7 @@
|
||||
"name": "text",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
},
|
||||
{
|
||||
@ -1293,9 +1279,7 @@
|
||||
"name": "dataframe",
|
||||
"selected": "DataFrame",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"DataFrame"
|
||||
],
|
||||
"types": ["DataFrame"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -1412,9 +1396,7 @@
|
||||
"name": "data",
|
||||
"selected": "Data",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Data"
|
||||
],
|
||||
"types": ["Data"],
|
||||
"value": "__UNDEFINED__"
|
||||
},
|
||||
{
|
||||
@ -1425,9 +1407,7 @@
|
||||
"name": "text",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
},
|
||||
{
|
||||
@ -1438,9 +1418,7 @@
|
||||
"name": "dataframe",
|
||||
"selected": "DataFrame",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"DataFrame"
|
||||
],
|
||||
"types": ["DataFrame"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -1563,9 +1541,7 @@
|
||||
"name": "data",
|
||||
"selected": "Data",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Data"
|
||||
],
|
||||
"types": ["Data"],
|
||||
"value": "__UNDEFINED__"
|
||||
},
|
||||
{
|
||||
@ -1576,9 +1552,7 @@
|
||||
"name": "text",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
},
|
||||
{
|
||||
@ -1589,9 +1563,7 @@
|
||||
"name": "dataframe",
|
||||
"selected": "DataFrame",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"DataFrame"
|
||||
],
|
||||
"types": ["DataFrame"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -1722,9 +1694,7 @@
|
||||
"required_inputs": [],
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
},
|
||||
{
|
||||
@ -1736,9 +1706,7 @@
|
||||
"required_inputs": ["api_key"],
|
||||
"selected": "LanguageModel",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"LanguageModel"
|
||||
],
|
||||
"types": ["LanguageModel"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -1753,7 +1721,7 @@
|
||||
"dynamic": false,
|
||||
"info": "Your Anthropic API key.",
|
||||
"input_types": ["Message"],
|
||||
"load_from_db": false,
|
||||
"load_from_db": true,
|
||||
"name": "api_key",
|
||||
"password": true,
|
||||
"placeholder": "",
|
||||
@ -1762,7 +1730,7 @@
|
||||
"show": true,
|
||||
"title_case": false,
|
||||
"type": "str",
|
||||
"value": ""
|
||||
"value": "ANTHROPIC_API_KEY"
|
||||
},
|
||||
"base_url": {
|
||||
"_input_type": "MessageTextInput",
|
||||
|
||||
@ -8,16 +8,12 @@
|
||||
"dataType": "File",
|
||||
"id": "File-GwJQZ",
|
||||
"name": "data",
|
||||
"output_types": [
|
||||
"Data"
|
||||
]
|
||||
"output_types": ["Data"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "data",
|
||||
"id": "ParseData-BbvKb",
|
||||
"inputTypes": [
|
||||
"Data"
|
||||
],
|
||||
"inputTypes": ["Data"],
|
||||
"type": "other"
|
||||
}
|
||||
},
|
||||
@ -35,17 +31,12 @@
|
||||
"dataType": "ParseData",
|
||||
"id": "ParseData-BbvKb",
|
||||
"name": "text",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "Document",
|
||||
"id": "Prompt-yvZHT",
|
||||
"inputTypes": [
|
||||
"Message",
|
||||
"Text"
|
||||
],
|
||||
"inputTypes": ["Message", "Text"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -63,16 +54,12 @@
|
||||
"dataType": "ChatInput",
|
||||
"id": "ChatInput-li477",
|
||||
"name": "message",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
"id": "OpenAIModel-atkmo",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -90,16 +77,12 @@
|
||||
"dataType": "Prompt",
|
||||
"id": "Prompt-yvZHT",
|
||||
"name": "prompt",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "system_message",
|
||||
"id": "OpenAIModel-atkmo",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -117,16 +100,12 @@
|
||||
"dataType": "OpenAIModel",
|
||||
"id": "OpenAIModel-atkmo",
|
||||
"name": "text_output",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
"id": "ChatOutput-8pgwS",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -145,9 +124,7 @@
|
||||
"display_name": "Chat Input",
|
||||
"id": "ChatInput-li477",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
@ -178,9 +155,7 @@
|
||||
"name": "message",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -193,9 +168,7 @@
|
||||
"display_name": "Background Color",
|
||||
"dynamic": false,
|
||||
"info": "The background color of the icon.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "background_color",
|
||||
@ -214,9 +187,7 @@
|
||||
"display_name": "Icon",
|
||||
"dynamic": false,
|
||||
"info": "The icon of the message.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "chat_icon",
|
||||
@ -313,10 +284,7 @@
|
||||
"dynamic": false,
|
||||
"info": "Type of sender.",
|
||||
"name": "sender",
|
||||
"options": [
|
||||
"Machine",
|
||||
"User"
|
||||
],
|
||||
"options": ["Machine", "User"],
|
||||
"placeholder": "",
|
||||
"required": false,
|
||||
"show": true,
|
||||
@ -330,9 +298,7 @@
|
||||
"display_name": "Sender Name",
|
||||
"dynamic": false,
|
||||
"info": "Name of the sender.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "sender_name",
|
||||
@ -350,9 +316,7 @@
|
||||
"display_name": "Session ID",
|
||||
"dynamic": false,
|
||||
"info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "session_id",
|
||||
@ -387,9 +351,7 @@
|
||||
"display_name": "Text Color",
|
||||
"dynamic": false,
|
||||
"info": "The text color of the name",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "text_color",
|
||||
@ -431,9 +393,7 @@
|
||||
"display_name": "Chat Output",
|
||||
"id": "ChatOutput-8pgwS",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
@ -467,9 +427,7 @@
|
||||
"name": "message",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -482,9 +440,7 @@
|
||||
"display_name": "Background Color",
|
||||
"dynamic": false,
|
||||
"info": "The background color of the icon.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "background_color",
|
||||
@ -504,9 +460,7 @@
|
||||
"display_name": "Icon",
|
||||
"dynamic": false,
|
||||
"info": "The icon of the message.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "chat_icon",
|
||||
@ -544,9 +498,7 @@
|
||||
"display_name": "Data Template",
|
||||
"dynamic": false,
|
||||
"info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "data_template",
|
||||
@ -566,9 +518,7 @@
|
||||
"display_name": "Text",
|
||||
"dynamic": false,
|
||||
"info": "Message to be passed as output.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "input_value",
|
||||
@ -589,10 +539,7 @@
|
||||
"dynamic": false,
|
||||
"info": "Type of sender.",
|
||||
"name": "sender",
|
||||
"options": [
|
||||
"Machine",
|
||||
"User"
|
||||
],
|
||||
"options": ["Machine", "User"],
|
||||
"placeholder": "",
|
||||
"required": false,
|
||||
"show": true,
|
||||
@ -608,9 +555,7 @@
|
||||
"display_name": "Sender Name",
|
||||
"dynamic": false,
|
||||
"info": "Name of the sender.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "sender_name",
|
||||
@ -630,9 +575,7 @@
|
||||
"display_name": "Session ID",
|
||||
"dynamic": false,
|
||||
"info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "session_id",
|
||||
@ -668,9 +611,7 @@
|
||||
"display_name": "Text Color",
|
||||
"dynamic": false,
|
||||
"info": "The text color of the name",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "text_color",
|
||||
@ -714,9 +655,7 @@
|
||||
"display_name": "Parse Data",
|
||||
"id": "ParseData-BbvKb",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
@ -724,16 +663,14 @@
|
||||
"display_name": "Parse Data",
|
||||
"documentation": "",
|
||||
"edited": false,
|
||||
"field_order": [
|
||||
"data",
|
||||
"template",
|
||||
"sep"
|
||||
],
|
||||
"field_order": ["data", "template", "sep"],
|
||||
"frozen": false,
|
||||
"icon": "message-square",
|
||||
"legacy": false,
|
||||
"lf_version": "1.0.19.post2",
|
||||
"metadata": {},
|
||||
"metadata": {
|
||||
"legacy_name": "Parse Data"
|
||||
},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
{
|
||||
@ -744,9 +681,7 @@
|
||||
"name": "text",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
},
|
||||
{
|
||||
@ -757,9 +692,7 @@
|
||||
"name": "data_list",
|
||||
"selected": "Data",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Data"
|
||||
],
|
||||
"types": ["Data"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -782,16 +715,14 @@
|
||||
"show": true,
|
||||
"title_case": false,
|
||||
"type": "code",
|
||||
"value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text, data_to_text_list\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Data to Message\"\n description = \"Convert Data objects into Messages using any {field_name} from input data.\"\n icon = \"message-square\"\n name = \"ParseData\"\n\n inputs = [\n DataInput(name=\"data\", display_name=\"Data\", info=\"The data to convert to text.\", is_list=True, required=True),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n required=True,\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"text\",\n info=\"Data as a single Message, with each input Data separated by Separator\",\n method=\"parse_data\",\n ),\n Output(\n display_name=\"Data List\",\n name=\"data_list\",\n info=\"Data as a list of new Data, each having `text` formatted by Template\",\n method=\"parse_data_as_list\",\n ),\n ]\n\n def _clean_args(self) -> tuple[list[Data], str, str]:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n sep = self.sep\n return data, template, sep\n\n def parse_data(self) -> Message:\n data, template, sep = self._clean_args()\n result_string = data_to_text(template, data, sep)\n self.status = result_string\n return Message(text=result_string)\n\n def parse_data_as_list(self) -> list[Data]:\n data, template, _ = self._clean_args()\n text_list, data_list = data_to_text_list(template, data)\n for item, text in zip(data_list, text_list, strict=True):\n item.set_text(text)\n self.status = data_list\n return data_list\n"
|
||||
"value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text, data_to_text_list\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Data to Message\"\n description = \"Convert Data objects into Messages using any {field_name} from input data.\"\n icon = \"message-square\"\n name = \"ParseData\"\n metadata = {\n \"legacy_name\": \"Parse Data\",\n }\n\n inputs = [\n DataInput(\n name=\"data\",\n display_name=\"Data\",\n info=\"The data to convert to text.\",\n is_list=True,\n required=True,\n ),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n required=True,\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"text\",\n info=\"Data as a single Message, with each input Data separated by Separator\",\n method=\"parse_data\",\n ),\n Output(\n display_name=\"Data List\",\n name=\"data_list\",\n info=\"Data as a list of new Data, each having `text` formatted by Template\",\n method=\"parse_data_as_list\",\n ),\n ]\n\n def _clean_args(self) -> tuple[list[Data], str, str]:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n sep = self.sep\n return data, template, sep\n\n def parse_data(self) -> Message:\n data, template, sep = self._clean_args()\n result_string = data_to_text(template, data, sep)\n self.status = result_string\n return Message(text=result_string)\n\n def parse_data_as_list(self) -> list[Data]:\n data, template, _ = self._clean_args()\n text_list, data_list = data_to_text_list(template, data)\n for item, text in zip(data_list, text_list, strict=True):\n item.set_text(text)\n self.status = data_list\n return data_list\n"
|
||||
},
|
||||
"data": {
|
||||
"advanced": false,
|
||||
"display_name": "Data",
|
||||
"dynamic": false,
|
||||
"info": "The data to convert to text.",
|
||||
"input_types": [
|
||||
"Data"
|
||||
],
|
||||
"input_types": ["Data"],
|
||||
"list": true,
|
||||
"name": "data",
|
||||
"placeholder": "",
|
||||
@ -824,9 +755,7 @@
|
||||
"display_name": "Template",
|
||||
"dynamic": false,
|
||||
"info": "The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"multiline": true,
|
||||
@ -934,9 +863,7 @@
|
||||
"data": {
|
||||
"id": "File-GwJQZ",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Data"
|
||||
],
|
||||
"base_classes": ["Data"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
@ -965,9 +892,7 @@
|
||||
"required_inputs": [],
|
||||
"selected": "Data",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Data"
|
||||
],
|
||||
"types": ["Data"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -1030,10 +955,7 @@
|
||||
"display_name": "Server File Path",
|
||||
"dynamic": false,
|
||||
"info": "Data object with a 'file_path' property pointing to server file or a Message object with a path to the file. Supercedes 'Path' but supports same file types.",
|
||||
"input_types": [
|
||||
"Data",
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Data", "Message"],
|
||||
"list": true,
|
||||
"name": "file_path",
|
||||
"placeholder": "",
|
||||
@ -1180,24 +1102,18 @@
|
||||
"display_name": "Prompt",
|
||||
"id": "Prompt-yvZHT",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {
|
||||
"template": [
|
||||
"Document"
|
||||
]
|
||||
"template": ["Document"]
|
||||
},
|
||||
"description": "Create a prompt template with dynamic variables.",
|
||||
"display_name": "Prompt",
|
||||
"documentation": "",
|
||||
"edited": false,
|
||||
"error": null,
|
||||
"field_order": [
|
||||
"template"
|
||||
],
|
||||
"field_order": ["template"],
|
||||
"frozen": false,
|
||||
"full_path": null,
|
||||
"icon": "prompts",
|
||||
@ -1218,9 +1134,7 @@
|
||||
"name": "prompt",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -1234,10 +1148,7 @@
|
||||
"fileTypes": [],
|
||||
"file_path": "",
|
||||
"info": "",
|
||||
"input_types": [
|
||||
"Message",
|
||||
"Text"
|
||||
],
|
||||
"input_types": ["Message", "Text"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"multiline": true,
|
||||
@ -1290,9 +1201,7 @@
|
||||
"display_name": "Tool Placeholder",
|
||||
"dynamic": false,
|
||||
"info": "A placeholder input for tool mode.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "tool_placeholder",
|
||||
@ -1334,10 +1243,7 @@
|
||||
"data": {
|
||||
"id": "OpenAIModel-atkmo",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"LanguageModel",
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["LanguageModel", "Message"],
|
||||
"beta": false,
|
||||
"category": "models",
|
||||
"conditional_paths": [],
|
||||
@ -1376,9 +1282,7 @@
|
||||
"required_inputs": [],
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
},
|
||||
{
|
||||
@ -1387,14 +1291,10 @@
|
||||
"display_name": "Language Model",
|
||||
"method": "build_model",
|
||||
"name": "model_output",
|
||||
"required_inputs": [
|
||||
"api_key"
|
||||
],
|
||||
"required_inputs": ["api_key"],
|
||||
"selected": "LanguageModel",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"LanguageModel"
|
||||
],
|
||||
"types": ["LanguageModel"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -1408,9 +1308,7 @@
|
||||
"display_name": "OpenAI API Key",
|
||||
"dynamic": false,
|
||||
"info": "The OpenAI API Key to use for the OpenAI model.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"load_from_db": true,
|
||||
"name": "api_key",
|
||||
"password": true,
|
||||
@ -1419,7 +1317,7 @@
|
||||
"show": true,
|
||||
"title_case": false,
|
||||
"type": "str",
|
||||
"value": ""
|
||||
"value": "OPENAI_API_KEY"
|
||||
},
|
||||
"code": {
|
||||
"advanced": true,
|
||||
@ -1445,9 +1343,7 @@
|
||||
"display_name": "Input",
|
||||
"dynamic": false,
|
||||
"info": "",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1629,9 +1525,7 @@
|
||||
"display_name": "System Message",
|
||||
"dynamic": false,
|
||||
"info": "System message to pass to the model.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1727,9 +1621,5 @@
|
||||
"is_component": false,
|
||||
"last_tested_version": "1.0.19.post2",
|
||||
"name": "Document Q&A",
|
||||
"tags": [
|
||||
"rag",
|
||||
"q-a",
|
||||
"openai"
|
||||
]
|
||||
}
|
||||
"tags": ["rag", "q-a", "openai"]
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -9,16 +9,12 @@
|
||||
"dataType": "StructuredOutputComponent",
|
||||
"id": "StructuredOutputComponent-XYoUc",
|
||||
"name": "structured_output",
|
||||
"output_types": [
|
||||
"Data"
|
||||
]
|
||||
"output_types": ["Data"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "data",
|
||||
"id": "ParseData-HzweJ",
|
||||
"inputTypes": [
|
||||
"Data"
|
||||
],
|
||||
"inputTypes": ["Data"],
|
||||
"type": "other"
|
||||
}
|
||||
},
|
||||
@ -37,16 +33,12 @@
|
||||
"dataType": "ParseData",
|
||||
"id": "ParseData-HzweJ",
|
||||
"name": "text",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
"id": "ChatOutput-xQxLm",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -64,16 +56,12 @@
|
||||
"dataType": "ChatInput",
|
||||
"id": "ChatInput-rAWlE",
|
||||
"name": "message",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
"id": "OpenAIModel-cqeNw",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -91,16 +79,12 @@
|
||||
"dataType": "Prompt",
|
||||
"id": "Prompt-AzK6t",
|
||||
"name": "prompt",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "system_message",
|
||||
"id": "OpenAIModel-cqeNw",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -118,16 +102,12 @@
|
||||
"dataType": "OpenAIModel",
|
||||
"id": "OpenAIModel-cqeNw",
|
||||
"name": "text_output",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
"id": "StructuredOutputComponent-XYoUc",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -145,16 +125,12 @@
|
||||
"dataType": "OpenAIModel",
|
||||
"id": "OpenAIModel-cqeNw",
|
||||
"name": "model_output",
|
||||
"output_types": [
|
||||
"LanguageModel"
|
||||
]
|
||||
"output_types": ["LanguageModel"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "llm",
|
||||
"id": "StructuredOutputComponent-XYoUc",
|
||||
"inputTypes": [
|
||||
"LanguageModel"
|
||||
],
|
||||
"inputTypes": ["LanguageModel"],
|
||||
"type": "other"
|
||||
}
|
||||
},
|
||||
@ -173,9 +149,7 @@
|
||||
"display_name": "Chat Input",
|
||||
"id": "ChatInput-rAWlE",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
@ -209,9 +183,7 @@
|
||||
"name": "message",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -224,9 +196,7 @@
|
||||
"display_name": "Background Color",
|
||||
"dynamic": false,
|
||||
"info": "The background color of the icon.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "background_color",
|
||||
@ -246,9 +216,7 @@
|
||||
"display_name": "Icon",
|
||||
"dynamic": false,
|
||||
"info": "The icon of the message.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "chat_icon",
|
||||
@ -351,10 +319,7 @@
|
||||
"dynamic": false,
|
||||
"info": "Type of sender.",
|
||||
"name": "sender",
|
||||
"options": [
|
||||
"Machine",
|
||||
"User"
|
||||
],
|
||||
"options": ["Machine", "User"],
|
||||
"placeholder": "",
|
||||
"required": false,
|
||||
"show": true,
|
||||
@ -370,9 +335,7 @@
|
||||
"display_name": "Sender Name",
|
||||
"dynamic": false,
|
||||
"info": "Name of the sender.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "sender_name",
|
||||
@ -392,9 +355,7 @@
|
||||
"display_name": "Session ID",
|
||||
"dynamic": false,
|
||||
"info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "session_id",
|
||||
@ -430,9 +391,7 @@
|
||||
"display_name": "Text Color",
|
||||
"dynamic": false,
|
||||
"info": "The text color of the name",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "text_color",
|
||||
@ -476,9 +435,7 @@
|
||||
"display_name": "Chat Output",
|
||||
"id": "ChatOutput-xQxLm",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
@ -512,9 +469,7 @@
|
||||
"name": "message",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -527,9 +482,7 @@
|
||||
"display_name": "Background Color",
|
||||
"dynamic": false,
|
||||
"info": "The background color of the icon.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "background_color",
|
||||
@ -549,9 +502,7 @@
|
||||
"display_name": "Icon",
|
||||
"dynamic": false,
|
||||
"info": "The icon of the message.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "chat_icon",
|
||||
@ -589,9 +540,7 @@
|
||||
"display_name": "Data Template",
|
||||
"dynamic": false,
|
||||
"info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "data_template",
|
||||
@ -611,9 +560,7 @@
|
||||
"display_name": "Text",
|
||||
"dynamic": false,
|
||||
"info": "Message to be passed as output.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "input_value",
|
||||
@ -634,10 +581,7 @@
|
||||
"dynamic": false,
|
||||
"info": "Type of sender.",
|
||||
"name": "sender",
|
||||
"options": [
|
||||
"Machine",
|
||||
"User"
|
||||
],
|
||||
"options": ["Machine", "User"],
|
||||
"placeholder": "",
|
||||
"required": false,
|
||||
"show": true,
|
||||
@ -653,9 +597,7 @@
|
||||
"display_name": "Sender Name",
|
||||
"dynamic": false,
|
||||
"info": "Name of the sender.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "sender_name",
|
||||
@ -675,9 +617,7 @@
|
||||
"display_name": "Session ID",
|
||||
"dynamic": false,
|
||||
"info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "session_id",
|
||||
@ -713,9 +653,7 @@
|
||||
"display_name": "Text Color",
|
||||
"dynamic": false,
|
||||
"info": "The text color of the name",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "text_color",
|
||||
@ -794,9 +732,7 @@
|
||||
"display_name": "Structured Output",
|
||||
"id": "StructuredOutputComponent-XYoUc",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Data"
|
||||
],
|
||||
"base_classes": ["Data"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
@ -824,9 +760,7 @@
|
||||
"method": "build_structured_output",
|
||||
"name": "structured_output",
|
||||
"selected": "Data",
|
||||
"types": [
|
||||
"Data"
|
||||
],
|
||||
"types": ["Data"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -857,9 +791,7 @@
|
||||
"display_name": "Input message",
|
||||
"dynamic": false,
|
||||
"info": "",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "input_value",
|
||||
@ -879,9 +811,7 @@
|
||||
"display_name": "Language Model",
|
||||
"dynamic": false,
|
||||
"info": "The language model to use to generate the structured output.",
|
||||
"input_types": [
|
||||
"LanguageModel"
|
||||
],
|
||||
"input_types": ["LanguageModel"],
|
||||
"list": false,
|
||||
"name": "llm",
|
||||
"placeholder": "",
|
||||
@ -1025,9 +955,7 @@
|
||||
"data": {
|
||||
"id": "ParseData-HzweJ",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
@ -1035,16 +963,14 @@
|
||||
"display_name": "Parse Data",
|
||||
"documentation": "",
|
||||
"edited": false,
|
||||
"field_order": [
|
||||
"data",
|
||||
"template",
|
||||
"sep"
|
||||
],
|
||||
"field_order": ["data", "template", "sep"],
|
||||
"frozen": false,
|
||||
"icon": "message-square",
|
||||
"legacy": false,
|
||||
"lf_version": "1.0.19.post2",
|
||||
"metadata": {},
|
||||
"metadata": {
|
||||
"legacy_name": "Parse Data"
|
||||
},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
{
|
||||
@ -1055,9 +981,7 @@
|
||||
"name": "text",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
},
|
||||
{
|
||||
@ -1068,9 +992,7 @@
|
||||
"name": "data_list",
|
||||
"selected": "Data",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Data"
|
||||
],
|
||||
"types": ["Data"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -1093,7 +1015,7 @@
|
||||
"show": true,
|
||||
"title_case": false,
|
||||
"type": "code",
|
||||
"value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text, data_to_text_list\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Data to Message\"\n description = \"Convert Data objects into Messages using any {field_name} from input data.\"\n icon = \"message-square\"\n name = \"ParseData\"\n\n inputs = [\n DataInput(name=\"data\", display_name=\"Data\", info=\"The data to convert to text.\", is_list=True, required=True),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n required=True,\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"text\",\n info=\"Data as a single Message, with each input Data separated by Separator\",\n method=\"parse_data\",\n ),\n Output(\n display_name=\"Data List\",\n name=\"data_list\",\n info=\"Data as a list of new Data, each having `text` formatted by Template\",\n method=\"parse_data_as_list\",\n ),\n ]\n\n def _clean_args(self) -> tuple[list[Data], str, str]:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n sep = self.sep\n return data, template, sep\n\n def parse_data(self) -> Message:\n data, template, sep = self._clean_args()\n result_string = data_to_text(template, data, sep)\n self.status = result_string\n return Message(text=result_string)\n\n def parse_data_as_list(self) -> list[Data]:\n data, template, _ = self._clean_args()\n text_list, data_list = data_to_text_list(template, data)\n for item, text in zip(data_list, text_list, strict=True):\n item.set_text(text)\n self.status = data_list\n return data_list\n"
|
||||
"value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text, data_to_text_list\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Data to Message\"\n description = \"Convert Data objects into Messages using any {field_name} from input data.\"\n icon = \"message-square\"\n name = \"ParseData\"\n metadata = {\n \"legacy_name\": \"Parse Data\",\n }\n\n inputs = [\n DataInput(\n name=\"data\",\n display_name=\"Data\",\n info=\"The data to convert to text.\",\n is_list=True,\n required=True,\n ),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n required=True,\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"text\",\n info=\"Data as a single Message, with each input Data separated by Separator\",\n method=\"parse_data\",\n ),\n Output(\n display_name=\"Data List\",\n name=\"data_list\",\n info=\"Data as a list of new Data, each having `text` formatted by Template\",\n method=\"parse_data_as_list\",\n ),\n ]\n\n def _clean_args(self) -> tuple[list[Data], str, str]:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n sep = self.sep\n return data, template, sep\n\n def parse_data(self) -> Message:\n data, template, sep = self._clean_args()\n result_string = data_to_text(template, data, sep)\n self.status = result_string\n return Message(text=result_string)\n\n def parse_data_as_list(self) -> list[Data]:\n data, template, _ = self._clean_args()\n text_list, data_list = data_to_text_list(template, data)\n for item, text in zip(data_list, text_list, strict=True):\n item.set_text(text)\n self.status = data_list\n return data_list\n"
|
||||
},
|
||||
"data": {
|
||||
"_input_type": "DataInput",
|
||||
@ -1101,9 +1023,7 @@
|
||||
"display_name": "Data",
|
||||
"dynamic": false,
|
||||
"info": "The data to convert to text.",
|
||||
"input_types": [
|
||||
"Data"
|
||||
],
|
||||
"input_types": ["Data"],
|
||||
"list": true,
|
||||
"name": "data",
|
||||
"placeholder": "",
|
||||
@ -1139,9 +1059,7 @@
|
||||
"display_name": "Template",
|
||||
"dynamic": false,
|
||||
"info": "The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"multiline": true,
|
||||
@ -1186,9 +1104,7 @@
|
||||
"display_name": "Prompt",
|
||||
"id": "Prompt-AzK6t",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {
|
||||
@ -1198,9 +1114,7 @@
|
||||
"display_name": "Prompt",
|
||||
"documentation": "",
|
||||
"edited": false,
|
||||
"field_order": [
|
||||
"template"
|
||||
],
|
||||
"field_order": ["template"],
|
||||
"frozen": false,
|
||||
"icon": "prompts",
|
||||
"legacy": false,
|
||||
@ -1216,9 +1130,7 @@
|
||||
"name": "prompt",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -1267,9 +1179,7 @@
|
||||
"display_name": "Tool Placeholder",
|
||||
"dynamic": false,
|
||||
"info": "A placeholder input for tool mode.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "tool_placeholder",
|
||||
@ -1311,10 +1221,7 @@
|
||||
"data": {
|
||||
"id": "OpenAIModel-cqeNw",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"LanguageModel",
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["LanguageModel", "Message"],
|
||||
"beta": false,
|
||||
"category": "models",
|
||||
"conditional_paths": [],
|
||||
@ -1353,9 +1260,7 @@
|
||||
"required_inputs": [],
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
},
|
||||
{
|
||||
@ -1364,14 +1269,10 @@
|
||||
"display_name": "Language Model",
|
||||
"method": "build_model",
|
||||
"name": "model_output",
|
||||
"required_inputs": [
|
||||
"api_key"
|
||||
],
|
||||
"required_inputs": ["api_key"],
|
||||
"selected": "LanguageModel",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"LanguageModel"
|
||||
],
|
||||
"types": ["LanguageModel"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -1385,9 +1286,7 @@
|
||||
"display_name": "OpenAI API Key",
|
||||
"dynamic": false,
|
||||
"info": "The OpenAI API Key to use for the OpenAI model.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"load_from_db": true,
|
||||
"name": "api_key",
|
||||
"password": true,
|
||||
@ -1396,7 +1295,7 @@
|
||||
"show": true,
|
||||
"title_case": false,
|
||||
"type": "str",
|
||||
"value": ""
|
||||
"value": "OPENAI_API_KEY"
|
||||
},
|
||||
"code": {
|
||||
"advanced": true,
|
||||
@ -1422,9 +1321,7 @@
|
||||
"display_name": "Input",
|
||||
"dynamic": false,
|
||||
"info": "",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1606,9 +1503,7 @@
|
||||
"display_name": "System Message",
|
||||
"dynamic": false,
|
||||
"info": "System message to pass to the model.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1704,7 +1599,5 @@
|
||||
"is_component": false,
|
||||
"last_tested_version": "1.0.19.post2",
|
||||
"name": "Image Sentiment Analysis",
|
||||
"tags": [
|
||||
"classification"
|
||||
]
|
||||
}
|
||||
"tags": ["classification"]
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -9,16 +9,12 @@
|
||||
"dataType": "ArXivComponent",
|
||||
"id": "ArXivComponent-LChQN",
|
||||
"name": "papers",
|
||||
"output_types": [
|
||||
"Data"
|
||||
]
|
||||
"output_types": ["Data"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "data",
|
||||
"id": "LoopComponent-3vpc1",
|
||||
"inputTypes": [
|
||||
"Data"
|
||||
],
|
||||
"inputTypes": ["Data"],
|
||||
"type": "other"
|
||||
}
|
||||
},
|
||||
@ -36,16 +32,12 @@
|
||||
"dataType": "LoopComponent",
|
||||
"id": "LoopComponent-3vpc1",
|
||||
"name": "item",
|
||||
"output_types": [
|
||||
"Data"
|
||||
]
|
||||
"output_types": ["Data"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "data",
|
||||
"id": "ParseData-Pf12J",
|
||||
"inputTypes": [
|
||||
"Data"
|
||||
],
|
||||
"inputTypes": ["Data"],
|
||||
"type": "other"
|
||||
}
|
||||
},
|
||||
@ -63,16 +55,12 @@
|
||||
"dataType": "ParseData",
|
||||
"id": "ParseData-Pf12J",
|
||||
"name": "text",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
"id": "AnthropicModel-beO6B",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -90,16 +78,12 @@
|
||||
"dataType": "AnthropicModel",
|
||||
"id": "AnthropicModel-beO6B",
|
||||
"name": "text_output",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "message",
|
||||
"id": "MessagetoData-QRSBb",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -117,17 +101,13 @@
|
||||
"dataType": "MessagetoData",
|
||||
"id": "MessagetoData-QRSBb",
|
||||
"name": "data",
|
||||
"output_types": [
|
||||
"Data"
|
||||
]
|
||||
"output_types": ["Data"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"dataType": "LoopComponent",
|
||||
"id": "LoopComponent-3vpc1",
|
||||
"name": "item",
|
||||
"output_types": [
|
||||
"Data"
|
||||
]
|
||||
"output_types": ["Data"]
|
||||
}
|
||||
},
|
||||
"id": "xy-edge__MessagetoData-QRSBb{œdataTypeœ:œMessagetoDataœ,œidœ:œMessagetoData-QRSBbœ,œnameœ:œdataœ,œoutput_typesœ:[œDataœ]}-LoopComponent-3vpc1{œdataTypeœ:œLoopComponentœ,œidœ:œLoopComponent-3vpc1œ,œnameœ:œitemœ,œoutput_typesœ:[œDataœ]}",
|
||||
@ -144,16 +124,12 @@
|
||||
"dataType": "LoopComponent",
|
||||
"id": "LoopComponent-3vpc1",
|
||||
"name": "done",
|
||||
"output_types": [
|
||||
"Data"
|
||||
]
|
||||
"output_types": ["Data"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "data",
|
||||
"id": "ParseData-igEkj",
|
||||
"inputTypes": [
|
||||
"Data"
|
||||
],
|
||||
"inputTypes": ["Data"],
|
||||
"type": "other"
|
||||
}
|
||||
},
|
||||
@ -171,16 +147,12 @@
|
||||
"dataType": "ParseData",
|
||||
"id": "ParseData-igEkj",
|
||||
"name": "text",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
"id": "ChatOutput-UZgon",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -198,16 +170,12 @@
|
||||
"dataType": "ChatInput",
|
||||
"id": "ChatInput-m10vc",
|
||||
"name": "message",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "search_query",
|
||||
"id": "ArXivComponent-LChQN",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -223,9 +191,7 @@
|
||||
"data": {
|
||||
"id": "ArXivComponent-LChQN",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Data"
|
||||
],
|
||||
"base_classes": ["Data"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
@ -233,11 +199,7 @@
|
||||
"display_name": "arXiv",
|
||||
"documentation": "",
|
||||
"edited": false,
|
||||
"field_order": [
|
||||
"search_query",
|
||||
"search_type",
|
||||
"max_results"
|
||||
],
|
||||
"field_order": ["search_query", "search_type", "max_results"],
|
||||
"frozen": false,
|
||||
"icon": "arXiv",
|
||||
"legacy": false,
|
||||
@ -254,9 +216,7 @@
|
||||
"name": "papers",
|
||||
"selected": "Data",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Data"
|
||||
],
|
||||
"types": ["Data"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -305,9 +265,7 @@
|
||||
"display_name": "Search Query",
|
||||
"dynamic": false,
|
||||
"info": "The search query for arXiv papers (e.g., 'quantum computing')",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -331,13 +289,7 @@
|
||||
"dynamic": false,
|
||||
"info": "The field to search in",
|
||||
"name": "search_type",
|
||||
"options": [
|
||||
"all",
|
||||
"title",
|
||||
"abstract",
|
||||
"author",
|
||||
"cat"
|
||||
],
|
||||
"options": ["all", "title", "abstract", "author", "cat"],
|
||||
"options_metadata": [],
|
||||
"placeholder": "",
|
||||
"required": false,
|
||||
@ -371,9 +323,7 @@
|
||||
"data": {
|
||||
"id": "LoopComponent-3vpc1",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Data"
|
||||
],
|
||||
"base_classes": ["Data"],
|
||||
"beta": false,
|
||||
"category": "logic",
|
||||
"conditional_paths": [],
|
||||
@ -382,9 +332,7 @@
|
||||
"display_name": "Loop",
|
||||
"documentation": "",
|
||||
"edited": false,
|
||||
"field_order": [
|
||||
"data"
|
||||
],
|
||||
"field_order": ["data"],
|
||||
"frozen": false,
|
||||
"icon": "infinity",
|
||||
"key": "LoopComponent",
|
||||
@ -402,9 +350,7 @@
|
||||
"name": "item",
|
||||
"selected": "Data",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Data"
|
||||
],
|
||||
"types": ["Data"],
|
||||
"value": "__UNDEFINED__"
|
||||
},
|
||||
{
|
||||
@ -415,9 +361,7 @@
|
||||
"name": "done",
|
||||
"selected": "Data",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Data"
|
||||
],
|
||||
"types": ["Data"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -449,9 +393,7 @@
|
||||
"display_name": "Data",
|
||||
"dynamic": false,
|
||||
"info": "The initial list of Data objects to iterate over.",
|
||||
"input_types": [
|
||||
"Data"
|
||||
],
|
||||
"input_types": ["Data"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"name": "data",
|
||||
@ -488,10 +430,7 @@
|
||||
"data": {
|
||||
"id": "ParseData-Pf12J",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Data",
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Data", "Message"],
|
||||
"beta": false,
|
||||
"category": "processing",
|
||||
"conditional_paths": [],
|
||||
@ -500,17 +439,15 @@
|
||||
"display_name": "Data to Message",
|
||||
"documentation": "",
|
||||
"edited": false,
|
||||
"field_order": [
|
||||
"data",
|
||||
"template",
|
||||
"sep"
|
||||
],
|
||||
"field_order": ["data", "template", "sep"],
|
||||
"frozen": false,
|
||||
"icon": "message-square",
|
||||
"key": "ParseData",
|
||||
"legacy": false,
|
||||
"lf_version": "1.1.5",
|
||||
"metadata": {},
|
||||
"metadata": {
|
||||
"legacy_name": "Parse Data"
|
||||
},
|
||||
"minimized": false,
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
@ -522,9 +459,7 @@
|
||||
"name": "text",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
},
|
||||
{
|
||||
@ -535,9 +470,7 @@
|
||||
"name": "data_list",
|
||||
"selected": "Data",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Data"
|
||||
],
|
||||
"types": ["Data"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -561,7 +494,7 @@
|
||||
"show": true,
|
||||
"title_case": false,
|
||||
"type": "code",
|
||||
"value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text, data_to_text_list\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Data to Message\"\n description = \"Convert Data objects into Messages using any {field_name} from input data.\"\n icon = \"message-square\"\n name = \"ParseData\"\n\n inputs = [\n DataInput(name=\"data\", display_name=\"Data\", info=\"The data to convert to text.\", is_list=True, required=True),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n required=True,\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"text\",\n info=\"Data as a single Message, with each input Data separated by Separator\",\n method=\"parse_data\",\n ),\n Output(\n display_name=\"Data List\",\n name=\"data_list\",\n info=\"Data as a list of new Data, each having `text` formatted by Template\",\n method=\"parse_data_as_list\",\n ),\n ]\n\n def _clean_args(self) -> tuple[list[Data], str, str]:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n sep = self.sep\n return data, template, sep\n\n def parse_data(self) -> Message:\n data, template, sep = self._clean_args()\n result_string = data_to_text(template, data, sep)\n self.status = result_string\n return Message(text=result_string)\n\n def parse_data_as_list(self) -> list[Data]:\n data, template, _ = self._clean_args()\n text_list, data_list = data_to_text_list(template, data)\n for item, text in zip(data_list, text_list, strict=True):\n item.set_text(text)\n self.status = data_list\n return data_list\n"
|
||||
"value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text, data_to_text_list\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Data to Message\"\n description = \"Convert Data objects into Messages using any {field_name} from input data.\"\n icon = \"message-square\"\n name = \"ParseData\"\n metadata = {\n \"legacy_name\": \"Parse Data\",\n }\n\n inputs = [\n DataInput(\n name=\"data\",\n display_name=\"Data\",\n info=\"The data to convert to text.\",\n is_list=True,\n required=True,\n ),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n required=True,\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"text\",\n info=\"Data as a single Message, with each input Data separated by Separator\",\n method=\"parse_data\",\n ),\n Output(\n display_name=\"Data List\",\n name=\"data_list\",\n info=\"Data as a list of new Data, each having `text` formatted by Template\",\n method=\"parse_data_as_list\",\n ),\n ]\n\n def _clean_args(self) -> tuple[list[Data], str, str]:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n sep = self.sep\n return data, template, sep\n\n def parse_data(self) -> Message:\n data, template, sep = self._clean_args()\n result_string = data_to_text(template, data, sep)\n self.status = result_string\n return Message(text=result_string)\n\n def parse_data_as_list(self) -> list[Data]:\n data, template, _ = self._clean_args()\n text_list, data_list = data_to_text_list(template, data)\n for item, text in zip(data_list, text_list, strict=True):\n item.set_text(text)\n self.status = data_list\n return data_list\n"
|
||||
},
|
||||
"data": {
|
||||
"_input_type": "DataInput",
|
||||
@ -569,9 +502,7 @@
|
||||
"display_name": "Data",
|
||||
"dynamic": false,
|
||||
"info": "The data to convert to text.",
|
||||
"input_types": [
|
||||
"Data"
|
||||
],
|
||||
"input_types": ["Data"],
|
||||
"list": true,
|
||||
"list_add_label": "Add More",
|
||||
"name": "data",
|
||||
@ -610,9 +541,7 @@
|
||||
"display_name": "Template",
|
||||
"dynamic": false,
|
||||
"info": "The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -651,10 +580,7 @@
|
||||
"data": {
|
||||
"id": "AnthropicModel-beO6B",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"LanguageModel",
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["LanguageModel", "Message"],
|
||||
"beta": false,
|
||||
"category": "models",
|
||||
"conditional_paths": [],
|
||||
@ -693,9 +619,7 @@
|
||||
"required_inputs": [],
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
},
|
||||
{
|
||||
@ -704,14 +628,10 @@
|
||||
"display_name": "Language Model",
|
||||
"method": "build_model",
|
||||
"name": "model_output",
|
||||
"required_inputs": [
|
||||
"api_key"
|
||||
],
|
||||
"required_inputs": ["api_key"],
|
||||
"selected": "LanguageModel",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"LanguageModel"
|
||||
],
|
||||
"types": ["LanguageModel"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -725,9 +645,7 @@
|
||||
"display_name": "Anthropic API Key",
|
||||
"dynamic": false,
|
||||
"info": "Your Anthropic API key.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"load_from_db": true,
|
||||
"name": "api_key",
|
||||
"password": true,
|
||||
@ -737,7 +655,7 @@
|
||||
"show": true,
|
||||
"title_case": false,
|
||||
"type": "str",
|
||||
"value": ""
|
||||
"value": "ANTHROPIC_API_KEY"
|
||||
},
|
||||
"base_url": {
|
||||
"_input_type": "MessageTextInput",
|
||||
@ -745,9 +663,7 @@
|
||||
"display_name": "Anthropic API URL",
|
||||
"dynamic": false,
|
||||
"info": "Endpoint of the Anthropic API. Defaults to 'https://api.anthropic.com' if not specified.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -787,9 +703,7 @@
|
||||
"display_name": "Input",
|
||||
"dynamic": false,
|
||||
"info": "",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -858,9 +772,7 @@
|
||||
"display_name": "Prefill",
|
||||
"dynamic": false,
|
||||
"info": "Prefill text to guide the model's response.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -899,9 +811,7 @@
|
||||
"display_name": "System Message",
|
||||
"dynamic": false,
|
||||
"info": "System message to pass to the model.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -987,9 +897,7 @@
|
||||
"data": {
|
||||
"id": "MessagetoData-QRSBb",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Data"
|
||||
],
|
||||
"base_classes": ["Data"],
|
||||
"beta": true,
|
||||
"category": "processing",
|
||||
"conditional_paths": [],
|
||||
@ -998,9 +906,7 @@
|
||||
"display_name": "Message to Data",
|
||||
"documentation": "",
|
||||
"edited": false,
|
||||
"field_order": [
|
||||
"message"
|
||||
],
|
||||
"field_order": ["message"],
|
||||
"frozen": false,
|
||||
"icon": "message-square-share",
|
||||
"key": "MessagetoData",
|
||||
@ -1018,9 +924,7 @@
|
||||
"name": "data",
|
||||
"selected": "Data",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Data"
|
||||
],
|
||||
"types": ["Data"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -1052,9 +956,7 @@
|
||||
"display_name": "Message",
|
||||
"dynamic": false,
|
||||
"info": "The Message object to convert to a Data object",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1092,10 +994,7 @@
|
||||
"data": {
|
||||
"id": "ParseData-igEkj",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Data",
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Data", "Message"],
|
||||
"beta": false,
|
||||
"category": "processing",
|
||||
"conditional_paths": [],
|
||||
@ -1104,17 +1003,15 @@
|
||||
"display_name": "Data to Message",
|
||||
"documentation": "",
|
||||
"edited": false,
|
||||
"field_order": [
|
||||
"data",
|
||||
"template",
|
||||
"sep"
|
||||
],
|
||||
"field_order": ["data", "template", "sep"],
|
||||
"frozen": false,
|
||||
"icon": "message-square",
|
||||
"key": "ParseData",
|
||||
"legacy": false,
|
||||
"lf_version": "1.1.5",
|
||||
"metadata": {},
|
||||
"metadata": {
|
||||
"legacy_name": "Parse Data"
|
||||
},
|
||||
"minimized": false,
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
@ -1126,9 +1023,7 @@
|
||||
"name": "text",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
},
|
||||
{
|
||||
@ -1139,9 +1034,7 @@
|
||||
"name": "data_list",
|
||||
"selected": "Data",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Data"
|
||||
],
|
||||
"types": ["Data"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -1165,7 +1058,7 @@
|
||||
"show": true,
|
||||
"title_case": false,
|
||||
"type": "code",
|
||||
"value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text, data_to_text_list\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Data to Message\"\n description = \"Convert Data objects into Messages using any {field_name} from input data.\"\n icon = \"message-square\"\n name = \"ParseData\"\n\n inputs = [\n DataInput(name=\"data\", display_name=\"Data\", info=\"The data to convert to text.\", is_list=True, required=True),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n required=True,\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"text\",\n info=\"Data as a single Message, with each input Data separated by Separator\",\n method=\"parse_data\",\n ),\n Output(\n display_name=\"Data List\",\n name=\"data_list\",\n info=\"Data as a list of new Data, each having `text` formatted by Template\",\n method=\"parse_data_as_list\",\n ),\n ]\n\n def _clean_args(self) -> tuple[list[Data], str, str]:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n sep = self.sep\n return data, template, sep\n\n def parse_data(self) -> Message:\n data, template, sep = self._clean_args()\n result_string = data_to_text(template, data, sep)\n self.status = result_string\n return Message(text=result_string)\n\n def parse_data_as_list(self) -> list[Data]:\n data, template, _ = self._clean_args()\n text_list, data_list = data_to_text_list(template, data)\n for item, text in zip(data_list, text_list, strict=True):\n item.set_text(text)\n self.status = data_list\n return data_list\n"
|
||||
"value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text, data_to_text_list\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Data to Message\"\n description = \"Convert Data objects into Messages using any {field_name} from input data.\"\n icon = \"message-square\"\n name = \"ParseData\"\n metadata = {\n \"legacy_name\": \"Parse Data\",\n }\n\n inputs = [\n DataInput(\n name=\"data\",\n display_name=\"Data\",\n info=\"The data to convert to text.\",\n is_list=True,\n required=True,\n ),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n required=True,\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"text\",\n info=\"Data as a single Message, with each input Data separated by Separator\",\n method=\"parse_data\",\n ),\n Output(\n display_name=\"Data List\",\n name=\"data_list\",\n info=\"Data as a list of new Data, each having `text` formatted by Template\",\n method=\"parse_data_as_list\",\n ),\n ]\n\n def _clean_args(self) -> tuple[list[Data], str, str]:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n sep = self.sep\n return data, template, sep\n\n def parse_data(self) -> Message:\n data, template, sep = self._clean_args()\n result_string = data_to_text(template, data, sep)\n self.status = result_string\n return Message(text=result_string)\n\n def parse_data_as_list(self) -> list[Data]:\n data, template, _ = self._clean_args()\n text_list, data_list = data_to_text_list(template, data)\n for item, text in zip(data_list, text_list, strict=True):\n item.set_text(text)\n self.status = data_list\n return data_list\n"
|
||||
},
|
||||
"data": {
|
||||
"_input_type": "DataInput",
|
||||
@ -1173,9 +1066,7 @@
|
||||
"display_name": "Data",
|
||||
"dynamic": false,
|
||||
"info": "The data to convert to text.",
|
||||
"input_types": [
|
||||
"Data"
|
||||
],
|
||||
"input_types": ["Data"],
|
||||
"list": true,
|
||||
"list_add_label": "Add More",
|
||||
"name": "data",
|
||||
@ -1214,9 +1105,7 @@
|
||||
"display_name": "Template",
|
||||
"dynamic": false,
|
||||
"info": "The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1255,9 +1144,7 @@
|
||||
"data": {
|
||||
"id": "ChatOutput-UZgon",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
@ -1292,9 +1179,7 @@
|
||||
"name": "message",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -1307,9 +1192,7 @@
|
||||
"display_name": "Background Color",
|
||||
"dynamic": false,
|
||||
"info": "The background color of the icon.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1330,9 +1213,7 @@
|
||||
"display_name": "Icon",
|
||||
"dynamic": false,
|
||||
"info": "The icon of the message.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1371,9 +1252,7 @@
|
||||
"display_name": "Data Template",
|
||||
"dynamic": false,
|
||||
"info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1394,9 +1273,7 @@
|
||||
"display_name": "Text",
|
||||
"dynamic": false,
|
||||
"info": "Message to be passed as output.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1420,10 +1297,7 @@
|
||||
"dynamic": false,
|
||||
"info": "Type of sender.",
|
||||
"name": "sender",
|
||||
"options": [
|
||||
"Machine",
|
||||
"User"
|
||||
],
|
||||
"options": ["Machine", "User"],
|
||||
"options_metadata": [],
|
||||
"placeholder": "",
|
||||
"required": false,
|
||||
@ -1440,9 +1314,7 @@
|
||||
"display_name": "Sender Name",
|
||||
"dynamic": false,
|
||||
"info": "Name of the sender.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1463,9 +1335,7 @@
|
||||
"display_name": "Session ID",
|
||||
"dynamic": false,
|
||||
"info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1504,9 +1374,7 @@
|
||||
"display_name": "Text Color",
|
||||
"dynamic": false,
|
||||
"info": "The text color of the name",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1544,9 +1412,7 @@
|
||||
"data": {
|
||||
"id": "ChatInput-m10vc",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
@ -1581,9 +1447,7 @@
|
||||
"name": "message",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -1596,9 +1460,7 @@
|
||||
"display_name": "Background Color",
|
||||
"dynamic": false,
|
||||
"info": "The background color of the icon.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1619,9 +1481,7 @@
|
||||
"display_name": "Icon",
|
||||
"dynamic": false,
|
||||
"info": "The icon of the message.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1728,10 +1588,7 @@
|
||||
"dynamic": false,
|
||||
"info": "Type of sender.",
|
||||
"name": "sender",
|
||||
"options": [
|
||||
"Machine",
|
||||
"User"
|
||||
],
|
||||
"options": ["Machine", "User"],
|
||||
"options_metadata": [],
|
||||
"placeholder": "",
|
||||
"required": false,
|
||||
@ -1748,9 +1605,7 @@
|
||||
"display_name": "Sender Name",
|
||||
"dynamic": false,
|
||||
"info": "Name of the sender.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1771,9 +1626,7 @@
|
||||
"display_name": "Session ID",
|
||||
"dynamic": false,
|
||||
"info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1812,9 +1665,7 @@
|
||||
"display_name": "Text Color",
|
||||
"dynamic": false,
|
||||
"info": "The text color of the name",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1919,8 +1770,5 @@
|
||||
"is_component": false,
|
||||
"last_tested_version": "1.1.5",
|
||||
"name": "Research Translation Loop",
|
||||
"tags": [
|
||||
"chatbots",
|
||||
"content-generation"
|
||||
]
|
||||
}
|
||||
"tags": ["chatbots", "content-generation"]
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -8,17 +8,12 @@
|
||||
"dataType": "Memory",
|
||||
"id": "Memory-gWJrq",
|
||||
"name": "messages_text",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "memory",
|
||||
"id": "Prompt-yhdMP",
|
||||
"inputTypes": [
|
||||
"Message",
|
||||
"Text"
|
||||
],
|
||||
"inputTypes": ["Message", "Text"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -36,16 +31,12 @@
|
||||
"dataType": "ChatInput",
|
||||
"id": "ChatInput-PEO9d",
|
||||
"name": "message",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
"id": "OpenAIModel-63o3Q",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -63,16 +54,12 @@
|
||||
"dataType": "Prompt",
|
||||
"id": "Prompt-yhdMP",
|
||||
"name": "prompt",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "system_message",
|
||||
"id": "OpenAIModel-63o3Q",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -90,16 +77,12 @@
|
||||
"dataType": "OpenAIModel",
|
||||
"id": "OpenAIModel-63o3Q",
|
||||
"name": "text_output",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
"id": "ChatOutput-BIXzI",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -116,9 +99,7 @@
|
||||
"data": {
|
||||
"id": "ChatInput-PEO9d",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
@ -152,9 +133,7 @@
|
||||
"name": "message",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -167,9 +146,7 @@
|
||||
"display_name": "Background Color",
|
||||
"dynamic": false,
|
||||
"info": "The background color of the icon.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "background_color",
|
||||
@ -189,9 +166,7 @@
|
||||
"display_name": "Icon",
|
||||
"dynamic": false,
|
||||
"info": "The icon of the message.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "chat_icon",
|
||||
@ -294,10 +269,7 @@
|
||||
"dynamic": false,
|
||||
"info": "Type of sender.",
|
||||
"name": "sender",
|
||||
"options": [
|
||||
"Machine",
|
||||
"User"
|
||||
],
|
||||
"options": ["Machine", "User"],
|
||||
"placeholder": "",
|
||||
"required": false,
|
||||
"show": true,
|
||||
@ -313,9 +285,7 @@
|
||||
"display_name": "Sender Name",
|
||||
"dynamic": false,
|
||||
"info": "Name of the sender.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "sender_name",
|
||||
@ -335,9 +305,7 @@
|
||||
"display_name": "Session ID",
|
||||
"dynamic": false,
|
||||
"info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "session_id",
|
||||
@ -373,9 +341,7 @@
|
||||
"display_name": "Text Color",
|
||||
"dynamic": false,
|
||||
"info": "The text color of the name",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "text_color",
|
||||
@ -419,9 +385,7 @@
|
||||
"display_name": "Chat Output",
|
||||
"id": "ChatOutput-BIXzI",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
@ -455,9 +419,7 @@
|
||||
"name": "message",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -470,9 +432,7 @@
|
||||
"display_name": "Background Color",
|
||||
"dynamic": false,
|
||||
"info": "The background color of the icon.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "background_color",
|
||||
@ -492,9 +452,7 @@
|
||||
"display_name": "Icon",
|
||||
"dynamic": false,
|
||||
"info": "The icon of the message.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "chat_icon",
|
||||
@ -532,9 +490,7 @@
|
||||
"display_name": "Data Template",
|
||||
"dynamic": false,
|
||||
"info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "data_template",
|
||||
@ -554,9 +510,7 @@
|
||||
"display_name": "Text",
|
||||
"dynamic": false,
|
||||
"info": "Message to be passed as output.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "input_value",
|
||||
@ -577,10 +531,7 @@
|
||||
"dynamic": false,
|
||||
"info": "Type of sender.",
|
||||
"name": "sender",
|
||||
"options": [
|
||||
"Machine",
|
||||
"User"
|
||||
],
|
||||
"options": ["Machine", "User"],
|
||||
"placeholder": "",
|
||||
"required": false,
|
||||
"show": true,
|
||||
@ -596,9 +547,7 @@
|
||||
"display_name": "Sender Name",
|
||||
"dynamic": false,
|
||||
"info": "Name of the sender.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "sender_name",
|
||||
@ -618,9 +567,7 @@
|
||||
"display_name": "Session ID",
|
||||
"dynamic": false,
|
||||
"info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "session_id",
|
||||
@ -656,9 +603,7 @@
|
||||
"display_name": "Text Color",
|
||||
"dynamic": false,
|
||||
"info": "The text color of the name",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "text_color",
|
||||
@ -767,10 +712,7 @@
|
||||
"data": {
|
||||
"id": "Memory-gWJrq",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Data",
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Data", "Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
@ -802,9 +744,7 @@
|
||||
"name": "messages",
|
||||
"selected": "Data",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Data"
|
||||
],
|
||||
"types": ["Data"],
|
||||
"value": "__UNDEFINED__"
|
||||
},
|
||||
{
|
||||
@ -815,9 +755,7 @@
|
||||
"name": "messages_text",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -848,9 +786,7 @@
|
||||
"display_name": "External Memory",
|
||||
"dynamic": false,
|
||||
"info": "Retrieve messages from an external memory. If empty, it will use the Langflow tables.",
|
||||
"input_types": [
|
||||
"Memory"
|
||||
],
|
||||
"input_types": ["Memory"],
|
||||
"list": false,
|
||||
"name": "memory",
|
||||
"placeholder": "",
|
||||
@ -885,10 +821,7 @@
|
||||
"dynamic": false,
|
||||
"info": "Order of the messages.",
|
||||
"name": "order",
|
||||
"options": [
|
||||
"Ascending",
|
||||
"Descending"
|
||||
],
|
||||
"options": ["Ascending", "Descending"],
|
||||
"placeholder": "",
|
||||
"required": false,
|
||||
"show": true,
|
||||
@ -906,11 +839,7 @@
|
||||
"dynamic": false,
|
||||
"info": "Filter by sender type.",
|
||||
"name": "sender",
|
||||
"options": [
|
||||
"Machine",
|
||||
"User",
|
||||
"Machine and User"
|
||||
],
|
||||
"options": ["Machine", "User", "Machine and User"],
|
||||
"placeholder": "",
|
||||
"required": false,
|
||||
"show": true,
|
||||
@ -926,9 +855,7 @@
|
||||
"display_name": "Sender Name",
|
||||
"dynamic": false,
|
||||
"info": "Filter by sender name.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "sender_name",
|
||||
@ -948,9 +875,7 @@
|
||||
"display_name": "Session ID",
|
||||
"dynamic": false,
|
||||
"info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "session_id",
|
||||
@ -970,9 +895,7 @@
|
||||
"display_name": "Template",
|
||||
"dynamic": false,
|
||||
"info": "The template to use for formatting the data. It can contain the keys {text}, {sender} or any other key in the message data.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"multiline": true,
|
||||
@ -1015,24 +938,18 @@
|
||||
"data": {
|
||||
"id": "Prompt-yhdMP",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {
|
||||
"template": [
|
||||
"memory"
|
||||
]
|
||||
"template": ["memory"]
|
||||
},
|
||||
"description": "Create a prompt template with dynamic variables.",
|
||||
"display_name": "Prompt",
|
||||
"documentation": "",
|
||||
"edited": false,
|
||||
"error": null,
|
||||
"field_order": [
|
||||
"template"
|
||||
],
|
||||
"field_order": ["template"],
|
||||
"frozen": false,
|
||||
"full_path": null,
|
||||
"icon": "prompts",
|
||||
@ -1053,9 +970,7 @@
|
||||
"name": "prompt",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -1088,10 +1003,7 @@
|
||||
"fileTypes": [],
|
||||
"file_path": "",
|
||||
"info": "",
|
||||
"input_types": [
|
||||
"Message",
|
||||
"Text"
|
||||
],
|
||||
"input_types": ["Message", "Text"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"multiline": true,
|
||||
@ -1126,9 +1038,7 @@
|
||||
"display_name": "Tool Placeholder",
|
||||
"dynamic": false,
|
||||
"info": "A placeholder input for tool mode.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "tool_placeholder",
|
||||
@ -1170,10 +1080,7 @@
|
||||
"data": {
|
||||
"id": "OpenAIModel-63o3Q",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"LanguageModel",
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["LanguageModel", "Message"],
|
||||
"beta": false,
|
||||
"category": "models",
|
||||
"conditional_paths": [],
|
||||
@ -1212,9 +1119,7 @@
|
||||
"required_inputs": [],
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
},
|
||||
{
|
||||
@ -1223,14 +1128,10 @@
|
||||
"display_name": "Language Model",
|
||||
"method": "build_model",
|
||||
"name": "model_output",
|
||||
"required_inputs": [
|
||||
"api_key"
|
||||
],
|
||||
"required_inputs": ["api_key"],
|
||||
"selected": "LanguageModel",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"LanguageModel"
|
||||
],
|
||||
"types": ["LanguageModel"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -1244,9 +1145,7 @@
|
||||
"display_name": "OpenAI API Key",
|
||||
"dynamic": false,
|
||||
"info": "The OpenAI API Key to use for the OpenAI model.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"load_from_db": true,
|
||||
"name": "api_key",
|
||||
"password": true,
|
||||
@ -1255,7 +1154,7 @@
|
||||
"show": true,
|
||||
"title_case": false,
|
||||
"type": "str",
|
||||
"value": ""
|
||||
"value": "OPENAI_API_KEY"
|
||||
},
|
||||
"code": {
|
||||
"advanced": true,
|
||||
@ -1281,9 +1180,7 @@
|
||||
"display_name": "Input",
|
||||
"dynamic": false,
|
||||
"info": "",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1465,9 +1362,7 @@
|
||||
"display_name": "System Message",
|
||||
"dynamic": false,
|
||||
"info": "System message to pass to the model.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1563,9 +1458,5 @@
|
||||
"is_component": false,
|
||||
"last_tested_version": "1.0.19.post2",
|
||||
"name": "Memory Chatbot",
|
||||
"tags": [
|
||||
"chatbots",
|
||||
"openai",
|
||||
"assistants"
|
||||
]
|
||||
}
|
||||
"tags": ["chatbots", "openai", "assistants"]
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -9,16 +9,12 @@
|
||||
"dataType": "Prompt",
|
||||
"id": "Prompt-ysecC",
|
||||
"name": "prompt",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
"id": "StructuredOutput-TArXO",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -36,16 +32,12 @@
|
||||
"dataType": "StructuredOutput",
|
||||
"id": "StructuredOutput-TArXO",
|
||||
"name": "structured_output",
|
||||
"output_types": [
|
||||
"Data"
|
||||
]
|
||||
"output_types": ["Data"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "data",
|
||||
"id": "ParseData-x7Rgx",
|
||||
"inputTypes": [
|
||||
"Data"
|
||||
],
|
||||
"inputTypes": ["Data"],
|
||||
"type": "other"
|
||||
}
|
||||
},
|
||||
@ -63,16 +55,12 @@
|
||||
"dataType": "ParseData",
|
||||
"id": "ParseData-sGhWo",
|
||||
"name": "text",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "resume",
|
||||
"id": "Prompt-ysecC",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -90,16 +78,12 @@
|
||||
"dataType": "AnthropicModel",
|
||||
"id": "AnthropicModel-sHFTc",
|
||||
"name": "model_output",
|
||||
"output_types": [
|
||||
"LanguageModel"
|
||||
]
|
||||
"output_types": ["LanguageModel"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "llm",
|
||||
"id": "StructuredOutput-TArXO",
|
||||
"inputTypes": [
|
||||
"LanguageModel"
|
||||
],
|
||||
"inputTypes": ["LanguageModel"],
|
||||
"type": "other"
|
||||
}
|
||||
},
|
||||
@ -117,16 +101,12 @@
|
||||
"dataType": "ParseData",
|
||||
"id": "ParseData-x7Rgx",
|
||||
"name": "text",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
"id": "AnthropicModel-IrjAe",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -144,16 +124,12 @@
|
||||
"dataType": "TextInput",
|
||||
"id": "TextInput-CPTOR",
|
||||
"name": "text",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "system_message",
|
||||
"id": "AnthropicModel-IrjAe",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -171,16 +147,12 @@
|
||||
"dataType": "AnthropicModel",
|
||||
"id": "AnthropicModel-IrjAe",
|
||||
"name": "text_output",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
"id": "ChatOutput-d1tmJ",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -197,16 +169,12 @@
|
||||
"dataType": "File",
|
||||
"id": "File-JgIx7",
|
||||
"name": "data",
|
||||
"output_types": [
|
||||
"Data"
|
||||
]
|
||||
"output_types": ["Data"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "data",
|
||||
"id": "ParseData-sGhWo",
|
||||
"inputTypes": [
|
||||
"Data"
|
||||
],
|
||||
"inputTypes": ["Data"],
|
||||
"type": "other"
|
||||
}
|
||||
},
|
||||
@ -222,25 +190,18 @@
|
||||
"data": {
|
||||
"id": "Prompt-ysecC",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {
|
||||
"template": [
|
||||
"resume"
|
||||
]
|
||||
"template": ["resume"]
|
||||
},
|
||||
"description": "Create a prompt template with dynamic variables.",
|
||||
"display_name": "Prompt",
|
||||
"documentation": "",
|
||||
"edited": false,
|
||||
"error": null,
|
||||
"field_order": [
|
||||
"template",
|
||||
"tool_placeholder"
|
||||
],
|
||||
"field_order": ["template", "tool_placeholder"],
|
||||
"frozen": false,
|
||||
"full_path": null,
|
||||
"icon": "prompts",
|
||||
@ -262,9 +223,7 @@
|
||||
"name": "prompt",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -297,9 +256,7 @@
|
||||
"fileTypes": [],
|
||||
"file_path": "",
|
||||
"info": "",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"multiline": true,
|
||||
@ -335,9 +292,7 @@
|
||||
"display_name": "Tool Placeholder",
|
||||
"dynamic": false,
|
||||
"info": "A placeholder input for tool mode.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -375,10 +330,7 @@
|
||||
"data": {
|
||||
"id": "ParseData-sGhWo",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Data",
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Data", "Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
@ -386,16 +338,14 @@
|
||||
"display_name": "Data to Message",
|
||||
"documentation": "",
|
||||
"edited": false,
|
||||
"field_order": [
|
||||
"data",
|
||||
"template",
|
||||
"sep"
|
||||
],
|
||||
"field_order": ["data", "template", "sep"],
|
||||
"frozen": false,
|
||||
"icon": "message-square",
|
||||
"legacy": false,
|
||||
"lf_version": "1.1.4.post1",
|
||||
"metadata": {},
|
||||
"metadata": {
|
||||
"legacy_name": "Parse Data"
|
||||
},
|
||||
"minimized": false,
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
@ -407,9 +357,7 @@
|
||||
"name": "text",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
},
|
||||
{
|
||||
@ -420,9 +368,7 @@
|
||||
"name": "data_list",
|
||||
"selected": "Data",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Data"
|
||||
],
|
||||
"types": ["Data"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -445,7 +391,7 @@
|
||||
"show": true,
|
||||
"title_case": false,
|
||||
"type": "code",
|
||||
"value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text, data_to_text_list\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Data to Message\"\n description = \"Convert Data objects into Messages using any {field_name} from input data.\"\n icon = \"message-square\"\n name = \"ParseData\"\n\n inputs = [\n DataInput(name=\"data\", display_name=\"Data\", info=\"The data to convert to text.\", is_list=True, required=True),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n required=True,\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"text\",\n info=\"Data as a single Message, with each input Data separated by Separator\",\n method=\"parse_data\",\n ),\n Output(\n display_name=\"Data List\",\n name=\"data_list\",\n info=\"Data as a list of new Data, each having `text` formatted by Template\",\n method=\"parse_data_as_list\",\n ),\n ]\n\n def _clean_args(self) -> tuple[list[Data], str, str]:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n sep = self.sep\n return data, template, sep\n\n def parse_data(self) -> Message:\n data, template, sep = self._clean_args()\n result_string = data_to_text(template, data, sep)\n self.status = result_string\n return Message(text=result_string)\n\n def parse_data_as_list(self) -> list[Data]:\n data, template, _ = self._clean_args()\n text_list, data_list = data_to_text_list(template, data)\n for item, text in zip(data_list, text_list, strict=True):\n item.set_text(text)\n self.status = data_list\n return data_list\n"
|
||||
"value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text, data_to_text_list\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Data to Message\"\n description = \"Convert Data objects into Messages using any {field_name} from input data.\"\n icon = \"message-square\"\n name = \"ParseData\"\n metadata = {\n \"legacy_name\": \"Parse Data\",\n }\n\n inputs = [\n DataInput(\n name=\"data\",\n display_name=\"Data\",\n info=\"The data to convert to text.\",\n is_list=True,\n required=True,\n ),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n required=True,\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"text\",\n info=\"Data as a single Message, with each input Data separated by Separator\",\n method=\"parse_data\",\n ),\n Output(\n display_name=\"Data List\",\n name=\"data_list\",\n info=\"Data as a list of new Data, each having `text` formatted by Template\",\n method=\"parse_data_as_list\",\n ),\n ]\n\n def _clean_args(self) -> tuple[list[Data], str, str]:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n sep = self.sep\n return data, template, sep\n\n def parse_data(self) -> Message:\n data, template, sep = self._clean_args()\n result_string = data_to_text(template, data, sep)\n self.status = result_string\n return Message(text=result_string)\n\n def parse_data_as_list(self) -> list[Data]:\n data, template, _ = self._clean_args()\n text_list, data_list = data_to_text_list(template, data)\n for item, text in zip(data_list, text_list, strict=True):\n item.set_text(text)\n self.status = data_list\n return data_list\n"
|
||||
},
|
||||
"data": {
|
||||
"_input_type": "DataInput",
|
||||
@ -453,9 +399,7 @@
|
||||
"display_name": "Data",
|
||||
"dynamic": false,
|
||||
"info": "The data to convert to text.",
|
||||
"input_types": [
|
||||
"Data"
|
||||
],
|
||||
"input_types": ["Data"],
|
||||
"list": true,
|
||||
"list_add_label": "Add More",
|
||||
"name": "data",
|
||||
@ -494,9 +438,7 @@
|
||||
"display_name": "Template",
|
||||
"dynamic": false,
|
||||
"info": "The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -535,9 +477,7 @@
|
||||
"data": {
|
||||
"id": "StructuredOutput-TArXO",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Data"
|
||||
],
|
||||
"base_classes": ["Data"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
@ -569,9 +509,7 @@
|
||||
"name": "structured_output",
|
||||
"selected": "Data",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Data"
|
||||
],
|
||||
"types": ["Data"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -602,9 +540,7 @@
|
||||
"display_name": "Input Message",
|
||||
"dynamic": false,
|
||||
"info": "The input message to the language model.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -625,9 +561,7 @@
|
||||
"display_name": "Language Model",
|
||||
"dynamic": false,
|
||||
"info": "The language model to use to generate the structured output.",
|
||||
"input_types": [
|
||||
"LanguageModel"
|
||||
],
|
||||
"input_types": ["LanguageModel"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"name": "llm",
|
||||
@ -846,10 +780,7 @@
|
||||
"data": {
|
||||
"id": "ParseData-x7Rgx",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Data",
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Data", "Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
@ -857,16 +788,14 @@
|
||||
"display_name": "Data to Message",
|
||||
"documentation": "",
|
||||
"edited": false,
|
||||
"field_order": [
|
||||
"data",
|
||||
"template",
|
||||
"sep"
|
||||
],
|
||||
"field_order": ["data", "template", "sep"],
|
||||
"frozen": false,
|
||||
"icon": "message-square",
|
||||
"legacy": false,
|
||||
"lf_version": "1.1.4.post1",
|
||||
"metadata": {},
|
||||
"metadata": {
|
||||
"legacy_name": "Parse Data"
|
||||
},
|
||||
"minimized": false,
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
@ -878,9 +807,7 @@
|
||||
"name": "text",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
},
|
||||
{
|
||||
@ -891,9 +818,7 @@
|
||||
"name": "data_list",
|
||||
"selected": "Data",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Data"
|
||||
],
|
||||
"types": ["Data"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -916,7 +841,7 @@
|
||||
"show": true,
|
||||
"title_case": false,
|
||||
"type": "code",
|
||||
"value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text, data_to_text_list\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Data to Message\"\n description = \"Convert Data objects into Messages using any {field_name} from input data.\"\n icon = \"message-square\"\n name = \"ParseData\"\n\n inputs = [\n DataInput(name=\"data\", display_name=\"Data\", info=\"The data to convert to text.\", is_list=True, required=True),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n required=True,\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"text\",\n info=\"Data as a single Message, with each input Data separated by Separator\",\n method=\"parse_data\",\n ),\n Output(\n display_name=\"Data List\",\n name=\"data_list\",\n info=\"Data as a list of new Data, each having `text` formatted by Template\",\n method=\"parse_data_as_list\",\n ),\n ]\n\n def _clean_args(self) -> tuple[list[Data], str, str]:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n sep = self.sep\n return data, template, sep\n\n def parse_data(self) -> Message:\n data, template, sep = self._clean_args()\n result_string = data_to_text(template, data, sep)\n self.status = result_string\n return Message(text=result_string)\n\n def parse_data_as_list(self) -> list[Data]:\n data, template, _ = self._clean_args()\n text_list, data_list = data_to_text_list(template, data)\n for item, text in zip(data_list, text_list, strict=True):\n item.set_text(text)\n self.status = data_list\n return data_list\n"
|
||||
"value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text, data_to_text_list\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Data to Message\"\n description = \"Convert Data objects into Messages using any {field_name} from input data.\"\n icon = \"message-square\"\n name = \"ParseData\"\n metadata = {\n \"legacy_name\": \"Parse Data\",\n }\n\n inputs = [\n DataInput(\n name=\"data\",\n display_name=\"Data\",\n info=\"The data to convert to text.\",\n is_list=True,\n required=True,\n ),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n required=True,\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"text\",\n info=\"Data as a single Message, with each input Data separated by Separator\",\n method=\"parse_data\",\n ),\n Output(\n display_name=\"Data List\",\n name=\"data_list\",\n info=\"Data as a list of new Data, each having `text` formatted by Template\",\n method=\"parse_data_as_list\",\n ),\n ]\n\n def _clean_args(self) -> tuple[list[Data], str, str]:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n sep = self.sep\n return data, template, sep\n\n def parse_data(self) -> Message:\n data, template, sep = self._clean_args()\n result_string = data_to_text(template, data, sep)\n self.status = result_string\n return Message(text=result_string)\n\n def parse_data_as_list(self) -> list[Data]:\n data, template, _ = self._clean_args()\n text_list, data_list = data_to_text_list(template, data)\n for item, text in zip(data_list, text_list, strict=True):\n item.set_text(text)\n self.status = data_list\n return data_list\n"
|
||||
},
|
||||
"data": {
|
||||
"_input_type": "DataInput",
|
||||
@ -924,9 +849,7 @@
|
||||
"display_name": "Data",
|
||||
"dynamic": false,
|
||||
"info": "The data to convert to text.",
|
||||
"input_types": [
|
||||
"Data"
|
||||
],
|
||||
"input_types": ["Data"],
|
||||
"list": true,
|
||||
"list_add_label": "Add More",
|
||||
"name": "data",
|
||||
@ -965,9 +888,7 @@
|
||||
"display_name": "Template",
|
||||
"dynamic": false,
|
||||
"info": "The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1006,9 +927,7 @@
|
||||
"data": {
|
||||
"id": "TextInput-CPTOR",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"category": "inputs",
|
||||
"conditional_paths": [],
|
||||
@ -1017,9 +936,7 @@
|
||||
"display_name": "Text Input",
|
||||
"documentation": "",
|
||||
"edited": false,
|
||||
"field_order": [
|
||||
"input_value"
|
||||
],
|
||||
"field_order": ["input_value"],
|
||||
"frozen": false,
|
||||
"icon": "type",
|
||||
"key": "TextInput",
|
||||
@ -1037,9 +954,7 @@
|
||||
"name": "text",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -1071,9 +986,7 @@
|
||||
"display_name": "Text",
|
||||
"dynamic": false,
|
||||
"info": "Text to be passed as input.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1112,10 +1025,7 @@
|
||||
"data": {
|
||||
"id": "AnthropicModel-sHFTc",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"LanguageModel",
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["LanguageModel", "Message"],
|
||||
"beta": false,
|
||||
"category": "models",
|
||||
"conditional_paths": [],
|
||||
@ -1154,9 +1064,7 @@
|
||||
"required_inputs": [],
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
},
|
||||
{
|
||||
@ -1165,14 +1073,10 @@
|
||||
"display_name": "Language Model",
|
||||
"method": "build_model",
|
||||
"name": "model_output",
|
||||
"required_inputs": [
|
||||
"api_key"
|
||||
],
|
||||
"required_inputs": ["api_key"],
|
||||
"selected": "LanguageModel",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"LanguageModel"
|
||||
],
|
||||
"types": ["LanguageModel"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -1186,10 +1090,8 @@
|
||||
"display_name": "Anthropic API Key",
|
||||
"dynamic": false,
|
||||
"info": "Your Anthropic API key.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"load_from_db": false,
|
||||
"input_types": ["Message"],
|
||||
"load_from_db": true,
|
||||
"name": "api_key",
|
||||
"password": true,
|
||||
"placeholder": "",
|
||||
@ -1198,7 +1100,7 @@
|
||||
"show": true,
|
||||
"title_case": false,
|
||||
"type": "str",
|
||||
"value": ""
|
||||
"value": "ANTHROPIC_API_KEY"
|
||||
},
|
||||
"base_url": {
|
||||
"_input_type": "MessageTextInput",
|
||||
@ -1206,9 +1108,7 @@
|
||||
"display_name": "Anthropic API URL",
|
||||
"dynamic": false,
|
||||
"info": "Endpoint of the Anthropic API. Defaults to 'https://api.anthropic.com' if not specified.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1248,9 +1148,7 @@
|
||||
"display_name": "Input",
|
||||
"dynamic": false,
|
||||
"info": "",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1319,9 +1217,7 @@
|
||||
"display_name": "Prefill",
|
||||
"dynamic": false,
|
||||
"info": "Prefill text to guide the model's response.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1360,9 +1256,7 @@
|
||||
"display_name": "System Message",
|
||||
"dynamic": false,
|
||||
"info": "System message to pass to the model.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1448,10 +1342,7 @@
|
||||
"data": {
|
||||
"id": "AnthropicModel-IrjAe",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"LanguageModel",
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["LanguageModel", "Message"],
|
||||
"beta": false,
|
||||
"category": "models",
|
||||
"conditional_paths": [],
|
||||
@ -1490,9 +1381,7 @@
|
||||
"required_inputs": [],
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
},
|
||||
{
|
||||
@ -1501,14 +1390,10 @@
|
||||
"display_name": "Language Model",
|
||||
"method": "build_model",
|
||||
"name": "model_output",
|
||||
"required_inputs": [
|
||||
"api_key"
|
||||
],
|
||||
"required_inputs": ["api_key"],
|
||||
"selected": "LanguageModel",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"LanguageModel"
|
||||
],
|
||||
"types": ["LanguageModel"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -1522,10 +1407,8 @@
|
||||
"display_name": "Anthropic API Key",
|
||||
"dynamic": false,
|
||||
"info": "Your Anthropic API key.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"load_from_db": false,
|
||||
"input_types": ["Message"],
|
||||
"load_from_db": true,
|
||||
"name": "api_key",
|
||||
"password": true,
|
||||
"placeholder": "",
|
||||
@ -1534,7 +1417,7 @@
|
||||
"show": true,
|
||||
"title_case": false,
|
||||
"type": "str",
|
||||
"value": ""
|
||||
"value": "ANTHROPIC_API_KEY"
|
||||
},
|
||||
"base_url": {
|
||||
"_input_type": "MessageTextInput",
|
||||
@ -1542,9 +1425,7 @@
|
||||
"display_name": "Anthropic API URL",
|
||||
"dynamic": false,
|
||||
"info": "Endpoint of the Anthropic API. Defaults to 'https://api.anthropic.com' if not specified.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1584,9 +1465,7 @@
|
||||
"display_name": "Input",
|
||||
"dynamic": false,
|
||||
"info": "",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1655,9 +1534,7 @@
|
||||
"display_name": "Prefill",
|
||||
"dynamic": false,
|
||||
"info": "Prefill text to guide the model's response.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1696,9 +1573,7 @@
|
||||
"display_name": "System Message",
|
||||
"dynamic": false,
|
||||
"info": "System message to pass to the model.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1784,9 +1659,7 @@
|
||||
"data": {
|
||||
"id": "ChatOutput-d1tmJ",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"category": "outputs",
|
||||
"conditional_paths": [],
|
||||
@ -1823,9 +1696,7 @@
|
||||
"name": "message",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -1839,9 +1710,7 @@
|
||||
"display_name": "Background Color",
|
||||
"dynamic": false,
|
||||
"info": "The background color of the icon.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1862,9 +1731,7 @@
|
||||
"display_name": "Icon",
|
||||
"dynamic": false,
|
||||
"info": "The icon of the message.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1903,9 +1770,7 @@
|
||||
"display_name": "Data Template",
|
||||
"dynamic": false,
|
||||
"info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1926,9 +1791,7 @@
|
||||
"display_name": "Text",
|
||||
"dynamic": false,
|
||||
"info": "Message to be passed as output.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1952,10 +1815,7 @@
|
||||
"dynamic": false,
|
||||
"info": "Type of sender.",
|
||||
"name": "sender",
|
||||
"options": [
|
||||
"Machine",
|
||||
"User"
|
||||
],
|
||||
"options": ["Machine", "User"],
|
||||
"options_metadata": [],
|
||||
"placeholder": "",
|
||||
"required": false,
|
||||
@ -1972,9 +1832,7 @@
|
||||
"display_name": "Sender Name",
|
||||
"dynamic": false,
|
||||
"info": "Name of the sender.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1995,9 +1853,7 @@
|
||||
"display_name": "Session ID",
|
||||
"dynamic": false,
|
||||
"info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -2036,9 +1892,7 @@
|
||||
"display_name": "Text Color",
|
||||
"dynamic": false,
|
||||
"info": "The text color of the name",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -2076,9 +1930,7 @@
|
||||
"data": {
|
||||
"id": "File-JgIx7",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Data"
|
||||
],
|
||||
"base_classes": ["Data"],
|
||||
"beta": false,
|
||||
"category": "data",
|
||||
"conditional_paths": [],
|
||||
@ -2114,9 +1966,7 @@
|
||||
"required_inputs": [],
|
||||
"selected": "Data",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Data"
|
||||
],
|
||||
"types": ["Data"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -2184,10 +2034,7 @@
|
||||
"display_name": "Server File Path",
|
||||
"dynamic": false,
|
||||
"info": "Data object with a 'file_path' property pointing to server file or a Message object with a path to the file. Supercedes 'Path' but supports same file types.",
|
||||
"input_types": [
|
||||
"Data",
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Data", "Message"],
|
||||
"list": true,
|
||||
"list_add_label": "Add More",
|
||||
"name": "file_path",
|
||||
@ -2491,8 +2338,5 @@
|
||||
"is_component": false,
|
||||
"last_tested_version": "1.1.4",
|
||||
"name": "Portfolio Website Code Generator",
|
||||
"tags": [
|
||||
"chatbots",
|
||||
"coding"
|
||||
]
|
||||
}
|
||||
"tags": ["chatbots", "coding"]
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -8,16 +8,12 @@
|
||||
"dataType": "Prompt",
|
||||
"id": "Prompt-a6SIY",
|
||||
"name": "prompt",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "system_message",
|
||||
"id": "OpenAIModel-brPVM",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -35,16 +31,12 @@
|
||||
"dataType": "Prompt",
|
||||
"id": "Prompt-jkCpO",
|
||||
"name": "prompt",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
"id": "OpenAIModel-brPVM",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -62,16 +54,12 @@
|
||||
"dataType": "OpenAIModel",
|
||||
"id": "OpenAIModel-brPVM",
|
||||
"name": "text_output",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
"id": "ChatOutput-oE2ic",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -90,9 +78,7 @@
|
||||
"display_name": "Prompt",
|
||||
"id": "Prompt-jkCpO",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {
|
||||
@ -109,9 +95,7 @@
|
||||
"display_name": "Prompt",
|
||||
"documentation": "",
|
||||
"edited": false,
|
||||
"field_order": [
|
||||
"template"
|
||||
],
|
||||
"field_order": ["template"],
|
||||
"frozen": false,
|
||||
"icon": "prompts",
|
||||
"legacy": false,
|
||||
@ -127,9 +111,7 @@
|
||||
"name": "prompt",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -162,10 +144,7 @@
|
||||
"fileTypes": [],
|
||||
"file_path": "",
|
||||
"info": "",
|
||||
"input_types": [
|
||||
"Message",
|
||||
"Text"
|
||||
],
|
||||
"input_types": ["Message", "Text"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"multiline": true,
|
||||
@ -185,10 +164,7 @@
|
||||
"fileTypes": [],
|
||||
"file_path": "",
|
||||
"info": "",
|
||||
"input_types": [
|
||||
"Message",
|
||||
"Text"
|
||||
],
|
||||
"input_types": ["Message", "Text"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"multiline": true,
|
||||
@ -208,10 +184,7 @@
|
||||
"fileTypes": [],
|
||||
"file_path": "",
|
||||
"info": "",
|
||||
"input_types": [
|
||||
"Message",
|
||||
"Text"
|
||||
],
|
||||
"input_types": ["Message", "Text"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"multiline": true,
|
||||
@ -231,10 +204,7 @@
|
||||
"fileTypes": [],
|
||||
"file_path": "",
|
||||
"info": "",
|
||||
"input_types": [
|
||||
"Message",
|
||||
"Text"
|
||||
],
|
||||
"input_types": ["Message", "Text"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"multiline": true,
|
||||
@ -254,10 +224,7 @@
|
||||
"fileTypes": [],
|
||||
"file_path": "",
|
||||
"info": "",
|
||||
"input_types": [
|
||||
"Message",
|
||||
"Text"
|
||||
],
|
||||
"input_types": ["Message", "Text"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"multiline": true,
|
||||
@ -277,10 +244,7 @@
|
||||
"fileTypes": [],
|
||||
"file_path": "",
|
||||
"info": "",
|
||||
"input_types": [
|
||||
"Message",
|
||||
"Text"
|
||||
],
|
||||
"input_types": ["Message", "Text"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"multiline": true,
|
||||
@ -316,9 +280,7 @@
|
||||
"display_name": "Tool Placeholder",
|
||||
"dynamic": false,
|
||||
"info": "A placeholder input for tool mode.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "tool_placeholder",
|
||||
@ -397,9 +359,7 @@
|
||||
"display_name": "Prompt",
|
||||
"id": "Prompt-a6SIY",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {
|
||||
@ -409,9 +369,7 @@
|
||||
"display_name": "Prompt",
|
||||
"documentation": "",
|
||||
"edited": false,
|
||||
"field_order": [
|
||||
"template"
|
||||
],
|
||||
"field_order": ["template"],
|
||||
"frozen": false,
|
||||
"icon": "prompts",
|
||||
"legacy": false,
|
||||
@ -427,9 +385,7 @@
|
||||
"name": "prompt",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -478,9 +434,7 @@
|
||||
"display_name": "Tool Placeholder",
|
||||
"dynamic": false,
|
||||
"info": "A placeholder input for tool mode.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "tool_placeholder",
|
||||
@ -524,9 +478,7 @@
|
||||
"display_name": "Chat Output",
|
||||
"id": "ChatOutput-oE2ic",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
@ -560,9 +512,7 @@
|
||||
"name": "message",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -575,9 +525,7 @@
|
||||
"display_name": "Background Color",
|
||||
"dynamic": false,
|
||||
"info": "The background color of the icon.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "background_color",
|
||||
@ -597,9 +545,7 @@
|
||||
"display_name": "Icon",
|
||||
"dynamic": false,
|
||||
"info": "The icon of the message.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "chat_icon",
|
||||
@ -637,9 +583,7 @@
|
||||
"display_name": "Data Template",
|
||||
"dynamic": false,
|
||||
"info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "data_template",
|
||||
@ -659,9 +603,7 @@
|
||||
"display_name": "Text",
|
||||
"dynamic": false,
|
||||
"info": "Message to be passed as output.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "input_value",
|
||||
@ -682,10 +624,7 @@
|
||||
"dynamic": false,
|
||||
"info": "Type of sender.",
|
||||
"name": "sender",
|
||||
"options": [
|
||||
"Machine",
|
||||
"User"
|
||||
],
|
||||
"options": ["Machine", "User"],
|
||||
"placeholder": "",
|
||||
"required": false,
|
||||
"show": true,
|
||||
@ -701,9 +640,7 @@
|
||||
"display_name": "Sender Name",
|
||||
"dynamic": false,
|
||||
"info": "Name of the sender.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "sender_name",
|
||||
@ -723,9 +660,7 @@
|
||||
"display_name": "Session ID",
|
||||
"dynamic": false,
|
||||
"info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "session_id",
|
||||
@ -761,9 +696,7 @@
|
||||
"display_name": "Text Color",
|
||||
"dynamic": false,
|
||||
"info": "The text color of the name",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "text_color",
|
||||
@ -835,10 +768,7 @@
|
||||
"data": {
|
||||
"id": "OpenAIModel-brPVM",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"LanguageModel",
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["LanguageModel", "Message"],
|
||||
"beta": false,
|
||||
"category": "models",
|
||||
"conditional_paths": [],
|
||||
@ -877,9 +807,7 @@
|
||||
"required_inputs": [],
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
},
|
||||
{
|
||||
@ -888,14 +816,10 @@
|
||||
"display_name": "Language Model",
|
||||
"method": "build_model",
|
||||
"name": "model_output",
|
||||
"required_inputs": [
|
||||
"api_key"
|
||||
],
|
||||
"required_inputs": ["api_key"],
|
||||
"selected": "LanguageModel",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"LanguageModel"
|
||||
],
|
||||
"types": ["LanguageModel"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -909,9 +833,7 @@
|
||||
"display_name": "OpenAI API Key",
|
||||
"dynamic": false,
|
||||
"info": "The OpenAI API Key to use for the OpenAI model.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"load_from_db": true,
|
||||
"name": "api_key",
|
||||
"password": true,
|
||||
@ -920,7 +842,7 @@
|
||||
"show": true,
|
||||
"title_case": false,
|
||||
"type": "str",
|
||||
"value": ""
|
||||
"value": "OPENAI_API_KEY"
|
||||
},
|
||||
"code": {
|
||||
"advanced": true,
|
||||
@ -946,9 +868,7 @@
|
||||
"display_name": "Input",
|
||||
"dynamic": false,
|
||||
"info": "",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1130,9 +1050,7 @@
|
||||
"display_name": "System Message",
|
||||
"dynamic": false,
|
||||
"info": "System message to pass to the model.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -1228,8 +1146,5 @@
|
||||
"is_component": false,
|
||||
"last_tested_version": "1.0.19.post2",
|
||||
"name": "SEO Keyword Generator",
|
||||
"tags": [
|
||||
"chatbots",
|
||||
"assistants"
|
||||
]
|
||||
}
|
||||
"tags": ["chatbots", "assistants"]
|
||||
}
|
||||
|
||||
@ -109,9 +109,7 @@
|
||||
"name": "prompt",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -338,9 +336,7 @@
|
||||
"name": "message",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -654,9 +650,7 @@
|
||||
"name": "response",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -745,7 +739,7 @@
|
||||
"show": true,
|
||||
"title_case": false,
|
||||
"type": "str",
|
||||
"value": ""
|
||||
"value": "OPENAI_API_KEY"
|
||||
},
|
||||
"code": {
|
||||
"advanced": true,
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -152,9 +152,7 @@
|
||||
"name": "response",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -243,7 +241,7 @@
|
||||
"show": true,
|
||||
"title_case": false,
|
||||
"type": "str",
|
||||
"value": ""
|
||||
"value": "OPENAI_API_KEY"
|
||||
},
|
||||
"code": {
|
||||
"advanced": true,
|
||||
@ -720,9 +718,7 @@
|
||||
"name": "message",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -1002,9 +998,7 @@
|
||||
"name": "message",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
|
||||
@ -199,9 +199,7 @@
|
||||
"name": "message",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -479,9 +477,7 @@
|
||||
"name": "message",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -760,9 +756,7 @@
|
||||
"name": "response",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -851,7 +845,7 @@
|
||||
"show": true,
|
||||
"title_case": false,
|
||||
"type": "str",
|
||||
"value": ""
|
||||
"value": "OPENAI_API_KEY"
|
||||
},
|
||||
"code": {
|
||||
"advanced": true,
|
||||
@ -1353,9 +1347,7 @@
|
||||
"name": "response",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -1444,7 +1436,7 @@
|
||||
"show": true,
|
||||
"title_case": false,
|
||||
"type": "str",
|
||||
"value": ""
|
||||
"value": "OPENAI_API_KEY"
|
||||
},
|
||||
"code": {
|
||||
"advanced": true,
|
||||
@ -1946,9 +1938,7 @@
|
||||
"name": "response",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -2037,7 +2027,7 @@
|
||||
"show": true,
|
||||
"title_case": false,
|
||||
"type": "str",
|
||||
"value": ""
|
||||
"value": "OPENAI_API_KEY"
|
||||
},
|
||||
"code": {
|
||||
"advanced": true,
|
||||
@ -3101,7 +3091,7 @@
|
||||
"dynamic": false,
|
||||
"info": "",
|
||||
"input_types": ["Message"],
|
||||
"load_from_db": false,
|
||||
"load_from_db": true,
|
||||
"name": "api_key",
|
||||
"password": true,
|
||||
"placeholder": "",
|
||||
@ -3109,7 +3099,7 @@
|
||||
"show": true,
|
||||
"title_case": false,
|
||||
"type": "str",
|
||||
"value": ""
|
||||
"value": "SEARCHAPI_API_KEY"
|
||||
},
|
||||
"code": {
|
||||
"advanced": true,
|
||||
|
||||
@ -9,17 +9,12 @@
|
||||
"dataType": "TextInput",
|
||||
"id": "TextInput-eClq5",
|
||||
"name": "text",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "CONTENT_GUIDELINES",
|
||||
"id": "Prompt-AWZtN",
|
||||
"inputTypes": [
|
||||
"Message",
|
||||
"Text"
|
||||
],
|
||||
"inputTypes": ["Message", "Text"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -38,17 +33,12 @@
|
||||
"dataType": "TextInput",
|
||||
"id": "TextInput-IpoG7",
|
||||
"name": "text",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "OUTPUT_FORMAT",
|
||||
"id": "Prompt-AWZtN",
|
||||
"inputTypes": [
|
||||
"Message",
|
||||
"Text"
|
||||
],
|
||||
"inputTypes": ["Message", "Text"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -67,17 +57,12 @@
|
||||
"dataType": "TextInput",
|
||||
"id": "TextInput-npraC",
|
||||
"name": "text",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "OUTPUT_LANGUAGE",
|
||||
"id": "Prompt-AWZtN",
|
||||
"inputTypes": [
|
||||
"Message",
|
||||
"Text"
|
||||
],
|
||||
"inputTypes": ["Message", "Text"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -96,17 +81,12 @@
|
||||
"dataType": "TextInput",
|
||||
"id": "TextInput-EZaR7",
|
||||
"name": "text",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "PROFILE_DETAILS",
|
||||
"id": "Prompt-AWZtN",
|
||||
"inputTypes": [
|
||||
"Message",
|
||||
"Text"
|
||||
],
|
||||
"inputTypes": ["Message", "Text"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -125,17 +105,12 @@
|
||||
"dataType": "TextInput",
|
||||
"id": "TextInput-fKGcs",
|
||||
"name": "text",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "PROFILE_TYPE",
|
||||
"id": "Prompt-AWZtN",
|
||||
"inputTypes": [
|
||||
"Message",
|
||||
"Text"
|
||||
],
|
||||
"inputTypes": ["Message", "Text"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -154,17 +129,12 @@
|
||||
"dataType": "TextInput",
|
||||
"id": "TextInput-92vEK",
|
||||
"name": "text",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "TONE_AND_STYLE",
|
||||
"id": "Prompt-AWZtN",
|
||||
"inputTypes": [
|
||||
"Message",
|
||||
"Text"
|
||||
],
|
||||
"inputTypes": ["Message", "Text"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -182,16 +152,12 @@
|
||||
"dataType": "ChatInput",
|
||||
"id": "ChatInput-ECcN8",
|
||||
"name": "message",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
"id": "OpenAIModel-p0R9m",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -209,16 +175,12 @@
|
||||
"dataType": "Prompt",
|
||||
"id": "Prompt-AWZtN",
|
||||
"name": "prompt",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "system_message",
|
||||
"id": "OpenAIModel-p0R9m",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -236,16 +198,12 @@
|
||||
"dataType": "OpenAIModel",
|
||||
"id": "OpenAIModel-p0R9m",
|
||||
"name": "text_output",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
"id": "ChatOutput-0jDYx",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
@ -262,9 +220,7 @@
|
||||
"data": {
|
||||
"id": "ChatInput-ECcN8",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
@ -298,9 +254,7 @@
|
||||
"name": "message",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -313,9 +267,7 @@
|
||||
"display_name": "Background Color",
|
||||
"dynamic": false,
|
||||
"info": "The background color of the icon.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "background_color",
|
||||
@ -334,9 +286,7 @@
|
||||
"display_name": "Icon",
|
||||
"dynamic": false,
|
||||
"info": "The icon of the message.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "chat_icon",
|
||||
@ -437,10 +387,7 @@
|
||||
"dynamic": false,
|
||||
"info": "Type of sender.",
|
||||
"name": "sender",
|
||||
"options": [
|
||||
"Machine",
|
||||
"User"
|
||||
],
|
||||
"options": ["Machine", "User"],
|
||||
"placeholder": "",
|
||||
"required": false,
|
||||
"show": true,
|
||||
@ -455,9 +402,7 @@
|
||||
"display_name": "Sender Name",
|
||||
"dynamic": false,
|
||||
"info": "Name of the sender.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "sender_name",
|
||||
@ -476,9 +421,7 @@
|
||||
"display_name": "Session ID",
|
||||
"dynamic": false,
|
||||
"info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "session_id",
|
||||
@ -513,9 +456,7 @@
|
||||
"display_name": "Text Color",
|
||||
"dynamic": false,
|
||||
"info": "The text color of the name",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "text_color",
|
||||
@ -555,9 +496,7 @@
|
||||
"data": {
|
||||
"id": "TextInput-eClq5",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
@ -565,9 +504,7 @@
|
||||
"display_name": "Content Guidelines",
|
||||
"documentation": "",
|
||||
"edited": false,
|
||||
"field_order": [
|
||||
"input_value"
|
||||
],
|
||||
"field_order": ["input_value"],
|
||||
"frozen": false,
|
||||
"icon": "type",
|
||||
"legacy": false,
|
||||
@ -583,9 +520,7 @@
|
||||
"name": "text",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -616,9 +551,7 @@
|
||||
"display_name": "Text",
|
||||
"dynamic": false,
|
||||
"info": "Text to be passed as input.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"multiline": true,
|
||||
@ -661,9 +594,7 @@
|
||||
"display_name": "Chat Output",
|
||||
"id": "ChatOutput-0jDYx",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
@ -697,9 +628,7 @@
|
||||
"name": "message",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -712,9 +641,7 @@
|
||||
"display_name": "Background Color",
|
||||
"dynamic": false,
|
||||
"info": "The background color of the icon.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "background_color",
|
||||
@ -734,9 +661,7 @@
|
||||
"display_name": "Icon",
|
||||
"dynamic": false,
|
||||
"info": "The icon of the message.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "chat_icon",
|
||||
@ -774,9 +699,7 @@
|
||||
"display_name": "Data Template",
|
||||
"dynamic": false,
|
||||
"info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "data_template",
|
||||
@ -796,9 +719,7 @@
|
||||
"display_name": "Text",
|
||||
"dynamic": false,
|
||||
"info": "Message to be passed as output.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "input_value",
|
||||
@ -819,10 +740,7 @@
|
||||
"dynamic": false,
|
||||
"info": "Type of sender.",
|
||||
"name": "sender",
|
||||
"options": [
|
||||
"Machine",
|
||||
"User"
|
||||
],
|
||||
"options": ["Machine", "User"],
|
||||
"placeholder": "",
|
||||
"required": false,
|
||||
"show": true,
|
||||
@ -838,9 +756,7 @@
|
||||
"display_name": "Sender Name",
|
||||
"dynamic": false,
|
||||
"info": "Name of the sender.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "sender_name",
|
||||
@ -860,9 +776,7 @@
|
||||
"display_name": "Session ID",
|
||||
"dynamic": false,
|
||||
"info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "session_id",
|
||||
@ -898,9 +812,7 @@
|
||||
"display_name": "Text Color",
|
||||
"dynamic": false,
|
||||
"info": "The text color of the name",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "text_color",
|
||||
@ -942,9 +854,7 @@
|
||||
"data": {
|
||||
"id": "TextInput-IpoG7",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
@ -952,9 +862,7 @@
|
||||
"display_name": "Output Format",
|
||||
"documentation": "",
|
||||
"edited": false,
|
||||
"field_order": [
|
||||
"input_value"
|
||||
],
|
||||
"field_order": ["input_value"],
|
||||
"frozen": false,
|
||||
"icon": "type",
|
||||
"legacy": false,
|
||||
@ -970,9 +878,7 @@
|
||||
"name": "text",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -1003,9 +909,7 @@
|
||||
"display_name": "Text",
|
||||
"dynamic": false,
|
||||
"info": "Text to be passed as input.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"multiline": true,
|
||||
@ -1046,9 +950,7 @@
|
||||
"data": {
|
||||
"id": "TextInput-npraC",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
@ -1056,9 +958,7 @@
|
||||
"display_name": "Output Language",
|
||||
"documentation": "",
|
||||
"edited": false,
|
||||
"field_order": [
|
||||
"input_value"
|
||||
],
|
||||
"field_order": ["input_value"],
|
||||
"frozen": false,
|
||||
"icon": "type",
|
||||
"legacy": false,
|
||||
@ -1074,9 +974,7 @@
|
||||
"name": "text",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -1107,9 +1005,7 @@
|
||||
"display_name": "Text",
|
||||
"dynamic": false,
|
||||
"info": "Text to be passed as input.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"multiline": true,
|
||||
@ -1150,9 +1046,7 @@
|
||||
"data": {
|
||||
"id": "TextInput-EZaR7",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
@ -1160,9 +1054,7 @@
|
||||
"display_name": "Profile Details",
|
||||
"documentation": "",
|
||||
"edited": false,
|
||||
"field_order": [
|
||||
"input_value"
|
||||
],
|
||||
"field_order": ["input_value"],
|
||||
"frozen": false,
|
||||
"icon": "type",
|
||||
"legacy": false,
|
||||
@ -1178,9 +1070,7 @@
|
||||
"name": "text",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -1211,9 +1101,7 @@
|
||||
"display_name": "Text",
|
||||
"dynamic": false,
|
||||
"info": "Text to be passed as input.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"multiline": true,
|
||||
@ -1254,9 +1142,7 @@
|
||||
"data": {
|
||||
"id": "TextInput-92vEK",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
@ -1264,9 +1150,7 @@
|
||||
"display_name": "Tone And Style",
|
||||
"documentation": "",
|
||||
"edited": false,
|
||||
"field_order": [
|
||||
"input_value"
|
||||
],
|
||||
"field_order": ["input_value"],
|
||||
"frozen": false,
|
||||
"icon": "type",
|
||||
"legacy": false,
|
||||
@ -1282,9 +1166,7 @@
|
||||
"name": "text",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -1315,9 +1197,7 @@
|
||||
"display_name": "Text",
|
||||
"dynamic": false,
|
||||
"info": "Text to be passed as input.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"multiline": true,
|
||||
@ -1358,9 +1238,7 @@
|
||||
"data": {
|
||||
"id": "TextInput-fKGcs",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
@ -1368,9 +1246,7 @@
|
||||
"display_name": "Profile Type",
|
||||
"documentation": "",
|
||||
"edited": false,
|
||||
"field_order": [
|
||||
"input_value"
|
||||
],
|
||||
"field_order": ["input_value"],
|
||||
"frozen": false,
|
||||
"icon": "type",
|
||||
"legacy": false,
|
||||
@ -1386,9 +1262,7 @@
|
||||
"name": "text",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -1419,9 +1293,7 @@
|
||||
"display_name": "Text",
|
||||
"dynamic": false,
|
||||
"info": "Text to be passed as input.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"multiline": true,
|
||||
@ -1501,9 +1373,7 @@
|
||||
"display_name": "Prompt",
|
||||
"id": "Prompt-AWZtN",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {
|
||||
@ -1520,9 +1390,7 @@
|
||||
"display_name": "Prompt",
|
||||
"documentation": "",
|
||||
"edited": false,
|
||||
"field_order": [
|
||||
"template"
|
||||
],
|
||||
"field_order": ["template"],
|
||||
"frozen": false,
|
||||
"icon": "prompts",
|
||||
"legacy": false,
|
||||
@ -1538,9 +1406,7 @@
|
||||
"name": "prompt",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -1554,10 +1420,7 @@
|
||||
"fileTypes": [],
|
||||
"file_path": "",
|
||||
"info": "",
|
||||
"input_types": [
|
||||
"Message",
|
||||
"Text"
|
||||
],
|
||||
"input_types": ["Message", "Text"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"multiline": true,
|
||||
@ -1577,10 +1440,7 @@
|
||||
"fileTypes": [],
|
||||
"file_path": "",
|
||||
"info": "",
|
||||
"input_types": [
|
||||
"Message",
|
||||
"Text"
|
||||
],
|
||||
"input_types": ["Message", "Text"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"multiline": true,
|
||||
@ -1600,10 +1460,7 @@
|
||||
"fileTypes": [],
|
||||
"file_path": "",
|
||||
"info": "",
|
||||
"input_types": [
|
||||
"Message",
|
||||
"Text"
|
||||
],
|
||||
"input_types": ["Message", "Text"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"multiline": true,
|
||||
@ -1623,10 +1480,7 @@
|
||||
"fileTypes": [],
|
||||
"file_path": "",
|
||||
"info": "",
|
||||
"input_types": [
|
||||
"Message",
|
||||
"Text"
|
||||
],
|
||||
"input_types": ["Message", "Text"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"multiline": true,
|
||||
@ -1646,10 +1500,7 @@
|
||||
"fileTypes": [],
|
||||
"file_path": "",
|
||||
"info": "",
|
||||
"input_types": [
|
||||
"Message",
|
||||
"Text"
|
||||
],
|
||||
"input_types": ["Message", "Text"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"multiline": true,
|
||||
@ -1669,10 +1520,7 @@
|
||||
"fileTypes": [],
|
||||
"file_path": "",
|
||||
"info": "",
|
||||
"input_types": [
|
||||
"Message",
|
||||
"Text"
|
||||
],
|
||||
"input_types": ["Message", "Text"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"multiline": true,
|
||||
@ -1727,9 +1575,7 @@
|
||||
"display_name": "Tool Placeholder",
|
||||
"dynamic": false,
|
||||
"info": "A placeholder input for tool mode.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"name": "tool_placeholder",
|
||||
@ -1771,10 +1617,7 @@
|
||||
"data": {
|
||||
"id": "OpenAIModel-p0R9m",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"LanguageModel",
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["LanguageModel", "Message"],
|
||||
"beta": false,
|
||||
"category": "models",
|
||||
"conditional_paths": [],
|
||||
@ -1813,9 +1656,7 @@
|
||||
"required_inputs": [],
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
},
|
||||
{
|
||||
@ -1824,14 +1665,10 @@
|
||||
"display_name": "Language Model",
|
||||
"method": "build_model",
|
||||
"name": "model_output",
|
||||
"required_inputs": [
|
||||
"api_key"
|
||||
],
|
||||
"required_inputs": ["api_key"],
|
||||
"selected": "LanguageModel",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"LanguageModel"
|
||||
],
|
||||
"types": ["LanguageModel"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
@ -1845,9 +1682,7 @@
|
||||
"display_name": "OpenAI API Key",
|
||||
"dynamic": false,
|
||||
"info": "The OpenAI API Key to use for the OpenAI model.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"load_from_db": true,
|
||||
"name": "api_key",
|
||||
"password": true,
|
||||
@ -1856,7 +1691,7 @@
|
||||
"show": true,
|
||||
"title_case": false,
|
||||
"type": "str",
|
||||
"value": ""
|
||||
"value": "OPENAI_API_KEY"
|
||||
},
|
||||
"code": {
|
||||
"advanced": true,
|
||||
@ -1882,9 +1717,7 @@
|
||||
"display_name": "Input",
|
||||
"dynamic": false,
|
||||
"info": "",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -2066,9 +1899,7 @@
|
||||
"display_name": "System Message",
|
||||
"dynamic": false,
|
||||
"info": "System message to pass to the model.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
@ -2164,8 +1995,5 @@
|
||||
"is_component": false,
|
||||
"last_tested_version": "1.0.19.post2",
|
||||
"name": "Twitter Thread Generator",
|
||||
"tags": [
|
||||
"chatbots",
|
||||
"content-generation"
|
||||
]
|
||||
}
|
||||
"tags": ["chatbots", "content-generation"]
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -37,20 +37,34 @@ class TableInput(BaseInputMixin, MetadataTraceMixin, TableMixin, ListableInputMi
|
||||
@field_validator("value")
|
||||
@classmethod
|
||||
def validate_value(cls, v: Any, _info):
|
||||
# Check if value is a list of dicts
|
||||
# Convert single dict or Data instance into a list.
|
||||
if isinstance(v, dict | Data):
|
||||
v = [v]
|
||||
# Automatically convert DataFrame into a list of dictionaries.
|
||||
if isinstance(v, DataFrame):
|
||||
v = v.to_dict(orient="records")
|
||||
# Verify the value is now a list.
|
||||
if not isinstance(v, list):
|
||||
msg = f"TableInput value must be a list of dictionaries or Data. Value '{v}' is not a list."
|
||||
raise ValueError(msg) # noqa: TRY004
|
||||
|
||||
for item in v:
|
||||
msg = (
|
||||
"The table input must be a list of rows. You provided a "
|
||||
f"{type(v).__name__}, which cannot be converted to table format. "
|
||||
"Please provide your data as either:\n"
|
||||
"- A list of dictionaries (each dict is a row)\n"
|
||||
"- A pandas DataFrame\n"
|
||||
"- A single dictionary (will become a one-row table)\n"
|
||||
"- A Data object (Langflow's internal data structure)\n"
|
||||
)
|
||||
raise ValueError(msg) # noqa: TRY004 Pydantic only catches ValueError or AssertionError
|
||||
# Ensure each item in the list is either a dict or a Data instance.
|
||||
for i, item in enumerate(v):
|
||||
if not isinstance(item, dict | Data):
|
||||
msg = (
|
||||
"TableInput value must be a list of dictionaries or Data. "
|
||||
f"Item '{item}' is not a dictionary or Data."
|
||||
f"Row {i + 1} in your table has an invalid format. Each row must be either:\n"
|
||||
"- A dictionary containing column name/value pairs\n"
|
||||
"- A Data object (Langflow's internal data structure for passing data between components)\n"
|
||||
f"Instead, got a {type(item).__name__}. Please check the format of your input data."
|
||||
)
|
||||
raise ValueError(msg) # noqa: TRY004
|
||||
raise ValueError(msg) # noqa: TRY004 Pydantic only catches ValueError or AssertionError
|
||||
return v
|
||||
|
||||
|
||||
|
||||
@ -34,7 +34,7 @@ from langflow.interface.components import get_and_cache_all_types_dict
|
||||
from langflow.interface.utils import setup_llm_caching
|
||||
from langflow.logging.logger import configure
|
||||
from langflow.middleware import ContentSizeLimitMiddleware
|
||||
from langflow.services.deps import get_settings_service, get_telemetry_service
|
||||
from langflow.services.deps import get_queue_service, get_settings_service, get_telemetry_service
|
||||
from langflow.services.utils import initialize_services, teardown_services
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -43,6 +43,7 @@ if TYPE_CHECKING:
|
||||
# Ignore Pydantic deprecation warnings from Langchain
|
||||
warnings.filterwarnings("ignore", category=PydanticDeprecatedSince20)
|
||||
|
||||
_tasks: list[asyncio.Task] = []
|
||||
|
||||
MAX_PORT = 65535
|
||||
|
||||
@ -127,6 +128,9 @@ def get_lifespan(*, fix_migration=False, version=None):
|
||||
await create_or_update_starter_projects(all_types_dict)
|
||||
telemetry_service.start()
|
||||
await load_flows_from_directory()
|
||||
queue_service = get_queue_service()
|
||||
if not queue_service.is_started(): # Start if not already started
|
||||
queue_service.start()
|
||||
yield
|
||||
|
||||
except Exception as exc:
|
||||
|
||||
@ -221,8 +221,8 @@ def _serialize_dispatcher(obj: Any, max_length: int | None, max_items: int | Non
|
||||
|
||||
def serialize(
|
||||
obj: Any,
|
||||
max_length: int | None = MAX_TEXT_LENGTH,
|
||||
max_items: int | None = MAX_ITEMS_LENGTH,
|
||||
max_length: int | None = None,
|
||||
max_items: int | None = None,
|
||||
*,
|
||||
to_str: bool = False,
|
||||
) -> Any:
|
||||
@ -275,7 +275,9 @@ def serialize(
|
||||
|
||||
|
||||
def serialize_or_str(
|
||||
obj: Any, max_length: int | None = MAX_TEXT_LENGTH, max_items: int | None = MAX_ITEMS_LENGTH
|
||||
obj: Any,
|
||||
max_length: int | None = MAX_TEXT_LENGTH,
|
||||
max_items: int | None = MAX_ITEMS_LENGTH,
|
||||
) -> Any:
|
||||
"""Calls serialize() and if it fails, returns a string representation of the object.
|
||||
|
||||
|
||||
@ -375,13 +375,29 @@ def encrypt_api_key(api_key: str, settings_service: SettingsService):
|
||||
|
||||
|
||||
def decrypt_api_key(encrypted_api_key: str, settings_service: SettingsService):
|
||||
"""Decrypt the provided encrypted API key using Fernet decryption.
|
||||
|
||||
This function first attempts to decrypt the API key by encoding it,
|
||||
assuming it is a properly encoded string. If that fails, it logs a detailed
|
||||
debug message including the exception information and retries decryption
|
||||
using the original string input.
|
||||
|
||||
Args:
|
||||
encrypted_api_key (str): The encrypted API key.
|
||||
settings_service (SettingsService): Service providing authentication settings.
|
||||
|
||||
Returns:
|
||||
str: The decrypted API key, or an empty string if decryption cannot be performed.
|
||||
"""
|
||||
fernet = get_fernet(settings_service)
|
||||
decrypted_key = ""
|
||||
# Two-way decryption
|
||||
if isinstance(encrypted_api_key, str):
|
||||
try:
|
||||
decrypted_key = fernet.decrypt(encrypted_api_key.encode()).decode()
|
||||
except Exception: # noqa: BLE001
|
||||
logger.debug("Failed to decrypt API key")
|
||||
decrypted_key = fernet.decrypt(encrypted_api_key).decode()
|
||||
return decrypted_key
|
||||
return fernet.decrypt(encrypted_api_key.encode()).decode()
|
||||
except Exception as primary_exception: # noqa: BLE001
|
||||
logger.debug(
|
||||
"Decryption using UTF-8 encoded API key failed. Error: %s. "
|
||||
"Retrying decryption using the raw string input.",
|
||||
primary_exception,
|
||||
)
|
||||
return fernet.decrypt(encrypted_api_key).decode()
|
||||
return ""
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
from uuid import UUID
|
||||
|
||||
from loguru import logger
|
||||
from sqlmodel import col, delete, select
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
|
||||
@ -25,7 +26,7 @@ async def get_transactions_by_flow_id(
|
||||
return list(transactions)
|
||||
|
||||
|
||||
async def log_transaction(db: AsyncSession, transaction: TransactionBase) -> TransactionTable:
|
||||
async def log_transaction(db: AsyncSession, transaction: TransactionBase) -> TransactionTable | None:
|
||||
"""Log a transaction and maintain a maximum number of transactions in the database.
|
||||
|
||||
This function logs a new transaction into the database and ensures that the number of transactions
|
||||
@ -42,6 +43,9 @@ async def log_transaction(db: AsyncSession, transaction: TransactionBase) -> Tra
|
||||
Raises:
|
||||
IntegrityError: If there is a database integrity error
|
||||
"""
|
||||
if not transaction.flow_id:
|
||||
logger.debug("Transaction flow_id is None")
|
||||
return None
|
||||
table = TransactionTable(**transaction.model_dump())
|
||||
|
||||
try:
|
||||
@ -63,7 +67,6 @@ async def log_transaction(db: AsyncSession, transaction: TransactionBase) -> Tra
|
||||
db.add(table)
|
||||
await db.exec(delete_older)
|
||||
await db.commit()
|
||||
await db.refresh(table)
|
||||
|
||||
except Exception:
|
||||
await db.rollback()
|
||||
|
||||
@ -5,7 +5,8 @@ from uuid import UUID, uuid4
|
||||
from pydantic import field_serializer, field_validator
|
||||
from sqlmodel import JSON, Column, Field, Relationship, SQLModel
|
||||
|
||||
from langflow.services.database.utils import truncate_json
|
||||
from langflow.serialization.constants import MAX_ITEMS_LENGTH, MAX_TEXT_LENGTH
|
||||
from langflow.serialization.serialization import serialize
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from langflow.services.database.models.flow.model import Flow
|
||||
@ -36,11 +37,11 @@ class TransactionBase(SQLModel):
|
||||
|
||||
@field_serializer("inputs")
|
||||
def serialize_inputs(self, data) -> dict:
|
||||
return truncate_json(data)
|
||||
return serialize(data, max_length=MAX_TEXT_LENGTH, max_items=MAX_ITEMS_LENGTH)
|
||||
|
||||
@field_serializer("outputs")
|
||||
def serialize_outputs(self, data) -> dict:
|
||||
return truncate_json(data)
|
||||
return serialize(data, max_length=MAX_TEXT_LENGTH, max_items=MAX_ITEMS_LENGTH)
|
||||
|
||||
|
||||
class TransactionTable(TransactionBase, table=True): # type: ignore[call-arg]
|
||||
|
||||
@ -143,4 +143,3 @@ async def delete_vertex_builds_by_flow_id(db: AsyncSession, flow_id: UUID) -> No
|
||||
"""
|
||||
stmt = delete(VertexBuildTable).where(VertexBuildTable.flow_id == flow_id)
|
||||
await db.exec(stmt)
|
||||
await db.commit()
|
||||
|
||||
@ -6,13 +6,12 @@ from pydantic import BaseModel, field_serializer, field_validator
|
||||
from sqlalchemy import Text
|
||||
from sqlmodel import JSON, Column, Field, Relationship, SQLModel
|
||||
|
||||
from langflow.services.database.utils import truncate_json
|
||||
from langflow.serialization.constants import MAX_ITEMS_LENGTH, MAX_TEXT_LENGTH
|
||||
from langflow.serialization.serialization import serialize
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from langflow.services.database.models.flow.model import Flow
|
||||
|
||||
from langflow.utils.util_strings import truncate_long_strings
|
||||
|
||||
|
||||
class VertexBuildBase(SQLModel):
|
||||
timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
||||
@ -45,15 +44,15 @@ class VertexBuildBase(SQLModel):
|
||||
|
||||
@field_serializer("data")
|
||||
def serialize_data(self, data) -> dict:
|
||||
return truncate_json(data)
|
||||
return serialize(data, max_length=MAX_TEXT_LENGTH, max_items=MAX_ITEMS_LENGTH)
|
||||
|
||||
@field_serializer("artifacts")
|
||||
def serialize_artifacts(self, data) -> dict:
|
||||
return truncate_json(data)
|
||||
return serialize(data, max_length=MAX_TEXT_LENGTH, max_items=MAX_ITEMS_LENGTH)
|
||||
|
||||
@field_serializer("params")
|
||||
def serialize_params(self, data) -> str:
|
||||
return truncate_long_strings(data)
|
||||
return serialize(data, max_length=MAX_TEXT_LENGTH, max_items=MAX_ITEMS_LENGTH)
|
||||
|
||||
|
||||
class VertexBuildTable(VertexBuildBase, table=True): # type: ignore[call-arg]
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from contextlib import asynccontextmanager
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING
|
||||
@ -10,54 +9,10 @@ from loguru import logger
|
||||
from sqlmodel import text
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
|
||||
from langflow.serialization import constants
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from langflow.services.database.service import DatabaseService
|
||||
|
||||
|
||||
def truncate_json(data, *, max_size: int = constants.MAX_TEXT_LENGTH):
|
||||
def calculate_size(data):
|
||||
return len(json.dumps(data))
|
||||
|
||||
def shrink_to_size(data, remaining_size):
|
||||
if isinstance(data, dict):
|
||||
truncated = {}
|
||||
for key, value in data.items():
|
||||
key_size = len(json.dumps(key))
|
||||
if remaining_size - key_size <= 0:
|
||||
break
|
||||
truncated[key] = shrink_to_size(value, remaining_size - key_size)
|
||||
remaining_size -= len(json.dumps({key: value})) - key_size
|
||||
return truncated
|
||||
|
||||
if isinstance(data, list):
|
||||
truncated = []
|
||||
for item in data:
|
||||
if remaining_size <= len('""'):
|
||||
break
|
||||
truncated.append(shrink_to_size(item, remaining_size))
|
||||
remaining_size -= len(json.dumps(item)) + 1
|
||||
return truncated
|
||||
|
||||
if isinstance(data, str):
|
||||
max_string_length = max(remaining_size - 2, 0)
|
||||
return data[:max_string_length] + "…" if max_string_length > 0 else "…"
|
||||
|
||||
return data
|
||||
|
||||
try:
|
||||
json.dumps(data)
|
||||
is_serialized = True
|
||||
except Exception: # noqa: BLE001
|
||||
is_serialized = False
|
||||
|
||||
if calculate_size(data) <= max_size or not is_serialized:
|
||||
return data
|
||||
|
||||
return shrink_to_size(data, max_size)
|
||||
|
||||
|
||||
async def initialize_database(*, fix_migration: bool = False) -> None:
|
||||
logger.debug("Initializing database")
|
||||
from langflow.services.deps import get_db_service
|
||||
|
||||
@ -15,6 +15,7 @@ if TYPE_CHECKING:
|
||||
from langflow.services.cache.service import AsyncBaseCacheService, CacheService
|
||||
from langflow.services.chat.service import ChatService
|
||||
from langflow.services.database.service import DatabaseService
|
||||
from langflow.services.job_queue.service import JobQueueService
|
||||
from langflow.services.session.service import SessionService
|
||||
from langflow.services.settings.service import SettingsService
|
||||
from langflow.services.socket.service import SocketIOService
|
||||
@ -239,3 +240,10 @@ def get_store_service() -> StoreService:
|
||||
StoreService: The StoreService instance.
|
||||
"""
|
||||
return get_service(ServiceType.STORE_SERVICE)
|
||||
|
||||
|
||||
def get_queue_service() -> JobQueueService:
|
||||
"""Retrieves the QueueService instance from the service manager."""
|
||||
from langflow.services.job_queue.factory import JobQueueServiceFactory
|
||||
|
||||
return get_service(ServiceType.JOB_QUEUE_SERVICE, JobQueueServiceFactory())
|
||||
|
||||
11
src/backend/base/langflow/services/job_queue/factory.py
Normal file
11
src/backend/base/langflow/services/job_queue/factory.py
Normal file
@ -0,0 +1,11 @@
|
||||
from langflow.services.base import Service
|
||||
from langflow.services.factory import ServiceFactory
|
||||
from langflow.services.job_queue.service import JobQueueService
|
||||
|
||||
|
||||
class JobQueueServiceFactory(ServiceFactory):
|
||||
def __init__(self):
|
||||
super().__init__(JobQueueService)
|
||||
|
||||
def create(self) -> Service:
|
||||
return JobQueueService()
|
||||
263
src/backend/base/langflow/services/job_queue/service.py
Normal file
263
src/backend/base/langflow/services/job_queue/service.py
Normal file
@ -0,0 +1,263 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from langflow.events.event_manager import EventManager, create_default_event_manager
|
||||
from langflow.services.base import Service
|
||||
|
||||
|
||||
class JobQueueService(Service):
|
||||
"""Asynchronous service for managing job-specific queues and their associated tasks.
|
||||
|
||||
This service allows clients to:
|
||||
- Create dedicated asyncio queues for individual jobs.
|
||||
- Associate each queue with an EventManager, enabling event-driven handling.
|
||||
- Launch and manage asynchronous tasks that process these job queues.
|
||||
- Safely clean up resources by cancelling active tasks and emptying queues.
|
||||
- Automatically perform periodic cleanup of inactive or completed job queues.
|
||||
|
||||
Attributes:
|
||||
name (str): Unique identifier for the service.
|
||||
_queues (dict[str, tuple[asyncio.Queue, EventManager, asyncio.Task | None]]):
|
||||
Dictionary mapping job IDs to a tuple containing:
|
||||
* The job's asyncio.Queue instance.
|
||||
* The associated EventManager instance.
|
||||
* The asyncio.Task processing the job (if any).
|
||||
_cleanup_task (asyncio.Task | None): Background task for periodic cleanup.
|
||||
_closed (bool): Flag indicating whether the service is currently active.
|
||||
|
||||
Example:
|
||||
service = JobQueueService()
|
||||
await service.start()
|
||||
queue, event_manager = service.create_queue("job123")
|
||||
service.start_job("job123", some_async_coroutine())
|
||||
# Retrieve and use the queue data as needed
|
||||
data = service.get_queue_data("job123")
|
||||
await service.cleanup_job("job123")
|
||||
await service.stop()
|
||||
"""
|
||||
|
||||
name = "job_queue_service"
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize the JobQueueService.
|
||||
|
||||
Sets up the internal registry for job queues, initializes the cleanup task, and sets the service state
|
||||
to active.
|
||||
"""
|
||||
self._queues: dict[str, tuple[asyncio.Queue, EventManager, asyncio.Task | None]] = {}
|
||||
self._cleanup_task: asyncio.Task | None = None
|
||||
self._closed = False
|
||||
self.ready = False
|
||||
|
||||
def is_started(self) -> bool:
|
||||
"""Check if the JobQueueService has started.
|
||||
|
||||
Returns:
|
||||
bool: True if the service has started, False otherwise.
|
||||
"""
|
||||
return self._cleanup_task is not None
|
||||
|
||||
def set_ready(self) -> None:
|
||||
if not self.is_started():
|
||||
self.start()
|
||||
super().set_ready()
|
||||
|
||||
def start(self) -> None:
|
||||
"""Start the JobQueueService and begin the periodic cleanup routine.
|
||||
|
||||
This method marks the service as active and launches a background task that
|
||||
periodically checks and cleans up job queues whose tasks have been completed or cancelled.
|
||||
"""
|
||||
self._closed = False
|
||||
self._cleanup_task = asyncio.create_task(self._periodic_cleanup())
|
||||
logger.debug("JobQueueService started: periodic cleanup task initiated.")
|
||||
|
||||
async def stop(self) -> None:
|
||||
"""Gracefully stop the JobQueueService by terminating background operations and cleaning up all resources.
|
||||
|
||||
This coroutine performs the following steps:
|
||||
1. Marks the service as closed, preventing further job queue creation.
|
||||
2. Cancels the background periodic cleanup task and awaits its termination.
|
||||
3. Iterates over all registered job queues to clean up their resources—cancelling active tasks and
|
||||
clearing queued items.
|
||||
"""
|
||||
self._closed = True
|
||||
if self._cleanup_task:
|
||||
self._cleanup_task.cancel()
|
||||
await asyncio.wait([self._cleanup_task])
|
||||
if not self._cleanup_task.cancelled():
|
||||
exc = self._cleanup_task.exception()
|
||||
if exc is not None:
|
||||
raise exc
|
||||
|
||||
# Clean up each registered job queue.
|
||||
for job_id in list(self._queues.keys()):
|
||||
await self.cleanup_job(job_id)
|
||||
logger.info("JobQueueService stopped: all job queues have been cleaned up.")
|
||||
|
||||
async def teardown(self) -> None:
|
||||
await self.stop()
|
||||
|
||||
def create_queue(self, job_id: str) -> tuple[asyncio.Queue, EventManager]:
|
||||
"""Create and register a new queue along with its corresponding event manager for a job.
|
||||
|
||||
Args:
|
||||
job_id (str): Unique identifier for the job.
|
||||
|
||||
Returns:
|
||||
tuple[asyncio.Queue, EventManager]: A tuple containing:
|
||||
- The asyncio.Queue instance for handling the job's tasks or messages.
|
||||
- The EventManager instance for event handling tied to the queue.
|
||||
"""
|
||||
if job_id in self._queues:
|
||||
msg = f"Queue for job_id {job_id} already exists"
|
||||
logger.error(msg)
|
||||
raise ValueError(msg)
|
||||
|
||||
if self._closed:
|
||||
msg = "Queue service is closed"
|
||||
logger.error(msg)
|
||||
raise RuntimeError(msg)
|
||||
|
||||
main_queue: asyncio.Queue = asyncio.Queue()
|
||||
event_manager = create_default_event_manager(main_queue)
|
||||
|
||||
# Register the queue without an active task.
|
||||
self._queues[job_id] = (main_queue, event_manager, None)
|
||||
logger.debug(f"Queue and event manager successfully created for job_id {job_id}")
|
||||
return main_queue, event_manager
|
||||
|
||||
def start_job(self, job_id: str, task_coro) -> None:
|
||||
"""Start an asynchronous task for a given job, replacing any existing active task.
|
||||
|
||||
The method performs the following:
|
||||
- Verifies the presence of a registered queue for the job.
|
||||
- Cancels any currently running task associated with the job.
|
||||
- Launches a new asynchronous task using the provided coroutine.
|
||||
- Updates the internal registry with the new task.
|
||||
|
||||
Args:
|
||||
job_id (str): Unique identifier for the job.
|
||||
task_coro: A coroutine representing the job's asynchronous task.
|
||||
"""
|
||||
if job_id not in self._queues:
|
||||
msg = f"No queue found for job_id {job_id}"
|
||||
logger.error(msg)
|
||||
raise ValueError(msg)
|
||||
|
||||
if self._closed:
|
||||
msg = "Queue service is closed"
|
||||
logger.error(msg)
|
||||
raise RuntimeError(msg)
|
||||
|
||||
main_queue, event_manager, existing_task = self._queues[job_id]
|
||||
|
||||
if existing_task and not existing_task.done():
|
||||
logger.debug(f"Existing task for job_id {job_id} detected; cancelling it.")
|
||||
existing_task.cancel()
|
||||
|
||||
# Initiate the new asynchronous task.
|
||||
task = asyncio.create_task(task_coro)
|
||||
self._queues[job_id] = (main_queue, event_manager, task)
|
||||
logger.debug(f"New task started for job_id {job_id}")
|
||||
|
||||
def get_queue_data(self, job_id: str) -> tuple[asyncio.Queue, EventManager, asyncio.Task | None]:
|
||||
"""Retrieve the complete data structure associated with a job's queue.
|
||||
|
||||
Args:
|
||||
job_id (str): Unique identifier for the job.
|
||||
|
||||
Returns:
|
||||
tuple[asyncio.Queue, EventManager, asyncio.Task | None]:
|
||||
A tuple containing the job's main queue, its linked event manager, and the associated task (if any).
|
||||
"""
|
||||
if job_id not in self._queues:
|
||||
msg = f"No queue found for job_id {job_id}"
|
||||
logger.error(msg)
|
||||
raise ValueError(msg)
|
||||
|
||||
if self._closed:
|
||||
msg = "Queue service is closed"
|
||||
logger.error(msg)
|
||||
raise RuntimeError(msg)
|
||||
|
||||
return self._queues[job_id]
|
||||
|
||||
async def cleanup_job(self, job_id: str) -> None:
|
||||
"""Clean up and release resources for a specific job.
|
||||
|
||||
The cleanup process includes:
|
||||
1. Verifying if the job's queue is registered.
|
||||
2. Cancelling the running task (if active) and awaiting its termination.
|
||||
3. Clearing all items from the job's queue.
|
||||
4. Removing the job's entry from the internal registry.
|
||||
|
||||
Args:
|
||||
job_id (str): Unique identifier for the job to be cleaned up.
|
||||
"""
|
||||
if job_id not in self._queues:
|
||||
logger.debug(f"No queue found for job_id {job_id} during cleanup.")
|
||||
return
|
||||
|
||||
logger.info(f"Commencing cleanup for job_id {job_id}")
|
||||
main_queue, event_manager, task = self._queues[job_id]
|
||||
|
||||
# Cancel the associated task if it is still running.
|
||||
if task and not task.done():
|
||||
logger.debug(f"Cancelling active task for job_id {job_id}")
|
||||
task.cancel()
|
||||
await asyncio.wait([task])
|
||||
# Log any exceptions that occurred during the task's execution.
|
||||
if exc := task.exception():
|
||||
logger.error(f"Error in task for job_id {job_id}: {exc}")
|
||||
logger.debug(f"Task cancellation complete for job_id {job_id}")
|
||||
|
||||
# Clear the queue since we just cancelled the task or it has completed
|
||||
items_cleared = 0
|
||||
while not main_queue.empty():
|
||||
try:
|
||||
main_queue.get_nowait()
|
||||
items_cleared += 1
|
||||
except asyncio.QueueEmpty:
|
||||
break
|
||||
|
||||
logger.debug(f"Removed {items_cleared} items from queue for job_id {job_id}")
|
||||
# Remove the job entry from the registry
|
||||
self._queues.pop(job_id, None)
|
||||
logger.info(f"Cleanup successful for job_id {job_id}: resources have been released.")
|
||||
|
||||
async def _periodic_cleanup(self) -> None:
|
||||
"""Execute a periodic task that cleans up completed or cancelled job queues.
|
||||
|
||||
This internal coroutine continuously:
|
||||
- Sleeps for a fixed interval (60 seconds).
|
||||
- Initiates the cleanup of job queues by calling _cleanup_old_queues.
|
||||
- Monitors and logs any exceptions during the cleanup cycle.
|
||||
|
||||
The loop terminates when the service is marked as closed.
|
||||
"""
|
||||
while not self._closed:
|
||||
try:
|
||||
await asyncio.sleep(60) # Sleep for 60 seconds before next cleanup attempt.
|
||||
await self._cleanup_old_queues()
|
||||
except asyncio.CancelledError:
|
||||
logger.debug("Periodic cleanup task received cancellation signal.")
|
||||
raise
|
||||
except Exception as exc: # noqa: BLE001
|
||||
logger.error(f"Exception encountered during periodic cleanup: {exc}")
|
||||
|
||||
async def _cleanup_old_queues(self) -> None:
|
||||
"""Scan all registered job queues and clean up those with inactive tasks.
|
||||
|
||||
For each job:
|
||||
- Check whether the associated task is either complete or cancelled.
|
||||
- If so, execute the cleanup_job method to release the job's resources.
|
||||
"""
|
||||
for job_id in list(self._queues.keys()):
|
||||
_, _, task = self._queues[job_id]
|
||||
if task and task.done():
|
||||
logger.debug(f"Job queue for job_id {job_id} marked for cleanup.")
|
||||
await self.cleanup_job(job_id)
|
||||
@ -19,3 +19,4 @@ class ServiceType(str, Enum):
|
||||
STATE_SERVICE = "state_service"
|
||||
TRACING_SERVICE = "tracing_service"
|
||||
TELEMETRY_SERVICE = "telemetry_service"
|
||||
JOB_QUEUE_SERVICE = "job_queue_service"
|
||||
|
||||
@ -76,14 +76,13 @@ class Settings(BaseSettings):
|
||||
`postgresql+psycopg` respectively)."""
|
||||
database_connection_retry: bool = False
|
||||
"""If True, Langflow will retry to connect to the database if it fails."""
|
||||
pool_size: int = 10
|
||||
"""DEPRECATED: Use db_connection_settings['pool_size'] instead.
|
||||
The number of connections to keep open in the connection pool. If not provided, the default is 10."""
|
||||
max_overflow: int = 20
|
||||
"""DEPRECATED: Use db_connection_settings['max_overflow'] instead.
|
||||
The number of connections to allow that can be opened beyond the pool size.
|
||||
If not provided, the default is 20."""
|
||||
db_connect_timeout: int = 20
|
||||
pool_size: int = 20
|
||||
"""The number of connections to keep open in the connection pool.
|
||||
For high load scenarios, this should be increased based on expected concurrent users."""
|
||||
max_overflow: int = 30
|
||||
"""The number of connections to allow that can be opened beyond the pool size.
|
||||
Should be 2x the pool_size for optimal performance under load."""
|
||||
db_connect_timeout: int = 30
|
||||
"""The number of seconds to wait before giving up on a lock to released or establishing a connection to the
|
||||
database."""
|
||||
|
||||
@ -92,12 +91,27 @@ class Settings(BaseSettings):
|
||||
"""SQLite pragmas to use when connecting to the database."""
|
||||
|
||||
db_connection_settings: dict | None = {
|
||||
"pool_size": 10,
|
||||
"max_overflow": 20,
|
||||
"pool_timeout": 30,
|
||||
"pool_pre_ping": True,
|
||||
"pool_size": 20, # Match the pool_size above
|
||||
"max_overflow": 30, # Match the max_overflow above
|
||||
"pool_timeout": 30, # Seconds to wait for a connection from pool
|
||||
"pool_pre_ping": True, # Check connection validity before using
|
||||
"pool_recycle": 1800, # Recycle connections after 30 minutes
|
||||
"echo": False, # Set to True for debugging only
|
||||
}
|
||||
"""Common database connection settings."""
|
||||
"""Database connection settings optimized for high load scenarios.
|
||||
Note: These settings are most effective with PostgreSQL. For SQLite:
|
||||
- Reduce pool_size and max_overflow if experiencing lock contention
|
||||
- SQLite has limited concurrent write capability even with WAL mode
|
||||
- Best for read-heavy or moderate write workloads
|
||||
|
||||
Settings:
|
||||
- pool_size: Number of connections to maintain (increase for higher concurrency)
|
||||
- max_overflow: Additional connections allowed beyond pool_size
|
||||
- pool_timeout: Seconds to wait for an available connection
|
||||
- pool_pre_ping: Validates connections before use to prevent stale connections
|
||||
- pool_recycle: Seconds before connections are recycled (prevents timeouts)
|
||||
- echo: Enable SQL query logging (development only)
|
||||
"""
|
||||
|
||||
# cache configuration
|
||||
cache_type: Literal["async", "redis", "memory", "disk"] = "async"
|
||||
@ -205,6 +219,9 @@ class Settings(BaseSettings):
|
||||
mcp_server_enable_progress_notifications: bool = False
|
||||
"""If set to False, Langflow will not send progress notifications in the MCP server."""
|
||||
|
||||
event_delivery: Literal["polling", "streaming"] = "streaming"
|
||||
"""How to deliver build events to the frontend. Can be 'polling' or 'streaming'."""
|
||||
|
||||
@field_validator("dev")
|
||||
@classmethod
|
||||
def set_dev(cls, value):
|
||||
|
||||
@ -1,19 +1,24 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import traceback
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import anyio
|
||||
from loguru import logger
|
||||
|
||||
from langflow.services.task.backends.base import TaskBackend
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
from types import TracebackType
|
||||
|
||||
|
||||
class AnyIOTaskResult:
|
||||
def __init__(self, scope) -> None:
|
||||
self._scope = scope
|
||||
def __init__(self) -> None:
|
||||
self._status = "PENDING"
|
||||
self._result = None
|
||||
self._exception: Exception | None = None
|
||||
self._traceback: TracebackType | None = None
|
||||
self.cancel_scope: anyio.CancelScope | None = None
|
||||
|
||||
@property
|
||||
def status(self) -> str:
|
||||
@ -34,9 +39,11 @@ class AnyIOTaskResult:
|
||||
def ready(self) -> bool:
|
||||
return self._status == "DONE"
|
||||
|
||||
async def run(self, func, *args, **kwargs) -> None:
|
||||
async def run(self, func: Callable[..., Any], *args: Any, **kwargs: Any) -> None:
|
||||
try:
|
||||
self._result = await func(*args, **kwargs)
|
||||
async with anyio.CancelScope() as scope:
|
||||
self.cancel_scope = scope
|
||||
self._result = await func(*args, **kwargs)
|
||||
except Exception as e: # noqa: BLE001
|
||||
self._exception = e
|
||||
self._traceback = e.__traceback__
|
||||
@ -45,36 +52,66 @@ class AnyIOTaskResult:
|
||||
|
||||
|
||||
class AnyIOBackend(TaskBackend):
|
||||
"""Backend for handling asynchronous tasks using AnyIO."""
|
||||
|
||||
name = "anyio"
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize the AnyIO backend with an empty task dictionary."""
|
||||
self.tasks: dict[str, AnyIOTaskResult] = {}
|
||||
self._run_tasks: list[anyio.TaskGroup] = []
|
||||
|
||||
async def launch_task(
|
||||
self, task_func: Callable[..., Any], *args: Any, **kwargs: Any
|
||||
) -> tuple[str | None, AnyIOTaskResult | None]:
|
||||
) -> tuple[str, AnyIOTaskResult]:
|
||||
"""Launch a new task in an asynchronous manner.
|
||||
|
||||
Parameters:
|
||||
Args:
|
||||
task_func: The asynchronous function to run.
|
||||
*args: Positional arguments to pass to task_func.
|
||||
**kwargs: Keyword arguments to pass to task_func.
|
||||
|
||||
Returns:
|
||||
A tuple containing a unique task ID and the task result object.
|
||||
"""
|
||||
async with anyio.create_task_group() as tg:
|
||||
try:
|
||||
task_result = AnyIOTaskResult(tg)
|
||||
tg.start_soon(task_result.run, task_func, *args, **kwargs)
|
||||
except Exception: # noqa: BLE001
|
||||
logger.exception("An error occurred while launching the task")
|
||||
return None, None
|
||||
tuple[str, AnyIOTaskResult]: A tuple containing the task ID and task result object.
|
||||
|
||||
Raises:
|
||||
RuntimeError: If task creation fails.
|
||||
"""
|
||||
try:
|
||||
task_result = AnyIOTaskResult()
|
||||
|
||||
# Create task ID before starting the task
|
||||
task_id = str(id(task_result))
|
||||
self.tasks[task_id] = task_result
|
||||
logger.info(f"Task {task_id} started.")
|
||||
return task_id, task_result
|
||||
|
||||
def get_task(self, task_id: str) -> Any:
|
||||
# Start the task in the background using TaskGroup
|
||||
async with anyio.create_task_group() as tg:
|
||||
tg.start_soon(task_result.run, task_func, *args, **kwargs)
|
||||
self._run_tasks.append(tg)
|
||||
|
||||
except Exception as e:
|
||||
msg = f"Failed to launch task: {e!s}"
|
||||
raise RuntimeError(msg) from e
|
||||
return task_id, task_result
|
||||
|
||||
def get_task(self, task_id: str) -> AnyIOTaskResult | None:
|
||||
"""Retrieve a task by its ID.
|
||||
|
||||
Args:
|
||||
task_id: The unique identifier of the task.
|
||||
|
||||
Returns:
|
||||
AnyIOTaskResult | None: The task result object if found, None otherwise.
|
||||
"""
|
||||
return self.tasks.get(task_id)
|
||||
|
||||
async def cleanup_task(self, task_id: str) -> None:
|
||||
"""Clean up a completed task and its resources.
|
||||
|
||||
Args:
|
||||
task_id: The unique identifier of the task to clean up.
|
||||
"""
|
||||
if task := self.tasks.get(task_id):
|
||||
if task.cancel_scope:
|
||||
task.cancel_scope.cancel()
|
||||
self.tasks.pop(task_id, None)
|
||||
|
||||
@ -3,45 +3,20 @@ from __future__ import annotations
|
||||
from collections.abc import Callable, Coroutine
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from langflow.services.base import Service
|
||||
from langflow.services.task.backends.anyio import AnyIOBackend
|
||||
from langflow.services.task.utils import get_celery_worker_status
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from langflow.services.settings.service import SettingsService
|
||||
from langflow.services.task.backends.base import TaskBackend
|
||||
|
||||
|
||||
def check_celery_availability():
|
||||
try:
|
||||
from langflow.worker import celery_app
|
||||
|
||||
status = get_celery_worker_status(celery_app)
|
||||
logger.debug(f"Celery status: {status}")
|
||||
except Exception: # noqa: BLE001
|
||||
logger.opt(exception=True).debug("Celery not available")
|
||||
status = {"availability": None}
|
||||
return status
|
||||
|
||||
|
||||
class TaskService(Service):
|
||||
name = "task_service"
|
||||
|
||||
def __init__(self, settings_service: SettingsService):
|
||||
self.settings_service = settings_service
|
||||
try:
|
||||
if self.settings_service.settings.celery_enabled:
|
||||
status = check_celery_availability()
|
||||
|
||||
use_celery = status.get("availability") is not None
|
||||
else:
|
||||
use_celery = False
|
||||
except ImportError:
|
||||
use_celery = False
|
||||
|
||||
self.use_celery = use_celery
|
||||
self.use_celery = False
|
||||
self.backend = self.get_backend()
|
||||
|
||||
@property
|
||||
@ -49,12 +24,6 @@ class TaskService(Service):
|
||||
return self.backend.name
|
||||
|
||||
def get_backend(self) -> TaskBackend:
|
||||
if self.use_celery:
|
||||
from langflow.services.task.backends.celery import CeleryBackend
|
||||
|
||||
logger.debug("Using Celery backend")
|
||||
return CeleryBackend()
|
||||
logger.debug("Using AnyIO backend")
|
||||
return AnyIOBackend()
|
||||
|
||||
# In your TaskService class
|
||||
@ -64,24 +33,8 @@ class TaskService(Service):
|
||||
*args: Any,
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
if not self.use_celery:
|
||||
return None, await task_func(*args, **kwargs)
|
||||
if not hasattr(task_func, "apply"):
|
||||
msg = f"Task function {task_func} does not have an apply method"
|
||||
raise ValueError(msg)
|
||||
task = task_func.apply(args=args, kwargs=kwargs)
|
||||
|
||||
result = task.get()
|
||||
# if result is coroutine
|
||||
if isinstance(result, Coroutine):
|
||||
result = await result
|
||||
return task.id, result
|
||||
return await task_func(*args, **kwargs)
|
||||
|
||||
async def launch_task(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"Using backend {self.backend}")
|
||||
task = self.backend.launch_task(task_func, *args, **kwargs)
|
||||
return await task if isinstance(task, Coroutine) else task
|
||||
|
||||
def get_task(self, task_id: str) -> Any:
|
||||
return self.backend.get_task(task_id)
|
||||
|
||||
@ -21,6 +21,7 @@ class VersionPayload(BaseModel):
|
||||
auto_login: bool = Field(serialization_alias="autoLogin")
|
||||
cache_type: str = Field(serialization_alias="cacheType")
|
||||
backend_only: bool = Field(serialization_alias="backendOnly")
|
||||
desktop: bool = False
|
||||
|
||||
|
||||
class PlaygroundPayload(BaseModel):
|
||||
|
||||
@ -90,6 +90,10 @@ class TelemetryService(Service):
|
||||
return
|
||||
await self.telemetry_queue.put(payload)
|
||||
|
||||
def _get_langflow_desktop(self) -> bool:
|
||||
# Coerce to bool, could be 1, 0, True, False, "1", "0", "True", "False"
|
||||
return str(os.getenv("LANGFLOW_DESKTOP", "False")).lower() in ("1", "true")
|
||||
|
||||
async def log_package_version(self) -> None:
|
||||
python_version = ".".join(platform.python_version().split(".")[:2])
|
||||
version_info = get_version_info()
|
||||
@ -104,6 +108,7 @@ class TelemetryService(Service):
|
||||
backend_only=self.settings_service.settings.backend_only,
|
||||
arch=self.architecture,
|
||||
auto_login=self.settings_service.auth_settings.AUTO_LOGIN,
|
||||
desktop=self._get_langflow_desktop(),
|
||||
)
|
||||
await self._queue_event((self.send_telemetry_data, payload, None))
|
||||
|
||||
|
||||
@ -39,7 +39,9 @@ class ArizePhoenixTracer(BaseTracer):
|
||||
chat_input_value: str
|
||||
chat_output_value: str
|
||||
|
||||
def __init__(self, trace_name: str, trace_type: str, project_name: str, trace_id: UUID):
|
||||
def __init__(
|
||||
self, trace_name: str, trace_type: str, project_name: str, trace_id: UUID, session_id: str | None = None
|
||||
):
|
||||
"""Initializes the ArizePhoenixTracer instance and sets up a root span."""
|
||||
self.trace_name = trace_name
|
||||
self.trace_type = trace_type
|
||||
@ -49,6 +51,7 @@ class ArizePhoenixTracer(BaseTracer):
|
||||
self.flow_id = trace_name.split(" - ")[-1]
|
||||
self.chat_input_value = ""
|
||||
self.chat_output_value = ""
|
||||
self.session_id = session_id
|
||||
|
||||
try:
|
||||
self._ready = self.setup_arize_phoenix()
|
||||
@ -63,7 +66,7 @@ class ArizePhoenixTracer(BaseTracer):
|
||||
name=self.flow_id,
|
||||
start_time=self._get_current_timestamp(),
|
||||
)
|
||||
self.root_span.set_attribute(SpanAttributes.SESSION_ID, self.flow_id)
|
||||
self.root_span.set_attribute(SpanAttributes.SESSION_ID, self.session_id or self.flow_id)
|
||||
self.root_span.set_attribute(SpanAttributes.OPENINFERENCE_SPAN_KIND, self.trace_type)
|
||||
self.root_span.set_attribute("langflow.project.name", self.project_name)
|
||||
self.root_span.set_attribute("langflow.flow.name", self.flow_name)
|
||||
|
||||
@ -65,6 +65,7 @@ class TracingService(Service):
|
||||
self.worker_task: asyncio.Task | None = None
|
||||
self.end_trace_tasks: set[asyncio.Task] = set()
|
||||
self.deactivated = self.settings_service.settings.deactivate_tracing
|
||||
self.session_id: str | None = None
|
||||
|
||||
async def log_worker(self) -> None:
|
||||
while self.running or not self.logs_queue.empty():
|
||||
@ -163,6 +164,7 @@ class TracingService(Service):
|
||||
trace_type="chain",
|
||||
project_name=self.project_name,
|
||||
trace_id=self.run_id,
|
||||
session_id=self.session_id,
|
||||
)
|
||||
|
||||
def set_run_name(self, name: str) -> None:
|
||||
@ -241,7 +243,7 @@ class TracingService(Service):
|
||||
trace_id,
|
||||
trace_name,
|
||||
trace_type,
|
||||
self._cleanup_inputs(inputs),
|
||||
inputs,
|
||||
metadata,
|
||||
component._vertex,
|
||||
)
|
||||
@ -286,3 +288,7 @@ class TracingService(Service):
|
||||
if langchain_callback:
|
||||
callbacks.append(langchain_callback)
|
||||
return callbacks
|
||||
|
||||
def set_session_id(self, session_id: str) -> None:
|
||||
"""Set the session ID for tracing."""
|
||||
self.session_id = session_id
|
||||
|
||||
@ -170,7 +170,7 @@ dev-dependencies = [
|
||||
module-root = "langflow"
|
||||
tests-root = "../tests/unit"
|
||||
test-framework = "pytest"
|
||||
ignore-paths = []
|
||||
ignore-paths = ["src/backend/base/langflow/components/"]
|
||||
formatter-cmds = ["ruff check --exit-zero --fix $file", "ruff format $file"]
|
||||
#disable plugins that might interfere with runtime measurement
|
||||
pytest-cmd = "pytest -p no:profiling -p no:sugar -p no:xdist -p no:cov -p no:split"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -135,11 +135,12 @@ def get_text():
|
||||
|
||||
|
||||
async def delete_transactions_by_flow_id(db: AsyncSession, flow_id: UUID):
|
||||
if not flow_id:
|
||||
return
|
||||
stmt = select(TransactionTable).where(TransactionTable.flow_id == flow_id)
|
||||
transactions = await db.exec(stmt)
|
||||
for transaction in transactions:
|
||||
await db.delete(transaction)
|
||||
await db.commit()
|
||||
|
||||
|
||||
async def _delete_transactions_and_vertex_builds(session, flows: list[Flow]):
|
||||
@ -147,8 +148,14 @@ async def _delete_transactions_and_vertex_builds(session, flows: list[Flow]):
|
||||
for flow_id in flow_ids:
|
||||
if not flow_id:
|
||||
continue
|
||||
await delete_vertex_builds_by_flow_id(session, flow_id)
|
||||
await delete_transactions_by_flow_id(session, flow_id)
|
||||
try:
|
||||
await delete_vertex_builds_by_flow_id(session, flow_id)
|
||||
except Exception as e: # noqa: BLE001
|
||||
logger.debug(f"Error deleting vertex builds for flow {flow_id}: {e}")
|
||||
try:
|
||||
await delete_transactions_by_flow_id(session, flow_id)
|
||||
except Exception as e: # noqa: BLE001
|
||||
logger.debug(f"Error deleting transactions for flow {flow_id}: {e}")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@ -433,12 +440,21 @@ async def active_user(client): # noqa: ARG001
|
||||
yield user
|
||||
# Clean up
|
||||
# Now cleanup transactions, vertex_build
|
||||
async with db_manager.with_session() as session:
|
||||
user = await session.get(User, user.id, options=[selectinload(User.flows)])
|
||||
await _delete_transactions_and_vertex_builds(session, user.flows)
|
||||
await session.delete(user)
|
||||
try:
|
||||
async with db_manager.with_session() as session:
|
||||
user = await session.get(User, user.id, options=[selectinload(User.flows)])
|
||||
await _delete_transactions_and_vertex_builds(session, user.flows)
|
||||
await session.commit()
|
||||
except Exception as e: # noqa: BLE001
|
||||
logger.exception(f"Error deleting transactions and vertex builds for user: {e}")
|
||||
|
||||
await session.commit()
|
||||
try:
|
||||
async with db_manager.with_session() as session:
|
||||
user = await session.get(User, user.id)
|
||||
await session.delete(user)
|
||||
await session.commit()
|
||||
except Exception as e: # noqa: BLE001
|
||||
logger.exception(f"Error deleting user: {e}")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@ -1,125 +1,125 @@
|
||||
import random
|
||||
import os
|
||||
import time
|
||||
from pathlib import Path
|
||||
from http import HTTPStatus
|
||||
|
||||
import httpx
|
||||
import orjson
|
||||
from locust import FastHttpUser, between, task
|
||||
from rich import print # noqa: A004
|
||||
from locust import FastHttpUser, between, events, task
|
||||
|
||||
|
||||
class NameTest(FastHttpUser):
|
||||
wait_time = between(1, 5)
|
||||
@events.quitting.add_listener
|
||||
def _(environment, **_kwargs):
|
||||
"""Print stats at test end for analysis."""
|
||||
if environment.stats.total.fail_ratio > 0.01:
|
||||
environment.process_exit_code = 1
|
||||
environment.runner.quit()
|
||||
|
||||
with Path("names.txt").open(encoding="utf-8") as file:
|
||||
names = [line.strip() for line in file]
|
||||
|
||||
headers: dict = {}
|
||||
class FlowRunUser(FastHttpUser):
|
||||
"""FlowRunUser simulates users sending requests to the Langflow run endpoint.
|
||||
|
||||
def poll_task(self, task_id, sleep_time=1):
|
||||
while True:
|
||||
with self.rest(
|
||||
"GET",
|
||||
f"/task/{task_id}",
|
||||
name="task_status",
|
||||
headers=self.headers,
|
||||
) as response:
|
||||
status = response.js.get("status")
|
||||
print(f"Poll Response: {response.js}")
|
||||
if status == "SUCCESS":
|
||||
return response.js.get("result")
|
||||
if status in {"FAILURE", "REVOKED"}:
|
||||
msg = f"Task failed with status: {status}"
|
||||
raise ValueError(msg)
|
||||
time.sleep(sleep_time)
|
||||
Designed for high-load testing with proper wait times and connection handling.
|
||||
Uses FastHttpUser for better performance with keep-alive connections and connection pooling.
|
||||
|
||||
def process(self, name, flow_id, payload):
|
||||
task_id = None
|
||||
print(f"Processing {payload}")
|
||||
with self.rest(
|
||||
"POST",
|
||||
f"/process/{flow_id}",
|
||||
json=payload,
|
||||
name="process",
|
||||
headers=self.headers,
|
||||
) as response:
|
||||
print(response.js)
|
||||
if response.status_code != 200:
|
||||
response.failure("Process call failed")
|
||||
msg = "Process call failed"
|
||||
raise ValueError(msg)
|
||||
task_id = response.js.get("id")
|
||||
session_id = response.js.get("session_id")
|
||||
assert task_id, "Inner Task ID not found"
|
||||
Environment Variables:
|
||||
- LANGFLOW_HOST: Base URL for the Langflow server (default: http://localhost:7860)
|
||||
- FLOW_ID: UUID or endpoint name of the flow to test (default: 62c21279-f7ca-43e2-b5e3-326ac573db04)
|
||||
- API_KEY: API key for authentication, sent as header 'x-api-key' (Required)
|
||||
- MIN_WAIT: Minimum wait time between requests in ms (default: 2000)
|
||||
- MAX_WAIT: Maximum wait time between requests in ms (default: 5000)
|
||||
- REQUEST_TIMEOUT: Timeout for each request in seconds (default: 30.0)
|
||||
"""
|
||||
|
||||
assert task_id, "Task ID not found"
|
||||
result = self.poll_task(task_id)
|
||||
print(f"Result for {name}: {result}")
|
||||
abstract = False # This user class can be instantiated
|
||||
connection_timeout = float(os.getenv("REQUEST_TIMEOUT", "30.0")) # Configurable timeout
|
||||
network_timeout = float(os.getenv("REQUEST_TIMEOUT", "30.0"))
|
||||
|
||||
return result, session_id
|
||||
# Dynamic wait time based on environment variables or defaults
|
||||
# Increased default minimum wait to reduce database pressure
|
||||
wait_time = between(
|
||||
float(os.getenv("MIN_WAIT", "2000")) / 1000,
|
||||
float(os.getenv("MAX_WAIT", "5000")) / 1000,
|
||||
)
|
||||
|
||||
@task
|
||||
def send_name_and_check(self):
|
||||
name = random.choice(self.names) # noqa: S311
|
||||
# Use the host provided by environment variable or default
|
||||
host = os.getenv("LANGFLOW_HOST", "http://localhost:7860")
|
||||
|
||||
payload1 = {
|
||||
"inputs": {"text": f"Hello, My name is {name}"},
|
||||
"sync": False,
|
||||
}
|
||||
_result1, session_id = self.process(name, self.flow_id, payload1)
|
||||
# Flow ID from environment variable or default example UUID
|
||||
flow_id = os.getenv("FLOW_ID")
|
||||
|
||||
payload2 = {
|
||||
"inputs": {"text": "What is my name? Please, answer like this: Your name is <name>"},
|
||||
"session_id": session_id,
|
||||
"sync": False,
|
||||
}
|
||||
result2, session_id = self.process(name, self.flow_id, payload2)
|
||||
|
||||
assert f"Your name is {name}" in str(result2), "Name not found in response"
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
self._last_response: dict | None = None
|
||||
self._consecutive_failures = 0
|
||||
|
||||
def on_start(self):
|
||||
print("Starting")
|
||||
login_data = {"username": "superuser", "password": "superuser"}
|
||||
response = httpx.post(f"{self.host}/login", data=login_data)
|
||||
print(response.json())
|
||||
"""Setup and validate required configurations."""
|
||||
if not os.getenv("API_KEY"):
|
||||
msg = "API_KEY environment variable is required for load testing"
|
||||
raise ValueError(msg)
|
||||
|
||||
tokens = response.json()
|
||||
print(tokens)
|
||||
a_token = tokens["access_token"]
|
||||
logged_in_headers = {"Authorization": f"Bearer {a_token}"}
|
||||
print("Logged in")
|
||||
json_flow = (Path(__file__).parent.parent / "data" / "BasicChatwithPromptandHistory.json").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
flow = orjson.loads(json_flow)
|
||||
data = flow["data"]
|
||||
# Create test data
|
||||
flow = {"name": "Flow 1", "description": "description", "data": data}
|
||||
print("Creating flow")
|
||||
# Make request to endpoint
|
||||
response = httpx.post(
|
||||
f"{self.host}/flows/",
|
||||
json=flow,
|
||||
headers=logged_in_headers,
|
||||
)
|
||||
self.flow_id = response.json()["id"]
|
||||
print(f"Flow ID: {self.flow_id}")
|
||||
# Test connection and auth before starting
|
||||
with self.client.get("/health", catch_response=True) as response:
|
||||
if response.status_code != HTTPStatus.OK:
|
||||
msg = f"Initial health check failed: {response.status_code}"
|
||||
raise ConnectionError(msg)
|
||||
|
||||
# read all users
|
||||
response = httpx.get(
|
||||
f"{self.host}/users/",
|
||||
headers=logged_in_headers,
|
||||
)
|
||||
print(response.json())
|
||||
user_id = next(
|
||||
(user["id"] for user in response.json()["users"] if user["username"] == "superuser"),
|
||||
None,
|
||||
)
|
||||
# Create api key
|
||||
response = httpx.post(
|
||||
f"{self.host}/api_key/",
|
||||
json={"user_id": user_id},
|
||||
headers=logged_in_headers,
|
||||
)
|
||||
print(response.json())
|
||||
self.headers["x-api-key"] = response.json()["api_key"]
|
||||
def log_error(self, name: str, exc: Exception, response_time: float):
|
||||
"""Helper method to log errors in a format Locust expects.
|
||||
|
||||
Args:
|
||||
name: The name/endpoint of the request
|
||||
exc: The exception that occurred
|
||||
response_time: The response time in milliseconds
|
||||
"""
|
||||
# Log error in stats
|
||||
self.environment.stats.log_error("ERROR", name, str(exc))
|
||||
# Log request with error
|
||||
self.environment.stats.log_request("ERROR", name, response_time, 0)
|
||||
|
||||
@task(1)
|
||||
def run_flow_endpoint(self):
|
||||
"""Sends a POST request to the run endpoint using a realistic payload.
|
||||
|
||||
Includes basic error handling.
|
||||
"""
|
||||
if not self.flow_id:
|
||||
msg = "FLOW_ID environment variable is required for load testing"
|
||||
raise ValueError(msg)
|
||||
endpoint = f"/api/v1/run/{self.flow_id}?stream=false"
|
||||
|
||||
# Realistic payload that exercises the system
|
||||
payload = {
|
||||
"input_value": (
|
||||
"Hey, Could you check https://docs.langflow.org for me? Later, could you calculate 1390 / 192 ?"
|
||||
),
|
||||
"output_type": "chat",
|
||||
"input_type": "chat",
|
||||
"tweaks": {},
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"x-api-key": os.getenv("API_KEY"),
|
||||
"Accept": "application/json",
|
||||
}
|
||||
|
||||
start_time = time.time()
|
||||
try:
|
||||
with self.client.post(
|
||||
endpoint, json=payload, headers=headers, catch_response=True, timeout=self.connection_timeout
|
||||
) as response:
|
||||
response_time = (time.time() - start_time) * 1000
|
||||
if response.status_code == HTTPStatus.OK:
|
||||
try:
|
||||
self._last_response = response.json()
|
||||
except ValueError as e:
|
||||
response.failure("Invalid JSON response")
|
||||
self.log_error(endpoint, e, response_time)
|
||||
else:
|
||||
error_text = response.text or "No response text"
|
||||
error_msg = f"Unexpected status code: {response.status_code}, Response: {error_text[:200]}"
|
||||
response.failure(error_msg)
|
||||
self.log_error(endpoint, Exception(error_msg), response_time)
|
||||
except Exception as e: # noqa: BLE001
|
||||
response_time = (time.time() - start_time) * 1000
|
||||
self.log_error(endpoint, e, response_time)
|
||||
response.failure(f"Error: {e}")
|
||||
|
||||
@ -35,17 +35,21 @@ def test_component_tool():
|
||||
|
||||
|
||||
@pytest.mark.api_key_required
|
||||
def test_component_tool_with_api_key():
|
||||
@pytest.mark.usefixtures("client")
|
||||
async def test_component_tool_with_api_key():
|
||||
chat_output = ChatOutput()
|
||||
openai_llm = OpenAIModelComponent()
|
||||
openai_llm.set(api_key=os.environ["OPENAI_API_KEY"])
|
||||
tool_calling_agent = ToolCallingAgentComponent()
|
||||
tools = await chat_output.to_toolkit()
|
||||
tool_calling_agent.set(
|
||||
llm=openai_llm.build_model, tools=[chat_output], input_value="Which tools are available? Please tell its name."
|
||||
llm=openai_llm.build_model,
|
||||
tools=tools,
|
||||
input_value="Which tools are available? Please tell its name.",
|
||||
)
|
||||
|
||||
g = Graph(start=tool_calling_agent, end=tool_calling_agent)
|
||||
assert g is not None
|
||||
results = list(g.start())
|
||||
results = [result async for result in g.async_start()]
|
||||
assert len(results) == 4
|
||||
assert "message_response" in tool_calling_agent._outputs_map["response"].value.get_text()
|
||||
|
||||
75
src/backend/tests/unit/build_utils.py
Normal file
75
src/backend/tests/unit/build_utils.py
Normal file
@ -0,0 +1,75 @@
|
||||
import json
|
||||
from typing import Any
|
||||
from uuid import UUID
|
||||
|
||||
from httpx import AsyncClient, codes
|
||||
|
||||
|
||||
async def create_flow(client: AsyncClient, flow_data: str, headers: dict[str, str]) -> UUID:
|
||||
"""Create a flow and return its ID."""
|
||||
response = await client.post("api/v1/flows/", json=json.loads(flow_data), headers=headers)
|
||||
assert response.status_code == codes.CREATED
|
||||
return UUID(response.json()["id"])
|
||||
|
||||
|
||||
async def build_flow(
|
||||
client: AsyncClient, flow_id: UUID, headers: dict[str, str], json: dict[str, Any] | None = None
|
||||
) -> dict[str, Any]:
|
||||
"""Start a flow build and return the job_id."""
|
||||
if json is None:
|
||||
json = {}
|
||||
response = await client.post(f"api/v1/build/{flow_id}/flow", json=json, headers=headers)
|
||||
assert response.status_code == codes.OK
|
||||
return response.json()
|
||||
|
||||
|
||||
async def get_build_events(client: AsyncClient, job_id: str, headers: dict[str, str]):
|
||||
"""Get events for a build job."""
|
||||
return await client.get(f"api/v1/build/{job_id}/events", headers=headers)
|
||||
|
||||
|
||||
async def consume_and_assert_stream(response, job_id):
|
||||
"""Consume the event stream and assert the expected event structure."""
|
||||
count = 0
|
||||
lines = []
|
||||
async for line in response.aiter_lines():
|
||||
# Skip empty lines (ndjson uses double newlines)
|
||||
if not line:
|
||||
continue
|
||||
|
||||
lines.append(line)
|
||||
parsed = json.loads(line)
|
||||
if "job_id" in parsed:
|
||||
assert parsed["job_id"] == job_id
|
||||
continue
|
||||
|
||||
if count == 0:
|
||||
# First event should be vertices_sorted
|
||||
assert parsed["event"] == "vertices_sorted", (
|
||||
"Invalid first event. Expected 'vertices_sorted'. Full event stream:\n" + "\n".join(lines)
|
||||
)
|
||||
ids = parsed["data"]["ids"]
|
||||
ids.sort()
|
||||
assert ids == ["ChatInput-CIGht"], "Invalid ids in first event. Full event stream:\n" + "\n".join(lines)
|
||||
|
||||
to_run = parsed["data"]["to_run"]
|
||||
to_run.sort()
|
||||
assert to_run == ["ChatInput-CIGht", "ChatOutput-QA7ej", "Memory-amN4Z", "Prompt-iWbCC"], (
|
||||
"Invalid to_run list in first event. Full event stream:\n" + "\n".join(lines)
|
||||
)
|
||||
elif count > 0 and count < 5:
|
||||
# Next events should be end_vertex events
|
||||
assert parsed["event"] == "end_vertex", (
|
||||
f"Invalid event at position {count}. Expected 'end_vertex'. Full event stream:\n" + "\n".join(lines)
|
||||
)
|
||||
assert parsed["data"]["build_data"] is not None, (
|
||||
f"Missing build_data at position {count}. Full event stream:\n" + "\n".join(lines)
|
||||
)
|
||||
elif count == 5:
|
||||
# Final event should be end
|
||||
assert parsed["event"] == "end", "Invalid final event. Expected 'end'. Full event stream:\n" + "\n".join(
|
||||
lines
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Unexpected event at position {count}. Full event stream:\n" + "\n".join(lines))
|
||||
count += 1
|
||||
@ -78,9 +78,8 @@ class TestAgentComponent(ComponentTestBaseWithoutClient):
|
||||
assert all(provider in updated_config["agent_llm"]["options"] for provider in MODEL_PROVIDERS_DICT)
|
||||
assert "Anthropic" in updated_config["agent_llm"]["options"]
|
||||
assert updated_config["agent_llm"]["input_types"] == []
|
||||
assert any("sonnet" in option.lower() for option in updated_config["model_name"]["options"]), (
|
||||
f"Options: {updated_config['model_name']['options']}"
|
||||
)
|
||||
options = updated_config["model_name"]["options"]
|
||||
assert any("sonnet" in option.lower() for option in options), f"Options: {options}"
|
||||
|
||||
# Test updating build config for Custom
|
||||
updated_config = await component.update_build_config(build_config, "Custom", "agent_llm")
|
||||
@ -113,6 +112,7 @@ async def test_agent_component_with_calculator():
|
||||
model_name="gpt-4o",
|
||||
llm_type="OpenAI",
|
||||
temperature=temperature,
|
||||
_session_id=str(uuid4()),
|
||||
)
|
||||
|
||||
response = await agent.message_response()
|
||||
|
||||
@ -19,7 +19,7 @@ class TestURLComponent(ComponentTestBaseWithoutClient):
|
||||
def default_kwargs(self):
|
||||
"""Return the default kwargs for the component."""
|
||||
return {
|
||||
"urls": ["https://example.com"],
|
||||
"urls": ["https://google.com"],
|
||||
"format": "Text",
|
||||
}
|
||||
|
||||
|
||||
@ -21,6 +21,7 @@ class TestBatchRunComponent(ComponentTestBaseWithoutClient):
|
||||
"model": MockLanguageModel(),
|
||||
"df": DataFrame({"text": ["Hello"]}),
|
||||
"column_name": "text",
|
||||
"enable_metadata": True,
|
||||
}
|
||||
|
||||
@pytest.fixture
|
||||
@ -33,7 +34,11 @@ class TestBatchRunComponent(ComponentTestBaseWithoutClient):
|
||||
test_df = DataFrame({"text": ["Hello", "World", "Test"]})
|
||||
|
||||
component = BatchRunComponent(
|
||||
model=MockLanguageModel(), system_message="You are a helpful assistant", df=test_df, column_name="text"
|
||||
model=MockLanguageModel(),
|
||||
system_message="You are a helpful assistant",
|
||||
df=test_df,
|
||||
column_name="text",
|
||||
enable_metadata=True,
|
||||
)
|
||||
|
||||
# Run the batch process
|
||||
@ -43,46 +48,188 @@ class TestBatchRunComponent(ComponentTestBaseWithoutClient):
|
||||
assert isinstance(result, DataFrame)
|
||||
assert "text_input" in result.columns
|
||||
assert "model_response" in result.columns
|
||||
assert "metadata" in result.columns
|
||||
assert len(result) == 3
|
||||
assert all(isinstance(resp, str) for resp in result["model_response"])
|
||||
# Convert DataFrame to list of dicts for easier testing
|
||||
result_dicts = result.to_dict("records")
|
||||
# Verify metadata
|
||||
assert all(row["metadata"]["has_system_message"] for row in result_dicts)
|
||||
assert all(row["metadata"]["processing_status"] == "success" for row in result_dicts)
|
||||
|
||||
async def test_batch_run_without_system_message(self):
|
||||
async def test_batch_run_without_metadata(self):
|
||||
test_df = DataFrame({"text": ["Hello", "World"]})
|
||||
|
||||
component = BatchRunComponent(model=MockLanguageModel(), df=test_df, column_name="text")
|
||||
component = BatchRunComponent(
|
||||
model=MockLanguageModel(),
|
||||
df=test_df,
|
||||
column_name="text",
|
||||
enable_metadata=False,
|
||||
)
|
||||
|
||||
result = await component.run_batch()
|
||||
|
||||
assert isinstance(result, DataFrame)
|
||||
assert len(result) == 2
|
||||
assert "metadata" not in result.columns
|
||||
assert all(isinstance(resp, str) for resp in result["model_response"])
|
||||
|
||||
async def test_batch_run_error_with_metadata(self):
|
||||
component = BatchRunComponent(
|
||||
model=MockLanguageModel(),
|
||||
df="not_a_dataframe", # This will cause a TypeError
|
||||
column_name="text",
|
||||
enable_metadata=True,
|
||||
)
|
||||
|
||||
with pytest.raises(TypeError, match=re.escape("Expected DataFrame input, got <class 'str'>")):
|
||||
await component.run_batch()
|
||||
|
||||
async def test_batch_run_error_without_metadata(self):
|
||||
component = BatchRunComponent(
|
||||
model=MockLanguageModel(),
|
||||
df="not_a_dataframe", # This will cause a TypeError
|
||||
column_name="text",
|
||||
enable_metadata=False,
|
||||
)
|
||||
|
||||
with pytest.raises(TypeError, match=re.escape("Expected DataFrame input, got <class 'str'>")):
|
||||
await component.run_batch()
|
||||
|
||||
async def test_operational_error_with_metadata(self):
|
||||
# Create a mock model that raises an AttributeError during processing
|
||||
class ErrorModel:
|
||||
def with_config(self, *_, **__):
|
||||
return self
|
||||
|
||||
async def abatch(self, *_):
|
||||
msg = "Mock error during batch processing"
|
||||
raise AttributeError(msg)
|
||||
|
||||
component = BatchRunComponent(
|
||||
model=ErrorModel(),
|
||||
df=DataFrame({"text": ["test1", "test2"]}),
|
||||
column_name="text",
|
||||
enable_metadata=True,
|
||||
)
|
||||
|
||||
result = await component.run_batch()
|
||||
assert isinstance(result, DataFrame)
|
||||
assert len(result) == 1 # Component returns a single error row
|
||||
error_row = result.iloc[0]
|
||||
# Verify error metadata
|
||||
assert error_row["metadata"]["processing_status"] == "failed"
|
||||
assert "Mock error during batch processing" in error_row["metadata"]["error"]
|
||||
# Verify base row structure
|
||||
assert error_row["text_input"] == ""
|
||||
assert error_row["model_response"] == ""
|
||||
assert error_row["batch_index"] == -1
|
||||
|
||||
async def test_operational_error_without_metadata(self):
|
||||
# Create a mock model that raises an AttributeError during processing
|
||||
class ErrorModel:
|
||||
def with_config(self, *_, **__):
|
||||
return self
|
||||
|
||||
async def abatch(self, *_):
|
||||
msg = "Mock error during batch processing"
|
||||
raise AttributeError(msg)
|
||||
|
||||
component = BatchRunComponent(
|
||||
model=ErrorModel(),
|
||||
df=DataFrame({"text": ["test1", "test2"]}),
|
||||
column_name="text",
|
||||
enable_metadata=False,
|
||||
)
|
||||
|
||||
result = await component.run_batch()
|
||||
assert isinstance(result, DataFrame)
|
||||
assert len(result) == 1 # Component returns a single error row
|
||||
error_row = result.iloc[0]
|
||||
# Verify no metadata
|
||||
assert "metadata" not in error_row
|
||||
# Verify base row structure
|
||||
assert error_row["text_input"] == ""
|
||||
assert error_row["model_response"] == ""
|
||||
assert error_row["batch_index"] == -1
|
||||
|
||||
def test_create_base_row(self):
|
||||
component = BatchRunComponent()
|
||||
row = component._create_base_row(text_input="test_input", model_response="test_response", batch_index=1)
|
||||
|
||||
assert row == {
|
||||
"text_input": "test_input",
|
||||
"model_response": "test_response",
|
||||
"batch_index": 1,
|
||||
}
|
||||
|
||||
def test_add_metadata_success(self):
|
||||
component = BatchRunComponent(enable_metadata=True)
|
||||
row = component._create_base_row(text_input="test_input", model_response="test_response", batch_index=1)
|
||||
component._add_metadata(row, success=True, system_msg="test_system")
|
||||
|
||||
assert "metadata" in row
|
||||
assert row["metadata"]["has_system_message"] is True
|
||||
assert row["metadata"]["processing_status"] == "success"
|
||||
assert row["metadata"]["input_length"] == len("test_input")
|
||||
assert row["metadata"]["response_length"] == len("test_response")
|
||||
|
||||
def test_add_metadata_failure(self):
|
||||
component = BatchRunComponent(enable_metadata=True)
|
||||
row = component._create_base_row()
|
||||
component._add_metadata(row, success=False, error="test_error")
|
||||
|
||||
assert "metadata" in row
|
||||
assert row["metadata"]["processing_status"] == "failed"
|
||||
assert row["metadata"]["error"] == "test_error"
|
||||
|
||||
def test_metadata_disabled(self):
|
||||
component = BatchRunComponent(enable_metadata=False)
|
||||
row = component._create_base_row(text_input="test")
|
||||
component._add_metadata(row, success=True)
|
||||
|
||||
assert "metadata" not in row
|
||||
|
||||
async def test_invalid_column_name(self):
|
||||
component = BatchRunComponent(
|
||||
model=MockLanguageModel(), df=DataFrame({"text": ["Hello"]}), column_name="nonexistent_column"
|
||||
model=MockLanguageModel(),
|
||||
df=DataFrame({"text": ["Hello"]}),
|
||||
column_name="nonexistent_column",
|
||||
enable_metadata=True,
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match=re.escape("Column 'nonexistent_column' not found in the DataFrame.")):
|
||||
with pytest.raises(
|
||||
ValueError,
|
||||
match=re.escape("Column 'nonexistent_column' not found in the DataFrame. Available columns: text"),
|
||||
):
|
||||
await component.run_batch()
|
||||
|
||||
async def test_empty_dataframe(self):
|
||||
component = BatchRunComponent(model=MockLanguageModel(), df=DataFrame({"text": []}), column_name="text")
|
||||
component = BatchRunComponent(
|
||||
model=MockLanguageModel(),
|
||||
df=DataFrame({"text": []}),
|
||||
column_name="text",
|
||||
enable_metadata=True,
|
||||
)
|
||||
|
||||
result = await component.run_batch()
|
||||
assert isinstance(result, DataFrame)
|
||||
assert len(result) == 0
|
||||
|
||||
async def test_non_string_column_conversion(self):
|
||||
test_df = DataFrame(
|
||||
{
|
||||
"text": [123, 456, 789] # Numeric values
|
||||
}
|
||||
)
|
||||
test_df = DataFrame({"text": [123, 456, 789]}) # Numeric values
|
||||
|
||||
component = BatchRunComponent(model=MockLanguageModel(), df=test_df, column_name="text")
|
||||
component = BatchRunComponent(
|
||||
model=MockLanguageModel(),
|
||||
df=test_df,
|
||||
column_name="text",
|
||||
enable_metadata=True,
|
||||
)
|
||||
|
||||
result = await component.run_batch()
|
||||
|
||||
assert isinstance(result, DataFrame)
|
||||
assert all(isinstance(text, str) for text in result["text_input"])
|
||||
assert all(str(num) in text for num, text in zip(test_df["text"], result["text_input"], strict=False))
|
||||
result_dicts = result.to_dict("records")
|
||||
assert all(row["metadata"]["processing_status"] == "success" for row in result_dicts)
|
||||
|
||||
@ -1,14 +1,15 @@
|
||||
from uuid import UUID
|
||||
|
||||
import orjson
|
||||
import pytest
|
||||
from httpx import AsyncClient
|
||||
from langflow.components.logic.loop import LoopComponent
|
||||
from langflow.memory import aget_messages
|
||||
from langflow.schema.data import Data
|
||||
from langflow.services.database.models.flow import FlowCreate
|
||||
from orjson import orjson
|
||||
|
||||
from tests.base import ComponentTestBaseWithClient
|
||||
from tests.unit.build_utils import build_flow, get_build_events
|
||||
|
||||
TEXT = (
|
||||
"lorem ipsum dolor sit amet lorem ipsum dolor sit amet lorem ipsum dolor sit amet. "
|
||||
@ -62,15 +63,25 @@ class TestLoopComponentWithAPI(ComponentTestBaseWithClient):
|
||||
assert len(messages[1].text) > 0
|
||||
|
||||
async def test_build_flow_loop(self, client, json_loop_test, logged_in_headers):
|
||||
# TODO: Add a test for the loop where the loop component gets updated even the component in json
|
||||
"""Test building a flow with a loop component."""
|
||||
# Create the flow
|
||||
flow_id = await self._create_flow(client, json_loop_test, logged_in_headers)
|
||||
|
||||
async with client.stream("POST", f"api/v1/build/{flow_id}/flow", json={}, headers=logged_in_headers) as r:
|
||||
async for line in r.aiter_lines():
|
||||
# httpx split by \n, but ndjson sends two \n for each line
|
||||
if line:
|
||||
# Process the line if needed
|
||||
pass
|
||||
# Start the build and get job_id
|
||||
build_response = await build_flow(client, flow_id, logged_in_headers)
|
||||
job_id = build_response["job_id"]
|
||||
assert job_id is not None
|
||||
|
||||
# Get the events stream
|
||||
events_response = await get_build_events(client, job_id, logged_in_headers)
|
||||
assert events_response.status_code == 200
|
||||
|
||||
# Process the events stream
|
||||
async for line in events_response.aiter_lines():
|
||||
if not line: # Skip empty lines
|
||||
continue
|
||||
# Process events if needed
|
||||
# We could add specific assertions here for loop-related events
|
||||
|
||||
await self.check_messages(flow_id)
|
||||
|
||||
|
||||
@ -0,0 +1,127 @@
|
||||
import pytest
|
||||
from langflow.components.processing.data_to_dataframe import DataToDataFrameComponent
|
||||
from langflow.schema import Data, DataFrame
|
||||
|
||||
from tests.base import ComponentTestBaseWithoutClient
|
||||
|
||||
|
||||
class TestDataToDataFrameComponent(ComponentTestBaseWithoutClient):
|
||||
@pytest.fixture
|
||||
def component_class(self):
|
||||
"""Return the component class to test."""
|
||||
return DataToDataFrameComponent
|
||||
|
||||
@pytest.fixture
|
||||
def default_kwargs(self):
|
||||
"""Return the default kwargs for the component."""
|
||||
return {
|
||||
"data_list": [
|
||||
Data(text="Row 1", data={"field1": "value1", "field2": 1}),
|
||||
Data(text="Row 2", data={"field1": "value2", "field2": 2}),
|
||||
]
|
||||
}
|
||||
|
||||
@pytest.fixture
|
||||
def file_names_mapping(self):
|
||||
"""Return the file names mapping for different versions."""
|
||||
# This is a new component, so we return an empty list
|
||||
return []
|
||||
|
||||
def test_basic_setup(self, component_class, default_kwargs):
|
||||
"""Test basic component initialization."""
|
||||
component = component_class()
|
||||
component.set_attributes(default_kwargs)
|
||||
assert component.data_list == default_kwargs["data_list"]
|
||||
|
||||
def test_build_dataframe_basic(self, component_class, default_kwargs):
|
||||
"""Test basic DataFrame construction."""
|
||||
component = component_class()
|
||||
component.set_attributes(default_kwargs)
|
||||
result_df = component.build_dataframe()
|
||||
|
||||
assert isinstance(result_df, DataFrame)
|
||||
assert len(result_df) == 2
|
||||
assert list(result_df.columns) == ["field1", "field2", "text"]
|
||||
assert result_df["text"].tolist() == ["Row 1", "Row 2"]
|
||||
assert result_df["field1"].tolist() == ["value1", "value2"]
|
||||
assert result_df["field2"].tolist() == [1, 2]
|
||||
|
||||
def test_single_data_input(self, component_class):
|
||||
"""Test handling single Data object input."""
|
||||
single_data = Data(text="Single Row", data={"field1": "value"})
|
||||
component = component_class()
|
||||
component.set_attributes({"data_list": single_data})
|
||||
|
||||
result_df = component.build_dataframe()
|
||||
|
||||
assert len(result_df) == 1
|
||||
assert result_df["text"].iloc[0] == "Single Row"
|
||||
assert result_df["field1"].iloc[0] == "value"
|
||||
|
||||
def test_empty_data_list(self, component_class):
|
||||
"""Test behavior with empty data list."""
|
||||
component = component_class()
|
||||
component.set_attributes({"data_list": []})
|
||||
|
||||
result_df = component.build_dataframe()
|
||||
|
||||
assert len(result_df) == 0
|
||||
|
||||
def test_data_without_text(self, component_class):
|
||||
"""Test handling Data objects without text field."""
|
||||
data_without_text = [Data(data={"field1": "value1"}), Data(data={"field1": "value2"})]
|
||||
component = component_class()
|
||||
component.set_attributes({"data_list": data_without_text})
|
||||
|
||||
result_df = component.build_dataframe()
|
||||
|
||||
assert len(result_df) == 2
|
||||
assert "text" not in result_df.columns
|
||||
assert result_df["field1"].tolist() == ["value1", "value2"]
|
||||
|
||||
def test_data_without_data_dict(self, component_class):
|
||||
"""Test handling Data objects without data dictionary."""
|
||||
data_without_dict = [Data(text="Text 1"), Data(text="Text 2")]
|
||||
component = component_class()
|
||||
component.set_attributes({"data_list": data_without_dict})
|
||||
|
||||
result_df = component.build_dataframe()
|
||||
|
||||
assert len(result_df) == 2
|
||||
assert list(result_df.columns) == ["text"]
|
||||
assert result_df["text"].tolist() == ["Text 1", "Text 2"]
|
||||
|
||||
def test_mixed_data_fields(self, component_class):
|
||||
"""Test handling Data objects with different fields."""
|
||||
mixed_data = [
|
||||
Data(text="Row 1", data={"field1": "value1", "field2": 1}),
|
||||
Data(text="Row 2", data={"field1": "value2", "field3": "extra"}),
|
||||
]
|
||||
component = component_class()
|
||||
component.set_attributes({"data_list": mixed_data})
|
||||
|
||||
result_df = component.build_dataframe()
|
||||
|
||||
assert len(result_df) == 2
|
||||
assert set(result_df.columns) == {"field1", "field2", "field3", "text"}
|
||||
assert result_df["field1"].tolist() == ["value1", "value2"]
|
||||
assert result_df["field2"].iloc[1] != result_df["field2"].iloc[1] # Check for NaN using inequality
|
||||
assert result_df["field3"].iloc[0] != result_df["field3"].iloc[0] # Check for NaN using inequality
|
||||
|
||||
def test_invalid_input_type(self, component_class):
|
||||
"""Test error handling for invalid input types."""
|
||||
invalid_data = [{"not": "a Data object"}]
|
||||
component = component_class()
|
||||
component.set_attributes({"data_list": invalid_data})
|
||||
|
||||
with pytest.raises(TypeError) as exc_info:
|
||||
component.build_dataframe()
|
||||
assert "Expected Data objects" in str(exc_info.value)
|
||||
|
||||
def test_status_update(self, component_class, default_kwargs):
|
||||
"""Test that status is properly updated."""
|
||||
component = component_class()
|
||||
component.set_attributes(default_kwargs)
|
||||
result = component.build_dataframe()
|
||||
|
||||
assert component.status is result # Status should be set to the DataFrame
|
||||
@ -0,0 +1,165 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pandas as pd
|
||||
import pytest
|
||||
from langflow.components.processing.save_to_file import SaveToFileComponent
|
||||
from langflow.schema import Data, Message
|
||||
|
||||
from tests.base import ComponentTestBaseWithoutClient
|
||||
|
||||
|
||||
class TestSaveToFileComponent(ComponentTestBaseWithoutClient):
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup_and_teardown(self):
|
||||
"""Setup and teardown for each test."""
|
||||
# Setup
|
||||
test_files = [
|
||||
"./test_output.csv",
|
||||
"./test_output.xlsx",
|
||||
"./test_output.json",
|
||||
"./test_output.md",
|
||||
"./test_output.txt",
|
||||
]
|
||||
# Teardown
|
||||
yield
|
||||
# Delete test files after each test
|
||||
for file_path in test_files:
|
||||
path = Path(file_path)
|
||||
if path.exists():
|
||||
path.unlink()
|
||||
|
||||
@pytest.fixture
|
||||
def component_class(self):
|
||||
"""Return the component class to test."""
|
||||
return SaveToFileComponent
|
||||
|
||||
@pytest.fixture
|
||||
def default_kwargs(self):
|
||||
"""Return the default kwargs for the component."""
|
||||
sample_df = pd.DataFrame([{"col1": 1, "col2": "a"}, {"col1": 2, "col2": "b"}])
|
||||
return {"input_type": "DataFrame", "df": sample_df, "file_format": "csv", "file_path": "./test_output.csv"}
|
||||
|
||||
@pytest.fixture
|
||||
def file_names_mapping(self):
|
||||
"""Return the file names mapping for different versions."""
|
||||
return [] # New component
|
||||
|
||||
def test_basic_setup(self, component_class, default_kwargs):
|
||||
"""Test basic component initialization."""
|
||||
component = component_class()
|
||||
component.set_attributes(default_kwargs)
|
||||
assert component.input_type == "DataFrame"
|
||||
assert component.file_format == "csv"
|
||||
assert component.file_path == "./test_output.csv"
|
||||
|
||||
def test_update_build_config_dataframe(self, component_class):
|
||||
"""Test build config update for DataFrame input type."""
|
||||
component = component_class()
|
||||
build_config = {
|
||||
"df": {"show": False},
|
||||
"data": {"show": False},
|
||||
"message": {"show": False},
|
||||
"file_format": {"options": []},
|
||||
}
|
||||
|
||||
updated_config = component.update_build_config(build_config, "DataFrame", "input_type")
|
||||
|
||||
assert updated_config["df"]["show"] is True
|
||||
assert updated_config["data"]["show"] is False
|
||||
assert updated_config["message"]["show"] is False
|
||||
assert set(updated_config["file_format"]["options"]) == set(component.DATA_FORMAT_CHOICES)
|
||||
|
||||
def test_save_message(self, component_class):
|
||||
"""Test saving Message to different formats."""
|
||||
test_cases = [
|
||||
("txt", "Test message"),
|
||||
("json", json.dumps({"message": "Test message"}, indent=2)),
|
||||
("markdown", "**Message:**\n\nTest message"),
|
||||
]
|
||||
|
||||
for fmt, expected_content in test_cases:
|
||||
mock_file = MagicMock()
|
||||
mock_parent = MagicMock()
|
||||
mock_parent.exists.return_value = True
|
||||
mock_file.parent = mock_parent
|
||||
mock_file.expanduser.return_value = mock_file
|
||||
|
||||
# Mock Path at the module level where it's imported
|
||||
with patch("langflow.components.processing.save_to_file.Path") as mock_path:
|
||||
mock_path.return_value = mock_file
|
||||
|
||||
component = component_class()
|
||||
component.set_attributes(
|
||||
{
|
||||
"input_type": "Message",
|
||||
"message": Message(text="Test message"),
|
||||
"file_format": fmt,
|
||||
"file_path": f"./test_output.{fmt}",
|
||||
}
|
||||
)
|
||||
|
||||
result = component.save_to_file()
|
||||
|
||||
mock_file.write_text.assert_called_once_with(expected_content, encoding="utf-8")
|
||||
assert "saved successfully" in result
|
||||
|
||||
def test_save_data(self, component_class):
|
||||
"""Test saving Data object to JSON."""
|
||||
test_data = {"col1": ["value1"], "col2": ["value2"]}
|
||||
|
||||
mock_file = MagicMock()
|
||||
mock_parent = MagicMock()
|
||||
mock_parent.exists.return_value = True
|
||||
mock_file.parent = mock_parent
|
||||
mock_file.expanduser.return_value = mock_file
|
||||
|
||||
with patch("langflow.components.processing.save_to_file.Path") as mock_path:
|
||||
mock_path.return_value = mock_file
|
||||
|
||||
component = component_class()
|
||||
component.set_attributes(
|
||||
{
|
||||
"input_type": "Data",
|
||||
"data": Data(data=test_data),
|
||||
"file_format": "json",
|
||||
"file_path": "./test_output.json",
|
||||
}
|
||||
)
|
||||
|
||||
result = component.save_to_file()
|
||||
|
||||
expected_json = json.dumps(test_data, indent=2)
|
||||
mock_file.write_text.assert_called_once_with(expected_json, encoding="utf-8")
|
||||
assert "saved successfully" in result
|
||||
|
||||
def test_directory_creation(self, component_class, default_kwargs):
|
||||
"""Test directory creation if it doesn't exist."""
|
||||
mock_file = MagicMock()
|
||||
mock_parent = MagicMock()
|
||||
mock_parent.exists.return_value = False
|
||||
mock_file.parent = mock_parent
|
||||
mock_file.expanduser.return_value = mock_file
|
||||
|
||||
with patch("langflow.components.processing.save_to_file.Path") as mock_path:
|
||||
mock_path.return_value = mock_file
|
||||
with patch.object(pd.DataFrame, "to_csv") as mock_to_csv:
|
||||
component = component_class()
|
||||
component.set_attributes(default_kwargs)
|
||||
|
||||
result = component.save_to_file()
|
||||
|
||||
mock_parent.mkdir.assert_called_once_with(parents=True, exist_ok=True)
|
||||
assert mock_to_csv.called
|
||||
assert "saved successfully" in result
|
||||
|
||||
def test_invalid_input_type(self, default_kwargs):
|
||||
"""Test handling of invalid input type."""
|
||||
component = SaveToFileComponent()
|
||||
invalid_kwargs = default_kwargs.copy() # Create a copy to modify
|
||||
invalid_kwargs["input_type"] = "InvalidType"
|
||||
component.set_attributes(invalid_kwargs)
|
||||
|
||||
with pytest.raises(ValueError, match="Unsupported input type"):
|
||||
component.save_to_file()
|
||||
@ -1,110 +0,0 @@
|
||||
import json
|
||||
|
||||
import pytest
|
||||
from langflow.services.database.utils import truncate_json
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def small_json():
|
||||
return [
|
||||
{"name": "Cole Ramos", "email": "egestas.fusce.aliquet@google.couk"},
|
||||
{"name": "Chancellor Torres", "email": "lorem.eu@hotmail.com"},
|
||||
{"name": "Deanna Lyons", "email": "neque.venenatis.lacus@outlook.couk"},
|
||||
{"name": "Ruby O'connor", "email": "lectus.justo.eu@hotmail.couk"},
|
||||
{"name": "Iona Dorsey", "email": "rutrum@yahoo.org"},
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def large_json():
|
||||
return [
|
||||
{
|
||||
"name": "Nash Briggs",
|
||||
"phone": "1-827-252-5669",
|
||||
"email": "magna.ut@icloud.edu",
|
||||
"address": "847-2983 Vel Rd.",
|
||||
"list": 5,
|
||||
"country": "South Korea",
|
||||
"region": "Gilgit Baltistan",
|
||||
"postalZip": "6088-8521",
|
||||
"text": "ipsum. Curabitur consequat, lectus sit amet luctus vulputate, nisi sem",
|
||||
"alphanumeric": "OJG47QKX4DO",
|
||||
"currency": "$46.88",
|
||||
"numberrange": 6,
|
||||
},
|
||||
{
|
||||
"name": "Keefe Cooley",
|
||||
"phone": "(164) 954-5395",
|
||||
"email": "congue.turpis.in@protonmail.ca",
|
||||
"address": "Ap #674-3382 Egestas. St.",
|
||||
"list": 3,
|
||||
"country": "Spain",
|
||||
"region": "Antioquia",
|
||||
"postalZip": "42452",
|
||||
"text": "nisl. Nulla eu neque pellentesque massa lobortis ultrices. Vivamus rhoncus.",
|
||||
"alphanumeric": "FIE81ZDK2RI",
|
||||
"currency": "$37.74",
|
||||
"numberrange": 3,
|
||||
},
|
||||
{
|
||||
"name": "Randall Booth",
|
||||
"phone": "(762) 778-9833",
|
||||
"email": "a@icloud.edu",
|
||||
"address": "Ap #116-8418 Nec Ave",
|
||||
"list": 9,
|
||||
"country": "Norway",
|
||||
"region": "Prince Edward Island",
|
||||
"postalZip": "39155",
|
||||
"text": "tempor arcu. Vestibulum ut eros non enim commodo hendrerit. Donec",
|
||||
"alphanumeric": "GMF33SGB4XD",
|
||||
"currency": "$87.24",
|
||||
"numberrange": 0,
|
||||
},
|
||||
{
|
||||
"name": "Aurora Mooney",
|
||||
"phone": "(626) 435-3885",
|
||||
"email": "morbi.sit.amet@icloud.org",
|
||||
"address": "837-8038 Duis Rd.",
|
||||
"list": 15,
|
||||
"country": "United States",
|
||||
"region": "West Sulawesi",
|
||||
"postalZip": "84466-29328",
|
||||
"text": "metus eu erat semper rutrum. Fusce dolor quam, elementum at,",
|
||||
"alphanumeric": "CVK31QJA8GZ",
|
||||
"currency": "$85.97",
|
||||
"numberrange": 1,
|
||||
},
|
||||
{
|
||||
"name": "Irma Snider",
|
||||
"phone": "1-682-186-4584",
|
||||
"email": "senectus.et@hotmail.org",
|
||||
"address": "718-8593 Mauris. Avenue",
|
||||
"list": 13,
|
||||
"country": "Italy",
|
||||
"region": "East Region",
|
||||
"postalZip": "47178",
|
||||
"text": "Cras convallis convallis dolor. Quisque tincidunt pede ac urna. Ut",
|
||||
"alphanumeric": "KXR03TWX8QA",
|
||||
"currency": "$65.54",
|
||||
"numberrange": 3,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def test_truncate_json__small_case(small_json):
|
||||
max_size = 400
|
||||
|
||||
result = truncate_json(small_json, max_size=max_size)
|
||||
|
||||
assert len(str(small_json)) < max_size, "small_json must be smaller than max_size"
|
||||
assert result == small_json, "small_json should not be truncated"
|
||||
|
||||
|
||||
def test_truncate_json__large_case(large_json):
|
||||
max_size = 1000
|
||||
|
||||
result = truncate_json(large_json, max_size=max_size)
|
||||
|
||||
assert len(str(large_json)) > max_size, "large_json must be larger than max_size"
|
||||
assert len(str(result)) < len(str(large_json)), "result must be smaller than large_json"
|
||||
assert json.dumps(result), "result must be a valid JSON object"
|
||||
@ -1,42 +1,63 @@
|
||||
import json
|
||||
import asyncio
|
||||
import uuid
|
||||
from uuid import UUID
|
||||
|
||||
import pytest
|
||||
from httpx import codes
|
||||
from langflow.memory import aget_messages
|
||||
from langflow.services.database.models.flow import FlowCreate, FlowUpdate
|
||||
from orjson import orjson
|
||||
from langflow.services.database.models.flow import FlowUpdate
|
||||
|
||||
from tests.unit.build_utils import build_flow, consume_and_assert_stream, create_flow, get_build_events
|
||||
|
||||
|
||||
@pytest.mark.benchmark
|
||||
async def test_build_flow(client, json_memory_chatbot_no_llm, logged_in_headers):
|
||||
flow_id = await _create_flow(client, json_memory_chatbot_no_llm, logged_in_headers)
|
||||
"""Test the build flow endpoint with the new two-step process."""
|
||||
# First create the flow
|
||||
flow_id = await create_flow(client, json_memory_chatbot_no_llm, logged_in_headers)
|
||||
|
||||
async with client.stream("POST", f"api/v1/build/{flow_id}/flow", json={}, headers=logged_in_headers) as r:
|
||||
await consume_and_assert_stream(r)
|
||||
# Start the build and get job_id
|
||||
build_response = await build_flow(client, flow_id, logged_in_headers)
|
||||
job_id = build_response["job_id"]
|
||||
assert job_id is not None
|
||||
|
||||
await check_messages(flow_id)
|
||||
# Get the events stream
|
||||
events_response = await get_build_events(client, job_id, logged_in_headers)
|
||||
assert events_response.status_code == codes.OK
|
||||
|
||||
# Consume and verify the events
|
||||
await consume_and_assert_stream(events_response, job_id)
|
||||
|
||||
|
||||
@pytest.mark.benchmark
|
||||
async def test_build_flow_from_request_data(client, json_memory_chatbot_no_llm, logged_in_headers):
|
||||
flow_id = await _create_flow(client, json_memory_chatbot_no_llm, logged_in_headers)
|
||||
response = await client.get("api/v1/flows/" + str(flow_id), headers=logged_in_headers)
|
||||
"""Test building a flow from request data."""
|
||||
flow_id = await create_flow(client, json_memory_chatbot_no_llm, logged_in_headers)
|
||||
response = await client.get(f"api/v1/flows/{flow_id}", headers=logged_in_headers)
|
||||
flow_data = response.json()
|
||||
|
||||
async with client.stream(
|
||||
"POST", f"api/v1/build/{flow_id}/flow", json={"data": flow_data["data"]}, headers=logged_in_headers
|
||||
) as r:
|
||||
await consume_and_assert_stream(r)
|
||||
# Start the build and get job_id
|
||||
build_response = await build_flow(client, flow_id, logged_in_headers, json={"data": flow_data["data"]})
|
||||
job_id = build_response["job_id"]
|
||||
|
||||
# Get the events stream
|
||||
events_response = await get_build_events(client, job_id, logged_in_headers)
|
||||
assert events_response.status_code == codes.OK
|
||||
|
||||
# Consume and verify the events
|
||||
await consume_and_assert_stream(events_response, job_id)
|
||||
await check_messages(flow_id)
|
||||
|
||||
|
||||
async def test_build_flow_with_frozen_path(client, json_memory_chatbot_no_llm, logged_in_headers):
|
||||
flow_id = await _create_flow(client, json_memory_chatbot_no_llm, logged_in_headers)
|
||||
"""Test building a flow with a frozen path."""
|
||||
flow_id = await create_flow(client, json_memory_chatbot_no_llm, logged_in_headers)
|
||||
|
||||
response = await client.get("api/v1/flows/" + str(flow_id), headers=logged_in_headers)
|
||||
response = await client.get(f"api/v1/flows/{flow_id}", headers=logged_in_headers)
|
||||
flow_data = response.json()
|
||||
flow_data["data"]["nodes"][0]["data"]["node"]["frozen"] = True
|
||||
|
||||
# Update the flow with frozen path
|
||||
response = await client.patch(
|
||||
f"api/v1/flows/{flow_id}",
|
||||
json=FlowUpdate(name="Flow", description="description", data=flow_data["data"]).model_dump(),
|
||||
@ -44,151 +65,131 @@ async def test_build_flow_with_frozen_path(client, json_memory_chatbot_no_llm, l
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
async with client.stream("POST", f"api/v1/build/{flow_id}/flow", json={}, headers=logged_in_headers) as r:
|
||||
await consume_and_assert_stream(r)
|
||||
# Start the build and get job_id
|
||||
build_response = await build_flow(client, flow_id, logged_in_headers)
|
||||
job_id = build_response["job_id"]
|
||||
|
||||
# Get the events stream
|
||||
events_response = await get_build_events(client, job_id, logged_in_headers)
|
||||
assert events_response.status_code == codes.OK
|
||||
|
||||
# Consume and verify the events
|
||||
await consume_and_assert_stream(events_response, job_id)
|
||||
await check_messages(flow_id)
|
||||
|
||||
|
||||
async def check_messages(flow_id):
|
||||
messages = await aget_messages(flow_id=UUID(flow_id), order="ASC")
|
||||
if isinstance(flow_id, str):
|
||||
flow_id = UUID(flow_id)
|
||||
messages = await aget_messages(flow_id=flow_id, order="ASC")
|
||||
flow_id_str = str(flow_id)
|
||||
assert len(messages) == 2
|
||||
assert messages[0].session_id == flow_id
|
||||
assert messages[0].session_id == flow_id_str
|
||||
assert messages[0].sender == "User"
|
||||
assert messages[0].sender_name == "User"
|
||||
assert messages[0].text == ""
|
||||
assert messages[1].session_id == flow_id
|
||||
assert messages[1].session_id == flow_id_str
|
||||
assert messages[1].sender == "Machine"
|
||||
assert messages[1].sender_name == "AI"
|
||||
|
||||
|
||||
async def consume_and_assert_stream(r):
|
||||
count = 0
|
||||
async for line in r.aiter_lines():
|
||||
# httpx split by \n, but ndjson sends two \n for each line
|
||||
if not line:
|
||||
continue
|
||||
parsed = json.loads(line)
|
||||
if count == 0:
|
||||
assert parsed["event"] == "vertices_sorted"
|
||||
ids = parsed["data"]["ids"]
|
||||
ids.sort()
|
||||
assert ids == ["ChatInput-CIGht"]
|
||||
|
||||
to_run = parsed["data"]["to_run"]
|
||||
to_run.sort()
|
||||
assert to_run == ["ChatInput-CIGht", "ChatOutput-QA7ej", "Memory-amN4Z", "Prompt-iWbCC"]
|
||||
elif count > 0 and count < 5:
|
||||
assert parsed["event"] == "end_vertex"
|
||||
assert parsed["data"]["build_data"] is not None
|
||||
elif count == 5:
|
||||
assert parsed["event"] == "end"
|
||||
else:
|
||||
msg = f"Unexpected line: {line}"
|
||||
raise ValueError(msg)
|
||||
count += 1
|
||||
@pytest.mark.benchmark
|
||||
async def test_build_flow_invalid_job_id(client, logged_in_headers):
|
||||
"""Test getting events for an invalid job ID."""
|
||||
invalid_job_id = str(uuid.uuid4())
|
||||
response = await get_build_events(client, invalid_job_id, logged_in_headers)
|
||||
assert response.status_code == codes.NOT_FOUND
|
||||
assert "No queue found for job_id" in response.json()["detail"]
|
||||
|
||||
|
||||
async def _create_flow(client, json_memory_chatbot_no_llm, logged_in_headers):
|
||||
vector_store = orjson.loads(json_memory_chatbot_no_llm)
|
||||
data = vector_store["data"]
|
||||
vector_store = FlowCreate(name="Flow", description="description", data=data, endpoint_name="f")
|
||||
response = await client.post("api/v1/flows/", json=vector_store.model_dump(), headers=logged_in_headers)
|
||||
response.raise_for_status()
|
||||
return response.json()["id"]
|
||||
@pytest.mark.benchmark
|
||||
async def test_build_flow_invalid_flow_id(client, logged_in_headers):
|
||||
"""Test starting a build with an invalid flow ID."""
|
||||
invalid_flow_id = uuid.uuid4()
|
||||
response = await client.post(f"api/v1/build/{invalid_flow_id}/flow", json={}, headers=logged_in_headers)
|
||||
assert response.status_code == codes.NOT_FOUND
|
||||
|
||||
|
||||
# TODO: Fix this test
|
||||
# async def test_multiple_runs_with_no_payload_generate_max_vertex_builds(
|
||||
# client, json_memory_chatbot_no_llm, logged_in_headers
|
||||
# ):
|
||||
# """Test that multiple builds of a flow generate the correct number of vertex builds."""
|
||||
# # Create the initial flow
|
||||
# flow_id = await _create_flow(client, json_memory_chatbot_no_llm, logged_in_headers)
|
||||
@pytest.mark.benchmark
|
||||
async def test_build_flow_start_only(client, json_memory_chatbot_no_llm, logged_in_headers):
|
||||
"""Test only the build flow start endpoint."""
|
||||
# First create the flow
|
||||
flow_id = await create_flow(client, json_memory_chatbot_no_llm, logged_in_headers)
|
||||
|
||||
# # Get the flow data to count nodes before making requests
|
||||
# response = await client.get(f"api/v1/flows/{flow_id}", headers=logged_in_headers)
|
||||
# flow_data = response.json()
|
||||
# num_nodes = len(flow_data["data"]["nodes"])
|
||||
# max_vertex_builds = get_settings_service().settings.max_vertex_builds_per_vertex
|
||||
# Start the build and get job_id
|
||||
build_response = await build_flow(client, flow_id, logged_in_headers)
|
||||
|
||||
# logger.debug(f"Starting test with {num_nodes} nodes, max_vertex_builds={max_vertex_builds}")
|
||||
# Assert response structure
|
||||
assert "job_id" in build_response
|
||||
assert isinstance(build_response["job_id"], str)
|
||||
# Verify it's a valid UUID
|
||||
assert uuid.UUID(build_response["job_id"])
|
||||
|
||||
# # Make multiple build requests - ensure we exceed max_vertex_builds significantly
|
||||
# num_requests = max_vertex_builds * 3 # Triple the max to ensure rotation
|
||||
# for i in range(num_requests):
|
||||
# # Generate a random session ID for each request
|
||||
# session_id = session_id_generator()
|
||||
# payload = {"inputs": {"session": session_id, "type": "chat", "input_value": f"Test message {i + 1}"}}
|
||||
|
||||
# async with client.stream("POST", f"api/v1/build/{flow_id}/flow",
|
||||
# json=payload, headers=logged_in_headers) as r:
|
||||
# await consume_and_assert_stream(r)
|
||||
@pytest.mark.benchmark
|
||||
async def test_build_flow_start_with_inputs(client, json_memory_chatbot_no_llm, logged_in_headers):
|
||||
"""Test the build flow start endpoint with input data."""
|
||||
flow_id = await create_flow(client, json_memory_chatbot_no_llm, logged_in_headers)
|
||||
|
||||
# # Add a small delay between requests to ensure proper ordering
|
||||
# await asyncio.sleep(0.1)
|
||||
# Start build with some input data
|
||||
test_inputs = {"inputs": {"session": "test_session", "input_value": "test message"}}
|
||||
|
||||
# # Track builds after each request
|
||||
# async with session_scope() as session:
|
||||
# builds = await get_vertex_builds_by_flow_id(db=session, flow_id=flow_id)
|
||||
# by_vertex = {}
|
||||
# for build in builds:
|
||||
# build_dict = build.model_dump()
|
||||
# vertex_id = build_dict.get("id")
|
||||
# by_vertex.setdefault(vertex_id, []).append(build_dict)
|
||||
build_response = await build_flow(client, flow_id, logged_in_headers, json=test_inputs)
|
||||
|
||||
# # Log state of each vertex with more details
|
||||
# for vertex_id, vertex_builds in by_vertex.items():
|
||||
# vertex_builds.sort(key=lambda x: x.get("timestamp"))
|
||||
# logger.debug(
|
||||
# f"Request {i + 1} (session={session_id}) - Vertex {vertex_id}: {len(vertex_builds)} builds "
|
||||
# f"(max allowed: {max_vertex_builds}), "
|
||||
# f"build_ids: {[b.get('build_id') for b in vertex_builds]}"
|
||||
# )
|
||||
assert "job_id" in build_response
|
||||
assert isinstance(build_response["job_id"], str)
|
||||
assert uuid.UUID(build_response["job_id"])
|
||||
|
||||
# # Wait a bit before final verification to ensure all DB operations complete
|
||||
# await asyncio.sleep(0.5)
|
||||
|
||||
# # Final verification with detailed logging
|
||||
# async with session_scope() as session:
|
||||
# vertex_builds = await get_vertex_builds_by_flow_id(db=session, flow_id=flow_id)
|
||||
# assert len(vertex_builds) > 0, "No vertex builds found"
|
||||
@pytest.mark.benchmark
|
||||
async def test_build_flow_polling(client, json_memory_chatbot_no_llm, logged_in_headers):
|
||||
"""Test the build flow endpoint with polling (non-streaming)."""
|
||||
# First create the flow
|
||||
flow_id = await create_flow(client, json_memory_chatbot_no_llm, logged_in_headers)
|
||||
|
||||
# builds_by_vertex = {}
|
||||
# for build in vertex_builds:
|
||||
# build_dict = build.model_dump()
|
||||
# vertex_id = build_dict.get("id")
|
||||
# builds_by_vertex.setdefault(vertex_id, []).append(build_dict)
|
||||
# Start the build and get job_id
|
||||
build_response = await build_flow(client, flow_id, logged_in_headers)
|
||||
job_id = build_response["job_id"]
|
||||
assert job_id is not None
|
||||
|
||||
# # Log detailed final state
|
||||
# logger.debug(f"\nFinal state after {num_requests} requests:")
|
||||
# for vertex_id, builds in builds_by_vertex.items():
|
||||
# builds.sort(key=lambda x: x.get("timestamp"))
|
||||
# logger.debug(
|
||||
# f"Vertex {vertex_id}: {len(builds)} builds "
|
||||
# f"(oldest: {builds[0].get('timestamp')}, "
|
||||
# f"newest: {builds[-1].get('timestamp')}), "
|
||||
# f"build_ids: {[b.get('build_id') for b in builds]}"
|
||||
# )
|
||||
# Create a response object that mimics a streaming response but uses polling
|
||||
class PollingResponse:
|
||||
def __init__(self, client, job_id, headers):
|
||||
self.client = client
|
||||
self.job_id = job_id
|
||||
self.headers = headers
|
||||
self.status_code = codes.OK
|
||||
|
||||
# # Log individual build details for debugging
|
||||
# for build in builds:
|
||||
# logger.debug(
|
||||
# f" - Build {build.get('build_id')}: timestamp={build.get('timestamp')}, "
|
||||
# f"valid={build.get('valid')}"
|
||||
# )
|
||||
async def aiter_lines(self):
|
||||
try:
|
||||
sleeps = 0
|
||||
max_sleeps = 100
|
||||
while True:
|
||||
response = await self.client.get(
|
||||
f"api/v1/build/{self.job_id}/events?stream=false", headers=self.headers
|
||||
)
|
||||
assert response.status_code == codes.OK
|
||||
data = response.json()
|
||||
|
||||
# # Verify each vertex has correct number of builds
|
||||
# for vertex_id, vertex_builds_list in builds_by_vertex.items():
|
||||
# assert len(vertex_builds_list) == max_vertex_builds, (
|
||||
# f"Vertex {vertex_id} has {len(vertex_builds_list)} builds, expected {max_vertex_builds}"
|
||||
# )
|
||||
if data["event"] is None:
|
||||
# No event available, add delay to prevent tight polling
|
||||
await asyncio.sleep(0.1)
|
||||
sleeps += 1
|
||||
continue
|
||||
|
||||
# # Verify total number of builds
|
||||
# total_builds = len(vertex_builds)
|
||||
# expected_total = max_vertex_builds * num_nodes
|
||||
# assert total_builds == expected_total, (
|
||||
# f"Total builds ({total_builds}) doesn't match expected "
|
||||
# f"({max_vertex_builds} builds/vertex * {num_nodes} nodes = {expected_total})"
|
||||
# )
|
||||
# assert all(vertex_build.get("valid") for vertex_build in vertex_builds)
|
||||
yield data["event"]
|
||||
|
||||
# If this was the end event, stop polling
|
||||
if '"end"' in data["event"]:
|
||||
break
|
||||
if sleeps > max_sleeps:
|
||||
msg = "Build event polling timed out."
|
||||
raise TimeoutError(msg)
|
||||
except asyncio.TimeoutError as e:
|
||||
msg = "Build event polling timed out."
|
||||
raise TimeoutError(msg) from e
|
||||
|
||||
polling_response = PollingResponse(client, job_id, logged_in_headers)
|
||||
|
||||
# Use the same consume_and_assert_stream function to verify the events
|
||||
await consume_and_assert_stream(polling_response, job_id)
|
||||
|
||||
9
src/frontend/package-lock.json
generated
9
src/frontend/package-lock.json
generated
@ -46,7 +46,7 @@
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.0.0",
|
||||
"dompurify": "^3.1.5",
|
||||
"dompurify": "^3.2.4",
|
||||
"dotenv": "^16.4.5",
|
||||
"elkjs": "^0.9.3",
|
||||
"emoji-regex": "^10.3.0",
|
||||
@ -7348,9 +7348,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/dompurify": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.3.tgz",
|
||||
"integrity": "sha512-U1U5Hzc2MO0oW3DF+G9qYN0aT7atAou4AgI0XjWz061nyBPbdxkfdhfy5uMgGn6+oLFCfn44ZGbdDqCzVmlOWA==",
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.4.tgz",
|
||||
"integrity": "sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn+Ms/mg==",
|
||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||
"optionalDependencies": {
|
||||
"@types/trusted-types": "^2.0.7"
|
||||
}
|
||||
|
||||
@ -41,7 +41,7 @@
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.0.0",
|
||||
"dompurify": "^3.1.5",
|
||||
"dompurify": "^3.2.4",
|
||||
"dotenv": "^16.4.5",
|
||||
"elkjs": "^0.9.3",
|
||||
"emoji-regex": "^10.3.0",
|
||||
|
||||
@ -66,8 +66,6 @@ body {
|
||||
}
|
||||
|
||||
.jv-indent {
|
||||
overflow-y: auto !important;
|
||||
max-height: 310px !important;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
|
||||
@ -21,8 +21,8 @@ export function DefaultEdge({
|
||||
const targetHandleObject = scapeJSONParse(targetHandleId!);
|
||||
|
||||
const sourceXNew =
|
||||
(sourceNode?.position.x ?? 0) + (sourceNode?.measured?.width ?? 0);
|
||||
const targetXNew = targetNode?.position.x ?? 0;
|
||||
(sourceNode?.position.x ?? 0) + (sourceNode?.measured?.width ?? 0) + 7;
|
||||
const targetXNew = (targetNode?.position.x ?? 0) - 7;
|
||||
|
||||
const distance = 200 + 0.1 * ((sourceXNew - targetXNew) / 2);
|
||||
|
||||
@ -42,15 +42,18 @@ export function DefaultEdge({
|
||||
200 * (1 - zeroOnNegative) +
|
||||
0.3 * Math.abs(sourceY - targetY) * zeroOnNegative;
|
||||
|
||||
const edgePathLoop = `M ${sourceXNew} ${sourceY} C ${sourceXNew + distance} ${sourceY + sourceDistanceY}, ${targetXNew - distance} ${targetY + distanceY}, ${targetXNew} ${targetY}`;
|
||||
const targetYNew = targetY + 1;
|
||||
const sourceYNew = sourceY + 1;
|
||||
|
||||
const edgePathLoop = `M ${sourceXNew} ${sourceYNew} C ${sourceXNew + distance} ${sourceYNew + sourceDistanceY}, ${targetXNew - distance} ${targetYNew + distanceY}, ${targetXNew} ${targetYNew}`;
|
||||
|
||||
const [edgePath] = getBezierPath({
|
||||
sourceX: sourceXNew,
|
||||
sourceY,
|
||||
sourceY: sourceYNew,
|
||||
sourcePosition: Position.Right,
|
||||
targetPosition: Position.Left,
|
||||
targetX: targetXNew,
|
||||
targetY,
|
||||
targetY: targetYNew,
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user