refactor: reorganize components and update PromptComponent with priority attribute (#8667)

* Update styleUtils.ts

* update to prompt component

* update to template

* update to mcp component

* update to smart function

* [autofix.ci] apply automated fixes

* update to templates

* fix sidebar

* change name

* update import

* update import

* update import

* [autofix.ci] apply automated fixes

* fix import

* fix ollama

* fix ruff

* refactor(agent): standardize memory handling and update chat history logic (#8715)

* update chat history

* update to agents

* Update Simple Agent.json

* update to templates

* ruff errors

* Update agent.py

* Update test_agent_component.py

* [autofix.ci] apply automated fixes

* update templates

* test fix

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Mike Fortman <michael.fortman@datastax.com>

* fix prompt change

* feat(message): support sequencing of multiple streamable models (#8434)

* feat: update OpenAI model parameters handling for reasoning models

* feat: extend input_value type in LCModelComponent to support AsyncIterator and Iterator

* refactor: remove assert_streaming_sequence method and related checks from Graph class

* feat: add consume_iterator method to Message class for handling iterators

* test: add unit tests for OpenAIModelComponent functionality and integration

* feat: update OpenAIModelComponent to include temperature and seed parameters in build_model method

* feat: rename consume_iterator method to consume_iterator_in_text and update its implementation for handling text

* feat: add is_connected_to_chat_output method to Component class for improved message handling

* feat: refactor LCModelComponent methods to support asynchronous message handling and improve chat output integration

* refactor: remove consume_iterator_in_text method from Message class and clean up LCModelComponent input handling

* fix: update import paths for input components in multiple starter project JSON files

* fix: enhance error message formatting in ErrorMessage class to handle additional exception attributes

* refactor: remove validate_stream calls from generate_flow_events and Graph class to streamline flow processing

* fix: handle asyncio.CancelledError in aadd_messagetables to ensure proper session rollback and retry logic

* refactor: streamline message handling in LCModelComponent by replacing async invocation with synchronous calls and updating message text handling

* refactor: enhance message handling in LCModelComponent by introducing lf_message for improved return value management and updating properties for consistency

* feat: add _build_source method to Component class for enhanced source handling and flexibility in source object management

* feat: enhance LCModelComponent by adding _handle_stream method for improved streaming response handling and refactoring chat output integration

* feat: update MemoryComponent to enhance message retrieval and storage functionality, including new sender type handling and output options for text and dataframe formats

* test: refactor LanguageModelComponent tests to use ComponentTestBaseWithoutClient and add tests for Google model creation and error handling

* test: add fixtures for API keys and implement live API tests for OpenAI, Anthropic, and Google models

* fix: reorder JSON properties for consistency in starter projects

* Updated JSON files for various starter projects to ensure consistent ordering of properties, specifically moving "type" to follow "selected_output" for better readability and maintainability.
* Affected files: Basic Prompt Chaining.json, Blog Writer.json, Financial Report Parser.json, Hybrid Search RAG.json, SEO Keyword Generator.json.

* refactor: simplify input_value type in LCModelComponent

* Updated the input_value parameter in LCModelComponent to remove AsyncIterator and Iterator types, streamlining the input options to only str and Message for improved clarity and maintainability.
* This change enhances the documentation and understanding of the expected input types for the component.

* fix: clarify comment for handling source in Component class

* refactor: remove unnecessary mocking in OpenAI model integration tests

* auto update

* update

* [autofix.ci] apply automated fixes

* fix openai import

* revert template changes

* test fixes

* update templates

* [autofix.ci] apply automated fixes

* fix tests

* fix order

* fix prompts import

* fix frontend tests

* fix frontend

* [autofix.ci] apply automated fixes

* add charmander

* [autofix.ci] apply automated fixes

* fix prompt frontend

* fix frontend

* test fix

* [autofix.ci] apply automated fixes

* change pokedex

* remove pokedex extra

* update template

* name fix

* update template

* mcp test fix

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: cristhianzl <cristhian.lousa@gmail.com>
Co-authored-by: Yuqi Tang <yuqi.tang@datastax.com>
Co-authored-by: Mike Fortman <michael.fortman@datastax.com>
Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>
This commit is contained in:
Edwin Jose 2025-06-27 12:02:06 -05:00 • committed by GitHub
commit 5ba8f91c9a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
96 changed files with 234 additions and 218 deletions

View file

@ -2,13 +2,13 @@ from typing_extensions import TypedDict
from langflow.base.models.model import LCModelComponent
from langflow.components.amazon.amazon_bedrock_model import AmazonBedrockComponent
from langflow.components.languagemodels.anthropic import AnthropicModelComponent
from langflow.components.languagemodels.azure_openai import AzureChatOpenAIComponent
from langflow.components.languagemodels.google_generative_ai import GoogleGenerativeAIComponent
from langflow.components.anthropic.anthropic import AnthropicModelComponent
from langflow.components.azure.azure_openai import AzureChatOpenAIComponent
from langflow.components.google.google_generative_ai import GoogleGenerativeAIComponent
from langflow.components.languagemodels.groq import GroqModel
from langflow.components.languagemodels.nvidia import NVIDIAModelComponent
from langflow.components.languagemodels.openai_chat_model import OpenAIModelComponent
from langflow.components.languagemodels.sambanova import SambaNovaComponent
from langflow.components.nvidia.nvidia import NVIDIAModelComponent
from langflow.components.openai.openai_chat_model import OpenAIModelComponent
from langflow.inputs.inputs import InputTypes, SecretStrInput
from langflow.template.field.base import Input
@ -89,7 +89,7 @@ def create_input_fields_dict(inputs: list[Input], prefix: str) -> dict[str, Inpu
def _get_google_generative_ai_inputs_and_fields():
try:
from langflow.components.languagemodels.google_generative_ai import GoogleGenerativeAIComponent
from langflow.components.google.google_generative_ai import GoogleGenerativeAIComponent
google_generative_ai_inputs = get_filtered_inputs(GoogleGenerativeAIComponent)
except ImportError as e:
@ -103,7 +103,7 @@ def _get_google_generative_ai_inputs_and_fields():
def _get_openai_inputs_and_fields():
try:
from langflow.components.languagemodels.openai_chat_model import OpenAIModelComponent
from langflow.components.openai.openai_chat_model import OpenAIModelComponent
openai_inputs = get_filtered_inputs(OpenAIModelComponent)
except ImportError as e:
@ -114,7 +114,7 @@ def _get_openai_inputs_and_fields():
def _get_azure_inputs_and_fields():
try:
from langflow.components.languagemodels.azure_openai import AzureChatOpenAIComponent
from langflow.components.azure.azure_openai import AzureChatOpenAIComponent
azure_inputs = get_filtered_inputs(AzureChatOpenAIComponent)
except ImportError as e:
@ -136,7 +136,7 @@ def _get_groq_inputs_and_fields():
def _get_anthropic_inputs_and_fields():
try:
from langflow.components.languagemodels.anthropic import AnthropicModelComponent
from langflow.components.anthropic.anthropic import AnthropicModelComponent
anthropic_inputs = get_filtered_inputs(AnthropicModelComponent)
except ImportError as e:
@ -147,7 +147,7 @@ def _get_anthropic_inputs_and_fields():
def _get_nvidia_inputs_and_fields():
try:
from langflow.components.languagemodels.nvidia import NVIDIAModelComponent
from langflow.components.nvidia.nvidia import NVIDIAModelComponent
nvidia_inputs = get_filtered_inputs(NVIDIAModelComponent)
except ImportError as e:

View file

@ -1,3 +1,4 @@
from .agent import AgentComponent
from .mcp_component import MCPToolsComponent
__all__ = ["AgentComponent"]
__all__ = ["AgentComponent", "MCPToolsComponent"]

View file

@ -0,0 +1,467 @@
import re
from typing import Any
from langflow.api.v2.mcp import get_server
from langflow.base.mcp.util import (
MCPSseClient,
MCPStdioClient,
create_input_schema_from_json_schema,
update_tools,
)
from langflow.custom.custom_component.component_with_cache import ComponentWithCache
from langflow.inputs.inputs import InputTypes
from langflow.io import DropdownInput, McpInput, MessageTextInput, Output # Import McpInput from langflow.io
from langflow.io.schema import flatten_schema, schema_to_langflow_inputs
from langflow.logging import logger
from langflow.schema.dataframe import DataFrame
from langflow.schema.message import Message
from langflow.services.auth.utils import create_user_longterm_token
from langflow.services.cache.utils import CacheMiss
# Import get_server from the backend API
from langflow.services.database.models.user.crud import get_user_by_id
from langflow.services.deps import get_session, get_settings_service, get_storage_service
def maybe_unflatten_dict(flat: dict[str, Any]) -> dict[str, Any]:
"""If any key looks nested (contains a dot or "[index]"), rebuild the.
full nested structure; otherwise return flat as is.
"""
# Quick check: do we have any nested keys?
if not any(re.search(r"\.|\[\d+\]", key) for key in flat):
return flat
# Otherwise, unflatten into dicts/lists
nested: dict[str, Any] = {}
array_re = re.compile(r"^(.+)\[(\d+)\]$")
for key, val in flat.items():
parts = key.split(".")
cur = nested
for i, part in enumerate(parts):
m = array_re.match(part)
# Array segment?
if m:
name, idx = m.group(1), int(m.group(2))
lst = cur.setdefault(name, [])
# Ensure list is big enough
while len(lst) <= idx:
lst.append({})
if i == len(parts) - 1:
lst[idx] = val
else:
cur = lst[idx]
# Normal object key
elif i == len(parts) - 1:
cur[part] = val
else:
cur = cur.setdefault(part, {})
return nested
class MCPToolsComponent(ComponentWithCache):
schema_inputs: list = []
stdio_client: MCPStdioClient = MCPStdioClient()
sse_client: MCPSseClient = MCPSseClient()
tools: list = []
_tool_cache: dict = {}
_last_selected_server: str | None = None # Cache for the last selected server
default_keys: list[str] = [
"code",
"_type",
"tool_mode",
"tool_placeholder",
"mcp_server",
"tool",
]
display_name = "MCP Tools"
description = "Connect to an MCP server to use its tools."
icon = "Mcp"
name = "MCPTools"
inputs = [
McpInput(
name="mcp_server",
display_name="MCP Server",
info="Select the MCP Server that will be used by this component",
real_time_refresh=True,
),
DropdownInput(
name="tool",
display_name="Tool",
options=[],
value="",
info="Select the tool to execute",
show=False,
required=True,
real_time_refresh=True,
),
MessageTextInput(
name="tool_placeholder",
display_name="Tool Placeholder",
info="Placeholder for the tool",
value="",
show=False,
tool_mode=True,
),
]
outputs = [
Output(display_name="Response", name="response", method="build_output"),
]
async def _validate_schema_inputs(self, tool_obj) -> list[InputTypes]:
"""Validate and process schema inputs for a tool."""
try:
if not tool_obj or not hasattr(tool_obj, "args_schema"):
msg = "Invalid tool object or missing input schema"
raise ValueError(msg)
flat_schema = flatten_schema(tool_obj.args_schema.schema())
input_schema = create_input_schema_from_json_schema(flat_schema)
if not input_schema:
msg = f"Empty input schema for tool '{tool_obj.name}'"
raise ValueError(msg)
schema_inputs = schema_to_langflow_inputs(input_schema)
if not schema_inputs:
msg = f"No input parameters defined for tool '{tool_obj.name}'"
logger.warning(msg)
return []
except Exception as e:
msg = f"Error validating schema inputs: {e!s}"
logger.exception(msg)
raise ValueError(msg) from e
else:
return schema_inputs
async def update_tool_list(self, mcp_server_value=None):
# Accepts mcp_server_value as dict {name, config} or uses self.mcp_server
mcp_server = mcp_server_value if mcp_server_value is not None else getattr(self, "mcp_server", None)
server_name = None
server_config_from_value = None
if isinstance(mcp_server, dict):
server_name = mcp_server.get("name")
server_config_from_value = mcp_server.get("config")
else:
server_name = mcp_server
if not server_name:
self.tools = []
return [], {"name": server_name, "config": server_config_from_value}
# Use shared cache if available
cached = self._shared_component_cache.get(server_name)
if not isinstance(cached, CacheMiss):
self.tools = cached["tools"]
self.tool_names = cached["tool_names"]
self._tool_cache = cached["tool_cache"]
server_config_from_value = cached["config"]
return self.tools, {"name": server_name, "config": server_config_from_value}
try:
async for db in get_session():
user_id, _ = await create_user_longterm_token(db)
current_user = await get_user_by_id(db, user_id)
# Try to get server config from DB/API
server_config = await get_server(
server_name,
current_user,
db,
storage_service=get_storage_service(),
settings_service=get_settings_service(),
)
# If get_server returns empty but we have a config, use it
if not server_config and server_config_from_value:
server_config = server_config_from_value
if not server_config:
self.tools = []
return [], {"name": server_name, "config": server_config}
_, tool_list, tool_cache = await update_tools(
server_name=server_name,
server_config=server_config,
mcp_stdio_client=self.stdio_client,
mcp_sse_client=self.sse_client,
)
self.tool_names = [tool.name for tool in tool_list if hasattr(tool, "name")]
self._tool_cache = tool_cache
self.tools = tool_list
# Cache the result using shared cache
self._shared_component_cache.set(
server_name,
{
"tools": tool_list,
"tool_names": self.tool_names,
"tool_cache": tool_cache,
"config": server_config,
},
)
return tool_list, {"name": server_name, "config": server_config}
except Exception as e:
msg = f"Error updating tool list: {e!s}"
logger.exception(msg)
raise ValueError(msg) from e
async def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None) -> dict:
"""Toggle the visibility of connection-specific fields based on the selected mode."""
try:
if field_name == "tool":
try:
if len(self.tools) == 0:
try:
self.tools, build_config["mcp_server"]["value"] = await self.update_tool_list()
build_config["tool"]["options"] = [tool.name for tool in self.tools]
except ValueError:
build_config["tool"]["options"] = []
build_config["tool"]["value"] = ""
build_config["tool"]["placeholder"] = "Error on MCP Server"
return build_config
build_config["tool"]["placeholder"] = ""
if field_value == "":
return build_config
tool_obj = None
for tool in self.tools:
if tool.name == self.tool:
tool_obj = tool
break
if tool_obj is None:
msg = f"Tool {self.tool} not found in available tools: {self.tools}"
logger.warning(msg)
return build_config
await self._update_tool_config(build_config, field_value)
except Exception as e:
build_config["tool"]["options"] = []
msg = f"Failed to update tools: {e!s}"
raise ValueError(msg) from e
else:
return build_config
elif field_name == "mcp_server":
if not field_value:
build_config["tool"]["show"] = False
build_config["tool"]["options"] = []
build_config["tool"]["value"] = ""
build_config["tool"]["placeholder"] = ""
self.remove_non_default_keys(build_config)
return build_config
current_server_name = field_value.get("name") if isinstance(field_value, dict) else field_value
# To avoid unnecessary updates, only proceed if the server has actually changed
if self._last_selected_server == current_server_name:
return build_config
# Determine if "Tool Mode" is active by checking if the tool dropdown is hidden.
# The check for _last_selected_server handles the initial state where the dropdown is
# hidden by default but we are not yet in "Tool Mode".
is_in_tool_mode = build_config["tools_metadata"]["value"]
self._last_selected_server = current_server_name
self.tools = [] # Clear previous tools
self.remove_non_default_keys(build_config) # Clear previous tool inputs
# Only show the tool dropdown if not in tool_mode
if not is_in_tool_mode:
build_config["tool"]["show"] = True
build_config["tool"]["placeholder"] = "Loading tools..."
build_config["tool"]["options"] = []
build_config["tool"]["value"] = ""
else:
# Keep the tool dropdown hidden if in tool_mode
build_config["tool"]["show"] = False
try:
# Fetch tools for the newly selected server
tools, server_info = await self.update_tool_list(field_value)
build_config["mcp_server"]["value"] = server_info
if tools:
tool_names = [tool.name for tool in tools]
if not is_in_tool_mode:
build_config["tool"]["options"] = tool_names
build_config["tool"]["placeholder"] = "Select a tool"
elif not is_in_tool_mode:
build_config["tool"]["placeholder"] = "No tools found"
except (ValueError, AttributeError, KeyError, TypeError, ConnectionError, TimeoutError) as e:
logger.error(f"Failed to fetch tools for server '{current_server_name}': {e}")
if not is_in_tool_mode:
build_config["tool"]["placeholder"] = "Error fetching tools"
build_config["tool"]["options"] = []
build_config["tool"]["value"] = ""
finally:
# Ensure we don't show inputs for a tool that might no longer be valid
await self._update_tool_config(build_config, "")
elif field_name == "tool_mode":
try:
self.tools, build_config["mcp_server"]["value"] = await self.update_tool_list()
except ValueError:
if not build_config["tools_metadata"]["show"]:
build_config["tool"]["show"] = True
build_config["tool"]["options"] = []
build_config["tool"]["value"] = ""
build_config["tool"]["placeholder"] = "Error on MCP Server"
else:
build_config["tool"]["show"] = False
build_config["tool"]["placeholder"] = ""
build_config["tool"]["show"] = not field_value
for key, value in list(build_config.items()):
if key not in self.default_keys and isinstance(value, dict) and "show" in value:
build_config[key]["show"] = not field_value
if not field_value:
build_config["tool"]["options"] = [tool.name for tool in self.tools]
await self._update_tool_config(build_config, build_config["tool"]["value"])
except Exception as e:
msg = f"Error in update_build_config: {e!s}"
logger.exception(msg)
raise ValueError(msg) from e
else:
return build_config
def get_inputs_for_all_tools(self, tools: list) -> dict:
"""Get input schemas for all tools."""
inputs = {}
for tool in tools:
if not tool or not hasattr(tool, "name"):
continue
try:
flat_schema = flatten_schema(tool.args_schema.schema())
input_schema = create_input_schema_from_json_schema(flat_schema)
langflow_inputs = schema_to_langflow_inputs(input_schema)
inputs[tool.name] = langflow_inputs
except (AttributeError, ValueError, TypeError, KeyError) as e:
msg = f"Error getting inputs for tool {getattr(tool, 'name', 'unknown')}: {e!s}"
logger.exception(msg)
continue
return inputs
def remove_input_schema_from_build_config(
self, build_config: dict, tool_name: str, input_schema: dict[list[InputTypes], Any]
):
"""Remove the input schema for the tool from the build config."""
# Keep only schemas that don't belong to the current tool
input_schema = {k: v for k, v in input_schema.items() if k != tool_name}
# Remove all inputs from other tools
for value in input_schema.values():
for _input in value:
if _input.name in build_config:
build_config.pop(_input.name)
def remove_non_default_keys(self, build_config: dict) -> None:
"""Remove non-default keys from the build config."""
for key in list(build_config.keys()):
if key not in self.default_keys:
build_config.pop(key)
async def _update_tool_config(self, build_config: dict, tool_name: str) -> None:
"""Update tool configuration with proper error handling."""
if not self.tools:
self.tools, build_config["mcp_server"]["value"] = await self.update_tool_list()
if not tool_name:
return
tool_obj = next((tool for tool in self.tools if tool.name == tool_name), None)
if not tool_obj:
msg = f"Tool {tool_name} not found in available tools: {self.tools}"
self.remove_non_default_keys(build_config)
build_config["tool"]["value"] = ""
logger.warning(msg)
return
try:
# Store current values before removing inputs
current_values = {}
for key, value in build_config.items():
if key not in self.default_keys and isinstance(value, dict) and "value" in value:
current_values[key] = value["value"]
# Get all tool inputs and remove old ones
input_schema_for_all_tools = self.get_inputs_for_all_tools(self.tools)
self.remove_input_schema_from_build_config(build_config, tool_name, input_schema_for_all_tools)
# Get and validate new inputs
self.schema_inputs = await self._validate_schema_inputs(tool_obj)
if not self.schema_inputs:
msg = f"No input parameters to configure for tool '{tool_name}'"
logger.info(msg)
return
# Add new inputs to build config
for schema_input in self.schema_inputs:
if not schema_input or not hasattr(schema_input, "name"):
msg = "Invalid schema input detected, skipping"
logger.warning(msg)
continue
try:
name = schema_input.name
input_dict = schema_input.to_dict()
input_dict.setdefault("value", None)
input_dict.setdefault("required", True)
build_config[name] = input_dict
# Preserve existing value if the parameter name exists in current_values
if name in current_values:
build_config[name]["value"] = current_values[name]
except (AttributeError, KeyError, TypeError) as e:
msg = f"Error processing schema input {schema_input}: {e!s}"
logger.exception(msg)
continue
except ValueError as e:
msg = f"Schema validation error for tool {tool_name}: {e!s}"
logger.exception(msg)
self.schema_inputs = []
return
except (AttributeError, KeyError, TypeError) as e:
msg = f"Error updating tool config: {e!s}"
logger.exception(msg)
raise ValueError(msg) from e
async def build_output(self) -> DataFrame:
"""Build output with improved error handling and validation."""
try:
self.tools, _ = await self.update_tool_list()
if self.tool != "":
exec_tool = self._tool_cache[self.tool]
tool_args = self.get_inputs_for_all_tools(self.tools)[self.tool]
kwargs = {}
for arg in tool_args:
value = getattr(self, arg.name, None)
if value:
if isinstance(value, Message):
kwargs[arg.name] = value.text
else:
kwargs[arg.name] = value
unflattened_kwargs = maybe_unflatten_dict(kwargs)
output = await exec_tool.coroutine(**unflattened_kwargs)
tool_content = []
for item in output.content:
item_dict = item.model_dump()
tool_content.append(item_dict)
return DataFrame(data=tool_content)
return DataFrame(data=[{"error": "You must select a tool"}])
except Exception as e:
msg = f"Error in build_output: {e!s}"
logger.exception(msg)
raise ValueError(msg) from e
async def _get_tools(self):
"""Get cached tools or update if necessary."""
mcp_server = getattr(self, "mcp_server", None)
tools, _ = await self.update_tool_list(mcp_server)
return tools

View file

@ -0,0 +1,7 @@
from .aiml import AIMLModelComponent
from .aiml_embeddings import AIMLEmbeddingsComponent
__all__ = [
"AIMLEmbeddingsComponent",
"AIMLModelComponent",
]

View file

@ -0,0 +1,112 @@
from langchain_openai import ChatOpenAI
from pydantic.v1 import SecretStr
from typing_extensions import override
from langflow.base.models.aiml_constants import AimlModels
from langflow.base.models.model import LCModelComponent
from langflow.field_typing import LanguageModel
from langflow.field_typing.range_spec import RangeSpec
from langflow.inputs.inputs import (
DictInput,
DropdownInput,
IntInput,
SecretStrInput,
SliderInput,
StrInput,
)
class AIMLModelComponent(LCModelComponent):
display_name = "AIML"
description = "Generates text using AIML LLMs."
icon = "AIML"
name = "AIMLModel"
documentation = "https://docs.aimlapi.com/api-reference"
inputs = [
*LCModelComponent._base_inputs,
IntInput(
name="max_tokens",
display_name="Max Tokens",
advanced=True,
info="The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
range_spec=RangeSpec(min=0, max=128000),
),
DictInput(name="model_kwargs", display_name="Model Kwargs", advanced=True),
DropdownInput(
name="model_name",
display_name="Model Name",
advanced=False,
options=[],
refresh_button=True,
),
StrInput(
name="aiml_api_base",
display_name="AIML API Base",
advanced=True,
info="The base URL of the OpenAI API. Defaults to https://api.aimlapi.com . "
"You can change this to use other APIs like JinaChat, LocalAI and Prem.",
),
SecretStrInput(
name="api_key",
display_name="AIML API Key",
info="The AIML API Key to use for the OpenAI model.",
advanced=False,
value="AIML_API_KEY",
required=True,
),
SliderInput(
name="temperature", display_name="Temperature", value=0.1, range_spec=RangeSpec(min=0, max=2, step=0.01)
),
]
@override
def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):
if field_name in {"api_key", "aiml_api_base", "model_name"}:
aiml = AimlModels()
aiml.get_aiml_models()
build_config["model_name"]["options"] = aiml.chat_models
return build_config
def build_model(self) -> LanguageModel: # type: ignore[type-var]
aiml_api_key = self.api_key
temperature = self.temperature
model_name: str = self.model_name
max_tokens = self.max_tokens
model_kwargs = self.model_kwargs or {}
aiml_api_base = self.aiml_api_base or "https://api.aimlapi.com/v2"
openai_api_key = aiml_api_key.get_secret_value() if isinstance(aiml_api_key, SecretStr) else aiml_api_key
# TODO: Once OpenAI fixes their o1 models, this part will need to be removed
# to work correctly with o1 temperature settings.
if "o1" in model_name:
temperature = 1
return ChatOpenAI(
model=model_name,
temperature=temperature,
api_key=openai_api_key,
base_url=aiml_api_base,
max_tokens=max_tokens or None,
**model_kwargs,
)
def _get_exception_message(self, e: Exception):
"""Get a message from an OpenAI exception.
Args:
e (Exception): The exception to get the message from.
Returns:
str: The message from the exception.
"""
try:
from openai.error import BadRequestError
except ImportError:
return None
if isinstance(e, BadRequestError):
message = e.json_body.get("error", {}).get("message", "")
if message:
return message
return None

View file

@ -0,0 +1,37 @@
from langflow.base.embeddings.aiml_embeddings import AIMLEmbeddingsImpl
from langflow.base.embeddings.model import LCEmbeddingsModel
from langflow.field_typing import Embeddings
from langflow.inputs.inputs import DropdownInput
from langflow.io import SecretStrInput
class AIMLEmbeddingsComponent(LCEmbeddingsModel):
display_name = "AI/ML Embeddings"
description = "Generate embeddings using the AI/ML API."
icon = "AI/ML"
name = "AIMLEmbeddings"
inputs = [
DropdownInput(
name="model_name",
display_name="Model Name",
options=[
"text-embedding-3-small",
"text-embedding-3-large",
"text-embedding-ada-002",
],
required=True,
),
SecretStrInput(
name="aiml_api_key",
display_name="AI/ML API Key",
value="AIML_API_KEY",
required=True,
),
]
def build_embeddings(self) -> Embeddings:
return AIMLEmbeddingsImpl(
api_key=self.aiml_api_key,
model=self.model_name,
)

View file

@ -0,0 +1,5 @@
from .anthropic import AnthropicModelComponent
__all__ = [
"AnthropicModelComponent",
]

View file

@ -0,0 +1,189 @@
from typing import Any, cast
import requests
from loguru import logger
from pydantic import ValidationError
from langflow.base.models.anthropic_constants import (
ANTHROPIC_MODELS,
DEFAULT_ANTHROPIC_API_URL,
TOOL_CALLING_SUPPORTED_ANTHROPIC_MODELS,
TOOL_CALLING_UNSUPPORTED_ANTHROPIC_MODELS,
)
from langflow.base.models.model import LCModelComponent
from langflow.field_typing import LanguageModel
from langflow.field_typing.range_spec import RangeSpec
from langflow.io import BoolInput, DropdownInput, IntInput, MessageTextInput, SecretStrInput, SliderInput
from langflow.schema.dotdict import dotdict
class AnthropicModelComponent(LCModelComponent):
display_name = "Anthropic"
description = "Generate text using Anthropic Chat&Completion LLMs with prefill support."
icon = "Anthropic"
name = "AnthropicModel"
inputs = [
*LCModelComponent._base_inputs,
IntInput(
name="max_tokens",
display_name="Max Tokens",
advanced=True,
value=4096,
info="The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
),
DropdownInput(
name="model_name",
display_name="Model Name",
options=ANTHROPIC_MODELS,
refresh_button=True,
value=ANTHROPIC_MODELS[0],
combobox=True,
),
SecretStrInput(
name="api_key",
display_name="Anthropic API Key",
info="Your Anthropic API key.",
value=None,
required=True,
real_time_refresh=True,
),
SliderInput(
name="temperature",
display_name="Temperature",
value=0.1,
info="Run inference with this temperature. Must by in the closed interval [0.0, 1.0].",
range_spec=RangeSpec(min=0, max=1, step=0.01),
advanced=True,
),
MessageTextInput(
name="base_url",
display_name="Anthropic API URL",
info="Endpoint of the Anthropic API. Defaults to 'https://api.anthropic.com' if not specified.",
value=DEFAULT_ANTHROPIC_API_URL,
real_time_refresh=True,
advanced=True,
),
BoolInput(
name="tool_model_enabled",
display_name="Enable Tool Models",
info=(
"Select if you want to use models that can work with tools. If yes, only those models will be shown."
),
advanced=False,
value=False,
real_time_refresh=True,
),
MessageTextInput(
name="prefill", display_name="Prefill", info="Prefill text to guide the model's response.", advanced=True
),
]
def build_model(self) -> LanguageModel: # type: ignore[type-var]
try:
from langchain_anthropic.chat_models import ChatAnthropic
except ImportError as e:
msg = "langchain_anthropic is not installed. Please install it with `pip install langchain_anthropic`."
raise ImportError(msg) from e
try:
max_tokens_value = getattr(self, "max_tokens", "")
max_tokens_value = 4096 if max_tokens_value == "" else int(max_tokens_value)
output = ChatAnthropic(
model=self.model_name,
anthropic_api_key=self.api_key,
max_tokens=max_tokens_value,
temperature=self.temperature,
anthropic_api_url=self.base_url or DEFAULT_ANTHROPIC_API_URL,
streaming=self.stream,
)
except ValidationError:
raise
except Exception as e:
msg = "Could not connect to Anthropic API."
raise ValueError(msg) from e
return output
def get_models(self, tool_model_enabled: bool | None = None) -> list[str]:
try:
import anthropic
client = anthropic.Anthropic(api_key=self.api_key)
models = client.models.list(limit=20).data
model_ids = ANTHROPIC_MODELS + [model.id for model in models]
except (ImportError, ValueError, requests.exceptions.RequestException) as e:
logger.exception(f"Error getting model names: {e}")
model_ids = ANTHROPIC_MODELS
if tool_model_enabled:
try:
from langchain_anthropic.chat_models import ChatAnthropic
except ImportError as e:
msg = "langchain_anthropic is not installed. Please install it with `pip install langchain_anthropic`."
raise ImportError(msg) from e
# Create a new list instead of modifying while iterating
filtered_models = []
for model in model_ids:
if model in TOOL_CALLING_SUPPORTED_ANTHROPIC_MODELS:
filtered_models.append(model)
continue
model_with_tool = ChatAnthropic(
model=model, # Use the current model being checked
anthropic_api_key=self.api_key,
anthropic_api_url=cast(str, self.base_url) or DEFAULT_ANTHROPIC_API_URL,
)
if (
not self.supports_tool_calling(model_with_tool)
or model in TOOL_CALLING_UNSUPPORTED_ANTHROPIC_MODELS
):
continue
filtered_models.append(model)
return filtered_models
return model_ids
def _get_exception_message(self, exception: Exception) -> str | None:
"""Get a message from an Anthropic exception.
Args:
exception (Exception): The exception to get the message from.
Returns:
str: The message from the exception.
"""
try:
from anthropic import BadRequestError
except ImportError:
return None
if isinstance(exception, BadRequestError):
message = exception.body.get("error", {}).get("message")
if message:
return message
return None
def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None):
if "base_url" in build_config and build_config["base_url"]["value"] is None:
build_config["base_url"]["value"] = DEFAULT_ANTHROPIC_API_URL
self.base_url = DEFAULT_ANTHROPIC_API_URL
if field_name in {"base_url", "model_name", "tool_model_enabled", "api_key"} and field_value:
try:
if len(self.api_key) == 0:
ids = ANTHROPIC_MODELS
else:
try:
ids = self.get_models(tool_model_enabled=self.tool_model_enabled)
except (ImportError, ValueError, requests.exceptions.RequestException) as e:
logger.exception(f"Error getting model names: {e}")
ids = ANTHROPIC_MODELS
build_config["model_name"]["options"] = ids
build_config["model_name"]["value"] = ids[0]
build_config["model_name"]["combobox"] = True
except Exception as e:
msg = f"Error getting model names: {e}"
raise ValueError(msg) from e
return build_config

View file

@ -0,0 +1,7 @@
from .azure_openai import AzureChatOpenAIComponent
from .azure_openai_embeddings import AzureOpenAIEmbeddingsComponent
__all__ = [
"AzureChatOpenAIComponent",
"AzureOpenAIEmbeddingsComponent",
]

View file

@ -0,0 +1,95 @@
from langchain_openai import AzureChatOpenAI
from langflow.base.models.model import LCModelComponent
from langflow.field_typing import LanguageModel
from langflow.field_typing.range_spec import RangeSpec
from langflow.inputs.inputs import MessageTextInput
from langflow.io import DropdownInput, IntInput, SecretStrInput, SliderInput
class AzureChatOpenAIComponent(LCModelComponent):
display_name: str = "Azure OpenAI"
description: str = "Generate text using Azure OpenAI LLMs."
documentation: str = "https://python.langchain.com/docs/integrations/llms/azure_openai"
beta = False
icon = "Azure"
name = "AzureOpenAIModel"
AZURE_OPENAI_API_VERSIONS = [
"2024-06-01",
"2024-07-01-preview",
"2024-08-01-preview",
"2024-09-01-preview",
"2024-10-01-preview",
"2023-05-15",
"2023-12-01-preview",
"2024-02-15-preview",
"2024-03-01-preview",
"2024-12-01-preview",
"2025-01-01-preview",
"2025-02-01-preview",
]
inputs = [
*LCModelComponent._base_inputs,
MessageTextInput(
name="azure_endpoint",
display_name="Azure Endpoint",
info="Your Azure endpoint, including the resource. Example: `https://example-resource.azure.openai.com/`",
required=True,
),
MessageTextInput(name="azure_deployment", display_name="Deployment Name", required=True),
SecretStrInput(name="api_key", display_name="API Key", required=True),
DropdownInput(
name="api_version",
display_name="API Version",
options=sorted(AZURE_OPENAI_API_VERSIONS, reverse=True),
value=next(
(
version
for version in sorted(AZURE_OPENAI_API_VERSIONS, reverse=True)
if not version.endswith("-preview")
),
AZURE_OPENAI_API_VERSIONS[0],
),
),
SliderInput(
name="temperature",
display_name="Temperature",
value=0.7,
range_spec=RangeSpec(min=0, max=2, step=0.01),
info="Controls randomness. Lower values are more deterministic, higher values are more creative.",
advanced=True,
),
IntInput(
name="max_tokens",
display_name="Max Tokens",
advanced=True,
info="The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
),
]
def build_model(self) -> LanguageModel: # type: ignore[type-var]
azure_endpoint = self.azure_endpoint
azure_deployment = self.azure_deployment
api_version = self.api_version
api_key = self.api_key
temperature = self.temperature
max_tokens = self.max_tokens
stream = self.stream
try:
output = AzureChatOpenAI(
azure_endpoint=azure_endpoint,
azure_deployment=azure_deployment,
api_version=api_version,
api_key=api_key,
temperature=temperature,
max_tokens=max_tokens or None,
streaming=stream,
)
except Exception as e:
msg = f"Could not connect to AzureOpenAI API: {e}"
raise ValueError(msg) from e
return output

View file

@ -0,0 +1,83 @@
from langchain_openai import AzureOpenAIEmbeddings
from langflow.base.models.model import LCModelComponent
from langflow.base.models.openai_constants import OPENAI_EMBEDDING_MODEL_NAMES
from langflow.field_typing import Embeddings
from langflow.io import DropdownInput, IntInput, MessageTextInput, Output, SecretStrInput
class AzureOpenAIEmbeddingsComponent(LCModelComponent):
display_name: str = "Azure OpenAI Embeddings"
description: str = "Generate embeddings using Azure OpenAI models."
documentation: str = "https://python.langchain.com/docs/integrations/text_embedding/azureopenai"
icon = "Azure"
name = "AzureOpenAIEmbeddings"
API_VERSION_OPTIONS = [
"2022-12-01",
"2023-03-15-preview",
"2023-05-15",
"2023-06-01-preview",
"2023-07-01-preview",
"2023-08-01-preview",
]
inputs = [
DropdownInput(
name="model",
display_name="Model",
advanced=False,
options=OPENAI_EMBEDDING_MODEL_NAMES,
value=OPENAI_EMBEDDING_MODEL_NAMES[0],
),
MessageTextInput(
name="azure_endpoint",
display_name="Azure Endpoint",
required=True,
info="Your Azure endpoint, including the resource. Example: `https://example-resource.azure.openai.com/`",
),
MessageTextInput(
name="azure_deployment",
display_name="Deployment Name",
required=True,
),
DropdownInput(
name="api_version",
display_name="API Version",
options=API_VERSION_OPTIONS,
value=API_VERSION_OPTIONS[-1],
advanced=True,
),
SecretStrInput(
name="api_key",
display_name="API Key",
required=True,
),
IntInput(
name="dimensions",
display_name="Dimensions",
info="The number of dimensions the resulting output embeddings should have. "
"Only supported by certain models.",
advanced=True,
),
]
outputs = [
Output(display_name="Embeddings", name="embeddings", method="build_embeddings"),
]
def build_embeddings(self) -> Embeddings:
try:
embeddings = AzureOpenAIEmbeddings(
model=self.model,
azure_endpoint=self.azure_endpoint,
azure_deployment=self.azure_deployment,
api_version=self.api_version,
api_key=self.api_key,
dimensions=self.dimensions or None,
)
except Exception as e:
msg = f"Could not connect to AzureOpenAIEmbeddings API: {e}"
raise ValueError(msg) from e
return embeddings

View file

@ -3,7 +3,6 @@ from .csv_to_data import CSVToDataComponent
from .directory import DirectoryComponent
from .file import FileComponent
from .json_to_data import JSONToDataComponent
from .mcp_component import MCPToolsComponent
from .news_search import NewsSearchComponent
from .rss import RSSReaderComponent
from .sql_executor import SQLComponent
@ -17,7 +16,6 @@ __all__ = [
"DirectoryComponent",
"FileComponent",
"JSONToDataComponent",
"MCPToolsComponent",
"NewsSearchComponent",
"RSSReaderComponent",
"SQLComponent",

View file

@ -1,6 +1,9 @@
from .astra_assistant_manager import AstraAssistantManager
from .astra_db import AstraDBChatMemory
from .astra_vectorize import AstraVectorizeComponent
from .astradb_cql import AstraDBCQLToolComponent
from .astradb_tool import AstraDBToolComponent
from .cassandra import CassandraChatMemory
from .create_assistant import AssistantsCreateAssistant
from .create_thread import AssistantsCreateThread
from .dotenv import Dotenv
@ -17,7 +20,10 @@ __all__ = [
"AssistantsRun",
"AstraAssistantManager",
"AstraDBCQLToolComponent",
"AstraDBChatMemory",
"AstraDBToolComponent",
"AstraVectorizeComponent",
"CassandraChatMemory",
"Dotenv",
"GetEnvVar",
]

View file

@ -0,0 +1,69 @@
import os
from astrapy.admin import parse_api_endpoint
from langflow.base.memory.model import LCChatMemoryComponent
from langflow.field_typing.constants import Memory
from langflow.inputs.inputs import MessageTextInput, SecretStrInput, StrInput
class AstraDBChatMemory(LCChatMemoryComponent):
display_name = "Astra DB Chat Memory"
description = "Retrieves and store chat messages from Astra DB."
name = "AstraDBChatMemory"
icon: str = "AstraDB"
inputs = [
SecretStrInput(
name="token",
display_name="Astra DB Application Token",
info="Authentication token for accessing Astra DB.",
value="ASTRA_DB_APPLICATION_TOKEN",
required=True,
advanced=os.getenv("ASTRA_ENHANCED", "false").lower() == "true",
),
SecretStrInput(
name="api_endpoint",
display_name="API Endpoint",
info="API endpoint URL for the Astra DB service.",
value="ASTRA_DB_API_ENDPOINT",
required=True,
),
StrInput(
name="collection_name",
display_name="Collection Name",
info="The name of the collection within Astra DB where the vectors will be stored.",
required=True,
),
StrInput(
name="namespace",
display_name="Namespace",
info="Optional namespace within Astra DB to use for the collection.",
advanced=True,
),
MessageTextInput(
name="session_id",
display_name="Session ID",
info="The session ID of the chat. If empty, the current session ID parameter will be used.",
advanced=True,
),
]
def build_message_history(self) -> Memory:
try:
from langchain_astradb.chat_message_histories import AstraDBChatMessageHistory
except ImportError as e:
msg = (
"Could not import langchain Astra DB integration package. "
"Please install it with `pip install langchain-astradb`."
)
raise ImportError(msg) from e
return AstraDBChatMessageHistory(
session_id=self.session_id,
collection_name=self.collection_name,
token=self.token,
api_endpoint=self.api_endpoint,
namespace=self.namespace or None,
environment=parse_api_endpoint(self.api_endpoint).environment,
)

View file

@ -0,0 +1,123 @@
from typing import Any
from langflow.custom.custom_component.component import Component
from langflow.inputs.inputs import DictInput, DropdownInput, MessageTextInput, SecretStrInput
from langflow.template.field.base import Output
class AstraVectorizeComponent(Component):
display_name: str = "Astra Vectorize [DEPRECATED]"
description: str = (
"Configuration options for Astra Vectorize server-side embeddings. "
"This component is deprecated. Please use the Astra DB Component directly."
)
documentation: str = "https://docs.datastax.com/en/astra-db-serverless/databases/embedding-generation.html"
icon = "AstraDB"
name = "AstraVectorize"
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 = [
DropdownInput(
name="provider",
display_name="Provider",
options=VECTORIZE_PROVIDERS_MAPPING.keys(),
value="",
required=True,
),
MessageTextInput(
name="model_name",
display_name="Model Name",
info="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 parameters",
is_list=True,
advanced=True,
),
SecretStrInput(
name="provider_api_key",
display_name="Provider API Key",
info="An alternative to the Astra Authentication that passes an API key for the provider with each request "
"to Astra DB. "
"This may be used when Vectorize is configured for the collection, "
"but no corresponding provider secret is stored within Astra's key management system.",
advanced=True,
),
DictInput(
name="authentication",
display_name="Authentication Parameters",
is_list=True,
advanced=True,
),
DictInput(
name="model_parameters",
display_name="Model Parameters",
advanced=True,
is_list=True,
),
]
outputs = [
Output(display_name="Vectorize", name="config", method="build_options", types=["dict"]),
]
def build_options(self) -> dict[str, Any]:
provider_value = self.VECTORIZE_PROVIDERS_MAPPING[self.provider][0]
authentication = {**(self.authentication or {})}
api_key_name = self.api_key_name
if api_key_name:
authentication["providerKey"] = api_key_name
return {
# must match astrapy.info.VectorServiceOptions
"collection_vector_service_options": {
"provider": provider_value,
"modelName": self.model_name,
"authentication": authentication,
"parameters": self.model_parameters or {},
},
"collection_embedding_api_key": self.provider_api_key,
}

View file

@ -0,0 +1,92 @@
from langflow.base.memory.model import LCChatMemoryComponent
from langflow.field_typing.constants import Memory
from langflow.inputs.inputs import DictInput, MessageTextInput, SecretStrInput
class CassandraChatMemory(LCChatMemoryComponent):
display_name = "Cassandra Chat Memory"
description = "Retrieves and store chat messages from Apache Cassandra."
name = "CassandraChatMemory"
icon = "Cassandra"
inputs = [
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="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="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,
),
MessageTextInput(
name="session_id", display_name="Session ID", info="Session ID for the message.", 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,
),
]
def build_message_history(self) -> Memory:
from langchain_community.chat_message_histories import CassandraChatMessageHistory
try:
import cassio
except ImportError as e:
msg = "Could not import cassio integration package. Please install it with `pip install cassio`."
raise ImportError(msg) from e
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,
)
return CassandraChatMessageHistory(
session_id=self.session_id,
table_name=self.table_name,
keyspace=self.keyspace,
)

