Skip to content

Commit 3d632d7

Browse files
committed
Configurable URL for local models + remove Aitta-specific code
This introduces new config options for configuring the url of the local models: - `llm_local_endpoint_url` (for LLMs), - `rag_settings.local_endpoint_url` (for the RAG embedding model). All references to Aitta are removed from the code. Instead, Aitta can be used by just setting the above URLs. The script for creating RAG DB has been adjusted: - removed redundant embedding calculation, - batching documents for better performance.
1 parent 146b478 commit 3d632d7

10 files changed

Lines changed: 92 additions & 212 deletions

File tree

api/routes/analysis.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ def _run_analysis(
278278
if "llm_combine" not in config:
279279
config["llm_combine"] = {}
280280
config["llm_combine"]["model_name"] = model_name
281-
if "gpt" in model_name or "o1" in model_name or "o3" in model_name:
281+
if model_name.startswith("gpt") or "o1" in model_name or "o3" in model_name:
282282
config["llm_combine"]["model_type"] = "openai"
283283
else:
284284
config["llm_combine"]["model_type"] = "local"

config.yml

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
#model_names: gpt-4o, o1-preview, o1-mini
2-
#model_type: "openai" #"openai / local / aitta
2+
#model_type: "openai" #"openai / local
33
llm_rag:
4-
model_type: "openai"
4+
model_type: "openai"
55
model_name: "gpt-5-mini" # used only for RAGs
66
llm_smart: #used only in smart_agent
7-
model_type: "openai"
7+
model_type: "openai"
88
model_name: "gpt-5.2" # used only for smart agent
99
llm_combine: #used only in combine_agent and intro
10-
model_type: "openai"
10+
model_type: "openai"
1111
model_name: "gpt-5.2" # used only for combine agent ("mkchaou/climsight-calm_ft_Q3_13k")
1212
llm_dataanalysis: #used only in data_analysis_agent
1313
model_type: "openai"
@@ -20,6 +20,7 @@ use_smart_agent: true
2020
use_era5_data: true # Download ERA5 time series from CDS API (requires credentials)
2121
use_destine_data: true # Download DestinE projections via HDA API (requires DESP credentials)
2222
use_powerful_data_analysis: true
23+
llm_local_endpoint_url: "http://localhost:8000/v1"
2324

2425
# ERA5 Climatology Configuration (pre-computed observational baseline)
2526
era5_climatology:
@@ -210,23 +211,22 @@ ecocrop:
210211
data_path: "./data/ecocrop/ecocrop_database/"
211212
rag_settings:
212213
rag_activated: True
213-
# Which embedding backend to use: openai, aitta, mistral, etc.
214-
embedding_model_type: "openai" # options: openai, aitta, mistral
214+
# Which embedding backend to use: openai, local, mistral, etc.
215+
embedding_model_type: "openai" # options: openai, local, mistral
216+
local_endpoint_url: "http://localhost:8000/v1"
215217
# Embedding model name for each backend
216218
embedding_model_openai: "text-embedding-3-large"
217-
embedding_model_aitta: "lightonai/modernbert-embed-large"
219+
embedding_model_local: "lightonai/modernbert-embed-large"
218220
# Add more as needed, e.g.:
219221
# embedding_model_mistral: "mistral-embed-xyz"
220222
# Chroma DB paths for each backend
221223
chroma_path_ipcc_openai: "rag_db/ipcc_reports_openai"
222-
chroma_path_ipcc_aitta: "rag_db/ipcc_reports_aitta"
224+
chroma_path_ipcc_local: "rag_db/ipcc_reports_local"
223225
# chroma_path_ipcc_mistral: "rag_db/ipcc_reports_mistral"
224226
chroma_path_general_openai: "rag_db/general_reports_openai"
225-
chroma_path_general_aitta: "rag_db/general_reports_aitta"
227+
chroma_path_general_local: "rag_db/general_reports_local"
226228
# chroma_path_general_mistral: "rag_db/general_reports_mistral"
227-
# AITTA configuration for open models (optional, only needed for aitta)
228-
aitta_url: "https://api-climatedt-aitta.2.rahtiapp.fi"
229-
document_path: './data/general_reports/' # or ipcc_text_reports
229+
document_path: './data/ipcc_text_reports/' # or general_reports
230230
chunk_size: 2000
231231
chunk_overlap: 200
232232
separators: [" ", ",", "\n"]

frontend/src/components/SettingsPanel.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ const MODEL_OPTIONS = [
1010
'gpt-4.1-nano',
1111
'gpt-4.1-mini',
1212
'gpt-4.1',
13+
'openai/gpt-oss-120b',
14+
'meta-llama/Llama-3.3-70B-Instruct',
1315
];
1416

1517
const CLIMATE_SOURCES = [

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ dependencies = [
7575
]
7676

7777
[project.optional-dependencies]
78-
aitta = ["aitta-client"]
7978
dev = ["pytest", "flake8"]
8079

8180
[build-system]

rag/db_generation.py

Lines changed: 28 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import os
55
import logging
6+
import tqdm
67
import yaml
78
import re
89

@@ -20,7 +21,6 @@
2021
# Import the new embedding utility
2122
import sys
2223
sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'src'))
23-
from climsight.embedding_utils import create_embeddings
2424

