- Finish editing of Specialists with overview, agent - task - tool editor
- Split differrent caching mechanisms (types, version tree, config) into different cachers - Improve resource usage on starting components, and correct gevent usage - Refine repopack usage for eveai_app (too large) - Change nginx dockerfile to allow for specialist overviews being served statically
This commit is contained in:
@@ -13,6 +13,7 @@ migrations/
|
|||||||
*material*
|
*material*
|
||||||
*nucleo*
|
*nucleo*
|
||||||
*package*
|
*package*
|
||||||
|
*.svg
|
||||||
nginx/mime.types
|
nginx/mime.types
|
||||||
*.gitignore*
|
*.gitignore*
|
||||||
.python-version
|
.python-version
|
||||||
|
|||||||
28
.repopackignore_eveai_app_documents
Normal file
28
.repopackignore_eveai_app_documents
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
docker/
|
||||||
|
eveai_api/
|
||||||
|
eveai_beat/
|
||||||
|
eveai_chat/
|
||||||
|
eveai_chat_workers/
|
||||||
|
eveai_entitlements/
|
||||||
|
eveai_workers/
|
||||||
|
instance/
|
||||||
|
integrations/
|
||||||
|
migrations/
|
||||||
|
nginx/
|
||||||
|
scripts/
|
||||||
|
common/models/entitlements.py
|
||||||
|
common/models/interaction.py
|
||||||
|
common/models/user.py
|
||||||
|
config/agents/
|
||||||
|
config/prompts/
|
||||||
|
config/specialists/
|
||||||
|
config/tasks/
|
||||||
|
config/tools/
|
||||||
|
eveai_app/templates/administration/
|
||||||
|
eveai_app/templates/entitlements/
|
||||||
|
eveai_app/templates/interaction/
|
||||||
|
eveai_app/templates/user/
|
||||||
|
eveai_app/views/administration*
|
||||||
|
eveai_app/views/entitlements*
|
||||||
|
eveai_app/views/interaction*
|
||||||
|
eveai_app/views/user*
|
||||||
28
.repopackignore_eveai_app_entitlements
Normal file
28
.repopackignore_eveai_app_entitlements
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
docker/
|
||||||
|
eveai_api/
|
||||||
|
eveai_beat/
|
||||||
|
eveai_chat/
|
||||||
|
eveai_chat_workers/
|
||||||
|
eveai_entitlements/
|
||||||
|
eveai_workers/
|
||||||
|
instance/
|
||||||
|
integrations/
|
||||||
|
migrations/
|
||||||
|
nginx/
|
||||||
|
scripts/
|
||||||
|
common/models/document.py
|
||||||
|
common/models/interaction.py
|
||||||
|
common/models/user.py
|
||||||
|
config/agents/
|
||||||
|
config/prompts/
|
||||||
|
config/specialists/
|
||||||
|
config/tasks/
|
||||||
|
config/tools/
|
||||||
|
eveai_app/templates/administration/
|
||||||
|
eveai_app/templates/document/
|
||||||
|
eveai_app/templates/interaction/
|
||||||
|
eveai_app/templates/user/
|
||||||
|
eveai_app/views/administration*
|
||||||
|
eveai_app/views/document*
|
||||||
|
eveai_app/views/interaction*
|
||||||
|
eveai_app/views/user*
|
||||||
23
.repopackignore_eveai_app_interaction
Normal file
23
.repopackignore_eveai_app_interaction
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
docker/
|
||||||
|
eveai_api/
|
||||||
|
eveai_beat/
|
||||||
|
eveai_chat/
|
||||||
|
eveai_chat_workers/
|
||||||
|
eveai_entitlements/
|
||||||
|
eveai_workers/
|
||||||
|
instance/
|
||||||
|
integrations/
|
||||||
|
migrations/
|
||||||
|
nginx/
|
||||||
|
scripts/
|
||||||
|
common/models/entitlements.py
|
||||||
|
common/models/document.py
|
||||||
|
common/models/user.py
|
||||||
|
eveai_app/templates/administration/
|
||||||
|
eveai_app/templates/entitlements/
|
||||||
|
eveai_app/templates/document/
|
||||||
|
eveai_app/templates/user/
|
||||||
|
eveai_app/views/administration*
|
||||||
|
eveai_app/views/entitlements*
|
||||||
|
eveai_app/views/document*
|
||||||
|
eveai_app/views/user*
|
||||||
28
.repopackignore_eveai_app_user
Normal file
28
.repopackignore_eveai_app_user
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
docker/
|
||||||
|
eveai_api/
|
||||||
|
eveai_beat/
|
||||||
|
eveai_chat/
|
||||||
|
eveai_chat_workers/
|
||||||
|
eveai_entitlements/
|
||||||
|
eveai_workers/
|
||||||
|
instance/
|
||||||
|
integrations/
|
||||||
|
migrations/
|
||||||
|
nginx/
|
||||||
|
scripts/
|
||||||
|
common/models/entitlements.py
|
||||||
|
common/models/interaction.py
|
||||||
|
common/models/document.py
|
||||||
|
config/agents/
|
||||||
|
config/prompts/
|
||||||
|
config/specialists/
|
||||||
|
config/tasks/
|
||||||
|
config/tools/
|
||||||
|
eveai_app/templates/administration/
|
||||||
|
eveai_app/templates/entitlements/
|
||||||
|
eveai_app/templates/interaction/
|
||||||
|
eveai_app/templates/document/
|
||||||
|
eveai_app/views/administration*
|
||||||
|
eveai_app/views/entitlements*
|
||||||
|
eveai_app/views/interaction*
|
||||||
|
eveai_app/views/document*
|
||||||
@@ -73,6 +73,7 @@ class EveAITask(db.Model):
|
|||||||
description = db.Column(db.Text, nullable=True)
|
description = db.Column(db.Text, nullable=True)
|
||||||
type = db.Column(db.String(50), nullable=False, default="STANDARD_RAG")
|
type = db.Column(db.String(50), nullable=False, default="STANDARD_RAG")
|
||||||
type_version = db.Column(db.String(20), nullable=True, default="1.0.0")
|
type_version = db.Column(db.String(20), nullable=True, default="1.0.0")
|
||||||
|
task_description = db.Column(db.Text, nullable=True)
|
||||||
expected_output = db.Column(db.Text, nullable=True)
|
expected_output = db.Column(db.Text, nullable=True)
|
||||||
tuning = db.Column(db.Boolean, nullable=True, default=False)
|
tuning = db.Column(db.Boolean, nullable=True, default=False)
|
||||||
configuration = db.Column(JSONB, nullable=True)
|
configuration = db.Column(JSONB, nullable=True)
|
||||||
|
|||||||
75
common/utils/cache/base.py
vendored
75
common/utils/cache/base.py
vendored
@@ -1,7 +1,8 @@
|
|||||||
from typing import Any, Dict, List, Optional, TypeVar, Generic, Type
|
from typing import Any, Dict, List, Optional, TypeVar, Generic, Type
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from flask import Flask
|
from flask import Flask, current_app
|
||||||
from dogpile.cache import CacheRegion
|
from dogpile.cache import CacheRegion
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
T = TypeVar('T') # Generic type parameter for cached data
|
T = TypeVar('T') # Generic type parameter for cached data
|
||||||
|
|
||||||
@@ -47,6 +48,46 @@ class CacheHandler(Generic[T]):
|
|||||||
self.prefix = prefix
|
self.prefix = prefix
|
||||||
self._key_components = [] # List of required key components
|
self._key_components = [] # List of required key components
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def _to_cache_data(self, instance: T) -> Any:
|
||||||
|
"""
|
||||||
|
Convert the data to a cacheable format for internal use.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
instance: The data to be cached.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A serializable format of the instance.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def _from_cache_data(self, data: Any, **kwargs) -> T:
|
||||||
|
"""
|
||||||
|
Convert cached data back to usable format for internal use.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data: The cached data.
|
||||||
|
**kwargs: Additional context.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The data in its usable format.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def _should_cache(self, value: T) -> bool:
|
||||||
|
"""
|
||||||
|
Validate if the value should be cached for internal use.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: The value to be cached.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if the value should be cached, False otherwise.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
def configure_keys(self, *components: str):
|
def configure_keys(self, *components: str):
|
||||||
"""
|
"""
|
||||||
Configure required components for cache key generation.
|
Configure required components for cache key generation.
|
||||||
@@ -77,8 +118,13 @@ class CacheHandler(Generic[T]):
|
|||||||
if missing:
|
if missing:
|
||||||
raise ValueError(f"Missing key components: {missing}")
|
raise ValueError(f"Missing key components: {missing}")
|
||||||
|
|
||||||
|
region_name = getattr(self.region, 'name', 'default_region')
|
||||||
|
|
||||||
|
current_app.logger.debug(f"Generating cache key in region {region_name} with prefix {self.prefix} "
|
||||||
|
f"for {self._key_components}")
|
||||||
|
|
||||||
key = CacheKey({k: identifiers[k] for k in self._key_components})
|
key = CacheKey({k: identifiers[k] for k in self._key_components})
|
||||||
return f"{self.prefix}:{str(key)}"
|
return f"{region_name}_{self.prefix}:{str(key)}"
|
||||||
|
|
||||||
def get(self, creator_func, **identifiers) -> T:
|
def get(self, creator_func, **identifiers) -> T:
|
||||||
"""
|
"""
|
||||||
@@ -92,18 +138,19 @@ class CacheHandler(Generic[T]):
|
|||||||
Cached or newly created value
|
Cached or newly created value
|
||||||
"""
|
"""
|
||||||
cache_key = self.generate_key(**identifiers)
|
cache_key = self.generate_key(**identifiers)
|
||||||
|
current_app.logger.debug(f"Cache key: {cache_key}")
|
||||||
|
|
||||||
def creator():
|
def creator():
|
||||||
instance = creator_func(**identifiers)
|
instance = creator_func(**identifiers)
|
||||||
return self.to_cache_data(instance)
|
return self._to_cache_data(instance)
|
||||||
|
|
||||||
cached_data = self.region.get_or_create(
|
cached_data = self.region.get_or_create(
|
||||||
cache_key,
|
cache_key,
|
||||||
creator,
|
creator,
|
||||||
should_cache_fn=self.should_cache
|
should_cache_fn=self._should_cache
|
||||||
)
|
)
|
||||||
|
|
||||||
return self.from_cache_data(cached_data, **identifiers)
|
return self._from_cache_data(cached_data, **identifiers)
|
||||||
|
|
||||||
def invalidate(self, **identifiers):
|
def invalidate(self, **identifiers):
|
||||||
"""
|
"""
|
||||||
@@ -128,3 +175,21 @@ class CacheHandler(Generic[T]):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
pass # Skip if cache key can't be generated from provided identifiers
|
pass # Skip if cache key can't be generated from provided identifiers
|
||||||
|
|
||||||
|
def invalidate_region(self):
|
||||||
|
"""
|
||||||
|
Invalidate all cache entries within this region.
|
||||||
|
|
||||||
|
Deletes all keys that start with the region prefix.
|
||||||
|
"""
|
||||||
|
# Construct the pattern for all keys in this region
|
||||||
|
pattern = f"{self.region}_{self.prefix}:*"
|
||||||
|
|
||||||
|
# Assuming Redis backend with dogpile, use `delete_multi` or direct Redis access
|
||||||
|
if hasattr(self.region.backend, 'client'):
|
||||||
|
redis_client = self.region.backend.client
|
||||||
|
keys_to_delete = redis_client.keys(pattern)
|
||||||
|
if keys_to_delete:
|
||||||
|
redis_client.delete(*keys_to_delete)
|
||||||
|
else:
|
||||||
|
# Fallback for other backends
|
||||||
|
raise NotImplementedError("Region invalidation is only supported for Redis backend.")
|
||||||
|
|||||||
370
common/utils/cache/config_cache.py
vendored
370
common/utils/cache/config_cache.py
vendored
@@ -5,11 +5,15 @@ from packaging import version
|
|||||||
import os
|
import os
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
|
|
||||||
from common.utils.cache.base import CacheHandler
|
from common.utils.cache.base import CacheHandler, CacheKey
|
||||||
|
|
||||||
from config.type_defs import agent_types, task_types, tool_types, specialist_types
|
from config.type_defs import agent_types, task_types, tool_types, specialist_types
|
||||||
|
|
||||||
|
|
||||||
|
def is_major_minor(version: str) -> bool:
|
||||||
|
parts = version.strip('.').split('.')
|
||||||
|
return len(parts) == 2 and all(part.isdigit() for part in parts)
|
||||||
|
|
||||||
|
|
||||||
class BaseConfigCacheHandler(CacheHandler[Dict[str, Any]]):
|
class BaseConfigCacheHandler(CacheHandler[Dict[str, Any]]):
|
||||||
"""Base handler for configuration caching"""
|
"""Base handler for configuration caching"""
|
||||||
|
|
||||||
@@ -23,18 +27,111 @@ class BaseConfigCacheHandler(CacheHandler[Dict[str, Any]]):
|
|||||||
self.config_type = config_type
|
self.config_type = config_type
|
||||||
self._types_module = None # Set by subclasses
|
self._types_module = None # Set by subclasses
|
||||||
self._config_dir = None # Set by subclasses
|
self._config_dir = None # Set by subclasses
|
||||||
|
self.version_tree_cache = None
|
||||||
|
self.configure_keys('type_name', 'version')
|
||||||
|
|
||||||
def configure_keys_for_operation(self, operation: str):
|
def _to_cache_data(self, instance: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
"""Configure required keys based on operation"""
|
"""Convert the data to a cacheable format"""
|
||||||
match operation:
|
# For configuration data, we can just return the dictionary as is
|
||||||
case 'get_types':
|
# since it's already in a serializable format
|
||||||
self.configure_keys('type_name') # Only require type_name for type definitions
|
return instance
|
||||||
case 'get_versions':
|
|
||||||
self.configure_keys('type_name') # Only type_name needed for version tree
|
def _from_cache_data(self, data: Dict[str, Any], **kwargs) -> Dict[str, Any]:
|
||||||
case 'get_config':
|
"""Convert cached data back to usable format"""
|
||||||
self.configure_keys('type_name', 'version') # Both needed for specific config
|
# Similarly, we can return the data directly since it's already
|
||||||
case _:
|
# in the format we need
|
||||||
raise ValueError(f"Unknown operation: {operation}")
|
return data
|
||||||
|
|
||||||
|
def _should_cache(self, value: Dict[str, Any]) -> bool:
|
||||||
|
"""
|
||||||
|
Validate if the value should be cached
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: The value to be cached
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if the value should be cached
|
||||||
|
"""
|
||||||
|
return isinstance(value, dict) # Cache all dictionaries
|
||||||
|
|
||||||
|
def set_version_tree_cache(self, cache):
|
||||||
|
"""Set the version tree cache dependency."""
|
||||||
|
self.version_tree_cache = cache
|
||||||
|
|
||||||
|
def _load_specific_config(self, type_name: str, version_str: str) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Load a specific configuration version
|
||||||
|
|
||||||
|
Args:
|
||||||
|
type_name: Type name
|
||||||
|
version_str: Version string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Configuration data
|
||||||
|
"""
|
||||||
|
current_app.logger.debug(f"Loading specific configuration for {type_name}, version: {version_str} - no cache")
|
||||||
|
version_tree = self.version_tree_cache.get_versions(type_name)
|
||||||
|
versions = version_tree['versions']
|
||||||
|
|
||||||
|
if version_str == 'latest':
|
||||||
|
version_str = version_tree['latest_version']
|
||||||
|
|
||||||
|
if version_str not in versions:
|
||||||
|
raise ValueError(f"Version {version_str} not found for {type_name}")
|
||||||
|
|
||||||
|
file_path = versions[version_str]['file_path']
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(file_path) as f:
|
||||||
|
config = yaml.safe_load(f)
|
||||||
|
current_app.logger.debug(f"Loaded config for {type_name}{version_str}: {config}")
|
||||||
|
return config
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError(f"Error loading config from {file_path}: {e}")
|
||||||
|
|
||||||
|
def get_config(self, type_name: str, version: Optional[str] = None) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Get configuration for a specific type and version
|
||||||
|
If version not specified, returns latest
|
||||||
|
|
||||||
|
Args:
|
||||||
|
type_name: Configuration type name
|
||||||
|
version: Optional specific version to retrieve
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Configuration data
|
||||||
|
"""
|
||||||
|
current_app.logger.debug(f"Trying to retrieve config for {self.config_type}, type name: {type_name}, "
|
||||||
|
f"version: {version}")
|
||||||
|
if version is None:
|
||||||
|
version_str = self.version_tree_cache.get_latest_version(type_name)
|
||||||
|
elif is_major_minor(version):
|
||||||
|
version_str = self.version_tree_cache.get_latest_patch_version(type_name, version)
|
||||||
|
else:
|
||||||
|
version_str = version
|
||||||
|
|
||||||
|
result = self.get(
|
||||||
|
lambda type_name, version: self._load_specific_config(type_name, version),
|
||||||
|
type_name=type_name,
|
||||||
|
version=version_str
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class BaseConfigVersionTreeCacheHandler(CacheHandler[Dict[str, Any]]):
|
||||||
|
"""Base handler for configuration version tree caching"""
|
||||||
|
|
||||||
|
def __init__(self, region, config_type: str):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
region: Cache region
|
||||||
|
config_type: Type of configuration (agents, tasks, etc.)
|
||||||
|
"""
|
||||||
|
super().__init__(region, f'config_{config_type}_version_tree')
|
||||||
|
self.config_type = config_type
|
||||||
|
self._types_module = None # Set by subclasses
|
||||||
|
self._config_dir = None # Set by subclasses
|
||||||
|
self.configure_keys('type_name')
|
||||||
|
|
||||||
def _load_version_tree(self, type_name: str) -> Dict[str, Any]:
|
def _load_version_tree(self, type_name: str) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
@@ -46,6 +143,7 @@ class BaseConfigCacheHandler(CacheHandler[Dict[str, Any]]):
|
|||||||
Returns:
|
Returns:
|
||||||
Dict containing available versions and their metadata
|
Dict containing available versions and their metadata
|
||||||
"""
|
"""
|
||||||
|
current_app.logger.debug(f"Loading version tree for {type_name} - no cache")
|
||||||
type_path = Path(self._config_dir) / type_name
|
type_path = Path(self._config_dir) / type_name
|
||||||
if not type_path.exists():
|
if not type_path.exists():
|
||||||
raise ValueError(f"No configuration found for type {type_name}")
|
raise ValueError(f"No configuration found for type {type_name}")
|
||||||
@@ -81,25 +179,25 @@ class BaseConfigCacheHandler(CacheHandler[Dict[str, Any]]):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
current_app.logger.debug(f"Loaded versions for {type_name}: {versions}")
|
current_app.logger.debug(f"Loaded versions for {type_name}: {versions}")
|
||||||
current_app.logger.debug(f"Loaded versions for {type_name}: {latest_version}")
|
current_app.logger.debug(f"Latest version for {type_name}: {latest_version}")
|
||||||
return {
|
return {
|
||||||
'versions': versions,
|
'versions': versions,
|
||||||
'latest_version': latest_version
|
'latest_version': latest_version
|
||||||
}
|
}
|
||||||
|
|
||||||
def to_cache_data(self, instance: Dict[str, Any]) -> Dict[str, Any]:
|
def _to_cache_data(self, instance: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
"""Convert the data to a cacheable format"""
|
"""Convert the data to a cacheable format"""
|
||||||
# For configuration data, we can just return the dictionary as is
|
# For configuration data, we can just return the dictionary as is
|
||||||
# since it's already in a serializable format
|
# since it's already in a serializable format
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
def from_cache_data(self, data: Dict[str, Any], **kwargs) -> Dict[str, Any]:
|
def _from_cache_data(self, data: Dict[str, Any], **kwargs) -> Dict[str, Any]:
|
||||||
"""Convert cached data back to usable format"""
|
"""Convert cached data back to usable format"""
|
||||||
# Similarly, we can return the data directly since it's already
|
# Similarly, we can return the data directly since it's already
|
||||||
# in the format we need
|
# in the format we need
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def should_cache(self, value: Dict[str, Any]) -> bool:
|
def _should_cache(self, value: Dict[str, Any]) -> bool:
|
||||||
"""
|
"""
|
||||||
Validate if the value should be cached
|
Validate if the value should be cached
|
||||||
|
|
||||||
@@ -109,65 +207,7 @@ class BaseConfigCacheHandler(CacheHandler[Dict[str, Any]]):
|
|||||||
Returns:
|
Returns:
|
||||||
bool: True if the value should be cached
|
bool: True if the value should be cached
|
||||||
"""
|
"""
|
||||||
if not isinstance(value, dict):
|
return isinstance(value, dict) # Cache all dictionaries
|
||||||
return False
|
|
||||||
|
|
||||||
# For type definitions
|
|
||||||
if 'name' in value and 'description' in value:
|
|
||||||
return True
|
|
||||||
|
|
||||||
# For configurations
|
|
||||||
if 'versions' in value and 'latest_version' in value:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _load_type_definitions(self) -> Dict[str, Dict[str, str]]:
|
|
||||||
"""Load type definitions from the corresponding type_defs module"""
|
|
||||||
if not self._types_module:
|
|
||||||
raise ValueError("_types_module must be set by subclass")
|
|
||||||
|
|
||||||
type_definitions = {
|
|
||||||
type_id: {
|
|
||||||
'name': info['name'],
|
|
||||||
'description': info['description']
|
|
||||||
}
|
|
||||||
for type_id, info in self._types_module.items()
|
|
||||||
}
|
|
||||||
|
|
||||||
current_app.logger.debug(f"Loaded type definitions: {type_definitions}")
|
|
||||||
|
|
||||||
return type_definitions
|
|
||||||
|
|
||||||
def _load_specific_config(self, type_name: str, version_str: str) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Load a specific configuration version
|
|
||||||
|
|
||||||
Args:
|
|
||||||
type_name: Type name
|
|
||||||
version_str: Version string
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Configuration data
|
|
||||||
"""
|
|
||||||
version_tree = self.get_versions(type_name)
|
|
||||||
versions = version_tree['versions']
|
|
||||||
|
|
||||||
if version_str == 'latest':
|
|
||||||
version_str = version_tree['latest_version']
|
|
||||||
|
|
||||||
if version_str not in versions:
|
|
||||||
raise ValueError(f"Version {version_str} not found for {type_name}")
|
|
||||||
|
|
||||||
file_path = versions[version_str]['file_path']
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(file_path) as f:
|
|
||||||
config = yaml.safe_load(f)
|
|
||||||
current_app.logger.debug(f"Loaded config for {type_name}{version_str}: {config}")
|
|
||||||
return config
|
|
||||||
except Exception as e:
|
|
||||||
raise ValueError(f"Error loading config from {file_path}: {e}")
|
|
||||||
|
|
||||||
def get_versions(self, type_name: str) -> Dict[str, Any]:
|
def get_versions(self, type_name: str) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
@@ -179,7 +219,7 @@ class BaseConfigCacheHandler(CacheHandler[Dict[str, Any]]):
|
|||||||
Returns:
|
Returns:
|
||||||
Dict with version information
|
Dict with version information
|
||||||
"""
|
"""
|
||||||
self.configure_keys_for_operation('get_versions')
|
current_app.logger.debug(f"Trying to get version tree for {self.config_type}, {type_name}")
|
||||||
return self.get(
|
return self.get(
|
||||||
lambda type_name: self._load_version_tree(type_name),
|
lambda type_name: self._load_version_tree(type_name),
|
||||||
type_name=type_name
|
type_name=type_name
|
||||||
@@ -235,72 +275,146 @@ class BaseConfigCacheHandler(CacheHandler[Dict[str, Any]]):
|
|||||||
latest_patch = max(matching_versions, key=version.parse)
|
latest_patch = max(matching_versions, key=version.parse)
|
||||||
return latest_patch
|
return latest_patch
|
||||||
|
|
||||||
|
|
||||||
|
class BaseConfigTypesCacheHandler(CacheHandler[Dict[str, Any]]):
|
||||||
|
"""Base handler for configuration types caching"""
|
||||||
|
|
||||||
|
def __init__(self, region, config_type: str):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
region: Cache region
|
||||||
|
config_type: Type of configuration (agents, tasks, etc.)
|
||||||
|
"""
|
||||||
|
super().__init__(region, f'config_{config_type}_types')
|
||||||
|
self.config_type = config_type
|
||||||
|
self._types_module = None # Set by subclasses
|
||||||
|
self._config_dir = None # Set by subclasses
|
||||||
|
self.configure_keys()
|
||||||
|
|
||||||
|
def _to_cache_data(self, instance: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""Convert the data to a cacheable format"""
|
||||||
|
# For configuration data, we can just return the dictionary as is
|
||||||
|
# since it's already in a serializable format
|
||||||
|
return instance
|
||||||
|
|
||||||
|
def _from_cache_data(self, data: Dict[str, Any], **kwargs) -> Dict[str, Any]:
|
||||||
|
"""Convert cached data back to usable format"""
|
||||||
|
# Similarly, we can return the data directly since it's already
|
||||||
|
# in the format we need
|
||||||
|
return data
|
||||||
|
|
||||||
|
def _should_cache(self, value: Dict[str, Any]) -> bool:
|
||||||
|
"""
|
||||||
|
Validate if the value should be cached
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: The value to be cached
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if the value should be cached
|
||||||
|
"""
|
||||||
|
return isinstance(value, dict) # Cache all dictionaries
|
||||||
|
|
||||||
|
def _load_type_definitions(self) -> Dict[str, Dict[str, str]]:
|
||||||
|
"""Load type definitions from the corresponding type_defs module"""
|
||||||
|
current_app.logger.debug(f"Loading type definitions for {self.config_type} - no cache")
|
||||||
|
if not self._types_module:
|
||||||
|
raise ValueError("_types_module must be set by subclass")
|
||||||
|
|
||||||
|
type_definitions = {
|
||||||
|
type_id: {
|
||||||
|
'name': info['name'],
|
||||||
|
'description': info['description']
|
||||||
|
}
|
||||||
|
for type_id, info in self._types_module.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
current_app.logger.debug(f"Loaded type definitions: {type_definitions}")
|
||||||
|
|
||||||
|
return type_definitions
|
||||||
|
|
||||||
def get_types(self) -> Dict[str, Dict[str, str]]:
|
def get_types(self) -> Dict[str, Dict[str, str]]:
|
||||||
"""Get dictionary of available types with name and description"""
|
"""Get dictionary of available types with name and description"""
|
||||||
self.configure_keys_for_operation('get_types')
|
current_app.logger.debug(f"Trying to retrieve type definitions for {self.config_type}")
|
||||||
result = self.get(
|
result = self.get(
|
||||||
lambda type_name: self._load_type_definitions(),
|
lambda type_name: self._load_type_definitions(),
|
||||||
type_name=f'{self.config_type}_types',
|
type_name=f'{self.config_type}_types',
|
||||||
)
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def get_config(self, type_name: str, version: Optional[str] = None) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Get configuration for a specific type and version
|
|
||||||
If version not specified, returns latest
|
|
||||||
|
|
||||||
Args:
|
def create_config_cache_handlers(config_type: str, config_dir: str, types_module: dict) -> tuple:
|
||||||
type_name: Configuration type name
|
"""
|
||||||
version: Optional specific version to retrieve
|
Factory function to dynamically create the 3 cache handler classes for a given configuration type.
|
||||||
|
The following cache names are created:
|
||||||
Returns:
|
- <config_type>_config_cache
|
||||||
Configuration data
|
- <config_type>_version_tree_cache
|
||||||
"""
|
- <config_type>_types_cache
|
||||||
self.configure_keys_for_operation('get_config')
|
|
||||||
version_str = version or 'latest'
|
|
||||||
|
|
||||||
return self.get(
|
|
||||||
lambda type_name, version: self._load_specific_config(type_name, version),
|
|
||||||
type_name=type_name,
|
|
||||||
version=version_str
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class AgentConfigCacheHandler(BaseConfigCacheHandler):
|
Args:
|
||||||
"""Handler for agent configurations"""
|
config_type: The configuration type (e.g., 'agents', 'tasks').
|
||||||
handler_name = 'agent_config_cache'
|
config_dir: The directory where configuration files are stored.
|
||||||
|
types_module: The types module defining the available types for this config.
|
||||||
|
|
||||||
def __init__(self, region):
|
Returns:
|
||||||
super().__init__(region, 'agents')
|
A tuple of dynamically created classes for config, version tree, and types handlers.
|
||||||
self._types_module = agent_types.AGENT_TYPES
|
"""
|
||||||
self._config_dir = os.path.join('config', 'agents')
|
|
||||||
|
class ConfigCacheHandler(BaseConfigCacheHandler):
|
||||||
|
handler_name = f"{config_type}_config_cache"
|
||||||
|
|
||||||
|
def __init__(self, region):
|
||||||
|
super().__init__(region, config_type)
|
||||||
|
self._types_module = types_module
|
||||||
|
self._config_dir = config_dir
|
||||||
|
|
||||||
|
class VersionTreeCacheHandler(BaseConfigVersionTreeCacheHandler):
|
||||||
|
handler_name = f"{config_type}_version_tree_cache"
|
||||||
|
|
||||||
|
def __init__(self, region):
|
||||||
|
super().__init__(region, config_type)
|
||||||
|
self._types_module = types_module
|
||||||
|
self._config_dir = config_dir
|
||||||
|
|
||||||
|
class TypesCacheHandler(BaseConfigTypesCacheHandler):
|
||||||
|
handler_name = f"{config_type}_types_cache"
|
||||||
|
|
||||||
|
def __init__(self, region):
|
||||||
|
super().__init__(region, config_type)
|
||||||
|
self._types_module = types_module
|
||||||
|
self._config_dir = config_dir
|
||||||
|
|
||||||
|
return ConfigCacheHandler, VersionTreeCacheHandler, TypesCacheHandler
|
||||||
|
|
||||||
|
|
||||||
class TaskConfigCacheHandler(BaseConfigCacheHandler):
|
AgentConfigCacheHandler, AgentConfigVersionTreeCacheHandler, AgentConfigTypesCacheHandler = (
|
||||||
"""Handler for task configurations"""
|
create_config_cache_handlers(
|
||||||
handler_name = 'task_config_cache'
|
config_type='agents',
|
||||||
|
config_dir='config/agents',
|
||||||
def __init__(self, region):
|
types_module=agent_types.AGENT_TYPES
|
||||||
super().__init__(region, 'tasks')
|
))
|
||||||
self._types_module = task_types.TASK_TYPES
|
|
||||||
self._config_dir = os.path.join('config', 'tasks')
|
|
||||||
|
|
||||||
|
|
||||||
class ToolConfigCacheHandler(BaseConfigCacheHandler):
|
TaskConfigCacheHandler, TaskConfigVersionTreeCacheHandler, TaskConfigTypesCacheHandler = (
|
||||||
"""Handler for tool configurations"""
|
create_config_cache_handlers(
|
||||||
handler_name = 'tool_config_cache'
|
config_type='tasks',
|
||||||
|
config_dir='config/tasks',
|
||||||
def __init__(self, region):
|
types_module=task_types.TASK_TYPES
|
||||||
super().__init__(region, 'tools')
|
))
|
||||||
self._types_module = tool_types.TOOL_TYPES
|
|
||||||
self._config_dir = os.path.join('config', 'tools')
|
|
||||||
|
|
||||||
|
|
||||||
class SpecialistConfigCacheHandler(BaseConfigCacheHandler):
|
ToolConfigCacheHandler, ToolConfigVersionTreeCacheHandler, ToolConfigTypesCacheHandler = (
|
||||||
"""Handler for specialist configurations"""
|
create_config_cache_handlers(
|
||||||
handler_name = 'specialist_config_cache'
|
config_type='tools',
|
||||||
|
config_dir='config/tools',
|
||||||
|
types_module=tool_types.TOOL_TYPES
|
||||||
|
))
|
||||||
|
|
||||||
def __init__(self, region):
|
|
||||||
super().__init__(region, 'specialists')
|
SpecialistConfigCacheHandler, SpecialistConfigVersionTreeCacheHandler, SpecialistConfigTypesCacheHandler = (
|
||||||
self._types_module = specialist_types.SPECIALIST_TYPES
|
create_config_cache_handlers(
|
||||||
self._config_dir = os.path.join('config', 'specialists')
|
config_type='specialists',
|
||||||
|
config_dir='config/specialists',
|
||||||
|
types_module=specialist_types.SPECIALIST_TYPES
|
||||||
|
))
|
||||||
|
|||||||
6
common/utils/cache/regions.py
vendored
6
common/utils/cache/regions.py
vendored
@@ -65,11 +65,7 @@ def create_cache_regions(app):
|
|||||||
|
|
||||||
eveai_config_region = make_region(name='eveai_config').configure(
|
eveai_config_region = make_region(name='eveai_config').configure(
|
||||||
'dogpile.cache.redis',
|
'dogpile.cache.redis',
|
||||||
arguments={
|
arguments=redis_config,
|
||||||
**redis_config,
|
|
||||||
'redis_expiration_time': None, # No expiration in Redis
|
|
||||||
'key_mangler': lambda key: f"startup_{startup_time}:{key}" # Prefix all keys
|
|
||||||
},
|
|
||||||
replace_existing_backend=True
|
replace_existing_backend=True
|
||||||
)
|
)
|
||||||
regions['eveai_config'] = eveai_config_region
|
regions['eveai_config'] = eveai_config_region
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ def initialize_specialist(specialist_id: int, specialist_type: str, specialist_v
|
|||||||
ValueError: If specialist not found or invalid configuration
|
ValueError: If specialist not found or invalid configuration
|
||||||
SQLAlchemyError: If database operations fail
|
SQLAlchemyError: If database operations fail
|
||||||
"""
|
"""
|
||||||
config = cache_manager.specialist_config_cache.get_config(specialist_type, specialist_version)
|
config = cache_manager.specialists_config_cache.get_config(specialist_type, specialist_version)
|
||||||
if not config:
|
if not config:
|
||||||
raise ValueError(f"No configuration found for {specialist_type} version {specialist_version}")
|
raise ValueError(f"No configuration found for {specialist_type} version {specialist_version}")
|
||||||
if config['framework'] == 'langchain':
|
if config['framework'] == 'langchain':
|
||||||
@@ -99,16 +99,18 @@ def _create_agent(
|
|||||||
timestamp: Optional[dt] = None
|
timestamp: Optional[dt] = None
|
||||||
) -> EveAIAgent:
|
) -> EveAIAgent:
|
||||||
"""Create an agent with the given configuration."""
|
"""Create an agent with the given configuration."""
|
||||||
|
current_app.logger.debug(f"Creating agent {agent_type} {agent_version} with {name}, {description}")
|
||||||
if timestamp is None:
|
if timestamp is None:
|
||||||
timestamp = dt.now(tz=tz.utc)
|
timestamp = dt.now(tz=tz.utc)
|
||||||
|
|
||||||
# Get agent configuration from cache
|
# Get agent configuration from cache
|
||||||
agent_config = cache_manager.agent_config_cache.get_config(agent_type, agent_version)
|
agent_config = cache_manager.agents_config_cache.get_config(agent_type, agent_version)
|
||||||
|
current_app.logger.debug(f"Agent Config: {agent_config}")
|
||||||
|
|
||||||
agent = EveAIAgent(
|
agent = EveAIAgent(
|
||||||
specialist_id=specialist_id,
|
specialist_id=specialist_id,
|
||||||
name=name or agent_config.get('name', agent_type),
|
name=name or agent_config.get('name', agent_type),
|
||||||
description=description or agent_config.get('description', ''),
|
description=description or agent_config.get('metadata').get('description', ''),
|
||||||
type=agent_type,
|
type=agent_type,
|
||||||
type_version=agent_version,
|
type_version=agent_version,
|
||||||
role=None,
|
role=None,
|
||||||
@@ -122,6 +124,7 @@ def _create_agent(
|
|||||||
set_logging_information(agent, timestamp)
|
set_logging_information(agent, timestamp)
|
||||||
|
|
||||||
db.session.add(agent)
|
db.session.add(agent)
|
||||||
|
current_app.logger.info(f"Created agent {agent.id} of type {agent_type}")
|
||||||
return agent
|
return agent
|
||||||
|
|
||||||
|
|
||||||
@@ -138,14 +141,16 @@ def _create_task(
|
|||||||
timestamp = dt.now(tz=tz.utc)
|
timestamp = dt.now(tz=tz.utc)
|
||||||
|
|
||||||
# Get task configuration from cache
|
# Get task configuration from cache
|
||||||
task_config = cache_manager.task_config_cache.get_config(task_type, task_version)
|
task_config = cache_manager.tasks_config_cache.get_config(task_type, task_version)
|
||||||
|
current_app.logger.debug(f"Task Config: {task_config}")
|
||||||
|
|
||||||
task = EveAITask(
|
task = EveAITask(
|
||||||
specialist_id=specialist_id,
|
specialist_id=specialist_id,
|
||||||
name=name or task_config.get('name', task_type),
|
name=name or task_config.get('name', task_type),
|
||||||
description=description or task_config.get('description', ''),
|
description=description or task_config.get('metadata').get('description', ''),
|
||||||
type=task_type,
|
type=task_type,
|
||||||
type_version=task_version,
|
type_version=task_version,
|
||||||
|
task_description=None,
|
||||||
expected_output=None,
|
expected_output=None,
|
||||||
tuning=False,
|
tuning=False,
|
||||||
configuration=None,
|
configuration=None,
|
||||||
@@ -157,6 +162,7 @@ def _create_task(
|
|||||||
set_logging_information(task, timestamp)
|
set_logging_information(task, timestamp)
|
||||||
|
|
||||||
db.session.add(task)
|
db.session.add(task)
|
||||||
|
current_app.logger.info(f"Created task {task.id} of type {task_type}")
|
||||||
return task
|
return task
|
||||||
|
|
||||||
|
|
||||||
@@ -173,12 +179,13 @@ def _create_tool(
|
|||||||
timestamp = dt.now(tz=tz.utc)
|
timestamp = dt.now(tz=tz.utc)
|
||||||
|
|
||||||
# Get tool configuration from cache
|
# Get tool configuration from cache
|
||||||
tool_config = cache_manager.tool_config_cache.get_config(tool_type, tool_version)
|
tool_config = cache_manager.tools_config_cache.get_config(tool_type, tool_version)
|
||||||
|
current_app.logger.debug(f"Tool Config: {tool_config}")
|
||||||
|
|
||||||
tool = EveAITool(
|
tool = EveAITool(
|
||||||
specialist_id=specialist_id,
|
specialist_id=specialist_id,
|
||||||
name=name or tool_config.get('name', tool_type),
|
name=name or tool_config.get('name', tool_type),
|
||||||
description=description or tool_config.get('description', ''),
|
description=description or tool_config.get('metadata').get('description', ''),
|
||||||
type=tool_type,
|
type=tool_type,
|
||||||
type_version=tool_version,
|
type_version=tool_version,
|
||||||
tuning=False,
|
tuning=False,
|
||||||
@@ -189,4 +196,5 @@ def _create_tool(
|
|||||||
set_logging_information(tool, timestamp)
|
set_logging_information(tool, timestamp)
|
||||||
|
|
||||||
db.session.add(tool)
|
db.session.add(tool)
|
||||||
|
current_app.logger.info(f"Created tool {tool.id} of type {tool_type}")
|
||||||
return tool
|
return tool
|
||||||
|
|||||||
@@ -28,10 +28,15 @@ def perform_startup_invalidation(app):
|
|||||||
try:
|
try:
|
||||||
# Check if invalidation was already performed
|
# Check if invalidation was already performed
|
||||||
if not redis_client.get(marker_key):
|
if not redis_client.get(marker_key):
|
||||||
|
app.logger.debug(f"Performing cache invalidation at startup time {startup_time}")
|
||||||
|
app.logger.debug(f"Current cache keys: {redis_client.keys('*')}")
|
||||||
|
|
||||||
# Perform invalidation
|
# Perform invalidation
|
||||||
cache_manager.invalidate_region('eveai_config')
|
cache_manager.invalidate_region('eveai_config')
|
||||||
# Set marker with 1 hour expiry (longer than any reasonable startup sequence)
|
|
||||||
redis_client.setex(marker_key, 300, str(startup_time))
|
app.logger.debug(f"Cache keys after invalidation: {redis_client.keys('*')}")
|
||||||
|
|
||||||
|
redis_client.setex(marker_key, 180, str(startup_time))
|
||||||
app.logger.info("Startup cache invalidation completed")
|
app.logger.info("Startup cache invalidation completed")
|
||||||
else:
|
else:
|
||||||
app.logger.info("Startup cache invalidation already performed")
|
app.logger.info("Startup cache invalidation already performed")
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ class DevConfig(Config):
|
|||||||
DEVELOPMENT = True
|
DEVELOPMENT = True
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
FLASK_DEBUG = True
|
FLASK_DEBUG = True
|
||||||
EXPLAIN_TEMPLATE_LOADING = True
|
EXPLAIN_TEMPLATE_LOADING = False
|
||||||
|
|
||||||
# Database Settings
|
# Database Settings
|
||||||
DB_HOST = environ.get('DB_HOST', 'localhost')
|
DB_HOST = environ.get('DB_HOST', 'localhost')
|
||||||
|
|||||||
@@ -89,37 +89,38 @@ results:
|
|||||||
type: "List[str]"
|
type: "List[str]"
|
||||||
description: "A list of needs"
|
description: "A list of needs"
|
||||||
required: false
|
required: false
|
||||||
agents:
|
agents:
|
||||||
- type: "RAG_AGENT"
|
- type: "RAG_AGENT"
|
||||||
version: 1.0
|
version: "1.0"
|
||||||
name: "Default RAG Agent" # Just added as an example. Overwrites the default agent name.
|
name: "Default RAG Agent" # Just added as an example. Overwrites the default agent name.
|
||||||
description: "An Agent that does RAG based on a user's question, RAG content & history" # Just added as an example. Overwrites the default agent description.
|
description: "An Agent that does RAG based on a user's question, RAG content & history" # Just added as an example. Overwrites the default agent description.
|
||||||
- type: "SPIN_DETECTION_AGENT"
|
- type: "SPIN_DETECTION_AGENT"
|
||||||
version: 1.0
|
version: "1.0"
|
||||||
- type: "SPIN_SALES_SPECIALIST_AGENT"
|
- type: "SPIN_SALES_SPECIALIST_AGENT"
|
||||||
version: 1.0
|
version: "1.0"
|
||||||
- type: "IDENTIFICATION_AGENT"
|
- type: "IDENTIFICATION_AGENT"
|
||||||
version: 1.0
|
version: "1.0"
|
||||||
- type: "EMAIL_CONTENT_AGENT"
|
- type: "EMAIL_CONTENT_AGENT"
|
||||||
version: 1.0
|
version: "1.0"
|
||||||
- type: "EMAIL_LEAD_ENGAGEMENT"
|
- type: "EMAIL_ENGAGEMENT_AGENT"
|
||||||
version: 1.0
|
version: "1.0"
|
||||||
tasks:
|
tasks:
|
||||||
- type: "RAG_TASK"
|
- type: "RAG_TASK"
|
||||||
version: 1.0
|
version: "1.0"
|
||||||
- type: "SPIN_DETECT_TASK"
|
- type: "SPIN_DETECT_TASK"
|
||||||
version: 1.0
|
version: "1.0"
|
||||||
- type: "SPIN_QUESTIONS_TASK"
|
- type: "SPIN_QUESTIONS_TASK"
|
||||||
version: 1.0
|
version: "1.0"
|
||||||
- type: "IDENTIFICATION_DETECTION_TASK"
|
- type: "IDENTIFICATION_DETECTION_TASK"
|
||||||
version: 1.0
|
version: "1.0"
|
||||||
- type: "IDENTIFICATION_QUESTIONS_TASK"
|
- type: "IDENTIFICATION_QUESTIONS_TASK"
|
||||||
version: 1.0
|
version: "1.0"
|
||||||
- type: "EMAIL_LEAD_DRAFTING"
|
- type: "EMAIL_LEAD_DRAFTING_TASK"
|
||||||
version: 1.0
|
version: "1.0"
|
||||||
- type: "EMAIL_LEAD_ENGAGEMENT"
|
- type: "EMAIL_LEAD_ENGAGEMENT_TASK"
|
||||||
version: 1.0
|
version: "1.0"
|
||||||
metadata:
|
metadata:
|
||||||
author: "Josako"
|
author: "Josako"
|
||||||
date_added: "2025-01-08"
|
date_added: "2025-01-08"
|
||||||
changes: "Initial version"
|
changes: "Initial version"
|
||||||
|
description: "A Specialist that performs both Q&A as SPIN (Sales Process) activities"
|
||||||
@@ -49,3 +49,4 @@ metadata:
|
|||||||
author: "Josako"
|
author: "Josako"
|
||||||
date_added: "2025-01-08"
|
date_added: "2025-01-08"
|
||||||
changes: "Initial version"
|
changes: "Initial version"
|
||||||
|
description: "A Specialist that performs standard Q&A"
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
version: "1.0.0"
|
version: "1.0.0"
|
||||||
name: "Email Lead Draft Creation"
|
name: "Email Lead Draft Creation"
|
||||||
description: >
|
task_description: >
|
||||||
Craft a highly personalized email using the lead's name, job title, company information, and any relevant personal or
|
Craft a highly personalized email using the lead's name, job title, company information, and any relevant personal or
|
||||||
company achievements when available. The email should speak directly to the lead's interests and the needs
|
company achievements when available. The email should speak directly to the lead's interests and the needs
|
||||||
of their company.
|
of their company.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
version: "1.0.0"
|
version: "1.0.0"
|
||||||
name: "Email Lead Engagement Creation"
|
name: "Email Lead Engagement Creation"
|
||||||
description: >
|
task_description: >
|
||||||
Review a personalized email and optimize it with strong CTAs and engagement hooks. Keep in mind that this email is
|
Review a personalized email and optimize it with strong CTAs and engagement hooks. Keep in mind that this email is
|
||||||
the consequence of a first conversation.
|
the consequence of a first conversation.
|
||||||
Don't use any salutations or closing remarks, nor too complex sentences. Keep it short and to the point.
|
Don't use any salutations or closing remarks, nor too complex sentences. Keep it short and to the point.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
version: "1.0.0"
|
version: "1.0.0"
|
||||||
name: "Identification Gathering"
|
name: "Identification Gathering"
|
||||||
description: >
|
task_description: >
|
||||||
Detect and pass on identification information in the ongoing conversation, from within the following information:
|
Detect and pass on identification information in the ongoing conversation, from within the following information:
|
||||||
{question}
|
{question}
|
||||||
Add to or refine the following already gathered identification information (between triple $)
|
Add to or refine the following already gathered identification information (between triple $)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
version: "1.0.0"
|
version: "1.0.0"
|
||||||
name: "Define Identification Questions"
|
name: "Define Identification Questions"
|
||||||
description: >
|
task_description: >
|
||||||
Ask questions to complete or confirm the identification information gathered.
|
Ask questions to complete or confirm the identification information gathered.
|
||||||
Current Identification Information:
|
Current Identification Information:
|
||||||
$$${Identification}$$$
|
$$${Identification}$$$
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
version: "1.0.0"
|
version: "1.0.0"
|
||||||
name: "RAG Task"
|
name: "RAG Task"
|
||||||
description: >
|
task_description: >
|
||||||
Answer the question based on the following context, delimited between triple backquotes, and taking into account
|
Answer the question based on the following context, delimited between triple backquotes, and taking into account
|
||||||
the history of the discussion, in between triple %
|
the history of the discussion, in between triple %
|
||||||
{custom_description}
|
{custom_description}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
version: "1.0.0"
|
version: "1.0.0"
|
||||||
name: "SPIN Information Detection"
|
name: "SPIN Information Detection"
|
||||||
description: >
|
task_description: >
|
||||||
Detect the SPIN-context, taking into account the history of the discussion (in between triple %) with main focus on
|
Detect the SPIN-context, taking into account the history of the discussion (in between triple %) with main focus on
|
||||||
the latest reply (which can contain answers on previously asked questions by the user). Do not remove elements from
|
the latest reply (which can contain answers on previously asked questions by the user). Do not remove elements from
|
||||||
the known SPIN (in between triple $) analysis unless explicitly stated by the end user in the latest reply. In all other cases, refine the
|
the known SPIN (in between triple $) analysis unless explicitly stated by the end user in the latest reply. In all other cases, refine the
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
version: "1.0.0"
|
version: "1.0.0"
|
||||||
name: "SPIN Question Identification"
|
name: "SPIN Question Identification"
|
||||||
description: >
|
task_description: >
|
||||||
Define, taking into account the history of the discussion (in between triple %), the latest reply and the currently
|
Define, taking into account the history of the discussion (in between triple %), the latest reply and the currently
|
||||||
known SPIN-elements (in between triple $), the top questions that need to be asked to understand the full SPIN context
|
known SPIN-elements (in between triple $), the top questions that need to be asked to understand the full SPIN context
|
||||||
of the customer. If you think this user could be a potential customer, please indicate so.
|
of the customer. If you think this user could be a potential customer, please indicate so.
|
||||||
|
|||||||
@@ -158,6 +158,9 @@ docker buildx use eveai_builder
|
|||||||
|
|
||||||
# Loop through services
|
# Loop through services
|
||||||
for SERVICE in "${SERVICES[@]}"; do
|
for SERVICE in "${SERVICES[@]}"; do
|
||||||
|
if [[ "$SERVICE" == "nginx" ]]; then
|
||||||
|
./copy_specialist_svgs.sh ../config ../nginx/static/assets
|
||||||
|
fi
|
||||||
if [[ "$SERVICE" == "nginx" || "$SERVICE" == eveai_* || "$SERVICE" == "flower" ]]; then
|
if [[ "$SERVICE" == "nginx" || "$SERVICE" == eveai_* || "$SERVICE" == "flower" ]]; then
|
||||||
if process_service "$SERVICE"; then
|
if process_service "$SERVICE"; then
|
||||||
echo "Successfully processed $SERVICE"
|
echo "Successfully processed $SERVICE"
|
||||||
|
|||||||
60
docker/copy_specialist_svgs.sh
Executable file
60
docker/copy_specialist_svgs.sh
Executable file
@@ -0,0 +1,60 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Script to copy specialist overview SVGs to nginx static directory
|
||||||
|
|
||||||
|
# Function to show usage
|
||||||
|
show_usage() {
|
||||||
|
echo "Usage: $0 <config_dir> <static_dir>"
|
||||||
|
echo " config_dir: Path to the config directory containing specialists"
|
||||||
|
echo " static_dir: Path to the nginx static directory"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check arguments
|
||||||
|
if [ $# -ne 2 ]; then
|
||||||
|
show_usage
|
||||||
|
fi
|
||||||
|
|
||||||
|
CONFIG_DIR="$1"
|
||||||
|
STATIC_DIR="$2"
|
||||||
|
SPECIALISTS_DIR="${CONFIG_DIR}/specialists"
|
||||||
|
OUTPUT_DIR="${STATIC_DIR}/specialists"
|
||||||
|
|
||||||
|
# Check if source directory exists
|
||||||
|
if [ ! -d "$SPECIALISTS_DIR" ]; then
|
||||||
|
echo "Error: Specialists directory not found at $SPECIALISTS_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create output directory
|
||||||
|
mkdir -p "$OUTPUT_DIR"
|
||||||
|
|
||||||
|
# Counter for processed files
|
||||||
|
processed=0
|
||||||
|
|
||||||
|
# Process each specialist type directory
|
||||||
|
for TYPE_DIR in "$SPECIALISTS_DIR"/*/ ; do
|
||||||
|
if [ -d "$TYPE_DIR" ]; then
|
||||||
|
# Get specialist type from directory name
|
||||||
|
SPECIALIST_TYPE=$(basename "$TYPE_DIR")
|
||||||
|
|
||||||
|
# Find and process overview SVG files
|
||||||
|
for SVG_FILE in "$TYPE_DIR"*_overview.svg; do
|
||||||
|
if [ -f "$SVG_FILE" ]; then
|
||||||
|
# Extract version (remove _overview.svg from filename)
|
||||||
|
VERSION=$(basename "$SVG_FILE" "_overview.svg")
|
||||||
|
|
||||||
|
# Create new filename
|
||||||
|
NEW_FILENAME="${SPECIALIST_TYPE}_${VERSION}_overview.svg"
|
||||||
|
|
||||||
|
# Copy file
|
||||||
|
cp -f "$SVG_FILE" "${OUTPUT_DIR}/${NEW_FILENAME}"
|
||||||
|
|
||||||
|
echo "Copied $(basename "$SVG_FILE") -> $NEW_FILENAME"
|
||||||
|
((processed++))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo -e "\nProcessed $processed overview SVG files"
|
||||||
@@ -9,11 +9,14 @@ COPY ../../nginx/mime.types /etc/nginx/mime.types
|
|||||||
|
|
||||||
# Copy static & public files
|
# Copy static & public files
|
||||||
RUN mkdir -p /etc/nginx/static /etc/nginx/public
|
RUN mkdir -p /etc/nginx/static /etc/nginx/public
|
||||||
|
|
||||||
COPY ../../nginx/static /etc/nginx/static
|
COPY ../../nginx/static /etc/nginx/static
|
||||||
COPY ../../integrations/Wordpress/eveai-chat/assets/css/eveai-chat-style.css /etc/nginx/static/css/
|
COPY ../../integrations/Wordpress/eveai-chat/assets/css/eveai-chat-style.css /etc/nginx/static/css/
|
||||||
COPY ../../integrations/Wordpress/eveai-chat/assets/js/eveai-chat-widget.js /etc/nginx/static/js/
|
COPY ../../integrations/Wordpress/eveai-chat/assets/js/eveai-chat-widget.js /etc/nginx/static/js/
|
||||||
COPY ../../integrations/Wordpress/eveai-chat/assets/js/eveai-token-manager.js /etc/nginx/static/js/
|
COPY ../../integrations/Wordpress/eveai-chat/assets/js/eveai-token-manager.js /etc/nginx/static/js/
|
||||||
COPY ../../integrations/Wordpress/eveai-chat/assets/js/eveai-sdk.js /etc/nginx/static/js
|
COPY ../../integrations/Wordpress/eveai-chat/assets/js/eveai-sdk.js /etc/nginx/static/js
|
||||||
|
|
||||||
|
# Copy public files
|
||||||
COPY ../../nginx/public /etc/nginx/public
|
COPY ../../nginx/public /etc/nginx/public
|
||||||
|
|
||||||
# Copy site-specific configurations
|
# Copy site-specific configurations
|
||||||
|
|||||||
@@ -161,10 +161,31 @@ def register_blueprints(app):
|
|||||||
|
|
||||||
def register_cache_handlers(app):
|
def register_cache_handlers(app):
|
||||||
from common.utils.cache.config_cache import (
|
from common.utils.cache.config_cache import (
|
||||||
AgentConfigCacheHandler, TaskConfigCacheHandler, ToolConfigCacheHandler, SpecialistConfigCacheHandler)
|
AgentConfigCacheHandler, AgentConfigTypesCacheHandler, AgentConfigVersionTreeCacheHandler,
|
||||||
|
TaskConfigCacheHandler, TaskConfigTypesCacheHandler, TaskConfigVersionTreeCacheHandler,
|
||||||
|
ToolConfigCacheHandler, ToolConfigTypesCacheHandler, ToolConfigVersionTreeCacheHandler,
|
||||||
|
SpecialistConfigCacheHandler, SpecialistConfigTypesCacheHandler, SpecialistConfigVersionTreeCacheHandler,)
|
||||||
|
|
||||||
cache_manager.register_handler(AgentConfigCacheHandler, 'eveai_config')
|
cache_manager.register_handler(AgentConfigCacheHandler, 'eveai_config')
|
||||||
|
cache_manager.register_handler(AgentConfigTypesCacheHandler, 'eveai_config')
|
||||||
|
cache_manager.register_handler(AgentConfigVersionTreeCacheHandler, 'eveai_config')
|
||||||
cache_manager.register_handler(TaskConfigCacheHandler, 'eveai_config')
|
cache_manager.register_handler(TaskConfigCacheHandler, 'eveai_config')
|
||||||
|
cache_manager.register_handler(TaskConfigTypesCacheHandler, 'eveai_config')
|
||||||
|
cache_manager.register_handler(TaskConfigVersionTreeCacheHandler, 'eveai_config')
|
||||||
cache_manager.register_handler(ToolConfigCacheHandler, 'eveai_config')
|
cache_manager.register_handler(ToolConfigCacheHandler, 'eveai_config')
|
||||||
|
cache_manager.register_handler(ToolConfigTypesCacheHandler, 'eveai_config')
|
||||||
|
cache_manager.register_handler(ToolConfigVersionTreeCacheHandler, 'eveai_config')
|
||||||
cache_manager.register_handler(SpecialistConfigCacheHandler, 'eveai_config')
|
cache_manager.register_handler(SpecialistConfigCacheHandler, 'eveai_config')
|
||||||
|
cache_manager.register_handler(SpecialistConfigTypesCacheHandler, 'eveai_config')
|
||||||
|
cache_manager.register_handler(SpecialistConfigVersionTreeCacheHandler, 'eveai_config')
|
||||||
|
|
||||||
|
cache_manager.agents_config_cache.set_version_tree_cache(cache_manager.agents_version_tree_cache)
|
||||||
|
cache_manager.tasks_config_cache.set_version_tree_cache(cache_manager.tasks_version_tree_cache)
|
||||||
|
cache_manager.tools_config_cache.set_version_tree_cache(cache_manager.tools_version_tree_cache)
|
||||||
|
cache_manager.specialists_config_cache.set_version_tree_cache(cache_manager.specialists_version_tree_cache)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,28 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% from "macros.html" import render_field %}
|
{% from "macros.html" import render_field %}
|
||||||
|
|
||||||
{% block title %}{{ title }}{% endblock %}
|
|
||||||
|
|
||||||
{% block content_title %}{{ title }}{% endblock %}
|
{% block content_title %}{{ title }}{% endblock %}
|
||||||
{% block content_description %}{{ description }}{% endblock %}
|
{% block content_description %}{{ description }}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<form method="post">
|
{% set disabled_fields = [] %}
|
||||||
{{ form.hidden_tag() }}
|
{% set exclude_fields = [] %}
|
||||||
{% set disabled_fields = [] %}
|
{% for field in form.get_static_fields() %}
|
||||||
{% set exclude_fields = [] %}
|
{{ render_field(field, disabled_fields, exclude_fields) }}
|
||||||
{% for field in form.get_static_fields() %}
|
{% endfor %}
|
||||||
{{ render_field(field, disabled_fields, exclude_fields) }}
|
{% if form.get_dynamic_fields is defined %}
|
||||||
{% endfor %}
|
{% for collection_name, fields in form.get_dynamic_fields().items() %}
|
||||||
{% if form.get_dynamic_fields is defined %}
|
{% if fields|length > 0 %}
|
||||||
{% for collection_name, fields in form.get_dynamic_fields().items() %}
|
<h4 class="mt-4">{{ collection_name }}</h4>
|
||||||
{% if fields|length > 0 %}
|
{% endif %}
|
||||||
<h4 class="mt-4">{{ collection_name }}</h4>
|
{% for field in fields %}
|
||||||
{% endif %}
|
{{ render_field(field, disabled_fields, exclude_fields) }}
|
||||||
{% for field in fields %}
|
|
||||||
{{ render_field(field, disabled_fields, exclude_fields) }}
|
|
||||||
{% endfor %}
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endfor %}
|
||||||
<button type="submit" class="btn btn-primary">{{ submit_text }}</button>
|
{% endif %}
|
||||||
</form>
|
<div class="btn-group mt-3">
|
||||||
|
<button type="submit" class="btn btn-primary component-submit">{{ submit_text }}</button>
|
||||||
|
<button type="button" class="btn btn-secondary ms-2" id="cancelEdit">Cancel</button>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content_footer %}
|
{% block content_footer %}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
{% extends "interaction/component.html" %}
|
||||||
@@ -1,33 +1,479 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% from "macros.html" import render_field %}
|
{% from "macros.html" import render_field, render_selectable_table %}
|
||||||
|
|
||||||
{% block title %}Edit Specialist{% endblock %}
|
{% block title %}Edit Specialist{% endblock %}
|
||||||
|
|
||||||
{% block content_title %}Edit Specialist{% endblock %}
|
{% block content_title %}Edit Specialist{% endblock %}
|
||||||
{% block content_description %}Edit a Specialist{% endblock %}
|
{% block content_description %}Edit a Specialist and its components{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<form method="post">
|
<div class="container-fluid px-0">
|
||||||
{{ form.hidden_tag() }}
|
<div class="row">
|
||||||
{% set disabled_fields = ['type'] %}
|
<!-- Main Specialist Editor -->
|
||||||
{% set exclude_fields = [] %}
|
<div class="col-12" id="mainEditorSection">
|
||||||
<!-- Render Static Fields -->
|
<form method="post" id="specialistForm" action="{{ url_for('interaction_bp.edit_specialist', specialist_id=specialist_id) }}">
|
||||||
{% for field in form.get_static_fields() %}
|
{{ form.hidden_tag() }}
|
||||||
{{ render_field(field, disabled_fields, exclude_fields) }}
|
{% set disabled_fields = ['type', 'type_version'] %}
|
||||||
{% endfor %}
|
{% set exclude_fields = [] %}
|
||||||
<!-- Render Dynamic Fields -->
|
<!-- Render Static Fields -->
|
||||||
{% for collection_name, fields in form.get_dynamic_fields().items() %}
|
{% for field in form.get_static_fields() %}
|
||||||
{% if fields|length > 0 %}
|
{{ render_field(field, disabled_fields, exclude_fields) }}
|
||||||
<h4 class="mt-4">{{ collection_name }}</h4>
|
{% endfor %}
|
||||||
{% endif %}
|
|
||||||
{% for field in fields %}
|
<!-- Overview Section -->
|
||||||
{{ render_field(field, disabled_fields, exclude_fields) }}
|
<div class="row mb-4">
|
||||||
{% endfor %}
|
<div class="col-12">
|
||||||
{% endfor %}
|
<div class="card">
|
||||||
<button type="submit" class="btn btn-primary">Save Specialist</button>
|
<div class="card-body">
|
||||||
</form>
|
<div class="specialist-overview" id="specialist-svg">
|
||||||
|
<img src="{{ svg_path }}" alt="Specialist Overview" class="w-100">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Nav Tabs -->
|
||||||
|
<div class="row mt-5">
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<div class="nav-wrapper position-relative end-0">
|
||||||
|
<ul class="nav nav-pills nav-fill p-1" role="tablist">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link mb-0 px-0 py-1 active" data-bs-toggle="tab" href="#configuration-tab" role="tab">
|
||||||
|
Configuration
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link mb-0 px-0 py-1" data-bs-toggle="tab" href="#agents-tab" role="tab">
|
||||||
|
Agents
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link mb-0 px-0 py-1" data-bs-toggle="tab" href="#tasks-tab" role="tab">
|
||||||
|
Tasks
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link mb-0 px-0 py-1" data-bs-toggle="tab" href="#tools-tab" role="tab">
|
||||||
|
Tools
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link mb-0 px-0 py-1 d-none" id="editor-tab-link" data-bs-toggle="tab" href="#editor-tab" role="tab">
|
||||||
|
Editor
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-content tab-space">
|
||||||
|
<!-- Configuration Tab -->
|
||||||
|
<div class="tab-pane fade show active" id="configuration-tab" role="tabpanel">
|
||||||
|
{% for collection_name, fields in form.get_dynamic_fields().items() %}
|
||||||
|
{% if fields|length > 0 %}
|
||||||
|
<h4 class="mt-4">{{ collection_name }}</h4>
|
||||||
|
{% endif %}
|
||||||
|
{% for field in fields %}
|
||||||
|
{{ render_field(field, disabled_fields, exclude_fields) }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Agents Tab -->
|
||||||
|
<div class="tab-pane fade" id="agents-tab" role="tabpanel">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
{{ render_selectable_table(
|
||||||
|
headers=["Agent ID", "Name", "Type", "Status"],
|
||||||
|
rows=agent_rows if agent_rows else [],
|
||||||
|
selectable=True,
|
||||||
|
id="agentsTable",
|
||||||
|
is_component_selector=True
|
||||||
|
) }}
|
||||||
|
<div class="form-group mt-3">
|
||||||
|
<button type="button" class="btn btn-primary edit-component"
|
||||||
|
data-component-type="agent"
|
||||||
|
data-edit-url="{{ prefixed_url_for('interaction_bp.edit_agent', agent_id=0) }}">Edit Agent
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tasks Tab -->
|
||||||
|
<div class="tab-pane fade" id="tasks-tab" role="tabpanel">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
{{ render_selectable_table(
|
||||||
|
headers=["Task ID", "Name", "Type", "Status"],
|
||||||
|
rows=task_rows if task_rows else [],
|
||||||
|
selectable=True,
|
||||||
|
id="tasksTable",
|
||||||
|
is_component_selector=True
|
||||||
|
) }}
|
||||||
|
<div class="form-group mt-3">
|
||||||
|
<button type="button" class="btn btn-primary edit-component"
|
||||||
|
data-component-type="task"
|
||||||
|
data-edit-url="{{ prefixed_url_for('interaction_bp.edit_task', task_id=0) }}">Edit Task
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tools Tab -->
|
||||||
|
<div class="tab-pane fade" id="tools-tab" role="tabpanel">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
{{ render_selectable_table(
|
||||||
|
headers=["Tool ID", "Name", "Type", "Status"],
|
||||||
|
rows=tool_rows if tool_rows else [],
|
||||||
|
selectable=True,
|
||||||
|
id="toolsTable",
|
||||||
|
is_component_selector=True
|
||||||
|
) }}
|
||||||
|
<div class="form-group mt-3">
|
||||||
|
<button type="button" class="btn btn-primary edit-component"
|
||||||
|
data-component-type="tool"
|
||||||
|
data-edit-url="{{ prefixed_url_for('interaction_bp.edit_tool', tool_id=0) }}">Edit Tool
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Editor Tab -->
|
||||||
|
<div class="tab-pane fade" id="editor-tab" role="tabpanel">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="mb-0" id="editorTitle"></h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body" id="editorContent">
|
||||||
|
<!-- Component editor will be loaded here -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary mt-4">Save Specialist</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content_footer %}
|
{% block scripts %}
|
||||||
|
{{ super() }}
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const editorTab = document.getElementById('editor-tab');
|
||||||
|
const editorTabLink = document.getElementById('editor-tab-link');
|
||||||
|
const editorTitle = document.getElementById('editorTitle');
|
||||||
|
const editorContent = document.getElementById('editorContent');
|
||||||
|
let previousTab = null;
|
||||||
|
|
||||||
|
// Add color classes to the tabs
|
||||||
|
const agentsTabLink = document.querySelector('[href="#agents-tab"]');
|
||||||
|
const tasksTabLink = document.querySelector('[href="#tasks-tab"]');
|
||||||
|
const toolsTabLink = document.querySelector('[href="#tools-tab"]');
|
||||||
|
|
||||||
|
agentsTabLink.classList.add('component-agent');
|
||||||
|
tasksTabLink.classList.add('component-task');
|
||||||
|
toolsTabLink.classList.add('component-tool');
|
||||||
|
|
||||||
|
// Add background colors to the tab panes
|
||||||
|
const agentsTab = document.getElementById('agents-tab');
|
||||||
|
const tasksTab = document.getElementById('tasks-tab');
|
||||||
|
const toolsTab = document.getElementById('tools-tab');
|
||||||
|
|
||||||
|
agentsTab.classList.add('component-agent-bg');
|
||||||
|
tasksTab.classList.add('component-task-bg');
|
||||||
|
toolsTab.classList.add('component-tool-bg');
|
||||||
|
|
||||||
|
// Ensure component selectors don't interfere with form submission
|
||||||
|
const form = document.getElementById('specialistForm');
|
||||||
|
|
||||||
|
form.addEventListener('submit', function(e) {
|
||||||
|
// Remove component selectors from form validation
|
||||||
|
const componentSelectors = form.querySelectorAll('input[data-component-selector]');
|
||||||
|
componentSelectors.forEach(selector => {
|
||||||
|
selector.removeAttribute('required');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get all tab links except the editor tab
|
||||||
|
const tabLinks = Array.from(document.querySelectorAll('.nav-link')).filter(link => link.id !== 'editor-tab-link');
|
||||||
|
|
||||||
|
// Function to toggle other tabs' disabled state
|
||||||
|
function toggleOtherTabs(disable) {
|
||||||
|
tabLinks.forEach(link => {
|
||||||
|
if (disable) {
|
||||||
|
link.classList.add('disabled');
|
||||||
|
} else {
|
||||||
|
link.classList.remove('disabled');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to toggle main form elements
|
||||||
|
const mainSubmitButton = document.querySelector('#specialistForm > .btn-primary');
|
||||||
|
|
||||||
|
function toggleMainFormElements(disable) {
|
||||||
|
// Toggle tabs
|
||||||
|
document.querySelectorAll('.nav-link').forEach(link => {
|
||||||
|
if (link.id !== 'editor-tab-link') {
|
||||||
|
if (disable) {
|
||||||
|
link.classList.add('disabled');
|
||||||
|
} else {
|
||||||
|
link.classList.remove('disabled');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Toggle main submit button
|
||||||
|
if (mainSubmitButton) {
|
||||||
|
mainSubmitButton.disabled = disable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle edit buttons
|
||||||
|
document.querySelectorAll('.edit-component').forEach(button => {
|
||||||
|
button.addEventListener('click', function() {
|
||||||
|
const componentType = this.dataset.componentType;
|
||||||
|
const form = this.closest('form');
|
||||||
|
const selectedRow = form.querySelector('input[type="radio"]:checked');
|
||||||
|
|
||||||
|
console.log("I'm in the custom click event listener!")
|
||||||
|
|
||||||
|
if (!selectedRow) {
|
||||||
|
alert('Please select a component to edit');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const valueMatch = selectedRow.value.match(/'value':\s*(\d+)/);
|
||||||
|
const selectedId = valueMatch ? valueMatch[1] : null;
|
||||||
|
|
||||||
|
if (!selectedId) {
|
||||||
|
console.error('Could not extract ID from value:', selectedRow.value);
|
||||||
|
alert('Error: Could not determine component ID');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make AJAX call to get component editor
|
||||||
|
const urlTemplate = this.dataset.editUrl.replace('/0', `/${selectedId}`);
|
||||||
|
fetch(urlTemplate, {
|
||||||
|
headers: {
|
||||||
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
return response.text();
|
||||||
|
})
|
||||||
|
.then(html => {
|
||||||
|
// Store the current active tab
|
||||||
|
previousTab = document.querySelector('.nav-link.active');
|
||||||
|
|
||||||
|
// Update editor content
|
||||||
|
editorTitle.textContent = `Edit ${componentType.charAt(0).toUpperCase() + componentType.slice(1)}`;
|
||||||
|
editorContent.innerHTML = html;
|
||||||
|
|
||||||
|
// Apply the appropriate color class to the editor tab
|
||||||
|
editorTabLink.classList.remove('component-agent', 'component-task', 'component-tool');
|
||||||
|
editorTab.classList.remove('component-agent-bg', 'component-task-bg', 'component-tool-bg');
|
||||||
|
|
||||||
|
editorTabLink.classList.add(`component-${componentType}`);
|
||||||
|
editorTab.classList.add(`component-${componentType}-bg`);
|
||||||
|
|
||||||
|
// Disable other tabs & main form elements
|
||||||
|
toggleOtherTabs(true);
|
||||||
|
toggleMainFormElements(true)
|
||||||
|
|
||||||
|
// Show editor tab and switch to it
|
||||||
|
editorTabLink.classList.remove('d-none');
|
||||||
|
editorTabLink.click();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error fetching editor:', error);
|
||||||
|
alert('Error loading editor. Please try again.');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clean up color classes when returning from editor
|
||||||
|
editorTabLink.addEventListener('hide.bs.tab', function() {
|
||||||
|
editorTabLink.classList.remove('component-agent', 'component-task', 'component-tool');
|
||||||
|
editorTab.classList.remove('component-agent-bg', 'component-task-bg', 'component-tool-bg');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle component editor form submissions
|
||||||
|
editorContent.addEventListener('click', function(e) {
|
||||||
|
if (e.target && e.target.classList.contains('component-submit')) {
|
||||||
|
e.preventDefault();
|
||||||
|
console.log('Submit button clicked');
|
||||||
|
|
||||||
|
// Get all form fields from the editor content
|
||||||
|
const formData = new FormData();
|
||||||
|
editorContent.querySelectorAll('input, textarea, select').forEach(field => {
|
||||||
|
if (field.type === 'checkbox') {
|
||||||
|
formData.append(field.name, field.checked ? 'y' : 'n');
|
||||||
|
} else {
|
||||||
|
formData.append(field.name, field.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add CSRF token
|
||||||
|
const csrfToken = document.querySelector('input[name="csrf_token"]').value;
|
||||||
|
formData.append('csrf_token', csrfToken);
|
||||||
|
|
||||||
|
// Get the component ID from the current state
|
||||||
|
const selectedRow = document.querySelector('input[name="selected_row"]:checked');
|
||||||
|
const componentData = JSON.parse(selectedRow.value.replace(/'/g, '"'));
|
||||||
|
const componentId = componentData.value;
|
||||||
|
|
||||||
|
// Determine the component type (agent, task, or tool)
|
||||||
|
const componentType = editorTabLink.classList.contains('component-agent') ? 'agent' :
|
||||||
|
editorTabLink.classList.contains('component-task') ? 'task' : 'tool';
|
||||||
|
|
||||||
|
// Submit the data
|
||||||
|
fetch(`/admin/interaction/${componentType}/${componentId}/save`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
// Handle success - reload the page
|
||||||
|
location.reload();
|
||||||
|
} else {
|
||||||
|
// Handle error
|
||||||
|
alert(data.message || 'Error saving component');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
alert('Error saving component');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle case when editor tab is hidden
|
||||||
|
editorTabLink.addEventListener('hide.bs.tab', function() {
|
||||||
|
// Re-enable all tabs & main form elements
|
||||||
|
toggleOtherTabs(false);
|
||||||
|
toggleMainFormElements(false)
|
||||||
|
|
||||||
|
// Remove color classes
|
||||||
|
editorTabLink.classList.remove('component-agent', 'component-task', 'component-tool');
|
||||||
|
editorTab.classList.remove('component-agent-bg', 'component-task-bg', 'component-tool-bg');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Function to handle canceling edit
|
||||||
|
function cancelEdit() {
|
||||||
|
// Re-enable all tabs & main form elements
|
||||||
|
toggleOtherTabs(false);
|
||||||
|
toggleMainFormElements()
|
||||||
|
|
||||||
|
// Return to previous tab
|
||||||
|
if (previousTab) {
|
||||||
|
previousTab.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide the editor tab
|
||||||
|
editorTabLink.classList.add('d-none');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle cancel button in editor
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
if (e.target && e.target.id === 'cancelEdit') {
|
||||||
|
// Get the previously active tab (stored before switching to editor)
|
||||||
|
const previousTab = document.querySelector('[href="#configuration-tab"]'); // default to configuration tab
|
||||||
|
|
||||||
|
cancelEdit()
|
||||||
|
|
||||||
|
// Hide the editor tab
|
||||||
|
document.getElementById('editor-tab-link').classList.add('d-none');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.tab-pane .card {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link.component-agent,
|
||||||
|
.nav-link.component-task,
|
||||||
|
.nav-link.component-tool {
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add new CSS for normal tabs */
|
||||||
|
.nav-link {
|
||||||
|
color: #344767 !important; /* Default dark color */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style for disabled tabs */
|
||||||
|
.nav-link.disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-agent {
|
||||||
|
background-color: #9c27b0 !important; /* Purple */
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-task {
|
||||||
|
background-color: #ff9800 !important; /* Orange */
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-tool {
|
||||||
|
background-color: #009688 !important; /* Teal */
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Lighter background versions for the tab content */
|
||||||
|
.tab-pane.component-agent-bg {
|
||||||
|
background-color: rgba(156, 39, 176, 0.2); /* Light purple */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-pane.component-task-bg {
|
||||||
|
background-color: rgba(255, 152, 0, 0.2); /* Light orange */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-pane.component-tool-bg {
|
||||||
|
background-color: rgba(0, 150, 136, 0.2); /* Light teal */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add some padding to the tab content */
|
||||||
|
.tab-pane {
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
.specialist-overview {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
min-height: 200px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.specialist-overview svg {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
max-height: 400px; /* Adjust as needed */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
@@ -19,5 +19,5 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content_footer %}
|
{% block content_footer %}
|
||||||
{{ render_pagination(pagination, 'document_bp.retrievers') }}
|
{{ render_pagination(pagination, 'interaction_bp.specialists') }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -135,7 +135,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro render_selectable_table(headers, rows, selectable, id) %}
|
{% macro render_selectable_table(headers, rows, selectable, id, is_component_selector=False) %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table align-items-center mb-0" id="{{ id }}">
|
<table class="table align-items-center mb-0" id="{{ id }}">
|
||||||
@@ -153,7 +153,16 @@
|
|||||||
{% for row in rows %}
|
{% for row in rows %}
|
||||||
<tr>
|
<tr>
|
||||||
{% if selectable %}
|
{% if selectable %}
|
||||||
<td><input type="radio" name="selected_row" value="{{ row[0] }}" required></td>
|
<td>
|
||||||
|
<input type="radio"
|
||||||
|
name="selected_row"
|
||||||
|
value="{{ row[0] }}"
|
||||||
|
{% if is_component_selector %}
|
||||||
|
data-component-selector="true"
|
||||||
|
{% else %}
|
||||||
|
required
|
||||||
|
{% endif %}>
|
||||||
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% for cell in row %}
|
{% for cell in row %}
|
||||||
<td class="{{ cell.class }}">
|
<td class="{{ cell.class }}">
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ def get_tools():
|
|||||||
|
|
||||||
class SpecialistForm(FlaskForm):
|
class SpecialistForm(FlaskForm):
|
||||||
name = StringField('Name', validators=[DataRequired(), Length(max=50)])
|
name = StringField('Name', validators=[DataRequired(), Length(max=50)])
|
||||||
description = TextAreaField('Description', validators=[DataRequired()])
|
|
||||||
|
|
||||||
retrievers = QuerySelectMultipleField(
|
retrievers = QuerySelectMultipleField(
|
||||||
'Retrievers',
|
'Retrievers',
|
||||||
@@ -37,14 +36,14 @@ class SpecialistForm(FlaskForm):
|
|||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
types_dict = cache_manager.specialist_config_cache.get_types()
|
types_dict = cache_manager.specialists_types_cache.get_types()
|
||||||
# Dynamically populate the 'type' field using the constructor
|
# Dynamically populate the 'type' field using the constructor
|
||||||
self.type.choices = [(key, value['name']) for key, value in types_dict.items()]
|
self.type.choices = [(key, value['name']) for key, value in types_dict.items()]
|
||||||
|
|
||||||
|
|
||||||
class EditSpecialistForm(DynamicFormBase):
|
class EditSpecialistForm(DynamicFormBase):
|
||||||
name = StringField('Name', validators=[DataRequired()])
|
name = StringField('Name', validators=[DataRequired()])
|
||||||
description = TextAreaField('Description', validators=[DataRequired()])
|
description = TextAreaField('Description', validators=[Optional()])
|
||||||
|
|
||||||
retrievers = QuerySelectMultipleField(
|
retrievers = QuerySelectMultipleField(
|
||||||
'Retrievers',
|
'Retrievers',
|
||||||
@@ -55,6 +54,7 @@ class EditSpecialistForm(DynamicFormBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
type = StringField('Specialist Type', validators=[DataRequired()], render_kw={'readonly': True})
|
type = StringField('Specialist Type', validators=[DataRequired()], render_kw={'readonly': True})
|
||||||
|
type_version = StringField('Type Version', validators=[DataRequired()], render_kw={'readonly': True})
|
||||||
tuning = BooleanField('Enable Retrieval Tuning', default=False)
|
tuning = BooleanField('Enable Retrieval Tuning', default=False)
|
||||||
|
|
||||||
|
|
||||||
@@ -76,41 +76,21 @@ class BaseEditComponentForm(DynamicFormBase):
|
|||||||
name = StringField('Name', validators=[DataRequired()])
|
name = StringField('Name', validators=[DataRequired()])
|
||||||
description = TextAreaField('Description', validators=[Optional()])
|
description = TextAreaField('Description', validators=[Optional()])
|
||||||
type = StringField('Type', validators=[DataRequired()], render_kw={'readonly': True})
|
type = StringField('Type', validators=[DataRequired()], render_kw={'readonly': True})
|
||||||
|
type_version = StringField('Type Version', validators=[DataRequired()], render_kw={'readonly': True})
|
||||||
tuning = BooleanField('Enable Tuning', default=False)
|
tuning = BooleanField('Enable Tuning', default=False)
|
||||||
|
|
||||||
|
|
||||||
class EveAIAgentForm(BaseComponentForm):
|
|
||||||
role = TextAreaField('Role', validators=[DataRequired()])
|
|
||||||
goal = TextAreaField('Goal', validators=[DataRequired()])
|
|
||||||
backstory = TextAreaField('Backstory', validators=[DataRequired()])
|
|
||||||
|
|
||||||
tools = QuerySelectMultipleField(
|
|
||||||
'Tools',
|
|
||||||
query_factory=get_tools,
|
|
||||||
get_label='name',
|
|
||||||
allow_blank=True,
|
|
||||||
description='Select one or more tools that can be used this agent'
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, *args, type_config=None, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
if type_config:
|
|
||||||
self.type.choices = [(key, value['name']) for key, value in type_config.items()]
|
|
||||||
|
|
||||||
|
|
||||||
class EditEveAIAgentForm(BaseEditComponentForm):
|
class EditEveAIAgentForm(BaseEditComponentForm):
|
||||||
role = StringField('Role', validators=[DataRequired()])
|
role = TextAreaField('Role', validators=[Optional()])
|
||||||
goal = StringField('Goal', validators=[DataRequired()])
|
goal = TextAreaField('Goal', validators=[Optional()])
|
||||||
backstory = StringField('Backstory', validators=[DataRequired()])
|
backstory = TextAreaField('Backstory', validators=[Optional()])
|
||||||
|
|
||||||
tools = QuerySelectMultipleField(
|
|
||||||
'Tools',
|
|
||||||
query_factory=get_tools,
|
|
||||||
get_label='name',
|
|
||||||
allow_blank=True,
|
|
||||||
description='Select one or more tools that can be used this agent'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class EveAITaskForm(BaseComponentForm):
|
class EditEveAITaskForm(BaseEditComponentForm):
|
||||||
expected_output = TextAreaField('Expected Output', validators=[DataRequired()])
|
task_description = StringField('Task Description', validators=[Optional()])
|
||||||
|
expected_outcome = StringField('Expected Outcome', validators=[Optional()])
|
||||||
|
|
||||||
|
|
||||||
|
class EditEveAIToolForm(BaseEditComponentForm):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import ast
|
import ast
|
||||||
from datetime import datetime as dt, timezone as tz
|
from datetime import datetime as dt, timezone as tz
|
||||||
|
|
||||||
from flask import request, redirect, flash, render_template, Blueprint, session, current_app
|
from flask import request, redirect, flash, render_template, Blueprint, session, current_app, jsonify, url_for
|
||||||
from flask_security import roles_accepted
|
from flask_security import roles_accepted
|
||||||
|
from langchain.agents import Agent
|
||||||
from sqlalchemy import desc
|
from sqlalchemy import desc
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
|
||||||
from common.models.document import Embedding, DocumentVersion, Retriever
|
from common.models.document import Embedding, DocumentVersion, Retriever
|
||||||
from common.models.interaction import (ChatSession, Interaction, InteractionEmbedding, Specialist, SpecialistRetriever)
|
from common.models.interaction import (ChatSession, Interaction, InteractionEmbedding, Specialist, SpecialistRetriever,
|
||||||
|
EveAIAgent, EveAITask, EveAITool)
|
||||||
|
|
||||||
from common.extensions import db, cache_manager
|
from common.extensions import db, cache_manager
|
||||||
from common.utils.model_logging_utils import set_logging_information, update_logging_information
|
from common.utils.model_logging_utils import set_logging_information, update_logging_information
|
||||||
@@ -19,7 +21,8 @@ from common.utils.specialist_utils import initialize_specialist
|
|||||||
|
|
||||||
from config.type_defs.specialist_types import SPECIALIST_TYPES
|
from config.type_defs.specialist_types import SPECIALIST_TYPES
|
||||||
|
|
||||||
from .interaction_forms import (SpecialistForm, EditSpecialistForm)
|
from .interaction_forms import (SpecialistForm, EditSpecialistForm, EditEveAIAgentForm, EditEveAITaskForm,
|
||||||
|
EditEveAIToolForm)
|
||||||
|
|
||||||
interaction_bp = Blueprint('interaction_bp', __name__, url_prefix='/interaction')
|
interaction_bp = Blueprint('interaction_bp', __name__, url_prefix='/interaction')
|
||||||
|
|
||||||
@@ -140,7 +143,7 @@ def specialist():
|
|||||||
new_specialist.name = form.name.data
|
new_specialist.name = form.name.data
|
||||||
new_specialist.description = form.description.data
|
new_specialist.description = form.description.data
|
||||||
new_specialist.type = form.type.data
|
new_specialist.type = form.type.data
|
||||||
new_specialist.type_version = cache_manager.specialist_config_cache.get_latest_version(new_specialist.type)
|
new_specialist.type_version = cache_manager.specialists_version_tree_cache.get_latest_version(new_specialist.type)
|
||||||
new_specialist.tuning = form.tuning.data
|
new_specialist.tuning = form.tuning.data
|
||||||
|
|
||||||
set_logging_information(new_specialist, dt.now(tz.utc))
|
set_logging_information(new_specialist, dt.now(tz.utc))
|
||||||
@@ -182,16 +185,29 @@ def edit_specialist(specialist_id):
|
|||||||
specialist = Specialist.query.get_or_404(specialist_id)
|
specialist = Specialist.query.get_or_404(specialist_id)
|
||||||
form = EditSpecialistForm(request.form, obj=specialist)
|
form = EditSpecialistForm(request.form, obj=specialist)
|
||||||
|
|
||||||
specialist_config = cache_manager.specialist_config_cache.get_config(specialist.type, specialist.type_version)
|
specialist_config = cache_manager.specialists_config_cache.get_config(specialist.type, specialist.type_version)
|
||||||
configuration_config = specialist_config.get('configuration')
|
configuration_config = specialist_config.get('configuration')
|
||||||
form.add_dynamic_fields("configuration", configuration_config, specialist.configuration)
|
form.add_dynamic_fields("configuration", configuration_config, specialist.configuration)
|
||||||
|
|
||||||
|
agent_rows = prepare_table_for_macro(specialist.agents,
|
||||||
|
[('id', ''), ('name', ''), ('type', ''), ('type_version', '')])
|
||||||
|
task_rows = prepare_table_for_macro(specialist.tasks,
|
||||||
|
[('id', ''), ('name', ''), ('type', ''), ('type_version', '')])
|
||||||
|
tool_rows = prepare_table_for_macro(specialist.tools,
|
||||||
|
[('id', ''), ('name', ''), ('type', ''), ('type_version', '')])
|
||||||
|
|
||||||
|
# Construct the SVG overview path
|
||||||
|
svg_filename = f"{specialist.type}_{specialist.type_version}_overview.svg"
|
||||||
|
svg_path = url_for('static', filename=f'assets/specialists/{svg_filename}')
|
||||||
|
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
# Get the actual Retriever objects for the associated retriever_ids
|
# Get the actual Retriever objects for the associated retriever_ids
|
||||||
retriever_objects = Retriever.query.filter(
|
retriever_objects = Retriever.query.filter(
|
||||||
Retriever.id.in_([sr.retriever_id for sr in specialist.retrievers])
|
Retriever.id.in_([sr.retriever_id for sr in specialist.retrievers])
|
||||||
).all()
|
).all()
|
||||||
form.retrievers.data = retriever_objects
|
form.retrievers.data = retriever_objects
|
||||||
|
if specialist.description is None:
|
||||||
|
specialist.description = specialist_config.get('metadata').get('description', '')
|
||||||
|
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
# Update the basic fields
|
# Update the basic fields
|
||||||
@@ -230,11 +246,25 @@ def edit_specialist(specialist_id):
|
|||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
flash(f'Failed to update specialist. Error: {str(e)}', 'danger')
|
flash(f'Failed to update specialist. Error: {str(e)}', 'danger')
|
||||||
current_app.logger.error(f'Failed to update specialist {specialist_id}. Error: {str(e)}')
|
current_app.logger.error(f'Failed to update specialist {specialist_id}. Error: {str(e)}')
|
||||||
return render_template('interaction/edit_specialist.html', form=form, specialist_id=specialist_id)
|
return render_template('interaction/edit_specialist.html',
|
||||||
|
form=form,
|
||||||
|
specialist_id=specialist_id,
|
||||||
|
agent_rows=agent_rows,
|
||||||
|
task_rows=task_rows,
|
||||||
|
tool_rows=tool_rows,
|
||||||
|
prefixed_url_for=prefixed_url_for,
|
||||||
|
svg_path=svg_path,)
|
||||||
else:
|
else:
|
||||||
form_validation_failed(request, form)
|
form_validation_failed(request, form)
|
||||||
|
|
||||||
return render_template('interaction/edit_specialist.html', form=form, specialist_id=specialist_id)
|
return render_template('interaction/edit_specialist.html',
|
||||||
|
form=form,
|
||||||
|
specialist_id=specialist_id,
|
||||||
|
agent_rows=agent_rows,
|
||||||
|
task_rows=task_rows,
|
||||||
|
tool_rows=tool_rows,
|
||||||
|
prefixed_url_for=prefixed_url_for,
|
||||||
|
svg_path=svg_path,)
|
||||||
|
|
||||||
|
|
||||||
@interaction_bp.route('/specialists', methods=['GET', 'POST'])
|
@interaction_bp.route('/specialists', methods=['GET', 'POST'])
|
||||||
@@ -268,3 +298,154 @@ def handle_specialist_selection():
|
|||||||
|
|
||||||
return redirect(prefixed_url_for('interaction_bp.specialists'))
|
return redirect(prefixed_url_for('interaction_bp.specialists'))
|
||||||
|
|
||||||
|
|
||||||
|
# Routes for Agent management
|
||||||
|
@interaction_bp.route('/agent/<int:agent_id>/edit', methods=['GET'])
|
||||||
|
@roles_accepted('Super User', 'Tenant Admin')
|
||||||
|
def edit_agent(agent_id):
|
||||||
|
agent = EveAIAgent.query.get_or_404(agent_id)
|
||||||
|
form = EditEveAIAgentForm(obj=agent)
|
||||||
|
|
||||||
|
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||||
|
# Return just the form portion for AJAX requests
|
||||||
|
return render_template('interaction/components/edit_agent.html',
|
||||||
|
form=form,
|
||||||
|
agent=agent,
|
||||||
|
title="Edit Agent",
|
||||||
|
description="Configure the agent with company-specific details if required",
|
||||||
|
submit_text="Save Agent")
|
||||||
|
|
||||||
|
|
||||||
|
@interaction_bp.route('/agent/<int:agent_id>/save', methods=['POST'])
|
||||||
|
@roles_accepted('Super User', 'Tenant Admin')
|
||||||
|
def save_agent(agent_id):
|
||||||
|
agent = EveAIAgent.query.get_or_404(agent_id) if agent_id else EveAIAgent()
|
||||||
|
tenant_id = session.get('tenant').get('id')
|
||||||
|
form = EditEveAIAgentForm(obj=agent)
|
||||||
|
|
||||||
|
if form.validate_on_submit():
|
||||||
|
try:
|
||||||
|
form.populate_obj(agent)
|
||||||
|
update_logging_information(agent, dt.now(tz.utc))
|
||||||
|
if not agent_id: # New agent
|
||||||
|
db.session.add(agent)
|
||||||
|
db.session.commit()
|
||||||
|
return jsonify({'success': True, 'message': 'Agent saved successfully'})
|
||||||
|
except Exception as e:
|
||||||
|
db.session.rollback()
|
||||||
|
current_app.logger.error(f'Failed to save agent {agent_id} for tenant {tenant_id}. Error: {str(e)}')
|
||||||
|
return jsonify({'success': False, 'message': f"Failed to save agent {agent_id}: {str(e)}"})
|
||||||
|
|
||||||
|
return jsonify({'success': False, 'message': 'Validation failed'})
|
||||||
|
|
||||||
|
|
||||||
|
# Routes for Task management
|
||||||
|
@interaction_bp.route('/task/<int:task_id>/edit', methods=['GET'])
|
||||||
|
@roles_accepted('Super User', 'Tenant Admin')
|
||||||
|
def edit_task(task_id):
|
||||||
|
task = EveAITask.query.get_or_404(task_id)
|
||||||
|
form = EditEveAITaskForm(obj=task)
|
||||||
|
|
||||||
|
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||||
|
return render_template('interaction/components/edit_task.html',
|
||||||
|
form=form,
|
||||||
|
task=task)
|
||||||
|
|
||||||
|
|
||||||
|
@interaction_bp.route('/task/<int:task_id>/save', methods=['POST'])
|
||||||
|
@roles_accepted('Super User', 'Tenant Admin')
|
||||||
|
def save_task(task_id):
|
||||||
|
task = EveAITask.query.get_or_404(task_id) if task_id else EveAITask()
|
||||||
|
tenant_id = session.get('tenant').get('id')
|
||||||
|
form = EditEveAITaskForm(obj=task) # Replace with actual task form
|
||||||
|
|
||||||
|
if form.validate_on_submit():
|
||||||
|
try:
|
||||||
|
form.populate_obj(task)
|
||||||
|
update_logging_information(task, dt.now(tz.utc))
|
||||||
|
if not task_id: # New task
|
||||||
|
db.session.add(task)
|
||||||
|
db.session.commit()
|
||||||
|
return jsonify({'success': True, 'message': 'Task saved successfully'})
|
||||||
|
except Exception as e:
|
||||||
|
db.session.rollback()
|
||||||
|
current_app.logger.error(f'Failed to save task {task_id} for tenant {tenant_id}. Error: {str(e)}')
|
||||||
|
return jsonify({'success': False, 'message': f"Failed to save task {task_id}: {str(e)}"})
|
||||||
|
|
||||||
|
return jsonify({'success': False, 'message': 'Validation failed'})
|
||||||
|
|
||||||
|
|
||||||
|
# Routes for Tool management
|
||||||
|
@interaction_bp.route('/tool/<int:tool_id>/edit', methods=['GET'])
|
||||||
|
@roles_accepted('Super User', 'Tenant Admin')
|
||||||
|
def edit_tool(tool_id):
|
||||||
|
tool = EveAITool.query.get_or_404(tool_id)
|
||||||
|
form = EditEveAIToolForm(obj=tool)
|
||||||
|
|
||||||
|
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||||
|
return render_template('interaction/components/edit_tool.html',
|
||||||
|
form=form,
|
||||||
|
tool=tool)
|
||||||
|
|
||||||
|
|
||||||
|
@interaction_bp.route('/tool/<int:tool_id>/save', methods=['POST'])
|
||||||
|
@roles_accepted('Super User', 'Tenant Admin')
|
||||||
|
def save_tool(tool_id):
|
||||||
|
tool = EveAITool.query.get_or_404(tool_id) if tool_id else EveAITool()
|
||||||
|
tenant_id = session.get('tenant').get('id')
|
||||||
|
form = EditEveAIToolForm(obj=tool) # Replace with actual tool form
|
||||||
|
|
||||||
|
if form.validate_on_submit():
|
||||||
|
try:
|
||||||
|
form.populate_obj(tool)
|
||||||
|
update_logging_information(tool, dt.now(tz.utc))
|
||||||
|
if not tool_id: # New tool
|
||||||
|
db.session.add(tool)
|
||||||
|
db.session.commit()
|
||||||
|
return jsonify({'success': True, 'message': 'Tool saved successfully'})
|
||||||
|
except Exception as e:
|
||||||
|
db.session.rollback()
|
||||||
|
current_app.logger.error(f'Failed to save tool {tool_id} for tenant {tenant_id}. Error: {str(e)}')
|
||||||
|
return jsonify({'success': False, 'message': f"Failed to save tool {tool_id}: {str(e)}"})
|
||||||
|
|
||||||
|
return jsonify({'success': False, 'message': 'Validation failed'})
|
||||||
|
|
||||||
|
|
||||||
|
# Component selection handlers
|
||||||
|
@interaction_bp.route('/handle_agent_selection', methods=['POST'])
|
||||||
|
@roles_accepted('Super User', 'Tenant Admin')
|
||||||
|
def handle_agent_selection():
|
||||||
|
agent_identification = request.form['selected_row']
|
||||||
|
agent_id = ast.literal_eval(agent_identification).get('value')
|
||||||
|
action = request.form.get('action')
|
||||||
|
|
||||||
|
if action == "edit_agent":
|
||||||
|
return redirect(prefixed_url_for('interaction_bp.edit_agent', agent_id=agent_id))
|
||||||
|
|
||||||
|
return redirect(prefixed_url_for('interaction_bp.edit_specialist'))
|
||||||
|
|
||||||
|
|
||||||
|
@interaction_bp.route('/handle_task_selection', methods=['POST'])
|
||||||
|
@roles_accepted('Super User', 'Tenant Admin')
|
||||||
|
def handle_task_selection():
|
||||||
|
task_identification = request.form['selected_row']
|
||||||
|
task_id = ast.literal_eval(task_identification).get('value')
|
||||||
|
action = request.form.get('action')
|
||||||
|
|
||||||
|
if action == "edit_task":
|
||||||
|
return redirect(prefixed_url_for('interaction_bp.edit_task', task_id=task_id))
|
||||||
|
|
||||||
|
return redirect(prefixed_url_for('interaction_bp.edit_specialist'))
|
||||||
|
|
||||||
|
|
||||||
|
@interaction_bp.route('/handle_tool_selection', methods=['POST'])
|
||||||
|
@roles_accepted('Super User', 'Tenant Admin')
|
||||||
|
def handle_tool_selection():
|
||||||
|
tool_identification = request.form['selected_row']
|
||||||
|
tool_id = ast.literal_eval(tool_identification).get('value')
|
||||||
|
action = request.form.get('action')
|
||||||
|
|
||||||
|
if action == "edit_tool":
|
||||||
|
return redirect(prefixed_url_for('interaction_bp.edit_tool', tool_id=tool_id))
|
||||||
|
|
||||||
|
return redirect(prefixed_url_for('interaction_bp.edit_specialist'))
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
"""Add task_description to EveAITask model
|
||||||
|
|
||||||
|
Revision ID: efcd6a0d2989
|
||||||
|
Revises: 1e8ed0bd9662
|
||||||
|
Create Date: 2025-01-20 08:08:48.401704
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import pgvector
|
||||||
|
from sqlalchemy.dialects import postgresql
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'efcd6a0d2989'
|
||||||
|
down_revision = '1e8ed0bd9662'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('eve_ai_task', sa.Column('task_description', sa.Text(), nullable=True))
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column('eve_ai_task', 'task_description')
|
||||||
|
# ### end Alembic commands ###
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 809 B After Width: | Height: | Size: 264 KiB |
@@ -50,7 +50,7 @@ python-iso639~=2024.4.27
|
|||||||
python-magic~=0.4.27
|
python-magic~=0.4.27
|
||||||
python-socketio~=5.11.3
|
python-socketio~=5.11.3
|
||||||
pytz~=2024.1
|
pytz~=2024.1
|
||||||
PyYAML~=6.0.2rc1
|
PyYAML~=6.0.2
|
||||||
redis~=5.0.4
|
redis~=5.0.4
|
||||||
requests~=2.32.3
|
requests~=2.32.3
|
||||||
SQLAlchemy~=2.0.35
|
SQLAlchemy~=2.0.35
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
rm -f *repo.txt
|
rm -f *repo.txt
|
||||||
|
|
||||||
# Define the list of components
|
# Define the list of components
|
||||||
components=("docker" "eveai_api" "eveai_app" "eveai_app_startup" "eveai_beat" "eveai_chat" "eveai_chat_workers" "eveai_entitlements" "eveai_workers" "nginx" "full" "integrations")
|
components=("docker" "eveai_api" "eveai_app" "eveai_app_startup" "eveai_beat" "eveai_chat" "eveai_chat_workers"
|
||||||
|
"eveai_entitlements" "eveai_workers" "nginx" "full" "integrations" "eveai_app_documents" "eveai_app_entitlements"
|
||||||
|
"eveai_app_interaction" "eveai_app_user")
|
||||||
|
|
||||||
# Get the current date and time in the format YYYY-MM-DD_HH-MM
|
# Get the current date and time in the format YYYY-MM-DD_HH-MM
|
||||||
timestamp=$(date +"%Y-%m-%d_%H-%M")
|
timestamp=$(date +"%Y-%m-%d_%H-%M")
|
||||||
|
|||||||
@@ -12,4 +12,4 @@ export FLASK_APP=${PROJECT_DIR}/scripts/run_eveai_app.py # Adjust the path to y
|
|||||||
chown -R appuser:appuser /app/logs
|
chown -R appuser:appuser /app/logs
|
||||||
|
|
||||||
# Start Flask app
|
# Start Flask app
|
||||||
gunicorn -w 1 -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker -b 0.0.0.0:5001 --worker-connections 100 scripts.run_eveai_api:app
|
gunicorn -w 1 -k gevent -b 0.0.0.0:5001 --worker-connections 100 scripts.run_eveai_api:app
|
||||||
|
|||||||
@@ -49,4 +49,4 @@ python ${PROJECT_DIR}/scripts/initialize_data.py # Adjust the path to your init
|
|||||||
|
|
||||||
# Start Flask app
|
# Start Flask app
|
||||||
# gunicorn -w 1 -k gevent -b 0.0.0.0:5001 --worker-connections 100 scripts.run_eveai_app:app
|
# gunicorn -w 1 -k gevent -b 0.0.0.0:5001 --worker-connections 100 scripts.run_eveai_app:app
|
||||||
gunicorn -w 1 -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker -b 0.0.0.0:5001 --worker-connections 100 scripts.run_eveai_app:app
|
gunicorn -w 1 -k gevent -b 0.0.0.0:5001 --worker-connections 100 scripts.run_eveai_app:app
|
||||||
|
|||||||
@@ -13,4 +13,4 @@ chown -R appuser:appuser /app/logs
|
|||||||
echo "Starting EveAI Chat"
|
echo "Starting EveAI Chat"
|
||||||
|
|
||||||
# Start Flask app
|
# Start Flask app
|
||||||
gunicorn -w 1 -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker -b 0.0.0.0:5001 --worker-connections 100 scripts.run_eveai_chat:app
|
gunicorn -w 1 -k gevent -b 0.0.0.0:5001 --worker-connections 100 scripts.run_eveai_chat:app
|
||||||
|
|||||||
Reference in New Issue
Block a user