- Introducing translation service prompts
- Ensure Traicie Role Definition Specialist complies to latest technical requirements - Ensure that empty historical messages do not cause a crash in eveai_client - take into account empty customisation options - make was not processed in the system dynamic attribute tenant_make - ensure only relevant makes are shown when creating magic links - refresh partner info when editing or adding Partner Services$
This commit is contained in:
@@ -317,3 +317,27 @@ class SpecialistMagicLinkTenant(db.Model):
|
|||||||
|
|
||||||
magic_link_code = db.Column(db.String(55), primary_key=True)
|
magic_link_code = db.Column(db.String(55), primary_key=True)
|
||||||
tenant_id = db.Column(db.Integer, db.ForeignKey('public.tenant.id'), nullable=False)
|
tenant_id = db.Column(db.Integer, db.ForeignKey('public.tenant.id'), nullable=False)
|
||||||
|
|
||||||
|
|
||||||
|
class TranslationCache(db.Model):
|
||||||
|
__bind_key__ = 'public'
|
||||||
|
__table_args__ = {'schema': 'public'}
|
||||||
|
|
||||||
|
cache_key = db.Column(db.String(16), primary_key=True)
|
||||||
|
source_text = db.Column(db.Text, nullable=False)
|
||||||
|
translated_text = db.Column(db.Text, nullable=False)
|
||||||
|
source_language = db.Column(db.String(2), nullable=False)
|
||||||
|
target_language = db.Column(db.String(2), nullable=False)
|
||||||
|
context = db.Column(db.Text, nullable=True)
|
||||||
|
|
||||||
|
# Translation cost
|
||||||
|
input_tokens = db.Column(db.Integer, nullable=False)
|
||||||
|
output_tokens = db.Column(db.Integer, nullable=False)
|
||||||
|
|
||||||
|
# Tracking
|
||||||
|
created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now())
|
||||||
|
created_by = db.Column(db.Integer, db.ForeignKey('public.user.id'), nullable=True)
|
||||||
|
updated_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now(), onupdate=db.func.now())
|
||||||
|
updated_by = db.Column(db.Integer, db.ForeignKey('public.user.id'), nullable=True)
|
||||||
|
|
||||||
|
last_used_at = db.Column(db.DateTime, nullable=True)
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Utility functions for chat customization.
|
Utility functions for chat customization.
|
||||||
"""
|
"""
|
||||||
|
from flask import current_app
|
||||||
|
|
||||||
|
|
||||||
def get_default_chat_customisation(tenant_customisation=None):
|
def get_default_chat_customisation(tenant_customisation=None):
|
||||||
"""
|
"""
|
||||||
Get chat customization options with default values for missing options.
|
Get chat customization options with default values for missing options.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
tenant_customization (dict, optional): The tenant's customization options.
|
tenant_customisation (dict or str, optional): The tenant's customization options.
|
||||||
Defaults to None.
|
Defaults to None. Can be a dict or a JSON string.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: A dictionary containing all customization options with default values
|
dict: A dictionary containing all customization options with default values
|
||||||
@@ -37,9 +41,20 @@ def get_default_chat_customisation(tenant_customisation=None):
|
|||||||
# Start with the default customization
|
# Start with the default customization
|
||||||
customisation = default_customisation.copy()
|
customisation = default_customisation.copy()
|
||||||
|
|
||||||
|
# Convert JSON string to dict if needed
|
||||||
|
if isinstance(tenant_customisation, str):
|
||||||
|
try:
|
||||||
|
tenant_customisation = json.loads(tenant_customisation)
|
||||||
|
current_app.logger.debug(f"Converted JSON string to dict: {tenant_customisation}")
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
current_app.logger.error(f"Error parsing JSON customisation: {e}")
|
||||||
|
return default_customisation
|
||||||
|
|
||||||
# Update with tenant customization
|
# Update with tenant customization
|
||||||
for key, value in tenant_customisation.items():
|
current_app.logger.debug(f"Tenant customisation - in default creation: {tenant_customisation}")
|
||||||
if key in customisation:
|
if tenant_customisation:
|
||||||
customisation[key] = value
|
for key, value in tenant_customisation.items():
|
||||||
|
if key in customisation:
|
||||||
|
customisation[key] = value
|
||||||
|
|
||||||
return customisation
|
return customisation
|
||||||
|
|||||||
21
common/utils/translation_utils.py
Normal file
21
common/utils/translation_utils.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import xxhash
|
||||||
|
import json
|
||||||
|
|
||||||
|
from common.utils.model_utils import get_template, replace_variable_in_template
|
||||||
|
|
||||||
|
|
||||||
|
def generate_cache_key(text: str, target_lang: str, source_lang: str = None, context: str = None) -> str:
|
||||||
|
cache_data = {
|
||||||
|
"text": text.strip(),
|
||||||
|
"target_lang": target_lang.lower(),
|
||||||
|
"source_lang": source_lang.lower() if source_lang else None,
|
||||||
|
"context": context.strip() if context else ""
|
||||||
|
}
|
||||||
|
|
||||||
|
cache_string = json.dumps(cache_data, sort_keys=True, ensure_ascii=False)
|
||||||
|
return xxhash.xxh64(cache_string.encode('utf-8')).hexdigest()
|
||||||
|
|
||||||
|
def translate_text(text: str, target_lang: str, source_lang: str = None, context: str = None) -> str:
|
||||||
|
if context:
|
||||||
|
prompt_text = get_template("translation_with_context")
|
||||||
|
prompt_text = replace_variable_in_template(prompt_text, "context", context)
|
||||||
@@ -148,7 +148,7 @@ class Config(object):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
SUPPORTED_LANGUAGES_Full = list(SUPPORTED_LANGUAGE_DETAILS.keys())
|
SUPPORTED_LANGUAGES_FULL = list(SUPPORTED_LANGUAGE_DETAILS.keys())
|
||||||
|
|
||||||
# supported currencies
|
# supported currencies
|
||||||
SUPPORTED_CURRENCIES = ['€', '$']
|
SUPPORTED_CURRENCIES = ['€', '$']
|
||||||
@@ -156,10 +156,7 @@ class Config(object):
|
|||||||
# supported LLMs
|
# supported LLMs
|
||||||
# SUPPORTED_EMBEDDINGS = ['openai.text-embedding-3-small', 'openai.text-embedding-3-large', 'mistral.mistral-embed']
|
# SUPPORTED_EMBEDDINGS = ['openai.text-embedding-3-small', 'openai.text-embedding-3-large', 'mistral.mistral-embed']
|
||||||
SUPPORTED_EMBEDDINGS = ['mistral.mistral-embed']
|
SUPPORTED_EMBEDDINGS = ['mistral.mistral-embed']
|
||||||
SUPPORTED_LLMS = ['openai.gpt-4o', 'openai.gpt-4o-mini',
|
SUPPORTED_LLMS = ['mistral.mistral-large-latest', 'mistral.mistral-medium_latest', 'mistral.mistral-small-latest']
|
||||||
'mistral.mistral-large-latest', 'mistral.mistral-medium_latest', 'mistral.mistral-small-latest']
|
|
||||||
|
|
||||||
ANTHROPIC_LLM_VERSIONS = {'claude-3-5-sonnet': 'claude-3-5-sonnet-20240620', }
|
|
||||||
|
|
||||||
# Annotation text chunk length
|
# Annotation text chunk length
|
||||||
ANNOTATION_TEXT_CHUNK_LENGTH = 10000
|
ANNOTATION_TEXT_CHUNK_LENGTH = 10000
|
||||||
|
|||||||
15
config/prompts/globals/translation_with_context/1.0.0.yaml
Normal file
15
config/prompts/globals/translation_with_context/1.0.0.yaml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
version: "1.0.0"
|
||||||
|
content: >
|
||||||
|
You are a top translator. We need you to translate {text_to_translate} into {target_language}, taking into account
|
||||||
|
this context:
|
||||||
|
|
||||||
|
{context}
|
||||||
|
|
||||||
|
I only want you to return the translation. No explanation, no options. I need to be able to directly use your answer
|
||||||
|
without further interpretation. If more than one option is available, present me with the most probable one.
|
||||||
|
|
||||||
|
metadata:
|
||||||
|
author: "Josako"
|
||||||
|
date_added: "2025-06-23"
|
||||||
|
description: "An assistant to translate given a context."
|
||||||
|
changes: "Initial version"
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
version: "1.0.0"
|
||||||
|
content: >
|
||||||
|
You are a top translator. We need you to translate {text_to_translate} into {target_language}.
|
||||||
|
|
||||||
|
I only want you to return the translation. No explanation, no options. I need to be able to directly use your answer
|
||||||
|
without further interpretation. If more than one option is available, present me with the most probable one.
|
||||||
|
|
||||||
|
metadata:
|
||||||
|
author: "Josako"
|
||||||
|
date_added: "2025-06-23"
|
||||||
|
description: "An assistant to translate without context."
|
||||||
|
changes: "Initial version"
|
||||||
@@ -28,4 +28,12 @@ PROMPT_TYPES = {
|
|||||||
"name": "transcript",
|
"name": "transcript",
|
||||||
"description": "An assistant to transform a transcript to markdown.",
|
"description": "An assistant to transform a transcript to markdown.",
|
||||||
},
|
},
|
||||||
|
"translation_with_context": {
|
||||||
|
"name": "translation_with_context",
|
||||||
|
"description": "An assistant to translate text with context",
|
||||||
|
},
|
||||||
|
"translation_without_context": {
|
||||||
|
"name": "translation_without_context",
|
||||||
|
"description": "An assistant to translate text without context",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -312,7 +312,7 @@ class DynamicFormBase(FlaskForm):
|
|||||||
field_class = SelectField
|
field_class = SelectField
|
||||||
tenant_id = session.get('tenant').get('id')
|
tenant_id = session.get('tenant').get('id')
|
||||||
makes = TenantMake.query.filter_by(tenant_id=tenant_id).all()
|
makes = TenantMake.query.filter_by(tenant_id=tenant_id).all()
|
||||||
choices = [(make.name, make.name) for make in makes]
|
choices = [(make.id, make.name) for make in makes]
|
||||||
extra_classes = ''
|
extra_classes = ''
|
||||||
field_kwargs = {'choices': choices}
|
field_kwargs = {'choices': choices}
|
||||||
|
|
||||||
@@ -581,7 +581,10 @@ class DynamicFormBase(FlaskForm):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(f"Error converting initial data to patterns: {e}")
|
current_app.logger.error(f"Error converting initial data to patterns: {e}")
|
||||||
elif isinstance(field, DateField):
|
elif isinstance(field, DateField):
|
||||||
data[original_field_name] = field.data.isoformat()
|
if field.data:
|
||||||
|
data[original_field_name] = field.data.isoformat()
|
||||||
|
else:
|
||||||
|
data[original_field_name] = None
|
||||||
else:
|
else:
|
||||||
data[original_field_name] = field.data
|
data[original_field_name] = field.data
|
||||||
return data
|
return data
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from flask import session
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import (StringField, BooleanField, SelectField, TextAreaField)
|
from wtforms import (StringField, BooleanField, SelectField, TextAreaField)
|
||||||
from wtforms.fields.datetime import DateField
|
from wtforms.fields.datetime import DateField
|
||||||
@@ -181,7 +182,8 @@ class EditSpecialistMagicLinkForm(DynamicFormBase):
|
|||||||
self.specialist_name.data = ''
|
self.specialist_name.data = ''
|
||||||
|
|
||||||
# Dynamically populate the tenant_make field with None as first option
|
# Dynamically populate the tenant_make field with None as first option
|
||||||
tenant_makes = TenantMake.query.all()
|
tenant_id = session.get('tenant').get('id')
|
||||||
|
tenant_makes = TenantMake.query.filter_by(tenant_id=tenant_id).all()
|
||||||
self.tenant_make_id.choices = [(0, 'None')] + [(make.id, make.name) for make in tenant_makes]
|
self.tenant_make_id.choices = [(0, 'None')] + [(make.id, make.name) for make in tenant_makes]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -702,12 +702,13 @@ def specialist_magic_link():
|
|||||||
new_spec_ml_tenant.tenant_id = tenant_id
|
new_spec_ml_tenant.tenant_id = tenant_id
|
||||||
|
|
||||||
# Define the make valid for this magic link
|
# Define the make valid for this magic link
|
||||||
make_id = SpecialistServices.get_specialist_system_field(new_specialist_magic_link.specialist_id,
|
specialist = Specialist.query.get(new_specialist_magic_link.specialist_id)
|
||||||
"make", "tenant_make")
|
make_id = specialist.configuration.get('make', None)
|
||||||
|
current_app.logger.debug(f"make_id defined in specialist: {make_id}")
|
||||||
if make_id:
|
if make_id:
|
||||||
new_spec_ml_tenant.tenant_make_id = make_id
|
new_specialist_magic_link.tenant_make_id = make_id
|
||||||
elif session.get('tenant').get('default_tenant_make_id'):
|
elif session.get('tenant').get('default_tenant_make_id'):
|
||||||
new_spec_ml_tenant.tenant_make_id = session.get('tenant').get('default_tenant_make_id')
|
new_specialist_magic_link.tenant_make_id = session.get('tenant').get('default_tenant_make_id')
|
||||||
|
|
||||||
db.session.add(new_specialist_magic_link)
|
db.session.add(new_specialist_magic_link)
|
||||||
db.session.add(new_spec_ml_tenant)
|
db.session.add(new_spec_ml_tenant)
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ def edit_partner(partner_id):
|
|||||||
update_logging_information(partner, dt.now(tz.utc))
|
update_logging_information(partner, dt.now(tz.utc))
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash('Partner updated successfully.', 'success')
|
flash('Partner updated successfully.', 'success')
|
||||||
|
refresh_session_partner(partner.id)
|
||||||
return redirect(
|
return redirect(
|
||||||
prefixed_url_for('partner_bp.edit_partner',
|
prefixed_url_for('partner_bp.edit_partner',
|
||||||
partner_id=partner.id)) # Assuming there's a user profile view to redirect to
|
partner_id=partner.id)) # Assuming there's a user profile view to redirect to
|
||||||
@@ -197,6 +198,7 @@ def edit_partner_service(partner_service_id):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash('Partner Service updated successfully.', 'success')
|
flash('Partner Service updated successfully.', 'success')
|
||||||
current_app.logger.info(f"Partner Service {partner_service.name} updated successfully! ")
|
current_app.logger.info(f"Partner Service {partner_service.name} updated successfully! ")
|
||||||
|
refresh_session_partner(partner_id)
|
||||||
except SQLAlchemyError as e:
|
except SQLAlchemyError as e:
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
flash(f'Failed to update Partner Service: {str(e)}', 'danger')
|
flash(f'Failed to update Partner Service: {str(e)}', 'danger')
|
||||||
@@ -339,4 +341,7 @@ def add_partner_service_for_tenant(partner_service_id):
|
|||||||
return redirect(prefixed_url_for('partner_bp.partner_services'))
|
return redirect(prefixed_url_for('partner_bp.partner_services'))
|
||||||
|
|
||||||
|
|
||||||
|
def refresh_session_partner(partner_id):
|
||||||
|
if session.get('partner', None):
|
||||||
|
if partner_id == session['partner']['id']:
|
||||||
|
session['partner'] = Partner.query.get_or_404(partner_id).to_dict()
|
||||||
|
|||||||
@@ -131,18 +131,20 @@ export const ChatApp = {
|
|||||||
const historicalMessages = chatConfig.messages || [];
|
const historicalMessages = chatConfig.messages || [];
|
||||||
|
|
||||||
if (historicalMessages.length > 0) {
|
if (historicalMessages.length > 0) {
|
||||||
this.allMessages = historicalMessages.map(msg => {
|
this.allMessages = historicalMessages
|
||||||
// Zorg voor een correct geformatteerde bericht-object
|
.filter(msg => msg !== null && msg !== undefined) // Filter null/undefined berichten uit
|
||||||
return {
|
.map(msg => {
|
||||||
id: this.messageIdCounter++,
|
// Zorg voor een correct geformatteerde bericht-object
|
||||||
content: typeof msg === 'string' ? msg : msg.content || '',
|
return {
|
||||||
sender: msg.sender || 'ai',
|
id: this.messageIdCounter++,
|
||||||
type: msg.type || 'text',
|
content: typeof msg === 'string' ? msg : (msg.content || ''),
|
||||||
timestamp: msg.timestamp || new Date().toISOString(),
|
sender: msg.sender || 'ai',
|
||||||
formData: msg.formData || null,
|
type: msg.type || 'text',
|
||||||
status: msg.status || 'delivered'
|
timestamp: msg.timestamp || new Date().toISOString(),
|
||||||
};
|
formData: msg.formData || null,
|
||||||
});
|
status: msg.status || 'delivered'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
console.log(`Loaded ${this.allMessages.length} historical messages`);
|
console.log(`Loaded ${this.allMessages.length} historical messages`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,7 +86,13 @@ def chat(magic_link_code):
|
|||||||
session['chat_session_id'] = SpecialistServices.start_session()
|
session['chat_session_id'] = SpecialistServices.start_session()
|
||||||
|
|
||||||
# Get customisation options with defaults
|
# Get customisation options with defaults
|
||||||
customisation = get_default_chat_customisation(tenant_make.chat_customisation_options)
|
current_app.logger.debug(f"Make Customisation Options: {tenant_make.chat_customisation_options}")
|
||||||
|
try:
|
||||||
|
customisation = get_default_chat_customisation(tenant_make.chat_customisation_options)
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.error(f"Error processing customisation options: {str(e)}")
|
||||||
|
# Fallback to default customisation
|
||||||
|
customisation = get_default_chat_customisation(None)
|
||||||
|
|
||||||
# Start a new chat session
|
# Start a new chat session
|
||||||
session['chat_session_id'] = SpecialistServices.start_session()
|
session['chat_session_id'] = SpecialistServices.start_session()
|
||||||
|
|||||||
@@ -69,6 +69,9 @@ class SpecialistExecutor(CrewAIBaseSpecialistExecutor):
|
|||||||
self.role_definition_crew
|
self.role_definition_crew
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _config_state_result_relations(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def execute(self, arguments: SpecialistArguments, formatted_context, citations) -> SpecialistResult:
|
def execute(self, arguments: SpecialistArguments, formatted_context, citations) -> SpecialistResult:
|
||||||
self.log_tuning("Traicie Role Definition Specialist execution started", {})
|
self.log_tuning("Traicie Role Definition Specialist execution started", {})
|
||||||
|
|
||||||
|
|||||||
@@ -94,4 +94,5 @@ scaleway~=2.9.0
|
|||||||
html2text~=2025.4.15
|
html2text~=2025.4.15
|
||||||
markdown~=3.8
|
markdown~=3.8
|
||||||
python-json-logger~=2.0.7
|
python-json-logger~=2.0.7
|
||||||
qrcode[pil]==8.2
|
qrcode[pil]==8.2
|
||||||
|
xxhash~=3.5.0
|
||||||
Reference in New Issue
Block a user