View file

@ -1,35 +1,15 @@
from .aiml import AIMLEmbeddingsComponent
from .astra_vectorize import AstraVectorizeComponent
from .azure_openai import AzureOpenAIEmbeddingsComponent
from .cloudflare import CloudflareWorkersAIEmbeddingsComponent
from .cohere import CohereEmbeddingsComponent
from .google_generative_ai import GoogleGenerativeAIEmbeddingsComponent
from .huggingface_inference_api import HuggingFaceInferenceAPIEmbeddingsComponent
from .lmstudioembeddings import LMStudioEmbeddingsComponent
from .mistral import MistralAIEmbeddingsComponent
from .nvidia import NVIDIAEmbeddingsComponent
from .ollama import OllamaEmbeddingsComponent
from .openai import OpenAIEmbeddingsComponent
from .similarity import EmbeddingSimilarityComponent
from .text_embedder import TextEmbedderComponent
from .vertexai import VertexAIEmbeddingsComponent
from .watsonx import WatsonxEmbeddingsComponent
__all__ = [
"AIMLEmbeddingsComponent",
"AstraVectorizeComponent",
"AzureOpenAIEmbeddingsComponent",
"CloudflareWorkersAIEmbeddingsComponent",
"CohereEmbeddingsComponent",
"EmbeddingSimilarityComponent",
"GoogleGenerativeAIEmbeddingsComponent",
"HuggingFaceInferenceAPIEmbeddingsComponent",
"LMStudioEmbeddingsComponent",
"MistralAIEmbeddingsComponent",
"NVIDIAEmbeddingsComponent",
"OllamaEmbeddingsComponent",
"OpenAIEmbeddingsComponent",
"TextEmbedderComponent",
"VertexAIEmbeddingsComponent",
"WatsonxEmbeddingsComponent",
]

