Skip to content

Commit 3deff52

Browse files
nuwangeekThirunayan22erangi-ar
authored
* updated docker compose ec2 * integrate streaming endpoint with test prodction connection page * formatted response with markdown * fe logic for the encryption * vault secret update after fixing issues * fixed formatting issue * integration with be * update cron manager vault script * tested integration of vault security update * fix security issues * fixed issue references are not sending with streming tokens * complete buerokratt#192 and buerokratt#206 bug fixes * change production inference display logic * Remove obsolete Vite configuration files and associated plugins * added new model --------- Co-authored-by: Thiru Dinesh <56014038+Thirunayan22@users.noreply.github.com> Co-authored-by: Thiru Dinesh <thiru.dinesh@rootcodelabs.com> Co-authored-by: erangi-ar <erangika.ariyasena@rootcode.io>
1 parent d5e5c51 commit 3deff52

File tree

8 files changed

+305
-77
lines changed

8 files changed

+305
-77
lines changed

DSL/Liquibase/changelog/rag-search-script-v1-llm-connections.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ INSERT INTO llm_models (platform_id, model_key, model_name) VALUES
102102
-- Azure models
103103
((SELECT id FROM llm_platforms WHERE platform_key = 'azure'), 'gpt-4o-mini', 'GPT-4o-mini'),
104104
((SELECT id FROM llm_platforms WHERE platform_key = 'azure'), 'gpt-4o', 'GPT-4o'),
105+
((SELECT id FROM llm_platforms WHERE platform_key = 'azure'), 'gpt-4.1', 'GPT-4.1'),
105106
-- AWS models
106107
((SELECT id FROM llm_platforms WHERE platform_key = 'aws'), 'anthropic-claude-3.5-sonnet', 'Anthropic Claude 3.5 Sonnet'),
107108
((SELECT id FROM llm_platforms WHERE platform_key = 'aws'), 'anthropic-claude-3.7-sonnet', 'Anthropic Claude 3.7 Sonnet');

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ The **BYK-RAG Module** is part of the Burokratt ecosystem, designed to provide *
1212
- Models searchable via dropdown with cache-enabled indicators.
1313

1414
- **Enhanced Security with RSA Encryption**
15-
- LLM credentials encrypted with RSA-2048 asymmetric encryption before storage.
15+
- LLM credentials encrypted with RSA-2048 asymmetric encryption before storage.
1616
- GUI encrypts using public key; CronManager decrypts with private key.
1717
- Additional security layer beyond HashiCorp Vault's encryption.
1818

src/llm_orchestration_service.py

Lines changed: 108 additions & 58 deletions
Large diffs are not rendered by default.

src/llm_orchestrator_config/config/llm_config.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ llm:
3232
temperature: 0.5
3333
deployment_name: "gpt-4o-deployment"
3434

35+
gpt-4.1:
36+
model_type: "chat"
37+
max_tokens: 13107
38+
temperature: 0.6
39+
deployment_name: "gpt-4.1"
40+
41+
3542
# AWS Bedrock Configuration
3643
aws_bedrock:
3744
cache: true # Keep caching enabled (DSPY default)

src/llm_orchestrator_config/llm_ochestrator_constants.py

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,36 @@
1-
OUT_OF_SCOPE_MESSAGE = (
2-
"I apologize, but I’m unable to provide a complete response because the available "
3-
"context does not sufficiently cover your request. Please try rephrasing or providing more details."
4-
)
5-
6-
TECHNICAL_ISSUE_MESSAGE = (
7-
"Technical issue with response generation\n"
8-
"I apologize, but I’m currently unable to generate a response due to a temporary technical issue. "
9-
"Please try again in a moment."
10-
)
1+
# Multilingual message dictionaries
2+
OUT_OF_SCOPE_MESSAGES = {
3+
"et": "Vabandust, kuid mul pole piisavalt konteksti, et teie küsimusele vastata. Palun püüdke ümber sõnastada või lisage rohkem üksikasju.",
4+
"ru": "Извините, но у меня недостаточно контекста для ответа на ваш вопрос. Пожалуйста, попробуйте переформулировать или предоставить больше деталей.",
5+
"en": "I apologize, but I'm unable to provide a complete response because the available context does not sufficiently cover your request. Please try rephrasing or providing more details.",
6+
}
7+
8+
TECHNICAL_ISSUE_MESSAGES = {
9+
"et": "Tehniline probleem vastuse genereerimisel\nVabandust, kuid ma ei saa praegu vastust genereerida ajutise tehnilise probleemi tõttu. Palun proovige mõne hetke pärast uuesti.",
10+
"ru": "Техническая проблема при генерации ответа\nИзвините, в настоящее время я не могу сгенерировать ответ из-за временной технической проблемы. Пожалуйста, попробуйте еще раз через мгновение.",
11+
"en": "Technical issue with response generation\nI apologize, but I'm currently unable to generate a response due to a temporary technical issue. Please try again in a moment.",
12+
}
13+
14+
INPUT_GUARDRAIL_VIOLATION_MESSAGES = {
15+
"et": "Vabandust, kuid ma ei saa selle taotlusega aidata, kuna see rikub meie kasutustingimusi.",
16+
"ru": "Извините, но я не могу помочь с этим запросом, так как он нарушает нашу политику использования.",
17+
"en": "I apologize, but I'm unable to assist with that request as it violates our usage policies.",
18+
}
19+
20+
OUTPUT_GUARDRAIL_VIOLATION_MESSAGES = {
21+
"et": "Vabandust, kuid ma ei saa vastust anda, kuna see võib rikkuda meie kasutustingimusi.",
22+
"ru": "Извините, но я не могу предоставить ответ, так как он может нарушить нашу политику использования.",
23+
"en": "I apologize, but I'm unable to provide a response as it may violate our usage policies.",
24+
}
25+
26+
# Legacy constants for backward compatibility (English defaults)
27+
OUT_OF_SCOPE_MESSAGE = OUT_OF_SCOPE_MESSAGES["en"]
28+
TECHNICAL_ISSUE_MESSAGE = TECHNICAL_ISSUE_MESSAGES["en"]
29+
INPUT_GUARDRAIL_VIOLATION_MESSAGE = INPUT_GUARDRAIL_VIOLATION_MESSAGES["en"]
30+
OUTPUT_GUARDRAIL_VIOLATION_MESSAGE = OUTPUT_GUARDRAIL_VIOLATION_MESSAGES["en"]
1131

1232
UNKNOWN_SOURCE = "Unknown source"
1333

14-
INPUT_GUARDRAIL_VIOLATION_MESSAGE = "I apologize, but I'm unable to assist with that request as it violates our usage policies."
15-
16-
OUTPUT_GUARDRAIL_VIOLATION_MESSAGE = "I apologize, but I'm unable to provide a response as it may violate our usage policies."
17-
1834
GUARDRAILS_BLOCKED_PHRASES = [
1935
"i'm sorry, i can't respond to that",
2036
"i cannot respond to that",
@@ -88,6 +104,22 @@
88104

89105
VALIDATION_GENERIC_ERROR = "I apologize, but I couldn't process your request. Please check your input and try again."
90106

107+
108+
# Helper function to get localized messages
109+
def get_localized_message(message_dict: dict, language_code: str = "en") -> str:
110+
"""
111+
Get message in the specified language, fallback to English.
112+
113+
Args:
114+
message_dict: Dictionary with language codes as keys
115+
language_code: Language code ('et', 'ru', 'en')
116+
117+
Returns:
118+
Localized message string
119+
"""
120+
return message_dict.get(language_code, message_dict.get("en", ""))
121+
122+
91123
# Service endpoints
92124
RAG_SEARCH_RESQL = "http://resql:8082/rag-search"
93125
RAG_SEARCH_RUUTER_PUBLIC = "http://ruuter-public:8086/rag-search"

src/prompt_refine_manager/prompt_refiner.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ class ConversationHistory(BaseModel):
2727
class PromptRefiner(dspy.Signature):
2828
"""Produce N distinct, concise rewrites of the user's question using chat history.
2929
30+
CRITICAL LANGUAGE RULE:
31+
- The rewrites MUST be in the SAME language as the input question
32+
- Estonian question → Estonian rewrites
33+
- Russian question → Russian rewrites
34+
- English question → English rewrites
35+
- Preserve the natural language of the original question
36+
3037
Constraints:
3138
- Preserve the original intent; don't inject unsupported constraints.
3239
- Resolve pronouns with context when safe; avoid changing semantics.
@@ -36,11 +43,13 @@ class PromptRefiner(dspy.Signature):
3643
"""
3744

3845
history: str = dspy.InputField(desc="Recent conversation history (turns).")
39-
question: str = dspy.InputField(desc="The user's latest question to refine.")
46+
question: str = dspy.InputField(
47+
desc="The user's latest question to refine. Preserve its language in rewrites."
48+
)
4049
n: int = dspy.InputField(desc="Number of rewrites to produce (N).")
4150

4251
rewrites: list[str] = dspy.OutputField(
43-
desc="Exactly N refined variations of the question, each a single sentence."
52+
desc="Exactly N refined variations of the question in THE SAME LANGUAGE as input, each a single sentence."
4453
)
4554

4655

src/response_generator/response_generate.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,27 @@
2222
class ResponseGenerator(dspy.Signature):
2323
"""Produce a grounded answer from the provided context ONLY.
2424
25+
CRITICAL LANGUAGE RULE:
26+
- The answer MUST be in the SAME language as the input question
27+
- Estonian question → Estonian answer
28+
- Russian question → Russian answer
29+
- English question → English answer
30+
- Maintain the natural language flow and grammar of the detected language
31+
2532
Rules:
2633
- Use ONLY the provided context blocks; do not invent facts.
2734
- If the context is insufficient, set questionOutOfLLMScope=true and say so briefly.
2835
- Do not include citations in the 'answer' field.
2936
"""
3037

31-
question: str = dspy.InputField()
38+
question: str = dspy.InputField(
39+
desc="User's question. Answer in the SAME language as this question."
40+
)
3241
context_blocks: List[str] = dspy.InputField()
3342
citations: List[str] = dspy.InputField()
34-
answer: str = dspy.OutputField(desc="Human-friendly answer without citations")
43+
answer: str = dspy.OutputField(
44+
desc="Human-friendly answer in THE SAME LANGUAGE as the question, without citations"
45+
)
3546
questionOutOfLLMScope: bool = dspy.OutputField(
3647
desc="True if context is insufficient to answer"
3748
)
@@ -40,6 +51,8 @@ class ResponseGenerator(dspy.Signature):
4051
class ScopeChecker(dspy.Signature):
4152
"""Quick check if question can be answered from context.
4253
54+
LANGUAGE NOTE: This is an internal check, language doesn't matter for scope determination.
55+
4356
Rules:
4457
- Return True ONLY if context is completely insufficient
4558
- Return False if context has ANY relevant information

src/utils/language_detector.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
"""Language detection utility for multilingual support.
2+
3+
Detects Estonian, Russian, and English based on character patterns and common words.
4+
"""
5+
6+
import re
7+
from typing import Literal
8+
from loguru import logger
9+
10+
LanguageCode = Literal["et", "ru", "en"]
11+
12+
13+
def detect_language(text: str) -> LanguageCode:
14+
"""
15+
Detect language from input text.
16+
17+
Detection Strategy:
18+
1. Check for Cyrillic characters (Russian)
19+
2. Check for Estonian-specific characters
20+
3. Check for Estonian common words
21+
4. Default to English
22+
23+
Args:
24+
text: Input text to analyze
25+
26+
Returns:
27+
Language code: 'et' (Estonian), 'ru' (Russian), 'en' (English)
28+
29+
Examples:
30+
>>> detect_language("Mis on sünnitoetus?")
31+
'et'
32+
>>> detect_language("Что такое пособие?")
33+
'ru'
34+
>>> detect_language("What is the benefit?")
35+
'en'
36+
"""
37+
if not text or not text.strip():
38+
logger.warning(
39+
"Empty text provided for language detection, defaulting to English"
40+
)
41+
return "en"
42+
43+
text_sample = text.strip()[:500] # Use first 500 chars for detection
44+
45+
# Check for Cyrillic characters (Russian) - use percentage-based detection
46+
cyrillic_count = len(re.findall(r"[а-яА-ЯёЁ]", text_sample))
47+
total_alpha = len(re.findall(r"[a-zA-Zа-яА-ЯёЁõäöüšžÕÄÖÜŠŽ]", text_sample))
48+
49+
if (
50+
total_alpha > 0 and cyrillic_count / total_alpha > 0.25
51+
): # 25% Cyrillic threshold
52+
logger.debug(
53+
f"Detected Russian (Cyrillic: {cyrillic_count}/{total_alpha} = {cyrillic_count / total_alpha:.1%})"
54+
)
55+
return "ru"
56+
57+
# Check for Estonian-specific characters (õ, ä, ö, ü, š, ž)
58+
estonian_chars = re.findall(r"[õäöüšž]", text_sample, re.IGNORECASE)
59+
if len(estonian_chars) > 0:
60+
logger.debug(f"Detected Estonian (special chars: {len(estonian_chars)})")
61+
return "et"
62+
63+
# Check for Estonian common words - use distinctive markers to avoid English false positives
64+
estonian_markers = [
65+
"kuidas",
66+
"miks",
67+
"kus",
68+
"millal",
69+
"kes",
70+
"võib",
71+
"olen",
72+
"oled",
73+
"see",
74+
"seda",
75+
"jah",
76+
"või",
77+
"ning",
78+
"siis",
79+
"veel",
80+
"aga",
81+
"kuid",
82+
"nii",
83+
"nagu",
84+
"oli",
85+
"mis",
86+
]
87+
88+
# Tokenize and check for Estonian markers
89+
words = re.findall(r"\b\w+\b", text_sample.lower())
90+
estonian_word_count = sum(1 for word in words if word in estonian_markers)
91+
92+
# Scale threshold based on text length for better accuracy
93+
threshold = 1 if len(words) < 10 else 2
94+
if estonian_word_count >= threshold:
95+
logger.debug(
96+
f"Detected Estonian (marker words: {estonian_word_count}/{len(words)}, threshold: {threshold})"
97+
)
98+
return "et"
99+
100+
# Default to English
101+
logger.debug("Detected English (default)")
102+
return "en"
103+
104+
105+
def get_language_name(language_code: LanguageCode) -> str:
106+
"""
107+
Get human-readable language name from code.
108+
109+
Args:
110+
language_code: ISO 639-1 language code
111+
112+
Returns:
113+
Full language name
114+
"""
115+
language_names = {"et": "Estonian", "ru": "Russian", "en": "English"}
116+
return language_names.get(language_code, "Unknown")

0 commit comments

Comments
 (0)