feat: add langgraph first tool; pydantic v2
This commit is contained in:
parent
68d6ab9890
commit
98b258ae53
7 changed files with 718 additions and 432 deletions
|
|
@ -1,7 +1,7 @@
|
|||
"""Security models: Token and AuthEvent."""
|
||||
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
from modules.shared.attributeUtils import register_model_labels, ModelMixin
|
||||
from modules.shared.timezoneUtils import get_utc_timestamp
|
||||
from .datamodelUam import AuthAuthority
|
||||
|
|
@ -18,21 +18,36 @@ class Token(BaseModel, ModelMixin):
|
|||
id: Optional[str] = None
|
||||
userId: str
|
||||
authority: AuthAuthority
|
||||
connectionId: Optional[str] = Field(None, description="ID of the connection this token belongs to")
|
||||
connectionId: Optional[str] = Field(
|
||||
None, description="ID of the connection this token belongs to"
|
||||
)
|
||||
tokenAccess: str
|
||||
tokenType: str = "bearer"
|
||||
expiresAt: float = Field(description="When the token expires (UTC timestamp in seconds)")
|
||||
expiresAt: float = Field(
|
||||
description="When the token expires (UTC timestamp in seconds)"
|
||||
)
|
||||
tokenRefresh: Optional[str] = None
|
||||
createdAt: Optional[float] = Field(None, description="When the token was created (UTC timestamp in seconds)")
|
||||
status: TokenStatus = Field(default=TokenStatus.ACTIVE, description="Token status: active/revoked")
|
||||
revokedAt: Optional[float] = Field(None, description="When the token was revoked (UTC timestamp in seconds)")
|
||||
revokedBy: Optional[str] = Field(None, description="User ID who revoked the token (admin/self)")
|
||||
createdAt: Optional[float] = Field(
|
||||
None, description="When the token was created (UTC timestamp in seconds)"
|
||||
)
|
||||
status: TokenStatus = Field(
|
||||
default=TokenStatus.ACTIVE, description="Token status: active/revoked"
|
||||
)
|
||||
revokedAt: Optional[float] = Field(
|
||||
None, description="When the token was revoked (UTC timestamp in seconds)"
|
||||
)
|
||||
revokedBy: Optional[str] = Field(
|
||||
None, description="User ID who revoked the token (admin/self)"
|
||||
)
|
||||
reason: Optional[str] = Field(None, description="Optional revocation reason")
|
||||
sessionId: Optional[str] = Field(None, description="Logical session grouping for logout revocation")
|
||||
mandateId: Optional[str] = Field(None, description="Mandate ID for tenant scoping of the token")
|
||||
sessionId: Optional[str] = Field(
|
||||
None, description="Logical session grouping for logout revocation"
|
||||
)
|
||||
mandateId: Optional[str] = Field(
|
||||
None, description="Mandate ID for tenant scoping of the token"
|
||||
)
|
||||
|
||||
class Config:
|
||||
use_enum_values = True
|
||||
model_config = ConfigDict(use_enum_values=True)
|
||||
|
||||
|
||||
register_model_labels(
|
||||
|
|
@ -59,14 +74,60 @@ register_model_labels(
|
|||
|
||||
|
||||
class AuthEvent(BaseModel, ModelMixin):
|
||||
id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Unique ID of the auth event", frontend_type="text", frontend_readonly=True, frontend_required=False)
|
||||
userId: str = Field(description="ID of the user this event belongs to", frontend_type="text", frontend_readonly=True, frontend_required=True)
|
||||
eventType: str = Field(description="Type of authentication event (e.g., 'login', 'logout', 'token_refresh')", frontend_type="text", frontend_readonly=True, frontend_required=True)
|
||||
timestamp: float = Field(default_factory=get_utc_timestamp, description="Unix timestamp when the event occurred", frontend_type="datetime", frontend_readonly=True, frontend_required=True)
|
||||
ipAddress: Optional[str] = Field(default=None, description="IP address from which the event originated", frontend_type="text", frontend_readonly=True, frontend_required=False)
|
||||
userAgent: Optional[str] = Field(default=None, description="User agent string from the request", frontend_type="text", frontend_readonly=True, frontend_required=False)
|
||||
success: bool = Field(default=True, description="Whether the authentication event was successful", frontend_type="boolean", frontend_readonly=True, frontend_required=True)
|
||||
details: Optional[str] = Field(default=None, description="Additional details about the event", frontend_type="text", frontend_readonly=True, frontend_required=False)
|
||||
id: str = Field(
|
||||
default_factory=lambda: str(uuid.uuid4()),
|
||||
description="Unique ID of the auth event",
|
||||
frontend_type="text",
|
||||
frontend_readonly=True,
|
||||
frontend_required=False,
|
||||
)
|
||||
userId: str = Field(
|
||||
description="ID of the user this event belongs to",
|
||||
frontend_type="text",
|
||||
frontend_readonly=True,
|
||||
frontend_required=True,
|
||||
)
|
||||
eventType: str = Field(
|
||||
description="Type of authentication event (e.g., 'login', 'logout', 'token_refresh')",
|
||||
frontend_type="text",
|
||||
frontend_readonly=True,
|
||||
frontend_required=True,
|
||||
)
|
||||
timestamp: float = Field(
|
||||
default_factory=get_utc_timestamp,
|
||||
description="Unix timestamp when the event occurred",
|
||||
frontend_type="datetime",
|
||||
frontend_readonly=True,
|
||||
frontend_required=True,
|
||||
)
|
||||
ipAddress: Optional[str] = Field(
|
||||
default=None,
|
||||
description="IP address from which the event originated",
|
||||
frontend_type="text",
|
||||
frontend_readonly=True,
|
||||
frontend_required=False,
|
||||
)
|
||||
userAgent: Optional[str] = Field(
|
||||
default=None,
|
||||
description="User agent string from the request",
|
||||
frontend_type="text",
|
||||
frontend_readonly=True,
|
||||
frontend_required=False,
|
||||
)
|
||||
success: bool = Field(
|
||||
default=True,
|
||||
description="Whether the authentication event was successful",
|
||||
frontend_type="boolean",
|
||||
frontend_readonly=True,
|
||||
frontend_required=True,
|
||||
)
|
||||
details: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Additional details about the event",
|
||||
frontend_type="text",
|
||||
frontend_readonly=True,
|
||||
frontend_required=False,
|
||||
)
|
||||
|
||||
|
||||
register_model_labels(
|
||||
|
|
@ -83,5 +144,3 @@ register_model_labels(
|
|||
"details": {"en": "Details", "fr": "Détails"},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
1
modules/features/chatBot/chatbotTools/__init__.py
Normal file
1
modules/features/chatBot/chatbotTools/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
"""Contains all tools available for the chatbot to use."""
|
||||
|
|
@ -1 +1,7 @@
|
|||
"""Tools that are custom to a specific customer go here."""
|
||||
"""Shared tools available across all chatbot implementations."""
|
||||
|
||||
from modules.features.chatBot.chatbotTools.sharedTools.toolTavilySearch import (
|
||||
tavily_search,
|
||||
)
|
||||
|
||||
__all__ = ["tavily_search"]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
"""Tavily Search Tool for LangGraph.
|
||||
|
||||
This tool provides web search capabilities using the Tavily API.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Annotated
|
||||
from langchain_core.tools import tool
|
||||
from modules.connectors.connectorAiTavily import ConnectorWeb
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@tool
|
||||
async def tavily_search(
|
||||
query: Annotated[str, "The search query to look up on the web"],
|
||||
) -> str:
|
||||
"""Search the web using Tavily API.
|
||||
|
||||
Use this tool to search for current information, news, or any web content.
|
||||
The tool returns relevant search results including titles and URLs.
|
||||
|
||||
Args:
|
||||
query: The search query string
|
||||
|
||||
Returns:
|
||||
A formatted string containing search results with titles and URLs
|
||||
"""
|
||||
try:
|
||||
# Create connector instance
|
||||
connector = await ConnectorWeb.create()
|
||||
|
||||
# Perform search with default parameters
|
||||
results = await connector._search(
|
||||
query=query,
|
||||
max_results=5,
|
||||
search_depth="basic",
|
||||
include_answer=True,
|
||||
include_raw_content=False,
|
||||
)
|
||||
|
||||
# Format results
|
||||
if not results:
|
||||
return f"No results found for query: {query}"
|
||||
|
||||
formatted_results = [f"Search results for '{query}':\n"]
|
||||
for i, result in enumerate(results, 1):
|
||||
formatted_results.append(f"{i}. {result.title}")
|
||||
formatted_results.append(f" URL: {result.url}\n")
|
||||
|
||||
return "\n".join(formatted_results)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in tavily_search tool: {str(e)}")
|
||||
return f"Error performing search: {str(e)}"
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -2,52 +2,55 @@
|
|||
Shared utilities for model attributes and labels.
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
from typing import Dict, Any, List, Type, Optional, Union
|
||||
import inspect
|
||||
import importlib
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class ModelMixin:
|
||||
"""Mixin class that provides serialization methods for Pydantic models."""
|
||||
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Convert a Pydantic model to a dictionary.
|
||||
Handles both Pydantic v1 and v2.
|
||||
All timestamp fields remain as float values.
|
||||
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: Dictionary representation of the model
|
||||
"""
|
||||
# Get the raw dictionary
|
||||
if hasattr(self, 'model_dump'):
|
||||
if hasattr(self, "model_dump"):
|
||||
data: Dict[str, Any] = self.model_dump() # Pydantic v2
|
||||
else:
|
||||
data: Dict[str, Any] = self.dict() # Pydantic v1
|
||||
|
||||
|
||||
# All fields (including timestamps) remain in their original format
|
||||
# No conversions needed - timestamps are already float
|
||||
|
||||
|
||||
return data
|
||||
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> 'ModelMixin':
|
||||
def from_dict(cls, data: Dict[str, Any]) -> "ModelMixin":
|
||||
"""
|
||||
Create a Pydantic model instance from a dictionary.
|
||||
|
||||
|
||||
Args:
|
||||
data: Dictionary containing the model data
|
||||
|
||||
|
||||
Returns:
|
||||
ModelMixin: New instance of the model class
|
||||
"""
|
||||
return cls(**data)
|
||||
|
||||
|
||||
# Define the AttributeDefinition class here instead of importing it
|
||||
class AttributeDefinition(BaseModel, ModelMixin):
|
||||
"""Definition of a model attribute with its metadata."""
|
||||
|
||||
name: str
|
||||
type: str
|
||||
label: str
|
||||
|
|
@ -64,41 +67,47 @@ class AttributeDefinition(BaseModel, ModelMixin):
|
|||
order: int = 0
|
||||
placeholder: Optional[str] = None
|
||||
|
||||
|
||||
# Global registry for model labels
|
||||
MODEL_LABELS: Dict[str, Dict[str, Dict[str, str]]] = {}
|
||||
|
||||
|
||||
def to_dict(model: BaseModel) -> Dict[str, Any]:
|
||||
"""
|
||||
Convert a Pydantic model to a dictionary.
|
||||
Handles both Pydantic v1 and v2.
|
||||
|
||||
|
||||
Args:
|
||||
model: The Pydantic model instance to convert
|
||||
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: Dictionary representation of the model
|
||||
"""
|
||||
if hasattr(model, 'model_dump'):
|
||||
if hasattr(model, "model_dump"):
|
||||
return model.model_dump() # Pydantic v2
|
||||
return model.dict() # Pydantic v1
|
||||
|
||||
|
||||
def from_dict(model_class: Type[BaseModel], data: Dict[str, Any]) -> BaseModel:
|
||||
"""
|
||||
Create a Pydantic model instance from a dictionary.
|
||||
|
||||
|
||||
Args:
|
||||
model_class: The Pydantic model class to instantiate
|
||||
data: Dictionary containing the model data
|
||||
|
||||
|
||||
Returns:
|
||||
BaseModel: New instance of the model class
|
||||
"""
|
||||
return model_class(**data)
|
||||
|
||||
def register_model_labels(model_name: str, model_label: Dict[str, str], labels: Dict[str, Dict[str, str]]):
|
||||
|
||||
def register_model_labels(
|
||||
model_name: str, model_label: Dict[str, str], labels: Dict[str, Dict[str, str]]
|
||||
):
|
||||
"""
|
||||
Register labels for a model's attributes and the model itself.
|
||||
|
||||
|
||||
Args:
|
||||
model_name: Name of the model class
|
||||
model_label: Dictionary mapping language codes to model labels
|
||||
|
|
@ -106,38 +115,37 @@ def register_model_labels(model_name: str, model_label: Dict[str, str], labels:
|
|||
labels: Dictionary mapping attribute names to their translations
|
||||
e.g. {"name": {"en": "Name", "fr": "Nom"}}
|
||||
"""
|
||||
MODEL_LABELS[model_name] = {
|
||||
"model": model_label,
|
||||
"attributes": labels
|
||||
}
|
||||
MODEL_LABELS[model_name] = {"model": model_label, "attributes": labels}
|
||||
|
||||
|
||||
def get_model_labels(model_name: str, language: str = "en") -> Dict[str, str]:
|
||||
"""
|
||||
Get labels for a model's attributes in the specified language.
|
||||
|
||||
|
||||
Args:
|
||||
model_name: Name of the model class
|
||||
language: Language code (default: "en")
|
||||
|
||||
|
||||
Returns:
|
||||
Dictionary mapping attribute names to their labels in the specified language
|
||||
"""
|
||||
model_data = MODEL_LABELS.get(model_name, {})
|
||||
attribute_labels = model_data.get("attributes", {})
|
||||
|
||||
|
||||
return {
|
||||
attr: translations.get(language, translations.get("en", attr))
|
||||
for attr, translations in attribute_labels.items()
|
||||
}
|
||||
|
||||
|
||||
def get_model_label(model_name: str, language: str = "en") -> str:
|
||||
"""
|
||||
Get the label for a model in the specified language.
|
||||
|
||||
|
||||
Args:
|
||||
model_name: Name of the model class
|
||||
language: Language code (default: "en")
|
||||
|
||||
|
||||
Returns:
|
||||
Model label in the specified language, or model name if no label exists
|
||||
"""
|
||||
|
|
@ -145,156 +153,205 @@ def get_model_label(model_name: str, language: str = "en") -> str:
|
|||
model_label = model_data.get("model", {})
|
||||
return model_label.get(language, model_label.get("en", model_name))
|
||||
|
||||
def getModelAttributeDefinitions(modelClass: Type[BaseModel] = None, userLanguage: str = "en") -> Dict[str, Any]:
|
||||
|
||||
def getModelAttributeDefinitions(
|
||||
modelClass: Type[BaseModel] = None, userLanguage: str = "en"
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Get attribute definitions for a model class.
|
||||
|
||||
|
||||
Args:
|
||||
modelClass: The model class to get attributes for
|
||||
userLanguage: Language code for translations (default: "en")
|
||||
|
||||
|
||||
Returns:
|
||||
Dictionary containing model label and attribute definitions
|
||||
"""
|
||||
if not modelClass:
|
||||
return {}
|
||||
|
||||
|
||||
attributes = []
|
||||
model_name = modelClass.__name__
|
||||
labels = get_model_labels(model_name, userLanguage)
|
||||
model_label = get_model_label(model_name, userLanguage)
|
||||
|
||||
|
||||
# Handle both Pydantic v1 and v2
|
||||
if hasattr(modelClass, 'model_fields'): # Pydantic v2
|
||||
if hasattr(modelClass, "model_fields"): # Pydantic v2
|
||||
fields = modelClass.model_fields
|
||||
for name, field in fields.items():
|
||||
# Extract frontend metadata from field info
|
||||
field_info = field.field_info if hasattr(field, 'field_info') else None
|
||||
field_info = field.field_info if hasattr(field, "field_info") else None
|
||||
# Check both direct attributes and extra field for frontend metadata
|
||||
frontend_type = None
|
||||
frontend_readonly = False
|
||||
frontend_required = field.is_required()
|
||||
frontend_options = None
|
||||
|
||||
|
||||
if field_info:
|
||||
# Try direct attributes first
|
||||
frontend_type = getattr(field_info, 'frontend_type', None)
|
||||
frontend_readonly = getattr(field_info, 'frontend_readonly', False)
|
||||
frontend_required = getattr(field_info, 'frontend_required', frontend_required)
|
||||
frontend_options = getattr(field_info, 'frontend_options', None)
|
||||
|
||||
frontend_type = getattr(field_info, "frontend_type", None)
|
||||
frontend_readonly = getattr(field_info, "frontend_readonly", False)
|
||||
frontend_required = getattr(
|
||||
field_info, "frontend_required", frontend_required
|
||||
)
|
||||
frontend_options = getattr(field_info, "frontend_options", None)
|
||||
|
||||
# If not found, check extra field
|
||||
if hasattr(field_info, 'extra') and field_info.extra:
|
||||
if hasattr(field_info, "extra") and field_info.extra:
|
||||
if frontend_type is None:
|
||||
frontend_type = field_info.extra.get('frontend_type')
|
||||
frontend_type = field_info.extra.get("frontend_type")
|
||||
if not frontend_readonly:
|
||||
frontend_readonly = field_info.extra.get('frontend_readonly', False)
|
||||
if frontend_required == field.is_required(): # Only override if we didn't get it from direct attribute
|
||||
frontend_required = field_info.extra.get('frontend_required', frontend_required)
|
||||
frontend_readonly = field_info.extra.get(
|
||||
"frontend_readonly", False
|
||||
)
|
||||
if (
|
||||
frontend_required == field.is_required()
|
||||
): # Only override if we didn't get it from direct attribute
|
||||
frontend_required = field_info.extra.get(
|
||||
"frontend_required", frontend_required
|
||||
)
|
||||
if frontend_options is None:
|
||||
frontend_options = field_info.extra.get('frontend_options')
|
||||
|
||||
frontend_options = field_info.extra.get("frontend_options")
|
||||
|
||||
# Use frontend type if available, otherwise fall back to Python type
|
||||
field_type = frontend_type if frontend_type else (field.annotation.__name__ if hasattr(field.annotation, "__name__") else str(field.annotation))
|
||||
|
||||
attributes.append({
|
||||
"name": name,
|
||||
"type": field_type,
|
||||
"required": frontend_required,
|
||||
"description": field.description if hasattr(field, "description") else "",
|
||||
"label": labels.get(name, name),
|
||||
"placeholder": f"Please enter {labels.get(name, name)}",
|
||||
"editable": not frontend_readonly,
|
||||
"visible": True,
|
||||
"order": len(attributes),
|
||||
"readonly": frontend_readonly,
|
||||
"options": frontend_options
|
||||
})
|
||||
field_type = (
|
||||
frontend_type
|
||||
if frontend_type
|
||||
else (
|
||||
field.annotation.__name__
|
||||
if hasattr(field.annotation, "__name__")
|
||||
else str(field.annotation)
|
||||
)
|
||||
)
|
||||
|
||||
attributes.append(
|
||||
{
|
||||
"name": name,
|
||||
"type": field_type,
|
||||
"required": frontend_required,
|
||||
"description": field.description
|
||||
if hasattr(field, "description")
|
||||
else "",
|
||||
"label": labels.get(name, name),
|
||||
"placeholder": f"Please enter {labels.get(name, name)}",
|
||||
"editable": not frontend_readonly,
|
||||
"visible": True,
|
||||
"order": len(attributes),
|
||||
"readonly": frontend_readonly,
|
||||
"options": frontend_options,
|
||||
}
|
||||
)
|
||||
else: # Pydantic v1
|
||||
fields = modelClass.__fields__
|
||||
for name, field in fields.items():
|
||||
# Extract frontend metadata from field info
|
||||
field_info = field.field_info if hasattr(field, 'field_info') else None
|
||||
field_info = field.field_info if hasattr(field, "field_info") else None
|
||||
# Check both direct attributes and extra field for frontend metadata
|
||||
frontend_type = None
|
||||
frontend_readonly = False
|
||||
frontend_required = field.required
|
||||
frontend_options = None
|
||||
|
||||
|
||||
if field_info:
|
||||
# Try direct attributes first
|
||||
frontend_type = getattr(field_info, 'frontend_type', None)
|
||||
frontend_readonly = getattr(field_info, 'frontend_readonly', False)
|
||||
frontend_required = getattr(field_info, 'frontend_required', frontend_required)
|
||||
frontend_options = getattr(field_info, 'frontend_options', None)
|
||||
|
||||
frontend_type = getattr(field_info, "frontend_type", None)
|
||||
frontend_readonly = getattr(field_info, "frontend_readonly", False)
|
||||
frontend_required = getattr(
|
||||
field_info, "frontend_required", frontend_required
|
||||
)
|
||||
frontend_options = getattr(field_info, "frontend_options", None)
|
||||
|
||||
# If not found, check extra field
|
||||
if hasattr(field_info, 'extra') and field_info.extra:
|
||||
if hasattr(field_info, "extra") and field_info.extra:
|
||||
if frontend_type is None:
|
||||
frontend_type = field_info.extra.get('frontend_type')
|
||||
frontend_type = field_info.extra.get("frontend_type")
|
||||
if not frontend_readonly:
|
||||
frontend_readonly = field_info.extra.get('frontend_readonly', False)
|
||||
if frontend_required == field.required: # Only override if we didn't get it from direct attribute
|
||||
frontend_required = field_info.extra.get('frontend_required', frontend_required)
|
||||
frontend_readonly = field_info.extra.get(
|
||||
"frontend_readonly", False
|
||||
)
|
||||
if (
|
||||
frontend_required == field.required
|
||||
): # Only override if we didn't get it from direct attribute
|
||||
frontend_required = field_info.extra.get(
|
||||
"frontend_required", frontend_required
|
||||
)
|
||||
if frontend_options is None:
|
||||
frontend_options = field_info.extra.get('frontend_options')
|
||||
|
||||
frontend_options = field_info.extra.get("frontend_options")
|
||||
|
||||
# Use frontend type if available, otherwise fall back to Python type
|
||||
field_type = frontend_type if frontend_type else (field.type_.__name__ if hasattr(field.type_, "__name__") else str(field.type_))
|
||||
|
||||
attributes.append({
|
||||
"name": name,
|
||||
"type": field_type,
|
||||
"required": frontend_required,
|
||||
"description": field.field_info.description if hasattr(field.field_info, "description") else "",
|
||||
"label": labels.get(name, name),
|
||||
"placeholder": f"Please enter {labels.get(name, name)}",
|
||||
"editable": not frontend_readonly,
|
||||
"visible": True,
|
||||
"order": len(attributes),
|
||||
"readonly": frontend_readonly,
|
||||
"options": frontend_options
|
||||
})
|
||||
|
||||
return {
|
||||
"model": model_label,
|
||||
"attributes": attributes
|
||||
}
|
||||
field_type = (
|
||||
frontend_type
|
||||
if frontend_type
|
||||
else (
|
||||
field.type_.__name__
|
||||
if hasattr(field.type_, "__name__")
|
||||
else str(field.type_)
|
||||
)
|
||||
)
|
||||
|
||||
attributes.append(
|
||||
{
|
||||
"name": name,
|
||||
"type": field_type,
|
||||
"required": frontend_required,
|
||||
"description": field.field_info.description
|
||||
if hasattr(field.field_info, "description")
|
||||
else "",
|
||||
"label": labels.get(name, name),
|
||||
"placeholder": f"Please enter {labels.get(name, name)}",
|
||||
"editable": not frontend_readonly,
|
||||
"visible": True,
|
||||
"order": len(attributes),
|
||||
"readonly": frontend_readonly,
|
||||
"options": frontend_options,
|
||||
}
|
||||
)
|
||||
|
||||
return {"model": model_label, "attributes": attributes}
|
||||
|
||||
|
||||
def getModelClasses() -> Dict[str, Type[BaseModel]]:
|
||||
"""
|
||||
Dynamically get all model classes from all model modules.
|
||||
|
||||
|
||||
Returns:
|
||||
Dict[str, Type[BaseModel]]: Dictionary of model class names to their classes
|
||||
"""
|
||||
modelClasses = {}
|
||||
|
||||
|
||||
# Get the interfaces directory path
|
||||
interfaces_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'interfaces')
|
||||
|
||||
interfaces_dir = os.path.join(
|
||||
os.path.dirname(os.path.dirname(__file__)), "interfaces"
|
||||
)
|
||||
|
||||
# Find all model files
|
||||
for fileName in os.listdir(interfaces_dir):
|
||||
if fileName.endswith('Model.py'):
|
||||
if fileName.endswith("Model.py"):
|
||||
# Convert fileName to module name (e.g., gatewayModel.py -> gatewayModel)
|
||||
module_name = fileName[:-3]
|
||||
|
||||
|
||||
# Import the module dynamically
|
||||
module = importlib.import_module(f'modules.interfaces.{module_name}')
|
||||
|
||||
module = importlib.import_module(f"modules.interfaces.{module_name}")
|
||||
|
||||
# Get all classes from the module
|
||||
for name, obj in inspect.getmembers(module):
|
||||
if inspect.isclass(obj) and issubclass(obj, BaseModel) and obj != BaseModel:
|
||||
if (
|
||||
inspect.isclass(obj)
|
||||
and issubclass(obj, BaseModel)
|
||||
and obj != BaseModel
|
||||
):
|
||||
modelClasses[name] = obj
|
||||
|
||||
|
||||
return modelClasses
|
||||
|
||||
|
||||
class AttributeResponse(BaseModel):
|
||||
"""Response model for entity attributes"""
|
||||
|
||||
attributes: List[AttributeDefinition]
|
||||
|
||||
class Config:
|
||||
schema_extra = {
|
||||
|
||||
model_config = ConfigDict(
|
||||
json_schema_extra={
|
||||
"example": {
|
||||
"attributes": [
|
||||
{
|
||||
|
|
@ -305,8 +362,9 @@ class AttributeResponse(BaseModel):
|
|||
"placeholder": "Please enter username",
|
||||
"editable": True,
|
||||
"visible": True,
|
||||
"order": 0
|
||||
"order": 0,
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ websockets==12.0
|
|||
uvicorn==0.23.2
|
||||
python-multipart==0.0.6
|
||||
httpx==0.25.0
|
||||
pydantic==1.10.13 # Ältere Version ohne Rust-Abhängigkeit
|
||||
pydantic>=2.0.0 # Upgraded to v2 for LangChain compatibility
|
||||
email-validator==2.0.0 # Required by Pydantic for email validation
|
||||
slowapi==0.1.8 # For rate limiting
|
||||
|
||||
|
|
@ -108,3 +108,8 @@ xyzservices>=2021.09.1
|
|||
|
||||
# PostgreSQL connector dependencies
|
||||
psycopg2-binary==2.9.9
|
||||
|
||||
## LangChain & LangGraph
|
||||
langchain==0.3.27
|
||||
langgraph==0.6.8
|
||||
langchain-core==0.3.77
|
||||
|
|
|
|||
Loading…
Reference in a new issue