View file

@ -2,6 +2,8 @@ from .gmail import GmailLoaderComponent
from .google_bq_sql_executor import BigQueryExecutorComponent
from .google_drive import GoogleDriveComponent
from .google_drive_search import GoogleDriveSearchComponent
from .google_generative_ai import GoogleGenerativeAIComponent
from .google_generative_ai_embeddings import GoogleGenerativeAIEmbeddingsComponent
from .google_oauth_token import GoogleOAuthToken
__all__ = [
@ -9,5 +11,7 @@ __all__ = [
"GmailLoaderComponent",
"GoogleDriveComponent",
"GoogleDriveSearchComponent",
"GoogleGenerativeAIComponent",
"GoogleGenerativeAIEmbeddingsComponent",
"GoogleOAuthToken",
]

View file

@ -0,0 +1,153 @@
from typing import Any
import requests
from loguru import logger
from pydantic.v1 import SecretStr
from langflow.base.models.google_generative_ai_constants import GOOGLE_GENERATIVE_AI_MODELS
from langflow.base.models.model import LCModelComponent
from langflow.field_typing import LanguageModel
from langflow.field_typing.range_spec import RangeSpec
from langflow.inputs.inputs import (
BoolInput,
DropdownInput,
FloatInput,
IntInput,
SecretStrInput,
SliderInput,
)
from langflow.schema.dotdict import dotdict
class GoogleGenerativeAIComponent(LCModelComponent):
display_name = "Google Generative AI"
description = "Generate text using Google Generative AI."
icon = "GoogleGenerativeAI"
name = "GoogleGenerativeAIModel"
inputs = [
*LCModelComponent._base_inputs,
IntInput(
name="max_output_tokens", display_name="Max Output Tokens", info="The maximum number of tokens to generate."
),
DropdownInput(
name="model_name",
display_name="Model",
info="The name of the model to use.",
options=GOOGLE_GENERATIVE_AI_MODELS,
value="gemini-1.5-pro",
refresh_button=True,
combobox=True,
),
SecretStrInput(
name="api_key",
display_name="Google API Key",
info="The Google API Key to use for the Google Generative AI.",
required=True,
real_time_refresh=True,
),
FloatInput(
name="top_p",
display_name="Top P",
info="The maximum cumulative probability of tokens to consider when sampling.",
advanced=True,
),
SliderInput(
name="temperature",
display_name="Temperature",
value=0.1,
range_spec=RangeSpec(min=0, max=1, step=0.01),
info="Controls randomness. Lower values are more deterministic, higher values are more creative.",
),
IntInput(
name="n",
display_name="N",
info="Number of chat completions to generate for each prompt. "
"Note that the API may not return the full n completions if duplicates are generated.",
advanced=True,
),
IntInput(
name="top_k",
display_name="Top K",
info="Decode using top-k sampling: consider the set of top_k most probable tokens. Must be positive.",
advanced=True,
),
BoolInput(
name="tool_model_enabled",
display_name="Tool Model Enabled",
info="Whether to use the tool model.",
value=False,
),
]
def build_model(self) -> LanguageModel: # type: ignore[type-var]
try:
from langchain_google_genai import ChatGoogleGenerativeAI
except ImportError as e:
msg = "The 'langchain_google_genai' package is required to use the Google Generative AI model."
raise ImportError(msg) from e
google_api_key = self.api_key
model = self.model_name
max_output_tokens = self.max_output_tokens
temperature = self.temperature
top_k = self.top_k
top_p = self.top_p
n = self.n
return ChatGoogleGenerativeAI(
model=model,
max_output_tokens=max_output_tokens or None,
temperature=temperature,
top_k=top_k or None,
top_p=top_p or None,
n=n or 1,
google_api_key=SecretStr(google_api_key).get_secret_value(),
)
def get_models(self, tool_model_enabled: bool | None = None) -> list[str]:
try:
import google.generativeai as genai
genai.configure(api_key=self.api_key)
model_ids = [
model.name.replace("models/", "")
for model in genai.list_models()
if "generateContent" in model.supported_generation_methods
]
model_ids.sort(reverse=True)
except (ImportError, ValueError) as e:
logger.exception(f"Error getting model names: {e}")
model_ids = GOOGLE_GENERATIVE_AI_MODELS
if tool_model_enabled:
try:
from langchain_google_genai.chat_models import ChatGoogleGenerativeAI
except ImportError as e:
msg = "langchain_google_genai is not installed."
raise ImportError(msg) from e
for model in model_ids:
model_with_tool = ChatGoogleGenerativeAI(
model=self.model_name,
google_api_key=self.api_key,
)
if not self.supports_tool_calling(model_with_tool):
model_ids.remove(model)
return model_ids
def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None):
if field_name in {"base_url", "model_name", "tool_model_enabled", "api_key"} and field_value:
try:
if len(self.api_key) == 0:
ids = GOOGLE_GENERATIVE_AI_MODELS
else:
try:
ids = self.get_models(tool_model_enabled=self.tool_model_enabled)
except (ImportError, ValueError, requests.exceptions.RequestException) as e:
logger.exception(f"Error getting model names: {e}")
ids = GOOGLE_GENERATIVE_AI_MODELS
build_config["model_name"]["options"] = ids
build_config["model_name"]["value"] = ids[0]
except Exception as e:
msg = f"Error getting model names: {e}"
raise ValueError(msg) from e
return build_config