2525
logger = logging.getLogger(__name__)
2626
logging.basicConfig(
@@ -141,22 +141,18 @@ def split_docs(documents, chunk_size=2000, chunk_overlap=200, separators=[" ", "
141141
return docs
142142

143143

144-
def chunk_and_embed_documents(document_path, embedding_model, openai_api_key, aitta_url, model_type, chunk_size=2000, chunk_overlap=200, separators=[" ", ",", "\n"]):
144+
def chunk_documents(document_path, chunk_size=2000, chunk_overlap=200, separators=[" ", ",", "\n"]):
145145
"""
146-
Chunks and embeds documents from the specified directory using provided embedding function.
146+
Chunks documents from the specified directory.
147147
148148
Args:
149149
- document_path (str): The path to the directory containing the documents.
150-
- embedding_model (str): The embedding model name to use for generating embeddings.
151-
- openai_api_key (str): OpenAI API key for OpenAI models.
152-
- aitta_url (str): AITTA API URL for open models.
153-
- model_type (str): The type of embedding model backend (e.g., 'openai', 'aitta').
154150
- chunk_size (int): maximum number of characters per chunk. Default: 2000.
155151
- chunk_overlap (int): number of characters to overlap per chunk. Default: 200.
156152
- separators (list): list of characters where text can be split. Default: [" ", ",", "\n"]
157153
158154
Returns:
159-
- list: A list of documents with embeddings.
155+
- list: A list of chunked documents.
160156
"""
161157
# load documents
162158
file_names = get_file_names(document_path)
@@ -167,7 +163,7 @@ def chunk_and_embed_documents(document_path, embedding_model, openai_api_key, ai
167163
all_documents.extend(documents) # save all of them into one
168164

169165
if not all_documents:
170-
logger.info("No documents found for chunking and embedding.")
166+
logger.info("No documents found for chunking.")
171167
return []
172168

173169
# Chunk documents
@@ -180,27 +176,7 @@ def chunk_and_embed_documents(document_path, embedding_model, openai_api_key, ai
180176

181177
logger.info(f"Chunked documents into {len(chunked_docs)} pieces.")
182178

183-
# Create embedding model using the utility function
184-
try:
185-
aitta_api_key = os.getenv('AITTA_API_KEY')
186-
embedding_item = create_embeddings(
187-
embedding_model=embedding_model,
188-
openai_api_key=openai_api_key,
189-
aitta_api_key=aitta_api_key,
190-
aitta_url=aitta_url,
191-
model_type=model_type
192-
)
193-
# embedding documents
194-
embedded_docs = []
195-
for doc in chunked_docs:
196-
embedding = embedding_item.embed_documents([doc.page_content])[0] # embed_documents returns a list, so we take the first element
197-
embedded_docs.append({"text": doc.page_content, "embedding": embedding, "metadata": doc.metadata})
198-
except Exception as e:
199-
logger.error(f"Failed to embed document chunks: {e}")
200-
return []
201-
202-
logger.info(f"Embedded {len(embedded_docs)} document chunks.")
203-
return embedded_docs
179+
return chunked_docs
204180

205181

206182
def initialize_rag(config):
@@ -222,9 +198,9 @@ def initialize_rag(config):
222198
if embedding_model_type == 'openai':
223199
embedding_model = rag_settings.get('embedding_model_openai')
224200
chroma_path = rag_settings.get('chroma_path_ipcc_openai')
225-
elif embedding_model_type == 'aitta':
226-
embedding_model = rag_settings.get('embedding_model_aitta')
227-
chroma_path = rag_settings.get('chroma_path_ipcc_aitta')
201+
elif embedding_model_type == 'local':
202+
embedding_model = rag_settings.get('embedding_model_local')
203+
chroma_path = rag_settings.get('chroma_path_ipcc_local')
228204
# Add more types here as needed
229205
# elif embedding_model_type == 'mistral':
230206
# embedding_model = rag_settings.get('embedding_model_mistral')
@@ -233,8 +209,7 @@ def initialize_rag(config):
233209
raise ValueError(f"Unknown embedding_model_type: {embedding_model_type}")
234210

235211
openai_api_key = os.getenv('OPENAI_API_KEY')
236-
aitta_api_key = os.getenv('AITTA_API_KEY')
237-
aitta_url = rag_settings.get('aitta_url', os.getenv('AITTA_URL', 'https://api-climatedt-aitta.2.rahtiapp.fi'))
212+
local_api_key = os.getenv('OPENAI_API_KEY_LOCAL')
238213
document_path = rag_settings['document_path']
239214
chunk_size = rag_settings['chunk_size']
240215
chunk_overlap = rag_settings['chunk_overlap']
@@ -244,8 +219,8 @@ def initialize_rag(config):
244219
if embedding_model_type == 'openai' and not openai_api_key:
245220
logger.warning("No OpenAI API Key found. Skipping RAG initialization.")
246221
return False
247-
if embedding_model_type == 'aitta' and not aitta_api_key:
248-
logger.warning("No AITTA API Key found. Skipping RAG initialization.")
222+
if embedding_model_type == 'local' and not local_api_key:
223+
logger.warning("No local API Key found. Skipping RAG initialization.")
249224
return False
250225

251226
# check if documents are present and valid
@@ -255,24 +230,24 @@ def initialize_rag(config):
255230

256231
# Perform chunking and embedding
257232
try:
258-
langchain_ef = create_embeddings(
259-
embedding_model=embedding_model,
260-
openai_api_key=openai_api_key,
261-
aitta_api_key=aitta_api_key,
262-
aitta_url=aitta_url,
263-
model_type=embedding_model_type
264-
)
265-
documents = chunk_and_embed_documents(document_path, embedding_model, openai_api_key, aitta_url, embedding_model_type, chunk_size, chunk_overlap, separators)
266-
converted_documents = [
267-
Document(page_content=doc['text'], metadata=doc['metadata'])
268-
for doc in documents
269-
]
270-
rag_db = Chroma.from_documents(
271-
documents=converted_documents,
233+
if config['rag_settings']['embedding_model_type'] == 'local':
234+
langchain_ef = OpenAIEmbeddings(
235+
api_key=local_api_key,
236+
base_url=rag_settings.get('local_endpoint_url'),
237+
model=embedding_model,
238+
tiktoken_enabled=False
239+
)
240+
else:
241+
langchain_ef = OpenAIEmbeddings(api_key=openai_api_key, model=embedding_model)
242+
documents = chunk_documents(document_path, chunk_size, chunk_overlap, separators)
243+
rag_db = Chroma(
244+
collection_name="ipcc_collection",
272245
persist_directory=chroma_path,
273-
embedding=langchain_ef,
274-
collection_name="ipcc_collection"
246+
embedding_function=langchain_ef
275247
)
248+
batch_size = 32
249+
for i in tqdm.tqdm(range(0, len(documents), batch_size)):
250+
rag_db.add_documents(documents[i:i+batch_size])
276251
rag_ready = True
277252
logger.info(f"RAG ready: {rag_ready}")
278253
logger.info("RAG database has been initialized and documents embedded.")

src/climsight/climsight_engine.py

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
write_climate_data_manifest,
7070
)
7171
# import smart_agent
72-
from smart_agent import get_aitta_chat_model, smart_agent
72+
from smart_agent import smart_agent
7373
# import data_analysis_agent
7474
from data_analysis_agent import data_analysis_agent
7575
# import predefined data preparation functions
@@ -663,7 +663,7 @@ def agent_llm_request(content_message, input_params, config, api_key, api_key_lo
663663
logger.info(f"start agent_request")
664664
if config['llm_combine']['model_type'] == "local":
665665
llm_combine_agent = ChatOpenAI(
666-
openai_api_base="http://localhost:8000/v1",
666+
openai_api_base=config['llm_local_endpoint_url'],
667667
model_name=config['llm_combine']['model_name'], # Match the exact model name you used
668668
openai_api_key=api_key_local,
669669
max_tokens=16000,
@@ -688,20 +688,14 @@ def agent_llm_request(content_message, input_params, config, api_key, api_key_lo
688688
max_tokens=16000,
689689
)
690690
llm_intro = llm_combine_agent
691-
elif config['llm_combine']['model_type'] == 'aitta':
692-
llm_combine_agent = get_aitta_chat_model(
693-
config['llm_combine']['model_name'],
694-
max_completion_tokens=4096
695-
)
696-
llm_intro = llm_combine_agent
697691

698692
# Data analysis LLM (separate from combine step).
699693
llm_dataanalysis_cfg = config.get("llm_dataanalysis")
700694
if not llm_dataanalysis_cfg:
701695
raise RuntimeError("Missing llm_dataanalysis configuration.")
702696
if llm_dataanalysis_cfg.get("model_type") == "local":
703697
llm_dataanalysis_agent = ChatOpenAI(
704-
openai_api_base="http://localhost:8000/v1",
698+
openai_api_base=config["llm_local_endpoint_url"],
705699
model_name=llm_dataanalysis_cfg.get("model_name"),
706700
openai_api_key=api_key_local,
707701
max_tokens=16000,
@@ -712,11 +706,6 @@ def agent_llm_request(content_message, input_params, config, api_key, api_key_lo
712706
model_name=llm_dataanalysis_cfg.get("model_name"),
713707
max_tokens=16000,
714708
)
715-
elif llm_dataanalysis_cfg.get("model_type") == "aitta":
716-
llm_dataanalysis_agent = get_aitta_chat_model(
717-
llm_dataanalysis_cfg.get("model_name"),
718-
max_completion_tokens=4096
719-
)
720709
else:
721710
llm_dataanalysis_agent = llm_combine_agent
722711

@@ -1187,7 +1176,7 @@ class routeResponse(BaseModel):
11871176
# Pass the dictionary to invoke
11881177
input = {"user_text": state.user}
11891178
response = chain.invoke(input)
1190-
elif config['llm_combine']['model_type'] in ("local", "aitta"):
1179+
elif config['llm_combine']['model_type'] == "local":
11911180
prompt_text = intro_prompt.format(user_text=state.user)
11921181
response_raw = llm_intro.invoke(prompt_text)
11931182
import re, json
@@ -1275,7 +1264,7 @@ def combine_agent(state: AgentState):
12751264
state.content_message += "\n ECOCROP Search Response: {ecocrop_search_response} "
12761265
logger.info(f"Ecocrop_search_response: {state.ecocrop_search_response}")
12771266

1278-
if config['llm_combine']['model_type'] in ("local", "aitta"):
1267+
if config['llm_combine']['model_type'] == "local":
12791268
system_message_prompt = SystemMessagePromptTemplate.from_template(config['system_role'])
12801269
elif config['llm_combine']['model_type'] == "openai":
12811270
if "o1" in config['llm_combine']['model_name']:

src/climsight/embedding_utils.py

Lines changed: 0 additions & 72 deletions
This file was deleted.

0 commit comments

Comments
 (0)