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:
parent
d93d95cbeb
commit
5ba8f91c9a
96 changed files with 234 additions and 218 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
from .agent import AgentComponent
|
||||
from .mcp_component import MCPToolsComponent
|
||||
|
||||
__all__ = ["AgentComponent"]
|
||||
__all__ = ["AgentComponent", "MCPToolsComponent"]
|
||||
|
|
|
|||
467
src/backend/base/langflow/components/agents/mcp_component.py
Normal file
467
src/backend/base/langflow/components/agents/mcp_component.py
Normal 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
|
||||
7
src/backend/base/langflow/components/aiml/__init__.py
Normal file
7
src/backend/base/langflow/components/aiml/__init__.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
from .aiml import AIMLModelComponent
|
||||
from .aiml_embeddings import AIMLEmbeddingsComponent
|
||||
|
||||
__all__ = [
|
||||
"AIMLEmbeddingsComponent",
|
||||
"AIMLModelComponent",
|
||||
]
|
||||
112
src/backend/base/langflow/components/aiml/aiml.py
Normal file
112
src/backend/base/langflow/components/aiml/aiml.py
Normal 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
|
||||
37
src/backend/base/langflow/components/aiml/aiml_embeddings.py
Normal file
37
src/backend/base/langflow/components/aiml/aiml_embeddings.py
Normal 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,
|
||||
)
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
from .anthropic import AnthropicModelComponent
|
||||
|
||||
__all__ = [
|
||||
"AnthropicModelComponent",
|
||||
]
|
||||
189
src/backend/base/langflow/components/anthropic/anthropic.py
Normal file
189
src/backend/base/langflow/components/anthropic/anthropic.py
Normal 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
|
||||
7
src/backend/base/langflow/components/azure/__init__.py
Normal file
7
src/backend/base/langflow/components/azure/__init__.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
from .azure_openai import AzureChatOpenAIComponent
|
||||
from .azure_openai_embeddings import AzureOpenAIEmbeddingsComponent
|
||||
|
||||
__all__ = [
|
||||
"AzureChatOpenAIComponent",
|
||||
"AzureOpenAIEmbeddingsComponent",
|
||||
]
|
||||
95
src/backend/base/langflow/components/azure/azure_openai.py
Normal file
95
src/backend/base/langflow/components/azure/azure_openai.py
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
]
|
||||
|
|
|
|||
69
src/backend/base/langflow/components/datastax/astra_db.py
Normal file
69
src/backend/base/langflow/components/datastax/astra_db.py
Normal 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,
|
||||
)
|
||||
123
src/backend/base/langflow/components/datastax/astra_vectorize.py
Normal file
123
src/backend/base/langflow/components/datastax/astra_vectorize.py
Normal 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,
|
||||
}
|
||||
92
src/backend/base/langflow/components/datastax/cassandra.py
Normal file
92
src/backend/base/langflow/components/datastax/cassandra.py
Normal 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,
|
||||
)
|
||||
|
|
@ -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",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
Loading…
Reference in a new issue