View file

@ -0,0 +1,141 @@
# from langflow.field_typing import Data
# TODO: remove ignore once the google package is published with types
from google.ai.generativelanguage_v1beta.types import BatchEmbedContentsRequest
from langchain_core.embeddings import Embeddings
from langchain_google_genai import GoogleGenerativeAIEmbeddings
from langchain_google_genai._common import GoogleGenerativeAIError
from langflow.custom.custom_component.component import Component
from langflow.io import MessageTextInput, Output, SecretStrInput
MIN_DIMENSION_ERROR = "Output dimensionality must be at least 1"
MAX_DIMENSION_ERROR = (
"Output dimensionality cannot exceed 768. Google's embedding models only support dimensions up to 768."
)
MAX_DIMENSION = 768
MIN_DIMENSION = 1
class GoogleGenerativeAIEmbeddingsComponent(Component):
display_name = "Google Generative AI Embeddings"
description = (
"Connect to Google's generative AI embeddings service using the GoogleGenerativeAIEmbeddings class, "
"found in the langchain-google-genai package."
)
documentation: str = "https://python.langchain.com/v0.2/docs/integrations/text_embedding/google_generative_ai/"
icon = "Google"
name = "Google Generative AI Embeddings"
inputs = [
SecretStrInput(name="api_key", display_name="API Key", required=True),
MessageTextInput(name="model_name", display_name="Model Name", value="models/text-embedding-004"),
]
outputs = [
Output(display_name="Embeddings", name="embeddings", method="build_embeddings"),
]
def build_embeddings(self) -> Embeddings:
if not self.api_key:
msg = "API Key is required"
raise ValueError(msg)
class HotaGoogleGenerativeAIEmbeddings(GoogleGenerativeAIEmbeddings):
def __init__(self, *args, **kwargs) -> None:
super(GoogleGenerativeAIEmbeddings, self).__init__(*args, **kwargs)
def embed_documents(
self,
texts: list[str],
*,
batch_size: int = 100,
task_type: str | None = None,
titles: list[str] | None = None,
output_dimensionality: int | None = 768,
) -> list[list[float]]:
"""Embed a list of strings.
Google Generative AI currently sets a max batch size of 100 strings.
Args:
texts: List[str] The list of strings to embed.
batch_size: [int] The batch size of embeddings to send to the model
task_type: task_type (https://ai.google.dev/api/rest/v1/TaskType)
titles: An optional list of titles for texts provided.
Only applicable when TaskType is RETRIEVAL_DOCUMENT.
output_dimensionality: Optional reduced dimension for the output embedding.
https://ai.google.dev/api/rest/v1/models/batchEmbedContents#EmbedContentRequest
Returns:
List of embeddings, one for each text.
"""
if output_dimensionality is not None and output_dimensionality < MIN_DIMENSION:
raise ValueError(MIN_DIMENSION_ERROR)
if output_dimensionality is not None and output_dimensionality > MAX_DIMENSION:
error_msg = MAX_DIMENSION_ERROR.format(output_dimensionality)
raise ValueError(error_msg)
embeddings: list[list[float]] = []
batch_start_index = 0
for batch in GoogleGenerativeAIEmbeddings._prepare_batches(texts, batch_size):
if titles:
titles_batch = titles[batch_start_index : batch_start_index + len(batch)]
batch_start_index += len(batch)
else:
titles_batch = [None] * len(batch) # type: ignore[list-item]
requests = [
self._prepare_request(
text=text,
task_type=task_type,
title=title,
output_dimensionality=output_dimensionality,
)
for text, title in zip(batch, titles_batch, strict=True)
]
try:
result = self.client.batch_embed_contents(
BatchEmbedContentsRequest(requests=requests, model=self.model)
)
except Exception as e:
msg = f"Error embedding content: {e}"
raise GoogleGenerativeAIError(msg) from e
embeddings.extend([list(e.values) for e in result.embeddings])
return embeddings
def embed_query(
self,
text: str,
task_type: str | None = None,
title: str | None = None,
output_dimensionality: int | None = 768,
) -> list[float]:
"""Embed a text.
Args:
text: The text to embed.
task_type: task_type (https://ai.google.dev/api/rest/v1/TaskType)
title: An optional title for the text.
Only applicable when TaskType is RETRIEVAL_DOCUMENT.
output_dimensionality: Optional reduced dimension for the output embedding.
https://ai.google.dev/api/rest/v1/models/batchEmbedContents#EmbedContentRequest
Returns:
Embedding for the text.
"""
if output_dimensionality is not None and output_dimensionality < MIN_DIMENSION:
raise ValueError(MIN_DIMENSION_ERROR)
if output_dimensionality is not None and output_dimensionality > MAX_DIMENSION:
error_msg = MAX_DIMENSION_ERROR.format(output_dimensionality)
raise ValueError(error_msg)
task_type = task_type or "RETRIEVAL_QUERY"
return self.embed_documents(
[text],
task_type=task_type,
titles=[title] if title else None,
output_dimensionality=output_dimensionality,
)[0]
return HotaGoogleGenerativeAIEmbeddings(model=self.model_name, google_api_key=self.api_key)

Some files were not shown because too many files have changed in this diff Show more