- Add configuration of agents, tasks, tools, specialist in context of SPIN specialist
- correct startup of applications using gevent - introduce startup scripts (eveai_app) - caching manager for all configurations
This commit is contained in:
@@ -13,6 +13,7 @@ import common.models.interaction
|
||||
import common.models.entitlements
|
||||
import common.models.document
|
||||
from common.utils.nginx_utils import prefixed_url_for
|
||||
from common.utils.startup_eveai import perform_startup_actions
|
||||
from config.logging_config import LOGGING
|
||||
from common.utils.security import set_tenant_session_data
|
||||
from .errors import register_error_handlers
|
||||
@@ -73,6 +74,9 @@ def create_app(config_file=None):
|
||||
# Register Error Handlers
|
||||
register_error_handlers(app)
|
||||
|
||||
# Register Cache Handlers
|
||||
register_cache_handlers(app)
|
||||
|
||||
# Debugging settings
|
||||
if app.config['DEBUG'] is True:
|
||||
app.logger.setLevel(logging.DEBUG)
|
||||
@@ -103,7 +107,19 @@ def create_app(config_file=None):
|
||||
# Register template filters
|
||||
register_filters(app)
|
||||
|
||||
app.logger.info("EveAI App Server Started Successfully")
|
||||
# Let all initialization complete before using cache
|
||||
# @app.before_first_request
|
||||
# def initialize_cache_data():
|
||||
# # Cache testing
|
||||
# agent_types = cache_manager.agent_config_cache.get_types()
|
||||
# app.logger.debug(f"Agent types: {agent_types}")
|
||||
# agent_config = cache_manager.agent_config_cache.get_config('RAG_AGENT')
|
||||
# app.logger.debug(f"Agent config: {agent_config}")
|
||||
|
||||
# Perform startup actions such as cache invalidation
|
||||
perform_startup_actions(app)
|
||||
|
||||
app.logger.info(f"EveAI App Server Started Successfully (PID: {os.getpid()})")
|
||||
app.logger.info("-------------------------------------------------------------------------------------------------")
|
||||
return app
|
||||
|
||||
@@ -123,7 +139,6 @@ def register_extensions(app):
|
||||
metrics.init_app(app)
|
||||
|
||||
|
||||
# Register Blueprints
|
||||
def register_blueprints(app):
|
||||
from .views.user_views import user_bp
|
||||
app.register_blueprint(user_bp)
|
||||
@@ -143,3 +158,13 @@ def register_blueprints(app):
|
||||
app.register_blueprint(healthz_bp)
|
||||
init_healtz(app)
|
||||
|
||||
|
||||
def register_cache_handlers(app):
|
||||
from common.utils.cache.config_cache import (
|
||||
AgentConfigCacheHandler, TaskConfigCacheHandler, ToolConfigCacheHandler, SpecialistConfigCacheHandler)
|
||||
|
||||
cache_manager.register_handler(AgentConfigCacheHandler, 'eveai_config')
|
||||
cache_manager.register_handler(TaskConfigCacheHandler, 'eveai_config')
|
||||
cache_manager.register_handler(ToolConfigCacheHandler, 'eveai_config')
|
||||
cache_manager.register_handler(SpecialistConfigCacheHandler, 'eveai_config')
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import jinja2
|
||||
from flask import render_template, request, jsonify, redirect, current_app
|
||||
from flask_login import current_user
|
||||
from common.utils.nginx_utils import prefixed_url_for
|
||||
@@ -47,3 +48,15 @@ def register_error_handlers(app):
|
||||
app.register_error_handler(403, not_authorised_error)
|
||||
app.register_error_handler(KeyError, key_error_handler)
|
||||
|
||||
@app.errorhandler(jinja2.TemplateNotFound)
|
||||
def template_not_found(error):
|
||||
app.logger.error(f'Template not found: {error.name}')
|
||||
app.logger.error(f'Search Paths: {app.jinja_loader.list_templates()}')
|
||||
return f'Template not found: {error.name}. Check logs for details.', 404
|
||||
|
||||
@app.errorhandler(jinja2.TemplateSyntaxError)
|
||||
def template_syntax_error(error):
|
||||
app.logger.error(f'Template syntax error: {error.message}')
|
||||
app.logger.error(f'In template {error.filename}, line {error.lineno}')
|
||||
return f'Template syntax error: {error.message}', 500
|
||||
|
||||
|
||||
32
eveai_app/templates/interaction/component.html
Normal file
32
eveai_app/templates/interaction/component.html
Normal file
@@ -0,0 +1,32 @@
|
||||
{% extends 'base.html' %}
|
||||
{% from "macros.html" import render_field %}
|
||||
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
|
||||
{% block content_title %}{{ title }}{% endblock %}
|
||||
{% block content_description %}{{ description }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
{{ form.hidden_tag() }}
|
||||
{% set disabled_fields = [] %}
|
||||
{% set exclude_fields = [] %}
|
||||
{% for field in form.get_static_fields() %}
|
||||
{{ render_field(field, disabled_fields, exclude_fields) }}
|
||||
{% endfor %}
|
||||
{% if form.get_dynamic_fields is defined %}
|
||||
{% 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 %}
|
||||
{% endif %}
|
||||
<button type="submit" class="btn btn-primary">{{ submit_text }}</button>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block content_footer %}
|
||||
{% endblock %}
|
||||
23
eveai_app/templates/interaction/component_list.html
Normal file
23
eveai_app/templates/interaction/component_list.html
Normal file
@@ -0,0 +1,23 @@
|
||||
{% extends 'base.html' %}
|
||||
{% from 'macros.html' import render_selectable_table, render_pagination %}
|
||||
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
|
||||
{% block content_title %}{{ title }}{% endblock %}
|
||||
{% block content_description %}View {{ component_type }}s for Tenant{% endblock %}
|
||||
{% block content_class %}<div class="col-xl-12 col-lg-5 col-md-7 mx-auto"></div>{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<form method="POST" action="{{ url_for(action_url) }}">
|
||||
{{ render_selectable_table(headers=["ID", "Name", "Type"], rows=rows, selectable=True, id=table_id) }}
|
||||
<div class="form-group mt-3">
|
||||
<button type="submit" name="action" value="edit_{{component_type|lower}}" class="btn btn-primary">Edit {{ component_type }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block content_footer %}
|
||||
{{ render_pagination(pagination, pagination_endpoint) }}
|
||||
{% endblock %}
|
||||
@@ -102,6 +102,16 @@
|
||||
{'name': 'Add Specialist', 'url': '/interaction/specialist', 'roles': ['Super User', 'Tenant Admin']},
|
||||
{'name': 'All Specialists', 'url': '/interaction/specialists', 'roles': ['Super User', 'Tenant Admin']},
|
||||
{'name': 'Chat Sessions', 'url': '/interaction/chat_sessions', 'roles': ['Super User', 'Tenant Admin']},
|
||||
{'name': 'Add PreProcessor', 'url': '/interaction/preprocessor', 'roles': ['Super User', 'Tenant Admin']},
|
||||
{'name': 'All PreProcessors', 'url': '/interaction/preprocessors', 'roles': ['Super User', 'Tenant Admin']},
|
||||
{'name': 'Add Compiler', 'url': '/interaction/compiler', 'roles': ['Super User', 'Tenant Admin']},
|
||||
{'name': 'All Compilers', 'url': '/interaction/compilers', 'roles': ['Super User', 'Tenant Admin']},
|
||||
{'name': 'Add Dispatcher', 'url': '/interaction/dispatcher', 'roles': ['Super User', 'Tenant Admin']},
|
||||
{'name': 'All Dispatchers', 'url': '/interaction/dispatchers', 'roles': ['Super User', 'Tenant Admin']},
|
||||
{'name': 'Add PostProcessor', 'url': '/interaction/postprocessor', 'roles': ['Super User', 'Tenant Admin']},
|
||||
{'name': 'All PostProcessors', 'url': '/interaction/postprocessors', 'roles': ['Super User', 'Tenant Admin']},
|
||||
{'name': 'Add Reasoner', 'url': '/interaction/reasoner', 'roles': ['Super User', 'Tenant Admin']},
|
||||
{'name': 'All Reasoners', 'url': '/interaction/reasoners', 'roles': ['Super User', 'Tenant Admin']},
|
||||
]) }}
|
||||
{% endif %}
|
||||
{% if current_user.is_authenticated %}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import (StringField, BooleanField, SelectField, TextAreaField)
|
||||
from wtforms.validators import DataRequired, Length
|
||||
from wtforms.validators import DataRequired, Length, Optional
|
||||
|
||||
from wtforms_sqlalchemy.fields import QuerySelectMultipleField
|
||||
|
||||
from common.models.document import Retriever
|
||||
from common.models.interaction import EveAITool
|
||||
from common.extensions import cache_manager
|
||||
|
||||
from config.type_defs.specialist_types import SPECIALIST_TYPES
|
||||
from .dynamic_form_base import DynamicFormBase
|
||||
|
||||
|
||||
@@ -14,6 +15,10 @@ def get_retrievers():
|
||||
return Retriever.query.all()
|
||||
|
||||
|
||||
def get_tools():
|
||||
return EveAITool.query.all()
|
||||
|
||||
|
||||
class SpecialistForm(FlaskForm):
|
||||
name = StringField('Name', validators=[DataRequired(), Length(max=50)])
|
||||
description = TextAreaField('Description', validators=[DataRequired()])
|
||||
@@ -32,8 +37,9 @@ class SpecialistForm(FlaskForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
types_dict = cache_manager.specialist_config_cache.get_types()
|
||||
# Dynamically populate the 'type' field using the constructor
|
||||
self.type.choices = [(key, value['name']) for key, value in SPECIALIST_TYPES.items()]
|
||||
self.type.choices = [(key, value['name']) for key, value in types_dict.items()]
|
||||
|
||||
|
||||
class EditSpecialistForm(DynamicFormBase):
|
||||
@@ -52,4 +58,59 @@ class EditSpecialistForm(DynamicFormBase):
|
||||
tuning = BooleanField('Enable Retrieval Tuning', default=False)
|
||||
|
||||
|
||||
class BaseComponentForm(DynamicFormBase):
|
||||
"""Base form for all processing components"""
|
||||
name = StringField('Name', validators=[DataRequired(), Length(max=50)])
|
||||
description = TextAreaField('Description', validators=[Optional()])
|
||||
type = SelectField('Type', validators=[DataRequired()])
|
||||
tuning = BooleanField('Enable Tuning', default=False)
|
||||
|
||||
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()]
|
||||
|
||||
|
||||
# Edit forms that support dynamic fields
|
||||
class BaseEditComponentForm(DynamicFormBase):
|
||||
name = StringField('Name', validators=[DataRequired()])
|
||||
description = TextAreaField('Description', validators=[Optional()])
|
||||
type = StringField('Type', validators=[DataRequired()], render_kw={'readonly': True})
|
||||
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):
|
||||
role = StringField('Role', validators=[DataRequired()])
|
||||
goal = StringField('Goal', validators=[DataRequired()])
|
||||
backstory = StringField('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'
|
||||
)
|
||||
|
||||
|
||||
class EveAITaskForm(BaseComponentForm):
|
||||
expected_output = TextAreaField('Expected Output', validators=[DataRequired()])
|
||||
|
||||
@@ -7,14 +7,19 @@ from sqlalchemy import desc
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
|
||||
from common.models.document import Embedding, DocumentVersion, Retriever
|
||||
from common.models.interaction import ChatSession, Interaction, InteractionEmbedding, Specialist, SpecialistRetriever
|
||||
from common.extensions import db
|
||||
from common.utils.document_utils import set_logging_information, update_logging_information
|
||||
from config.type_defs.specialist_types import SPECIALIST_TYPES
|
||||
from common.models.interaction import (ChatSession, Interaction, InteractionEmbedding, Specialist, SpecialistRetriever)
|
||||
|
||||
from common.extensions import db, cache_manager
|
||||
from common.utils.model_logging_utils import set_logging_information, update_logging_information
|
||||
|
||||
from common.utils.middleware import mw_before_request
|
||||
from common.utils.nginx_utils import prefixed_url_for
|
||||
from common.utils.view_assistants import form_validation_failed, prepare_table_for_macro
|
||||
from .interaction_forms import SpecialistForm, EditSpecialistForm
|
||||
from common.utils.specialist_utils import initialize_specialist
|
||||
|
||||
from config.type_defs.specialist_types import SPECIALIST_TYPES
|
||||
|
||||
from .interaction_forms import (SpecialistForm, EditSpecialistForm)
|
||||
|
||||
interaction_bp = Blueprint('interaction_bp', __name__, url_prefix='/interaction')
|
||||
|
||||
@@ -135,6 +140,7 @@ def specialist():
|
||||
new_specialist.name = form.name.data
|
||||
new_specialist.description = form.description.data
|
||||
new_specialist.type = form.type.data
|
||||
new_specialist.type_version = cache_manager.specialist_config_cache.get_latest_version(new_specialist.type)
|
||||
new_specialist.tuning = form.tuning.data
|
||||
|
||||
set_logging_information(new_specialist, dt.now(tz.utc))
|
||||
@@ -156,6 +162,9 @@ def specialist():
|
||||
flash('Specialist successfully added!', 'success')
|
||||
current_app.logger.info(f'Specialist {new_specialist.name} successfully added for tenant {tenant_id}!')
|
||||
|
||||
# Initialize the newly create specialist
|
||||
initialize_specialist(new_specialist.id, new_specialist.type, new_specialist.type_version)
|
||||
|
||||
return redirect(prefixed_url_for('interaction_bp.edit_specialist', specialist_id=new_specialist.id))
|
||||
|
||||
except Exception as e:
|
||||
@@ -173,7 +182,8 @@ def edit_specialist(specialist_id):
|
||||
specialist = Specialist.query.get_or_404(specialist_id)
|
||||
form = EditSpecialistForm(request.form, obj=specialist)
|
||||
|
||||
configuration_config = SPECIALIST_TYPES[specialist.type]["configuration"]
|
||||
specialist_config = cache_manager.specialist_config_cache.get_config(specialist.type, specialist.type_version)
|
||||
configuration_config = specialist_config.get('configuration')
|
||||
form.add_dynamic_fields("configuration", configuration_config, specialist.configuration)
|
||||
|
||||
if request.method == 'GET':
|
||||
@@ -257,3 +267,4 @@ def handle_specialist_selection():
|
||||
return redirect(prefixed_url_for('interaction_bp.edit_specialist', specialist_id=specialist_id))
|
||||
|
||||
return redirect(prefixed_url_for('interaction_bp.specialists'))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user