Merge branch 'dev' into update_components
This commit is contained in:
commit
724f325e08
106 changed files with 2402 additions and 1322 deletions
|
|
@ -276,15 +276,15 @@ async def webhook_run_flow(
|
|||
# get all webhook components in the flow
|
||||
webhook_components = get_all_webhook_components_in_flow(flow.data)
|
||||
tweaks = {}
|
||||
data_dict = await request.json()
|
||||
|
||||
for component in webhook_components:
|
||||
tweaks[component["id"]] = {"data": data.decode() if isinstance(data, bytes) else data}
|
||||
input_request = SimplifiedAPIRequest(
|
||||
input_value=data_dict.get("input_value", ""),
|
||||
input_type=data_dict.get("input_type", "chat"),
|
||||
output_type=data_dict.get("output_type", "chat"),
|
||||
input_value="",
|
||||
input_type="chat",
|
||||
output_type="chat",
|
||||
tweaks=tweaks,
|
||||
session_id=data_dict.get("session_id"),
|
||||
session_id=None,
|
||||
)
|
||||
logger.debug("Starting background task")
|
||||
background_tasks.add_task( # type: ignore
|
||||
|
|
|
|||
|
|
@ -131,8 +131,8 @@ async def list_profile_pictures(storage_service: StorageService = Depends(get_st
|
|||
people = await storage_service.list_files(flow_id=people_path) # type: ignore
|
||||
space = await storage_service.list_files(flow_id=space_path) # type: ignore
|
||||
|
||||
files = [Path("People") / i for i in people]
|
||||
files += [Path("Space") / i for i in space]
|
||||
files = [f"People/{i}" for i in people]
|
||||
files += [f"Space/{i}" for i in space]
|
||||
|
||||
return {"files": files}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
from datetime import datetime, timezone
|
||||
import re
|
||||
from datetime import datetime, timezone
|
||||
from typing import List
|
||||
from uuid import UUID
|
||||
|
||||
|
|
@ -211,8 +211,7 @@ def update_flow(
|
|||
if settings_service.settings.remove_api_keys:
|
||||
flow_data = remove_api_keys(flow_data)
|
||||
for key, value in flow_data.items():
|
||||
if value is not None:
|
||||
setattr(db_flow, key, value)
|
||||
setattr(db_flow, key, value)
|
||||
webhook_component = get_webhook_component_in_flow(db_flow.data)
|
||||
db_flow.webhook = webhook_component is not None
|
||||
db_flow.updated_at = datetime.now(timezone.utc)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from typing import Any
|
||||
from langflow.custom import Component
|
||||
from langflow.inputs.inputs import DictInput, SecretStrInput, MessageTextInput
|
||||
from langflow.inputs.inputs import DictInput, SecretStrInput, MessageTextInput, DropdownInput
|
||||
from langflow.template.field.base import Output
|
||||
|
||||
|
||||
|
|
@ -10,32 +10,77 @@ class AstraVectorize(Component):
|
|||
documentation: str = "https://docs.datastax.com/en/astra-db-serverless/databases/embedding-generation.html"
|
||||
icon = "AstraDB"
|
||||
|
||||
VECTORIZE_PROVIDERS_MAPPING = {
|
||||
"Azure OpenAI": ["azureOpenAI", ["text-embedding-3-small", "text-embedding-3-large", "text-embedding-ada-002"]],
|
||||
"Hugging Face - Dedicated": ["huggingfaceDedicated", ["endpoint-defined-model"]],
|
||||
"Hugging Face - Serverless": [
|
||||
"huggingface",
|
||||
[
|
||||
"sentence-transformers/all-MiniLM-L6-v2",
|
||||
"intfloat/multilingual-e5-large",
|
||||
"intfloat/multilingual-e5-large-instruct",
|
||||
"BAAI/bge-small-en-v1.5",
|
||||
"BAAI/bge-base-en-v1.5",
|
||||
"BAAI/bge-large-en-v1.5",
|
||||
],
|
||||
],
|
||||
"Jina AI": [
|
||||
"jinaAI",
|
||||
[
|
||||
"jina-embeddings-v2-base-en",
|
||||
"jina-embeddings-v2-base-de",
|
||||
"jina-embeddings-v2-base-es",
|
||||
"jina-embeddings-v2-base-code",
|
||||
"jina-embeddings-v2-base-zh",
|
||||
],
|
||||
],
|
||||
"Mistral AI": ["mistral", ["mistral-embed"]],
|
||||
"NVIDIA": ["nvidia", ["NV-Embed-QA"]],
|
||||
"OpenAI": ["openai", ["text-embedding-3-small", "text-embedding-3-large", "text-embedding-ada-002"]],
|
||||
"Upstage": ["upstageAI", ["solar-embedding-1-large"]],
|
||||
"Voyage AI": [
|
||||
"voyageAI",
|
||||
["voyage-large-2-instruct", "voyage-law-2", "voyage-code-2", "voyage-large-2", "voyage-2"],
|
||||
],
|
||||
}
|
||||
VECTORIZE_MODELS_STR = "\n\n".join(
|
||||
[provider + ": " + (", ".join(models[1])) for provider, models in VECTORIZE_PROVIDERS_MAPPING.items()]
|
||||
)
|
||||
|
||||
inputs = [
|
||||
MessageTextInput(
|
||||
DropdownInput(
|
||||
name="provider",
|
||||
display_name="Provider name",
|
||||
info="The embedding provider to use.",
|
||||
options=VECTORIZE_PROVIDERS_MAPPING.keys(),
|
||||
value="",
|
||||
),
|
||||
MessageTextInput(
|
||||
name="model_name",
|
||||
display_name="Model name",
|
||||
info="The embedding model to use.",
|
||||
info=f"The embedding model to use for the selected provider. Each provider has a different set of models "
|
||||
f"available (full list at https://docs.datastax.com/en/astra-db-serverless/databases/embedding-generation.html):\n\n{VECTORIZE_MODELS_STR}",
|
||||
required=True,
|
||||
),
|
||||
MessageTextInput(
|
||||
name="api_key_name",
|
||||
display_name="API Key name",
|
||||
info="The name of the embeddings provider API key stored on Astra. If set, it will override the 'ProviderKey' in the authentication parameters.",
|
||||
),
|
||||
DictInput(
|
||||
name="authentication",
|
||||
display_name="Authentication",
|
||||
info="Authentication parameters. Use the Astra Portal to add the embedding provider integration to your Astra organization.",
|
||||
display_name="Authentication parameters",
|
||||
is_list=True,
|
||||
advanced=True,
|
||||
),
|
||||
SecretStrInput(
|
||||
name="provider_api_key",
|
||||
display_name="Provider API Key",
|
||||
info="An alternative to the Astra Authentication that let you use directly the API key of the provider.",
|
||||
advanced=True,
|
||||
),
|
||||
DictInput(
|
||||
name="model_parameters",
|
||||
display_name="Model parameters",
|
||||
info="Additional model parameters.",
|
||||
advanced=True,
|
||||
is_list=True,
|
||||
),
|
||||
|
|
@ -45,12 +90,17 @@ class AstraVectorize(Component):
|
|||
]
|
||||
|
||||
def build_options(self) -> dict[str, Any]:
|
||||
provider_value = self.VECTORIZE_PROVIDERS_MAPPING[self.provider][0]
|
||||
authentication = {**self.authentication}
|
||||
api_key_name = self.api_key_name
|
||||
if api_key_name:
|
||||
authentication["providerKey"] = api_key_name
|
||||
return {
|
||||
# must match exactly astra CollectionVectorServiceOptions
|
||||
"collection_vector_service_options": {
|
||||
"provider": self.provider,
|
||||
"provider": provider_value,
|
||||
"modelName": self.model_name,
|
||||
"authentication": self.authentication,
|
||||
"authentication": authentication,
|
||||
"parameters": self.model_parameters,
|
||||
},
|
||||
"collection_embedding_api_key": self.provider_api_key,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
import uuid
|
||||
from typing import Optional
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.schema import Data
|
||||
|
||||
|
||||
class FirecrawlCrawlApi(CustomComponent):
|
||||
display_name: str = "FirecrawlCrawlApi"
|
||||
description: str = "Firecrawl Crawl API."
|
||||
output_types: list[str] = ["Document"]
|
||||
documentation: str = "https://docs.firecrawl.dev/api-reference/endpoint/crawl"
|
||||
field_config = {
|
||||
"api_key": {
|
||||
"display_name": "API Key",
|
||||
"field_type": "str",
|
||||
"required": True,
|
||||
"password": True,
|
||||
"info": "The API key to use Firecrawl API.",
|
||||
},
|
||||
"url": {
|
||||
"display_name": "URL",
|
||||
"field_type": "str",
|
||||
"required": True,
|
||||
"info": "The base URL to start crawling from.",
|
||||
},
|
||||
"timeout": {
|
||||
"display_name": "Timeout",
|
||||
"field_type": "int",
|
||||
"info": "The timeout in milliseconds.",
|
||||
},
|
||||
"crawlerOptions": {
|
||||
"display_name": "Crawler Options",
|
||||
"info": "Options for the crawler behavior.",
|
||||
},
|
||||
"pageOptions": {
|
||||
"display_name": "Page Options",
|
||||
"info": "The page options to send with the request.",
|
||||
},
|
||||
"idempotency_key": {
|
||||
"display_name": "Idempotency Key",
|
||||
"field_type": "str",
|
||||
"info": "Optional idempotency key to ensure unique requests.",
|
||||
},
|
||||
}
|
||||
|
||||
def build(
|
||||
self,
|
||||
api_key: str,
|
||||
url: str,
|
||||
timeout: int = 30000,
|
||||
crawlerOptions: Optional[Data] = None,
|
||||
pageOptions: Optional[Data] = None,
|
||||
idempotency_key: Optional[str] = None,
|
||||
) -> Data:
|
||||
try:
|
||||
from firecrawl.firecrawl import FirecrawlApp # type: ignore
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"Could not import firecrawl integration package. " "Please install it with `pip install firecrawl-py`."
|
||||
)
|
||||
|
||||
if crawlerOptions:
|
||||
crawler_options_dict = crawlerOptions.__dict__["data"]["text"]
|
||||
else:
|
||||
crawler_options_dict = {}
|
||||
|
||||
if pageOptions:
|
||||
page_options_dict = pageOptions.__dict__["data"]["text"]
|
||||
else:
|
||||
page_options_dict = {}
|
||||
|
||||
if not idempotency_key:
|
||||
idempotency_key = str(uuid.uuid4())
|
||||
|
||||
app = FirecrawlApp(api_key=api_key)
|
||||
crawl_result = app.crawl_url(
|
||||
url,
|
||||
{
|
||||
"crawlerOptions": crawler_options_dict,
|
||||
"pageOptions": page_options_dict,
|
||||
},
|
||||
True,
|
||||
int(timeout / 1000),
|
||||
idempotency_key,
|
||||
)
|
||||
|
||||
records = Data(data={"results": crawl_result})
|
||||
return records
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
from typing import Optional
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.schema import Data
|
||||
|
||||
|
||||
class FirecrawlScrapeApi(CustomComponent):
|
||||
display_name: str = "FirecrawlScrapeApi"
|
||||
description: str = "Firecrawl Scrape API."
|
||||
output_types: list[str] = ["Document"]
|
||||
documentation: str = "https://docs.firecrawl.dev/api-reference/endpoint/scrape"
|
||||
field_config = {
|
||||
"api_key": {
|
||||
"display_name": "API Key",
|
||||
"field_type": "str",
|
||||
"required": True,
|
||||
"password": True,
|
||||
"info": "The API key to use Firecrawl API.",
|
||||
},
|
||||
"url": {
|
||||
"display_name": "URL",
|
||||
"field_type": "str",
|
||||
"required": True,
|
||||
"info": "The URL to scrape.",
|
||||
},
|
||||
"timeout": {
|
||||
"display_name": "Timeout",
|
||||
"info": "Timeout in milliseconds for the request.",
|
||||
"field_type": "int",
|
||||
"default_value": 10000,
|
||||
},
|
||||
"pageOptions": {
|
||||
"display_name": "Page Options",
|
||||
"info": "The page options to send with the request.",
|
||||
},
|
||||
"extractorOptions": {
|
||||
"display_name": "Extractor Options",
|
||||
"info": "The extractor options to send with the request.",
|
||||
},
|
||||
}
|
||||
|
||||
def build(
|
||||
self,
|
||||
api_key: str,
|
||||
url: str,
|
||||
timeout: Optional[int] = 10000,
|
||||
pageOptions: Optional[Data] = None,
|
||||
extractorOptions: Optional[Data] = None,
|
||||
) -> Data:
|
||||
try:
|
||||
from firecrawl.firecrawl import FirecrawlApp # type: ignore
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"Could not import firecrawl integration package. " "Please install it with `pip install firecrawl-py`."
|
||||
)
|
||||
if extractorOptions:
|
||||
extractor_options_dict = extractorOptions.__dict__["data"]["text"]
|
||||
else:
|
||||
extractor_options_dict = {}
|
||||
|
||||
if pageOptions:
|
||||
page_options_dict = pageOptions.__dict__["data"]["text"]
|
||||
else:
|
||||
page_options_dict = {}
|
||||
|
||||
app = FirecrawlApp(api_key=api_key)
|
||||
results = app.scrape_url(
|
||||
url,
|
||||
{
|
||||
"timeout": str(timeout),
|
||||
"extractorOptions": extractor_options_dict,
|
||||
"pageOptions": page_options_dict,
|
||||
},
|
||||
)
|
||||
|
||||
record = Data(data=results)
|
||||
return record
|
||||
|
|
@ -34,6 +34,12 @@ class OpenAIModelComponent(LCModelComponent):
|
|||
info="The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
|
||||
),
|
||||
DictInput(name="model_kwargs", display_name="Model Kwargs", advanced=True),
|
||||
BoolInput(
|
||||
name="json_mode",
|
||||
display_name="JSON Mode",
|
||||
advanced=True,
|
||||
info="If True, it will output JSON regardless of passing a schema.",
|
||||
),
|
||||
DictInput(
|
||||
name="output_schema",
|
||||
is_list=True,
|
||||
|
|
@ -84,7 +90,7 @@ class OpenAIModelComponent(LCModelComponent):
|
|||
max_tokens = self.max_tokens
|
||||
model_kwargs = self.model_kwargs or {}
|
||||
openai_api_base = self.openai_api_base or "https://api.openai.com/v1"
|
||||
json_mode = bool(output_schema_dict)
|
||||
json_mode = bool(output_schema_dict) or self.json_mode
|
||||
seed = self.seed
|
||||
model_kwargs["seed"] = seed
|
||||
|
||||
|
|
@ -101,7 +107,10 @@ class OpenAIModelComponent(LCModelComponent):
|
|||
temperature=temperature or 0.1,
|
||||
)
|
||||
if json_mode:
|
||||
output = output.with_structured_output(schema=output_schema_dict, method="json_mode") # type: ignore
|
||||
if output_schema_dict:
|
||||
output = output.with_structured_output(schema=output_schema_dict, method="json_mode") # type: ignore
|
||||
else:
|
||||
output = output.bind(response_format={"type": "json_object"}) # type: ignore
|
||||
|
||||
return output
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from langchain_community.vectorstores import Cassandra
|
|||
|
||||
from langflow.base.vectorstores.model import LCVectorStoreComponent
|
||||
from langflow.helpers.data import docs_to_data
|
||||
from langflow.inputs import DictInput
|
||||
from langflow.io import (
|
||||
DataInput,
|
||||
DropdownInput,
|
||||
|
|
@ -23,24 +24,32 @@ class CassandraVectorStoreComponent(LCVectorStoreComponent):
|
|||
icon = "Cassandra"
|
||||
|
||||
inputs = [
|
||||
SecretStrInput(
|
||||
name="token",
|
||||
display_name="Token",
|
||||
info="Authentication token for accessing Cassandra on Astra DB.",
|
||||
MessageTextInput(
|
||||
name="database_ref",
|
||||
display_name="Contact Points / Astra Database ID",
|
||||
info="Contact points for the database (or AstraDB database ID)",
|
||||
required=True,
|
||||
),
|
||||
MessageTextInput(name="database_id", display_name="Database ID", info="The Astra database ID.", required=True),
|
||||
MessageTextInput(
|
||||
name="table_name",
|
||||
display_name="Table Name",
|
||||
info="The name of the table where vectors will be stored.",
|
||||
name="username", display_name="Username", info="Username for the database (leave empty for AstraDB)."
|
||||
),
|
||||
SecretStrInput(
|
||||
name="token",
|
||||
display_name="Password / AstraDB Token",
|
||||
info="User password for the database (or AstraDB token).",
|
||||
required=True,
|
||||
),
|
||||
MessageTextInput(
|
||||
name="keyspace",
|
||||
display_name="Keyspace",
|
||||
info="Optional key space within Astra DB. The keyspace should already be created.",
|
||||
advanced=False,
|
||||
info="Table Keyspace (or AstraDB namespace).",
|
||||
required=True,
|
||||
),
|
||||
MessageTextInput(
|
||||
name="table_name",
|
||||
display_name="Table Name",
|
||||
info="The name of the table (or AstraDB collection) where vectors will be stored.",
|
||||
required=True,
|
||||
),
|
||||
IntInput(
|
||||
name="ttl_seconds",
|
||||
|
|
@ -69,6 +78,13 @@ class CassandraVectorStoreComponent(LCVectorStoreComponent):
|
|||
value="Sync",
|
||||
advanced=True,
|
||||
),
|
||||
DictInput(
|
||||
name="cluster_kwargs",
|
||||
display_name="Cluster arguments",
|
||||
info="Optional dictionary of additional keyword arguments for the Cassandra cluster.",
|
||||
advanced=True,
|
||||
is_list=True,
|
||||
),
|
||||
MultilineInput(name="search_query", display_name="Search Query"),
|
||||
DataInput(
|
||||
name="ingest_data",
|
||||
|
|
@ -96,10 +112,35 @@ class CassandraVectorStoreComponent(LCVectorStoreComponent):
|
|||
"Could not import cassio integration package. " "Please install it with `pip install cassio`."
|
||||
)
|
||||
|
||||
cassio.init(
|
||||
database_id=self.database_id,
|
||||
token=self.token,
|
||||
)
|
||||
from uuid import UUID
|
||||
|
||||
database_ref = self.database_ref
|
||||
|
||||
try:
|
||||
UUID(self.database_ref)
|
||||
is_astra = True
|
||||
except ValueError:
|
||||
is_astra = False
|
||||
if "," in self.database_ref:
|
||||
# use a copy because we can't change the type of the parameter
|
||||
database_ref = self.database_ref.split(",")
|
||||
|
||||
if is_astra:
|
||||
cassio.init(
|
||||
database_id=database_ref,
|
||||
token=self.token,
|
||||
cluster_kwargs=self.cluster_kwargs,
|
||||
)
|
||||
else:
|
||||
cassio.init(
|
||||
contact_points=database_ref,
|
||||
username=self.username,
|
||||
password=self.token,
|
||||
cluster_kwargs=self.cluster_kwargs,
|
||||
)
|
||||
|
||||
if not self.ttl_seconds: # type: ignore
|
||||
self.ttl_seconds = None
|
||||
|
||||
documents = []
|
||||
|
||||
|
|
|
|||
|
|
@ -343,10 +343,10 @@ class Graph:
|
|||
except Exception as exc:
|
||||
logger.exception(exc)
|
||||
tb = traceback.format_exc()
|
||||
await self.end_all_traces(error=f"{exc.__class__.__name__}: {exc}\n\n{tb}")
|
||||
asyncio.create_task(self.end_all_traces(error=f"{exc.__class__.__name__}: {exc}\n\n{tb}"))
|
||||
raise ValueError(f"Error running graph: {exc}") from exc
|
||||
finally:
|
||||
await self.end_all_traces()
|
||||
asyncio.create_task(self.end_all_traces())
|
||||
# Get the outputs
|
||||
vertex_outputs = []
|
||||
for vertex in self.vertices:
|
||||
|
|
@ -1444,7 +1444,7 @@ class Graph:
|
|||
|
||||
def is_vertex_runnable(self, vertex_id: str) -> bool:
|
||||
"""Returns whether a vertex is runnable."""
|
||||
return self.run_manager.is_vertex_runnable(vertex_id)
|
||||
return self.run_manager.is_vertex_runnable(vertex_id, self.inactivated_vertices)
|
||||
|
||||
def build_run_map(self):
|
||||
"""
|
||||
|
|
@ -1464,7 +1464,7 @@ class Graph:
|
|||
This checks the direct predecessors of each successor to identify any that are
|
||||
immediately runnable, expanding the search to ensure progress can be made.
|
||||
"""
|
||||
return self.run_manager.find_runnable_predecessors_for_successors(vertex_id)
|
||||
return self.run_manager.find_runnable_predecessors_for_successors(vertex_id, self.inactivated_vertices)
|
||||
|
||||
def remove_from_predecessors(self, vertex_id: str):
|
||||
self.run_manager.remove_from_predecessors(vertex_id)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import asyncio
|
||||
from collections import defaultdict
|
||||
from typing import TYPE_CHECKING, Callable, List, Coroutine
|
||||
from typing import TYPE_CHECKING, Callable, Coroutine, List
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from langflow.graph.graph.base import Graph
|
||||
|
|
@ -40,19 +40,23 @@ class RunnableVerticesManager:
|
|||
self.run_predecessors = state["run_predecessors"]
|
||||
self.vertices_to_run = state["vertices_to_run"]
|
||||
|
||||
def is_vertex_runnable(self, vertex_id: str) -> bool:
|
||||
def is_vertex_runnable(self, vertex_id: str, inactivated_vertices: set[str]) -> bool:
|
||||
"""Determines if a vertex is runnable."""
|
||||
|
||||
return vertex_id in self.vertices_to_run and not self.run_predecessors.get(vertex_id)
|
||||
return (
|
||||
vertex_id in self.vertices_to_run
|
||||
and not self.run_predecessors.get(vertex_id)
|
||||
and vertex_id not in inactivated_vertices
|
||||
)
|
||||
|
||||
def find_runnable_predecessors_for_successors(self, vertex_id: str) -> List[str]:
|
||||
def find_runnable_predecessors_for_successors(self, vertex_id: str, inactivated_vertices: set[str]) -> List[str]:
|
||||
"""Finds runnable predecessors for the successors of a given vertex."""
|
||||
runnable_vertices = []
|
||||
visited = set()
|
||||
|
||||
for successor_id in self.run_map.get(vertex_id, []):
|
||||
for predecessor_id in self.run_predecessors.get(successor_id, []):
|
||||
if predecessor_id not in visited and self.is_vertex_runnable(predecessor_id):
|
||||
if predecessor_id not in visited and self.is_vertex_runnable(predecessor_id, inactivated_vertices):
|
||||
runnable_vertices.append(predecessor_id)
|
||||
visited.add(predecessor_id)
|
||||
return runnable_vertices
|
||||
|
|
@ -104,10 +108,14 @@ class RunnableVerticesManager:
|
|||
"""
|
||||
async with lock:
|
||||
self.remove_from_predecessors(vertex.id)
|
||||
direct_successors_ready = [v for v in vertex.successors_ids if self.is_vertex_runnable(v)]
|
||||
direct_successors_ready = [
|
||||
v for v in vertex.successors_ids if self.is_vertex_runnable(v, graph.inactivated_vertices)
|
||||
]
|
||||
if not direct_successors_ready:
|
||||
# No direct successors ready, look for runnable predecessors of successors
|
||||
next_runnable_vertices = self.find_runnable_predecessors_for_successors(vertex.id)
|
||||
next_runnable_vertices = self.find_runnable_predecessors_for_successors(
|
||||
vertex.id, graph.inactivated_vertices
|
||||
)
|
||||
else:
|
||||
next_runnable_vertices = direct_successors_ready
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import warnings
|
||||
from typing import List, Optional
|
||||
from uuid import UUID
|
||||
|
||||
from loguru import logger
|
||||
|
|
@ -8,17 +7,18 @@ from sqlmodel import Session, col, select
|
|||
|
||||
from langflow.schema.message import Message
|
||||
from langflow.services.database.models.message.model import MessageRead, MessageTable
|
||||
from langflow.services.database.utils import migrate_messages_from_monitor_service_to_database
|
||||
from langflow.services.deps import session_scope
|
||||
|
||||
|
||||
def get_messages(
|
||||
sender: Optional[str] = None,
|
||||
sender_name: Optional[str] = None,
|
||||
session_id: Optional[str] = None,
|
||||
order_by: Optional[str] = "timestamp",
|
||||
order: Optional[str] = "DESC",
|
||||
flow_id: Optional[UUID] = None,
|
||||
limit: Optional[int] = None,
|
||||
sender: str | None = None,
|
||||
sender_name: str | None = None,
|
||||
session_id: str | None = None,
|
||||
order_by: str | None = "timestamp",
|
||||
order: str | None = "DESC",
|
||||
flow_id: UUID | None = None,
|
||||
limit: int | None = None,
|
||||
):
|
||||
"""
|
||||
Retrieves messages from the monitor service based on the provided filters.
|
||||
|
|
@ -33,6 +33,8 @@ def get_messages(
|
|||
Returns:
|
||||
List[Data]: A list of Data objects representing the retrieved messages.
|
||||
"""
|
||||
with session_scope() as session:
|
||||
migrate_messages_from_monitor_service_to_database(session)
|
||||
messages_read: list[Message] = []
|
||||
with session_scope() as session:
|
||||
stmt = select(MessageTable)
|
||||
|
|
@ -58,7 +60,7 @@ def get_messages(
|
|||
return messages_read
|
||||
|
||||
|
||||
def add_messages(messages: Message | list[Message], flow_id: Optional[str] = None):
|
||||
def add_messages(messages: Message | list[Message], flow_id: str | None = None):
|
||||
"""
|
||||
Add a message to the monitor service.
|
||||
"""
|
||||
|
|
@ -111,8 +113,8 @@ def delete_messages(session_id: str):
|
|||
|
||||
def store_message(
|
||||
message: Message,
|
||||
flow_id: Optional[str] = None,
|
||||
) -> List[Message]:
|
||||
flow_id: str | None = None,
|
||||
) -> list[Message]:
|
||||
"""
|
||||
Stores a message in the memory.
|
||||
|
||||
|
|
|
|||
|
|
@ -41,6 +41,12 @@ class Message(Data):
|
|||
value = str(value)
|
||||
return value
|
||||
|
||||
@field_serializer("flow_id")
|
||||
def serialize_flow_id(value):
|
||||
if isinstance(value, str):
|
||||
return UUID(value)
|
||||
return value
|
||||
|
||||
@field_validator("files", mode="before")
|
||||
@classmethod
|
||||
def validate_files(cls, value):
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ class MessageBase(SQLModel):
|
|||
return value
|
||||
|
||||
@classmethod
|
||||
def from_message(cls, message: "Message", flow_id: str | None = None):
|
||||
def from_message(cls, message: "Message", flow_id: str | UUID | None = None):
|
||||
# first check if the record has all the required fields
|
||||
if message.text is None or not message.sender or not message.sender_name:
|
||||
raise ValueError("The message does not have the required fields (text, sender, sender_name).")
|
||||
|
|
@ -34,6 +34,8 @@ class MessageBase(SQLModel):
|
|||
timestamp = datetime.fromisoformat(message.timestamp)
|
||||
else:
|
||||
timestamp = message.timestamp
|
||||
if not flow_id and message.flow_id:
|
||||
flow_id = message.flow_id
|
||||
return cls(
|
||||
sender=message.sender,
|
||||
sender_name=message.sender_name,
|
||||
|
|
@ -52,6 +54,15 @@ class MessageTable(MessageBase, table=True):
|
|||
flow: "Flow" = Relationship(back_populates="messages")
|
||||
files: List[str] = Field(sa_column=Column(JSON))
|
||||
|
||||
@field_validator("flow_id", mode="before")
|
||||
@classmethod
|
||||
def validate_flow_id(cls, value):
|
||||
if value is None:
|
||||
return value
|
||||
if isinstance(value, str):
|
||||
value = UUID(value)
|
||||
return value
|
||||
|
||||
# Needed for Column(JSON)
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
|
|
|
|||
|
|
@ -6,22 +6,24 @@ from typing import TYPE_CHECKING
|
|||
import sqlalchemy as sa
|
||||
from alembic import command, util
|
||||
from alembic.config import Config
|
||||
from langflow.services.base import Service
|
||||
from langflow.services.database import models # noqa
|
||||
from langflow.services.database.models.user.crud import get_user_by_username
|
||||
from langflow.services.database.utils import Result, TableResults
|
||||
from langflow.services.deps import get_settings_service
|
||||
from langflow.services.utils import teardown_superuser
|
||||
from loguru import logger
|
||||
from sqlalchemy import event, inspect
|
||||
from sqlalchemy.engine import Engine
|
||||
from sqlalchemy.exc import OperationalError
|
||||
from sqlmodel import Session, SQLModel, create_engine, select, text
|
||||
|
||||
from langflow.services.base import Service
|
||||
from langflow.services.database import models # noqa
|
||||
from langflow.services.database.models.user.crud import get_user_by_username
|
||||
from langflow.services.database.utils import Result, TableResults, migrate_messages_from_monitor_service_to_database
|
||||
from langflow.services.deps import get_settings_service
|
||||
from langflow.services.utils import teardown_superuser
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from langflow.services.settings.service import SettingsService
|
||||
from sqlalchemy.engine import Engine
|
||||
|
||||
from langflow.services.settings.service import SettingsService
|
||||
|
||||
|
||||
class DatabaseService(Service):
|
||||
name = "database_service"
|
||||
|
|
@ -205,6 +207,10 @@ class DatabaseService(Service):
|
|||
logger.error(f"AutogenerateDiffsDetected: {exc}")
|
||||
if not fix:
|
||||
raise RuntimeError(f"There's a mismatch between the models and the database.\n{exc}")
|
||||
try:
|
||||
migrate_messages_from_monitor_service_to_database(session)
|
||||
except Exception as exc:
|
||||
logger.error(f"Error migrating messages from monitor service to database: {exc}")
|
||||
|
||||
if fix:
|
||||
self.try_downgrade_upgrade_until_success(alembic_cfg)
|
||||
|
|
|
|||
|
|
@ -4,11 +4,78 @@ from typing import TYPE_CHECKING
|
|||
|
||||
from alembic.util.exc import CommandError
|
||||
from loguru import logger
|
||||
from sqlmodel import Session, text
|
||||
from sqlmodel import Session, select, text
|
||||
|
||||
from langflow.services.deps import get_monitor_service
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from langflow.services.database.service import DatabaseService
|
||||
|
||||
from typing import Dict, List
|
||||
|
||||
|
||||
def migrate_messages_from_monitor_service_to_database(session: Session) -> bool:
|
||||
from langflow.schema.message import Message
|
||||
from langflow.services.database.models.message import MessageTable
|
||||
|
||||
monitor_service = get_monitor_service()
|
||||
messages_df = monitor_service.get_messages()
|
||||
|
||||
if messages_df.empty:
|
||||
logger.info("No messages to migrate.")
|
||||
return True
|
||||
|
||||
original_messages: List[Dict] = messages_df.to_dict(orient="records")
|
||||
|
||||
db_messages = session.exec(select(MessageTable)).all()
|
||||
db_messages = [msg[0] for msg in db_messages] # type: ignore
|
||||
db_msg_dict = {(msg.text, msg.timestamp.isoformat(), str(msg.flow_id), msg.session_id): msg for msg in db_messages}
|
||||
# Filter out messages that already exist in the database
|
||||
original_messages_filtered = []
|
||||
for message in original_messages:
|
||||
key = (message["text"], message["timestamp"].isoformat(), str(message["flow_id"]), message["session_id"])
|
||||
if key not in db_msg_dict:
|
||||
original_messages_filtered.append(message)
|
||||
if not original_messages_filtered:
|
||||
logger.info("No messages to migrate.")
|
||||
return True
|
||||
try:
|
||||
# Bulk insert messages
|
||||
session.bulk_insert_mappings(
|
||||
MessageTable, # type: ignore
|
||||
[MessageTable.from_message(Message(**msg)).model_dump() for msg in original_messages_filtered],
|
||||
)
|
||||
session.commit()
|
||||
except Exception as e:
|
||||
logger.error(f"Error during message insertion: {str(e)}")
|
||||
session.rollback()
|
||||
return False
|
||||
|
||||
# Create a dictionary for faster lookup
|
||||
|
||||
all_ok = True
|
||||
for orig_msg in original_messages_filtered:
|
||||
key = (orig_msg["text"], orig_msg["timestamp"].isoformat(), str(orig_msg["flow_id"]), orig_msg["session_id"])
|
||||
matching_db_msg = db_msg_dict.get(key)
|
||||
|
||||
if matching_db_msg is None:
|
||||
logger.warning(f"Message not found in database: {orig_msg}")
|
||||
all_ok = False
|
||||
else:
|
||||
# Validate other fields
|
||||
if any(getattr(matching_db_msg, k) != v for k, v in orig_msg.items() if k != "index"):
|
||||
logger.warning(f"Message mismatch in database: {orig_msg}")
|
||||
all_ok = False
|
||||
|
||||
if all_ok:
|
||||
messages_ids = [message["index"] for message in original_messages]
|
||||
monitor_service.delete_messages(messages_ids)
|
||||
logger.info("Migration completed successfully. Original messages deleted.")
|
||||
else:
|
||||
logger.warning("Migration completed with errors. Original messages not deleted.")
|
||||
|
||||
return all_ok
|
||||
|
||||
|
||||
def initialize_database(fix_migration: bool = False):
|
||||
logger.debug("Initializing database")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import json
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field, field_serializer, field_validator
|
||||
|
|
@ -28,15 +28,15 @@ class DefaultModel(BaseModel):
|
|||
|
||||
|
||||
class TransactionModel(DefaultModel):
|
||||
index: Optional[int] = Field(default=None)
|
||||
timestamp: Optional[datetime] = Field(default_factory=datetime.now, alias="timestamp")
|
||||
index: int | None = Field(default=None)
|
||||
timestamp: datetime | None = Field(default_factory=datetime.now, alias="timestamp")
|
||||
vertex_id: str
|
||||
target_id: str | None = None
|
||||
inputs: dict
|
||||
outputs: Optional[dict] = None
|
||||
outputs: dict | None = None
|
||||
status: str
|
||||
error: Optional[str] = None
|
||||
flow_id: Optional[str] = Field(default=None, alias="flow_id")
|
||||
error: str | None = None
|
||||
flow_id: str | None = Field(default=None, alias="flow_id")
|
||||
|
||||
# validate target_args in case it is a JSON
|
||||
@field_validator("outputs", "inputs", mode="before")
|
||||
|
|
@ -53,16 +53,16 @@ class TransactionModel(DefaultModel):
|
|||
|
||||
|
||||
class TransactionModelResponse(DefaultModel):
|
||||
index: Optional[int] = Field(default=None)
|
||||
timestamp: Optional[datetime] = Field(default_factory=datetime.now, alias="timestamp")
|
||||
index: int | None = Field(default=None)
|
||||
timestamp: datetime | None = Field(default_factory=datetime.now, alias="timestamp")
|
||||
vertex_id: str
|
||||
inputs: dict
|
||||
outputs: Optional[dict] = None
|
||||
outputs: dict | None = None
|
||||
status: str
|
||||
error: Optional[str] = None
|
||||
flow_id: Optional[str] = Field(default=None, alias="flow_id")
|
||||
source: Optional[str] = None
|
||||
target: Optional[str] = None
|
||||
error: str | None = None
|
||||
flow_id: str | None = Field(default=None, alias="flow_id")
|
||||
source: str | None = None
|
||||
target: str | None = None
|
||||
|
||||
# validate target_args in case it is a JSON
|
||||
@field_validator("outputs", "inputs", mode="before")
|
||||
|
|
@ -81,9 +81,9 @@ class TransactionModelResponse(DefaultModel):
|
|||
return v
|
||||
|
||||
|
||||
class MessageModel(DefaultModel):
|
||||
id: Optional[str | UUID] = Field(default=None)
|
||||
flow_id: Optional[UUID] = Field(default=None)
|
||||
class DuckDbMessageModel(DefaultModel):
|
||||
index: int | None = Field(default=None, alias="index")
|
||||
flow_id: str | None = Field(default=None, alias="flow_id")
|
||||
timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
||||
sender: str
|
||||
sender_name: str
|
||||
|
|
@ -112,7 +112,53 @@ class MessageModel(DefaultModel):
|
|||
return v
|
||||
|
||||
@classmethod
|
||||
def from_message(cls, message: Message, flow_id: Optional[str] = None):
|
||||
def from_message(cls, message: Message, flow_id: str | None = None):
|
||||
# first check if the record has all the required fields
|
||||
if message.text is None or not message.sender or not message.sender_name:
|
||||
raise ValueError("The message does not have the required fields (text, sender, sender_name).")
|
||||
return cls(
|
||||
sender=message.sender,
|
||||
sender_name=message.sender_name,
|
||||
text=message.text,
|
||||
session_id=message.session_id,
|
||||
files=message.files or [],
|
||||
timestamp=message.timestamp,
|
||||
flow_id=flow_id,
|
||||
)
|
||||
|
||||
|
||||
class MessageModel(DefaultModel):
|
||||
id: str | UUID | None = Field(default=None)
|
||||
flow_id: UUID | None = Field(default=None)
|
||||
timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
||||
sender: str
|
||||
sender_name: str
|
||||
session_id: str
|
||||
text: str
|
||||
files: list[str] = []
|
||||
|
||||
@field_validator("files", mode="before")
|
||||
@classmethod
|
||||
def validate_files(cls, v):
|
||||
if isinstance(v, str):
|
||||
v = json.loads(v)
|
||||
return v
|
||||
|
||||
@field_serializer("timestamp")
|
||||
@classmethod
|
||||
def serialize_timestamp(cls, v):
|
||||
v = v.replace(microsecond=0)
|
||||
return v.strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
@field_serializer("files")
|
||||
@classmethod
|
||||
def serialize_files(cls, v):
|
||||
if isinstance(v, list):
|
||||
return json.dumps(v)
|
||||
return v
|
||||
|
||||
@classmethod
|
||||
def from_message(cls, message: Message, flow_id: str | None = None):
|
||||
# first check if the record has all the required fields
|
||||
if message.text is None or not message.sender or not message.sender_name:
|
||||
raise ValueError("The message does not have the required fields (text, sender, sender_name).")
|
||||
|
|
@ -139,8 +185,8 @@ class MessageModelRequest(MessageModel):
|
|||
|
||||
|
||||
class VertexBuildModel(DefaultModel):
|
||||
index: Optional[int] = Field(default=None, alias="index", exclude=True)
|
||||
id: Optional[str] = Field(default=None, alias="id")
|
||||
index: int | None = Field(default=None, alias="index", exclude=True)
|
||||
id: str | None = Field(default=None, alias="id")
|
||||
flow_id: str
|
||||
valid: bool
|
||||
params: Any
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, List, Optional, Union
|
||||
from typing import TYPE_CHECKING, Union
|
||||
|
||||
import duckdb
|
||||
from loguru import logger
|
||||
|
|
@ -10,7 +10,7 @@ from langflow.services.base import Service
|
|||
from langflow.services.monitor.utils import add_row_to_table, drop_and_create_table_if_schema_mismatch
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from langflow.services.monitor.schema import MessageModel, TransactionModel, VertexBuildModel
|
||||
from langflow.services.monitor.schema import DuckDbMessageModel, TransactionModel, VertexBuildModel
|
||||
from langflow.services.settings.service import SettingsService
|
||||
|
||||
|
||||
|
|
@ -18,14 +18,14 @@ class MonitorService(Service):
|
|||
name = "monitor_service"
|
||||
|
||||
def __init__(self, settings_service: "SettingsService"):
|
||||
from langflow.services.monitor.schema import MessageModel, TransactionModel, VertexBuildModel
|
||||
from langflow.services.monitor.schema import DuckDbMessageModel, TransactionModel, VertexBuildModel
|
||||
|
||||
self.settings_service = settings_service
|
||||
self.base_cache_dir = Path(user_cache_dir("langflow"))
|
||||
self.db_path = self.base_cache_dir / "monitor.duckdb"
|
||||
self.table_map: dict[str, type[TransactionModel | MessageModel | VertexBuildModel]] = {
|
||||
self.table_map: dict[str, type[TransactionModel | DuckDbMessageModel | VertexBuildModel]] = {
|
||||
"transactions": TransactionModel,
|
||||
"messages": MessageModel,
|
||||
"messages": DuckDbMessageModel,
|
||||
"vertex_builds": VertexBuildModel,
|
||||
}
|
||||
|
||||
|
|
@ -48,7 +48,7 @@ class MonitorService(Service):
|
|||
def add_row(
|
||||
self,
|
||||
table_name: str,
|
||||
data: Union[dict, "TransactionModel", "MessageModel", "VertexBuildModel"],
|
||||
data: Union[dict, "TransactionModel", "DuckDbMessageModel", "VertexBuildModel"],
|
||||
):
|
||||
# Make sure the model passed matches the table
|
||||
|
||||
|
|
@ -68,12 +68,48 @@ class MonitorService(Service):
|
|||
def get_timestamp():
|
||||
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
def get_messages(
|
||||
self,
|
||||
flow_id: str | None = None,
|
||||
sender: str | None = None,
|
||||
sender_name: str | None = None,
|
||||
session_id: str | None = None,
|
||||
order_by: str | None = "timestamp",
|
||||
order: str | None = "DESC",
|
||||
limit: int | None = None,
|
||||
):
|
||||
query = "SELECT index, flow_id, sender_name, sender, session_id, text, files, timestamp FROM messages"
|
||||
conditions = []
|
||||
if sender:
|
||||
conditions.append(f"sender = '{sender}'")
|
||||
if sender_name:
|
||||
conditions.append(f"sender_name = '{sender_name}'")
|
||||
if session_id:
|
||||
conditions.append(f"session_id = '{session_id}'")
|
||||
if flow_id:
|
||||
conditions.append(f"flow_id = '{flow_id}'")
|
||||
|
||||
if conditions:
|
||||
query += " WHERE " + " AND ".join(conditions)
|
||||
|
||||
if order_by and order:
|
||||
# Make sure the order is from newest to oldest
|
||||
query += f" ORDER BY {order_by} {order.upper()}"
|
||||
|
||||
if limit is not None:
|
||||
query += f" LIMIT {limit}"
|
||||
|
||||
with duckdb.connect(str(self.db_path), read_only=True) as conn:
|
||||
df = conn.execute(query).df()
|
||||
|
||||
return df
|
||||
|
||||
def get_vertex_builds(
|
||||
self,
|
||||
flow_id: Optional[str] = None,
|
||||
vertex_id: Optional[str] = None,
|
||||
valid: Optional[bool] = None,
|
||||
order_by: Optional[str] = "timestamp",
|
||||
flow_id: str | None = None,
|
||||
vertex_id: str | None = None,
|
||||
valid: bool | None = None,
|
||||
order_by: str | None = "timestamp",
|
||||
):
|
||||
query = "SELECT id, index,flow_id, valid, params, data, artifacts, timestamp FROM vertex_builds"
|
||||
conditions = []
|
||||
|
|
@ -96,7 +132,7 @@ class MonitorService(Service):
|
|||
|
||||
return df.to_dict(orient="records")
|
||||
|
||||
def delete_vertex_builds(self, flow_id: Optional[str] = None):
|
||||
def delete_vertex_builds(self, flow_id: str | None = None):
|
||||
query = "DELETE FROM vertex_builds"
|
||||
if flow_id:
|
||||
query += f" WHERE flow_id = '{flow_id}'"
|
||||
|
|
@ -109,7 +145,7 @@ class MonitorService(Service):
|
|||
|
||||
return self.exec_query(query, read_only=False)
|
||||
|
||||
def delete_messages(self, message_ids: Union[List[int], str]):
|
||||
def delete_messages(self, message_ids: list[int] | str):
|
||||
if isinstance(message_ids, list):
|
||||
# If message_ids is a list, join the string representations of the integers
|
||||
ids_str = ",".join(map(str, message_ids))
|
||||
|
|
@ -132,11 +168,11 @@ class MonitorService(Service):
|
|||
|
||||
def get_transactions(
|
||||
self,
|
||||
source: Optional[str] = None,
|
||||
target: Optional[str] = None,
|
||||
status: Optional[str] = None,
|
||||
order_by: Optional[str] = "timestamp",
|
||||
flow_id: Optional[str] = None,
|
||||
source: str | None = None,
|
||||
target: str | None = None,
|
||||
status: str | None = None,
|
||||
order_by: str | None = "timestamp",
|
||||
flow_id: str | None = None,
|
||||
):
|
||||
query = (
|
||||
"SELECT index,flow_id, status, error, timestamp, vertex_id, inputs, outputs, target_id FROM transactions"
|
||||
|
|
|
|||
79
src/backend/base/poetry.lock
generated
79
src/backend/base/poetry.lock
generated
|
|
@ -112,13 +112,13 @@ frozenlist = ">=1.1.0"
|
|||
|
||||
[[package]]
|
||||
name = "alembic"
|
||||
version = "1.13.1"
|
||||
version = "1.13.2"
|
||||
description = "A database migration tool for SQLAlchemy."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "alembic-1.13.1-py3-none-any.whl", hash = "sha256:2edcc97bed0bd3272611ce3a98d98279e9c209e7186e43e75bbb1b2bdfdbcc43"},
|
||||
{file = "alembic-1.13.1.tar.gz", hash = "sha256:4932c8558bf68f2ee92b9bbcb8218671c627064d5b08939437af6d77dc05e595"},
|
||||
{file = "alembic-1.13.2-py3-none-any.whl", hash = "sha256:6b8733129a6224a9a711e17c99b08462dbf7cc9670ba8f2e2ae9af860ceb1953"},
|
||||
{file = "alembic-1.13.2.tar.gz", hash = "sha256:1ff0ae32975f4fd96028c39ed9bb3c867fe3af956bd7bb37343b54c9fe7445ef"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
|
@ -739,6 +739,20 @@ typer = ">=0.12.3"
|
|||
[package.extras]
|
||||
standard = ["fastapi", "uvicorn[standard] (>=0.15.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "firecrawl-py"
|
||||
version = "0.0.16"
|
||||
description = "Python SDK for Firecrawl API"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "firecrawl_py-0.0.16-py3-none-any.whl", hash = "sha256:9024f483b501852a6b9c4e6cdfc9e8dde452d922afac357080bb278a0c9c2a26"},
|
||||
{file = "firecrawl_py-0.0.16.tar.gz", hash = "sha256:6c662fa0a549bc7f5c0acb704baba6731869ca0451094034264dfc1b4eb086e4"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
requests = "*"
|
||||
|
||||
[[package]]
|
||||
name = "frozenlist"
|
||||
version = "1.4.1"
|
||||
|
|
@ -1158,19 +1172,19 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "langchain"
|
||||
version = "0.2.5"
|
||||
version = "0.2.6"
|
||||
description = "Building applications with LLMs through composability"
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.8.1"
|
||||
files = [
|
||||
{file = "langchain-0.2.5-py3-none-any.whl", hash = "sha256:9aded9a65348254e1c93dcdaacffe4d1b6a5e7f74ef80c160c88ff78ad299228"},
|
||||
{file = "langchain-0.2.5.tar.gz", hash = "sha256:ffdbf4fcea46a10d461bcbda2402220fcfd72a0c70e9f4161ae0510067b9b3bd"},
|
||||
{file = "langchain-0.2.6-py3-none-any.whl", hash = "sha256:f86e8a7afd3e56f8eb5ba47f01dd00144fb9fc2f1db9873bd197347be2857aa4"},
|
||||
{file = "langchain-0.2.6.tar.gz", hash = "sha256:867f6add370c1e3911b0e87d3dd0e36aec1e8f513bf06131340fe8f151d89dc5"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
aiohttp = ">=3.8.3,<4.0.0"
|
||||
async-timeout = {version = ">=4.0.0,<5.0.0", markers = "python_version < \"3.11\""}
|
||||
langchain-core = ">=0.2.7,<0.3.0"
|
||||
langchain-core = ">=0.2.10,<0.3.0"
|
||||
langchain-text-splitters = ">=0.2.0,<0.3.0"
|
||||
langsmith = ">=0.1.17,<0.2.0"
|
||||
numpy = [
|
||||
|
|
@ -1181,24 +1195,24 @@ pydantic = ">=1,<3"
|
|||
PyYAML = ">=5.3"
|
||||
requests = ">=2,<3"
|
||||
SQLAlchemy = ">=1.4,<3"
|
||||
tenacity = ">=8.1.0,<9.0.0"
|
||||
tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<9.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "langchain-community"
|
||||
version = "0.2.5"
|
||||
version = "0.2.6"
|
||||
description = "Community contributed LangChain integrations."
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.8.1"
|
||||
files = [
|
||||
{file = "langchain_community-0.2.5-py3-none-any.whl", hash = "sha256:bf37a334952e42c7676d083cf2d2c4cbfbb7de1949c4149fe19913e2b06c485f"},
|
||||
{file = "langchain_community-0.2.5.tar.gz", hash = "sha256:476787b8c8c213b67e7b0eceb53346e787f00fbae12d8e680985bd4f93b0bf64"},
|
||||
{file = "langchain_community-0.2.6-py3-none-any.whl", hash = "sha256:758cc800acfe5dd396bf8ba1b57c4792639ead0eab48ed0367f0732ec6ee1f68"},
|
||||
{file = "langchain_community-0.2.6.tar.gz", hash = "sha256:40ce09a50ed798aa651ddb34c8978200fa8589b9813c7a28ce8af027bbf249f0"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
aiohttp = ">=3.8.3,<4.0.0"
|
||||
dataclasses-json = ">=0.5.7,<0.7"
|
||||
langchain = ">=0.2.5,<0.3.0"
|
||||
langchain-core = ">=0.2.7,<0.3.0"
|
||||
langchain = ">=0.2.6,<0.3.0"
|
||||
langchain-core = ">=0.2.10,<0.3.0"
|
||||
langsmith = ">=0.1.0,<0.2.0"
|
||||
numpy = [
|
||||
{version = ">=1,<2", markers = "python_version < \"3.12\""},
|
||||
|
|
@ -1207,17 +1221,17 @@ numpy = [
|
|||
PyYAML = ">=5.3"
|
||||
requests = ">=2,<3"
|
||||
SQLAlchemy = ">=1.4,<3"
|
||||
tenacity = ">=8.1.0,<9.0.0"
|
||||
tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<9.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "langchain-core"
|
||||
version = "0.2.9"
|
||||
version = "0.2.10"
|
||||
description = "Building applications with LLMs through composability"
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.8.1"
|
||||
files = [
|
||||
{file = "langchain_core-0.2.9-py3-none-any.whl", hash = "sha256:426a5a4fea95a5db995ba5ab560b76edd4998fb6fe52ccc28ac987092a4cbfcd"},
|
||||
{file = "langchain_core-0.2.9.tar.gz", hash = "sha256:f1c59082642921727844e1cd0eb36d451edd1872c20e193aa3142aac03495986"},
|
||||
{file = "langchain_core-0.2.10-py3-none-any.whl", hash = "sha256:6eb72086b6bc86db9812da98f79e507c2209a15c0112aefd214a04182ada8586"},
|
||||
{file = "langchain_core-0.2.10.tar.gz", hash = "sha256:33d1fc234ab58c80476eb5bbde2107ef522a2ce8f46bdf47d9e1bd21e054208f"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
|
@ -1233,35 +1247,32 @@ tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<9.0.0"
|
|||
|
||||
[[package]]
|
||||
name = "langchain-experimental"
|
||||
version = "0.0.61"
|
||||
version = "0.0.62"
|
||||
description = "Building applications with LLMs through composability"
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.8.1"
|
||||
files = [
|
||||
{file = "langchain_experimental-0.0.61-py3-none-any.whl", hash = "sha256:f9c516f528f55919743bd56fe1689a53bf74ae7f8902d64b9d8aebc61249cbe2"},
|
||||
{file = "langchain_experimental-0.0.61.tar.gz", hash = "sha256:e9538efb994be5db3045cc582cddb9787c8299c86ffeee9d3779b7f58eef2226"},
|
||||
{file = "langchain_experimental-0.0.62-py3-none-any.whl", hash = "sha256:9240f9e3490e819976f20a37863970036e7baacb7104b9eb6833d19ab6d518c9"},
|
||||
{file = "langchain_experimental-0.0.62.tar.gz", hash = "sha256:9737fbc8429d24457ea4d368e3c9ba9ed1cace0564fb5f1a96a3027a588bd0ac"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
langchain-community = ">=0.2.5,<0.3.0"
|
||||
langchain-core = ">=0.2.7,<0.3.0"
|
||||
langchain-community = ">=0.2.6,<0.3.0"
|
||||
langchain-core = ">=0.2.10,<0.3.0"
|
||||
|
||||
[[package]]
|
||||
name = "langchain-text-splitters"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
description = "LangChain text splitting utilities"
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.8.1"
|
||||
files = [
|
||||
{file = "langchain_text_splitters-0.2.1-py3-none-any.whl", hash = "sha256:c2774a85f17189eaca50339629d2316d13130d4a8d9f1a1a96f3a03670c4a138"},
|
||||
{file = "langchain_text_splitters-0.2.1.tar.gz", hash = "sha256:06853d17d7241ecf5c97c7b6ef01f600f9b0fb953dd997838142a527a4f32ea4"},
|
||||
{file = "langchain_text_splitters-0.2.2-py3-none-any.whl", hash = "sha256:1c80d4b11b55e2995f02d2a326c0323ee1eeff24507329bb22924e420c782dff"},
|
||||
{file = "langchain_text_splitters-0.2.2.tar.gz", hash = "sha256:a1e45de10919fa6fb080ef0525deab56557e9552083600455cb9fa4238076140"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
langchain-core = ">=0.2.0,<0.3.0"
|
||||
|
||||
[package.extras]
|
||||
extended-testing = ["beautifulsoup4 (>=4.12.3,<5.0.0)", "lxml (>=4.9.3,<6.0)"]
|
||||
langchain-core = ">=0.2.10,<0.3.0"
|
||||
|
||||
[[package]]
|
||||
name = "langchainhub"
|
||||
|
|
@ -2468,13 +2479,13 @@ pyasn1 = ">=0.1.3"
|
|||
|
||||
[[package]]
|
||||
name = "sentry-sdk"
|
||||
version = "2.6.0"
|
||||
version = "2.7.0"
|
||||
description = "Python client for Sentry (https://sentry.io)"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "sentry_sdk-2.6.0-py2.py3-none-any.whl", hash = "sha256:422b91cb49378b97e7e8d0e8d5a1069df23689d45262b86f54988a7db264e874"},
|
||||
{file = "sentry_sdk-2.6.0.tar.gz", hash = "sha256:65cc07e9c6995c5e316109f138570b32da3bd7ff8d0d0ee4aaf2628c3dd8127d"},
|
||||
{file = "sentry_sdk-2.7.0-py2.py3-none-any.whl", hash = "sha256:db9594c27a4d21c1ebad09908b1f0dc808ef65c2b89c1c8e7e455143262e37c1"},
|
||||
{file = "sentry_sdk-2.7.0.tar.gz", hash = "sha256:d846a211d4a0378b289ced3c434480945f110d0ede00450ba631fc2852e7a0d4"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
|
@ -2506,7 +2517,7 @@ langchain = ["langchain (>=0.0.210)"]
|
|||
loguru = ["loguru (>=0.5)"]
|
||||
openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"]
|
||||
opentelemetry = ["opentelemetry-distro (>=0.35b0)"]
|
||||
opentelemetry-experimental = ["opentelemetry-distro (>=0.40b0,<1.0)", "opentelemetry-instrumentation-aiohttp-client (>=0.40b0,<1.0)", "opentelemetry-instrumentation-django (>=0.40b0,<1.0)", "opentelemetry-instrumentation-fastapi (>=0.40b0,<1.0)", "opentelemetry-instrumentation-flask (>=0.40b0,<1.0)", "opentelemetry-instrumentation-requests (>=0.40b0,<1.0)", "opentelemetry-instrumentation-sqlite3 (>=0.40b0,<1.0)", "opentelemetry-instrumentation-urllib (>=0.40b0,<1.0)"]
|
||||
opentelemetry-experimental = ["opentelemetry-instrumentation-aio-pika (==0.46b0)", "opentelemetry-instrumentation-aiohttp-client (==0.46b0)", "opentelemetry-instrumentation-aiopg (==0.46b0)", "opentelemetry-instrumentation-asgi (==0.46b0)", "opentelemetry-instrumentation-asyncio (==0.46b0)", "opentelemetry-instrumentation-asyncpg (==0.46b0)", "opentelemetry-instrumentation-aws-lambda (==0.46b0)", "opentelemetry-instrumentation-boto (==0.46b0)", "opentelemetry-instrumentation-boto3sqs (==0.46b0)", "opentelemetry-instrumentation-botocore (==0.46b0)", "opentelemetry-instrumentation-cassandra (==0.46b0)", "opentelemetry-instrumentation-celery (==0.46b0)", "opentelemetry-instrumentation-confluent-kafka (==0.46b0)", "opentelemetry-instrumentation-dbapi (==0.46b0)", "opentelemetry-instrumentation-django (==0.46b0)", "opentelemetry-instrumentation-elasticsearch (==0.46b0)", "opentelemetry-instrumentation-falcon (==0.46b0)", "opentelemetry-instrumentation-fastapi (==0.46b0)", "opentelemetry-instrumentation-flask (==0.46b0)", "opentelemetry-instrumentation-grpc (==0.46b0)", "opentelemetry-instrumentation-httpx (==0.46b0)", "opentelemetry-instrumentation-jinja2 (==0.46b0)", "opentelemetry-instrumentation-kafka-python (==0.46b0)", "opentelemetry-instrumentation-logging (==0.46b0)", "opentelemetry-instrumentation-mysql (==0.46b0)", "opentelemetry-instrumentation-mysqlclient (==0.46b0)", "opentelemetry-instrumentation-pika (==0.46b0)", "opentelemetry-instrumentation-psycopg (==0.46b0)", "opentelemetry-instrumentation-psycopg2 (==0.46b0)", "opentelemetry-instrumentation-pymemcache (==0.46b0)", "opentelemetry-instrumentation-pymongo (==0.46b0)", "opentelemetry-instrumentation-pymysql (==0.46b0)", "opentelemetry-instrumentation-pyramid (==0.46b0)", "opentelemetry-instrumentation-redis (==0.46b0)", "opentelemetry-instrumentation-remoulade (==0.46b0)", "opentelemetry-instrumentation-requests (==0.46b0)", "opentelemetry-instrumentation-sklearn (==0.46b0)", "opentelemetry-instrumentation-sqlalchemy (==0.46b0)", "opentelemetry-instrumentation-sqlite3 (==0.46b0)", "opentelemetry-instrumentation-starlette (==0.46b0)", "opentelemetry-instrumentation-system-metrics (==0.46b0)", "opentelemetry-instrumentation-threading (==0.46b0)", "opentelemetry-instrumentation-tornado (==0.46b0)", "opentelemetry-instrumentation-tortoiseorm (==0.46b0)", "opentelemetry-instrumentation-urllib (==0.46b0)", "opentelemetry-instrumentation-urllib3 (==0.46b0)", "opentelemetry-instrumentation-wsgi (==0.46b0)"]
|
||||
pure-eval = ["asttokens", "executing", "pure-eval"]
|
||||
pymongo = ["pymongo (>=3.1)"]
|
||||
pyspark = ["pyspark (>=2.4.4)"]
|
||||
|
|
@ -3235,4 +3246,4 @@ local = []
|
|||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.10,<3.13"
|
||||
content-hash = "4f566531a8539ddc81cb91a7e7f9b723c84679f0af5bb8619f7b02f9ffc6cfaa"
|
||||
content-hash = "7e46144d27c633214f00e73e496c0e4d56db1fb47032a21861677ec275b79d86"
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ pyperclip = "^1.8.2"
|
|||
uncurl = "^0.0.11"
|
||||
sentry-sdk = {extras = ["fastapi", "loguru"], version = "^2.5.1"}
|
||||
chardet = "^5.2.0"
|
||||
firecrawl-py = "^0.0.16"
|
||||
|
||||
|
||||
[tool.poetry.extras]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue