-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathsettings.py
More file actions
241 lines (207 loc) · 12.6 KB
/
Copy pathsettings.py
File metadata and controls
241 lines (207 loc) · 12.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
"""
Central configuration for AtlasMind.
All hardcoded defaults live here. Override any value via the corresponding
environment variable without touching this file.
"""
import os
from pathlib import Path
_ROOT = Path(__file__).parent
# -- LLM backend selection --------------------------------------------
# "ollama" (default) or "groq"
LLM_BACKEND = os.getenv("LLM_BACKEND", "ollama")
# -- Ollama / LLM -----------------------------------------------------
OLLAMA_URL = os.getenv("JQL_OLLAMA_URL", "http://localhost:11434")
OLLAMA_MODEL = os.getenv("JQL_LOCAL_MODEL", "qwen2.5:3b-instruct-q4_K_M")
OLLAMA_TEMPERATURE = float(os.getenv("JQL_OLLAMA_TEMPERATURE", "0.1"))
OLLAMA_TIMEOUT = int(os.getenv("JQL_OLLAMA_TIMEOUT", "240"))
OLLAMA_NUM_CTX = int(os.getenv("JQL_OLLAMA_NUM_CTX", "2048"))
OLLAMA_NUM_PREDICT = int(os.getenv("JQL_OLLAMA_NUM_PREDICT", "768"))
OLLAMA_NUM_THREAD = int(os.getenv("JQL_OLLAMA_NUM_THREAD", "4"))
OLLAMA_NUM_BATCH = int(os.getenv("JQL_OLLAMA_NUM_BATCH", "256"))
OLLAMA_TOP_P = float(os.getenv("JQL_OLLAMA_TOP_P", "0.5"))
OLLAMA_TOP_K = int(os.getenv("JQL_OLLAMA_TOP_K", "20"))
OLLAMA_REPEAT_PENALTY = float(os.getenv("JQL_OLLAMA_REPEAT_PENALTY", "1.1"))
# -- vLLM local inference server ---------------------------------------
# VLLM_MODEL: auto-detected from /v1/models if left empty.
# VLLM_API_KEY: only needed if vLLM was started with --api-key.
VLLM_URL = os.getenv("VLLM_URL", "http://localhost:8002")
VLLM_MODEL = os.getenv("VLLM_MODEL", "")
VLLM_TEMPERATURE = float(os.getenv("VLLM_TEMPERATURE", "0.1"))
VLLM_TIMEOUT = int(os.getenv("VLLM_TIMEOUT", "240"))
VLLM_MAX_TOKENS = int(os.getenv("VLLM_MAX_TOKENS", "500"))
VLLM_API_KEY = os.getenv("VLLM_API_KEY", "")
VLLM_FALLBACK = os.getenv("VLLM_FALLBACK", "ollama")
# -- Groq cloud LLM ---------------------------------------------------
# GROQ_API_KEY_OCID: set this to your OCI Vault secret OCID on cloud deployments.
# GROQ_API_KEY: used as plaintext fallback for local development.
from cloud.oci_vault import resolve_secret
GROQ_API_KEY = resolve_secret("GROQ_API_KEY_OCID", "GROQ_API_KEY")
GROQ_MODEL = os.getenv("GROQ_MODEL", "meta-llama/llama-4-scout-17b-16e-instruct")
GROQ_TEMPERATURE = float(os.getenv("GROQ_TEMPERATURE", "0.1"))
GROQ_TIMEOUT = int(os.getenv("GROQ_TIMEOUT", "30"))
GROQ_MAX_TOKENS = int(os.getenv("GROQ_MAX_TOKENS", "650"))
# -- Anthropic Claude direct API ---------------------------------------
# CLAUDE_API_KEY_OCID: set this to your OCI Vault secret OCID on cloud deployments.
# ANTHROPIC_API_KEY: standard Anthropic SDK env var, used as plaintext fallback.
CLAUDE_API_KEY = resolve_secret("CLAUDE_API_KEY_OCID", "ANTHROPIC_API_KEY")
CLAUDE_MODEL = os.getenv("CLAUDE_MODEL", "claude-sonnet-4-6")
CLAUDE_TEMPERATURE = float(os.getenv("CLAUDE_TEMPERATURE", "0.1"))
CLAUDE_TIMEOUT = int(os.getenv("CLAUDE_TIMEOUT", "30"))
CLAUDE_MAX_TOKENS = int(os.getenv("CLAUDE_MAX_TOKENS", "650"))
# -- Anthropic Claude via AWS Bedrock custom gateway --------------------
# AWS_BEARER_TOKEN_BEDROCK: bearer token for the Bedrock-compatible endpoint.
# CUSTOM_ENDPOINT: the Bedrock-compatible API gateway URL — must be set explicitly, no default.
BEDROCK_API_KEY = os.getenv("AWS_BEARER_TOKEN_BEDROCK", "")
CUSTOM_ENDPOINT = os.getenv("CUSTOM_ENDPOINT", "")
BEDROCK_REGION = os.getenv("BEDROCK_REGION", "custom")
BEDROCK_MODEL = os.getenv("BEDROCK_MODEL", "claude-sonnet-4.6")
BEDROCK_TEMPERATURE = float(os.getenv("BEDROCK_TEMPERATURE", "0.1"))
BEDROCK_TIMEOUT = int(os.getenv("BEDROCK_TIMEOUT", "30"))
BEDROCK_MAX_TOKENS = int(os.getenv("BEDROCK_MAX_TOKENS", "650"))
# -- pgvector / Embeddings ---------------------------------------------
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:5432/jql_vectordb")
EMBEDDING_MODEL = os.getenv("EMBEDDING_MODEL", "BAAI/bge-small-en-v1.5")
EMBEDDING_BATCH_SIZE = int(os.getenv("EMBEDDING_BATCH_SIZE", "256"))
# -- JQL Embeddings DB schema ---------------------------------------------------------
JQL_TABLE = "jql_annotations"
JQL_COL_ANNOTATION = "annotation"
JQL_COL_JQL = "jql"
JQL_COL_EMBEDDING = "embedding"
JQL_SEARCH_LIMIT = 3
# -- Jira field Embeddings DB schema ---------------------------------------------------------
JIRA_FIELD_TABLE = "jira_field_annotations"
JIRA_FIELD_COL_DESCRIPTION = "description"
JIRA_FIELD_COL_EMBEDDING = "embedding"
JIRA_FIELD_SEARCH_LIMIT = 5
# -- Jira field value embeddings (one row per allowed value per field) ----------------
JIRA_FIELD_VALUES_TABLE = "jira_field_values"
JIRA_FIELD_VALUES_COL_EMBEDDING = "embedding"
# Cosine distance threshold for field name correction in JqlSemanticValidator.
# Matches below this value are accepted; above it the clause is left unchanged.
SEMANTIC_FIELD_THRESHOLD = float(os.getenv("SEMANTIC_FIELD_THRESHOLD", "0.45"))
# L2 distance threshold below which a single candidate is auto-corrected without LLM.
VALUE_AUTO_CORRECT_THRESHOLD = float(os.getenv("VALUE_AUTO_CORRECT_THRESHOLD", "0.15"))
# L2 distance threshold below which candidates are emitted as LLM retry hints.
VALUE_HINT_THRESHOLD = float(os.getenv("VALUE_HINT_THRESHOLD", "0.40"))
VALUE_HINT_MAX_CANDIDATES = int(os.getenv("VALUE_HINT_MAX_CANDIDATES", "3"))
# Top-N query-relevant values injected into the RAG prompt before LLM generation.
VALUE_PROMPT_MAX_CANDIDATES = int(os.getenv("VALUE_PROMPT_MAX_CANDIDATES", "3"))
# Cross-field value search: L2 distance threshold for suggesting the correct field
# when a value is rejected by Jira for the field the LLM used.
# Tighter than VALUE_HINT_THRESHOLD to avoid false positives across all fields.
VALUE_FIELD_SUGGEST_THRESHOLD = float(os.getenv("VALUE_FIELD_SUGGEST_THRESHOLD", "0.20"))
# Maximum number of allowed values to embed per field description.
# Fields like version or status can have hundreds of values — including all of them
# inflates the RAG prompt massively. The full list is stored separately in the
# allowed_values column for JQL validation; this cap only affects the LLM context.
MAX_ALLOWED_VALUES_IN_DESC = int(os.getenv("MAX_ALLOWED_VALUES_IN_DESC", "20"))
# Maximum number of allowed values to embed per field in the jira_field_values table.
# The sanitizer uses exact match (in-memory dict, all values) for casing correction —
# embeddings are only needed for misspelling/similarity correction and RAG prompt hints.
# Fields with ≤ this count are fully embedded; high-cardinality fields (versions,
# components) are capped so the embedding table stays small and seeding stays fast.
MAX_VALUES_FOR_EMBEDDING = int(os.getenv("MAX_VALUES_FOR_EMBEDDING", "50"))
# Field IDs to exclude from embedding regardless of type.
# Add custom fields that are internal, deprecated, or irrelevant to JQL queries.
# Example: JIRA_FIELD_IGNORE_IDS=customfield_10200,customfield_10300
JIRA_FIELD_IGNORE_IDS: set[str] = set(
f.strip() for f in os.getenv("JIRA_FIELD_IGNORE_IDS", "").split(",") if f.strip()
)
# -- CA certificate for outbound TLS (CF / proxy environments) ---------
# Cert injection (CA_BUNDLE_B64 env var or VCAP_SERVICES CredHub bindings) is
# handled entirely by cloud/tls.py at import time. Importing tls here ensures
# the cert is set before any outbound HTTPS call made during settings evaluation
# (e.g. the JQL_ANNOTATION_URL fetch below).
from cloud.tls import tls as _tls # noqa: F401 triggers _init_ca_bundle()
# -- JQL annotation file -----------------------------------------------
DEFAULT_ANNOTATION_FILE = (
os.getenv("JQL_ANNOTATION_FILE")
or str(_ROOT / "data" / "jira_jql_annotated_queries.md")
)
_ANNOTATION_URL = os.getenv("JQL_ANNOTATION_URL")
if _ANNOTATION_URL:
import tempfile as _tempfile
from cloud.config_fetcher import fetch_to_file as _fetch
_ann_tmp = Path(_tempfile.gettempdir()) / "jira_jql_annotated_queries.md"
_fetch(
_ANNOTATION_URL,
_ann_tmp,
username=os.getenv("CONFIG_REGISTRY_USER", ""),
token=os.getenv("CONFIG_REGISTRY_TOKEN", ""),
)
DEFAULT_ANNOTATION_FILE = str(_ann_tmp)
# -- Data directory (domain-scoped subdirs are created inside here) ----
DATA_DIR = _ROOT / "data"
# -- File names — change here to rename files project-wide ------------
JIRA_FIELDS_FILENAME = "jira_fields.json"
JIRA_ALLOWED_VALUES_FILENAME = "jira_allowed_values.json"
JIRA_ASSETS_FILENAME = "jira_assets.json"
JIRA_ASSETS_CONFIG_FILE = str(_ROOT / "config" / "jira_assets_fields.json")
# -- Jira Asset value embeddings (one row per asset object label per field) ----------
JIRA_ASSET_VALUES_TABLE = "jira_asset_values"
JIRA_ASSET_VALUES_COL_EMBEDDING = "embedding"
SYSTEM_PROMPT_FILE = str(_ROOT / "config" / "system_prompt.md")
ROUTER_PROMPT_FILE = str(_ROOT / "config" / "router_prompt.md")
ROUTER_PROMPT_FILE_OLLAMA = str(_ROOT / "config" / "router_prompt_ollama.md")
CHART_SPEC_PROMPT_FILE = str(_ROOT / "config" / "chart_spec_prompt.md")
JQL_RETRY_TEMPLATE = (
"\n\nRETRY: your previous JQL was rejected by Jira.\n"
" Bad JQL : {bad_jql}\n"
" Error : {error}\n\n"
"Generate corrected JQL. Return the same JSON format. Do not repeat the same mistake."
)
# Used when the Jira error identifies a specific invalid field by name.
# More directive than JQL_RETRY_TEMPLATE — suited for small models that need
# explicit instruction rather than general guidance.
JQL_RETRY_FIELD_TEMPLATE = (
"\n\nRETRY: '{field}' is not a valid JQL field.\n"
" Bad JQL : {bad_jql}\n\n"
"Remove the entire condition containing '{field}' from the JQL.\n"
"Do not quote it, do not rename it — remove it entirely.\n"
"Return corrected JQL in the same JSON format."
)
# Used when multiple invalid fields are identified in one Jira error response.
JQL_RETRY_FIELDS_TEMPLATE = (
"\n\nRETRY: multiple invalid fields in the JQL.\n"
" Bad JQL : {bad_jql}\n"
" Invalid fields: {fields}\n\n"
"Remove ALL conditions containing these fields entirely.\n"
"Do not rename or quote them — remove each condition entirely.\n"
"Return corrected JQL in the same JSON format."
)
# Appended to any retry prompt when the sanitizer detected invalid values
# and found close candidates via cosine similarity. Bounded to top-N candidates
# so token cost is always small regardless of field size.
JQL_RETRY_VALUE_HINTS_TEMPLATE = (
"\n\nVALUE CORRECTIONS NEEDED:\n"
"{hints}\n"
"Rewrite the JQL using one of the valid values shown above for each field."
)
# -- Jira query defaults -----------------------------------------------
DEFAULT_JQL = "statusCategory != Done ORDER BY created DESC"
MAX_RESULTS = 1000
MAX_JIRA_RESULTS = int(os.getenv("MAX_JIRA_RESULTS", "2000"))
# -- POST /issue_details limits ----------------------------------------
MAX_ISSUE_DETAILS_KEYS = int(os.getenv("MAX_ISSUE_DETAILS_KEYS", "50"))
MAX_ISSUE_DETAILS_COMMENTS = int(os.getenv("MAX_ISSUE_DETAILS_COMMENTS", "50"))
# -- JQL retry -----------------------------------------------------------
# Total attempts per query: 1 initial + (JQL_MAX_ATTEMPTS - 1) retries.
JQL_MAX_ATTEMPTS = int(os.getenv("JQL_MAX_ATTEMPTS", "4"))
# -- Intent field resolution -------------------------------------------
# Maximum number of extra fields the LLM may propose per query.
MAX_INTENT_FIELDS = int(os.getenv("MAX_INTENT_FIELDS", "5"))
# Desired standard column IDs — always shown in the frontend.
# Validated against jira_fields.json at startup; missing IDs are logged and dropped.
# Override via env: STANDARD_FIELD_IDS=key,summary,assignee,status
_STANDARD_FIELD_IDS_DEFAULT = "key,summary,assignee,status,priority,issuetype,created,resolutiondate,project,fixVersion"
STANDARD_FIELD_IDS: list[str] = [
f.strip() for f in (os.getenv("STANDARD_FIELD_IDS") or _STANDARD_FIELD_IDS_DEFAULT).split(",") if f.strip()
]
# -- Atlassian Rovo MCP ------------------------------------------------
ROVO_MCP_URL = "https://mcp.atlassian.com/v1/mcp"
# -- OAuth 2.1 ---------------------------------------------------------
OAUTH_REDIRECT_URI = "http://localhost:3334/oauth/callback"
OAUTH_SCOPES = ["search:rovo:mcp", "read:me", "read:account", "offline_access"]
OAUTH_AUTH_URL = "https://auth.atlassian.com/authorize"
OAUTH_TOKEN_URL = "https://auth.atlassian.com/oauth/token"
OAUTH_ENV_FILE = ".env"