euriai 0.4__py3-none-any.whl → 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- euriai/__init__.py +10 -10
- euriai/autogen.py +511 -0
- euriai/crewai.py +211 -0
- euriai/langchain.py +864 -0
- euriai/langgraph.py +1012 -0
- euriai/llamaindex.py +798 -0
- euriai/n8n.py +102 -0
- euriai/smolagents.py +819 -0
- {euriai-0.4.dist-info → euriai-1.0.0.dist-info}/METADATA +1 -1
- euriai-1.0.0.dist-info/RECORD +18 -0
- euriai/euri_autogen.py +0 -74
- euriai/euri_crewai.py +0 -92
- euriai/euri_langgraph.py +0 -64
- euriai/euri_llamaindex.py +0 -58
- euriai/euri_n8n.py +0 -30
- euriai/euri_smolagents.py +0 -44
- euriai/langchain_embed.py +0 -17
- euriai/langchain_llm.py +0 -29
- euriai-0.4.dist-info/RECORD +0 -19
- {euriai-0.4.dist-info → euriai-1.0.0.dist-info}/WHEEL +0 -0
- {euriai-0.4.dist-info → euriai-1.0.0.dist-info}/entry_points.txt +0 -0
- {euriai-0.4.dist-info → euriai-1.0.0.dist-info}/top_level.txt +0 -0
euriai/llamaindex.py
ADDED
@@ -0,0 +1,798 @@
|
|
1
|
+
"""
|
2
|
+
Enhanced LlamaIndex Integration for Euri API
|
3
|
+
==========================================
|
4
|
+
|
5
|
+
This module provides a comprehensive LlamaIndex integration with the Euri API,
|
6
|
+
including full LLM and embedding support with advanced features like streaming,
|
7
|
+
async operations, and seamless integration with LlamaIndex workflows.
|
8
|
+
|
9
|
+
Usage:
|
10
|
+
from euriai.llamaindex_enhanced import EuriaiLlamaIndexLLM, EuriaiLlamaIndexEmbedding, EuriaiLlamaIndex
|
11
|
+
|
12
|
+
# LLM with full features
|
13
|
+
llm = EuriaiLlamaIndexLLM(
|
14
|
+
api_key="your_api_key",
|
15
|
+
model="gpt-4.1-nano",
|
16
|
+
temperature=0.7
|
17
|
+
)
|
18
|
+
|
19
|
+
# Embedding model
|
20
|
+
embedding = EuriaiLlamaIndexEmbedding(
|
21
|
+
api_key="your_api_key",
|
22
|
+
model="text-embedding-3-small"
|
23
|
+
)
|
24
|
+
|
25
|
+
# Complete integration
|
26
|
+
llama_index = EuriaiLlamaIndex(
|
27
|
+
api_key="your_api_key",
|
28
|
+
llm_model="gpt-4.1-nano",
|
29
|
+
embedding_model="text-embedding-3-small"
|
30
|
+
)
|
31
|
+
"""
|
32
|
+
|
33
|
+
import asyncio
|
34
|
+
import json
|
35
|
+
import logging
|
36
|
+
from typing import (
|
37
|
+
Any, Dict, List, Optional, Iterator, AsyncIterator,
|
38
|
+
Union, Callable, Sequence, Generator
|
39
|
+
)
|
40
|
+
from concurrent.futures import ThreadPoolExecutor
|
41
|
+
import time
|
42
|
+
|
43
|
+
try:
|
44
|
+
from llama_index.core.llms import LLM
|
45
|
+
from llama_index.core.embeddings import BaseEmbedding
|
46
|
+
from llama_index.core.base.llms.types import (
|
47
|
+
ChatMessage, MessageRole, CompletionResponse, CompletionResponseGen,
|
48
|
+
ChatResponse, ChatResponseGen, LLMMetadata
|
49
|
+
)
|
50
|
+
from llama_index.core.schema import Document, TextNode, BaseNode
|
51
|
+
from llama_index.core import VectorStoreIndex, ServiceContext, Settings
|
52
|
+
from llama_index.core.callbacks import CallbackManager
|
53
|
+
from pydantic import Field, BaseModel, SecretStr
|
54
|
+
LLAMAINDEX_AVAILABLE = True
|
55
|
+
except ImportError:
|
56
|
+
LLAMAINDEX_AVAILABLE = False
|
57
|
+
# Fallback base classes
|
58
|
+
class LLM:
|
59
|
+
pass
|
60
|
+
class BaseEmbedding:
|
61
|
+
pass
|
62
|
+
class ChatMessage:
|
63
|
+
pass
|
64
|
+
class MessageRole:
|
65
|
+
pass
|
66
|
+
class CompletionResponse:
|
67
|
+
pass
|
68
|
+
class CompletionResponseGen:
|
69
|
+
pass
|
70
|
+
class ChatResponse:
|
71
|
+
pass
|
72
|
+
class ChatResponseGen:
|
73
|
+
pass
|
74
|
+
class LLMMetadata:
|
75
|
+
pass
|
76
|
+
class Document:
|
77
|
+
pass
|
78
|
+
class TextNode:
|
79
|
+
pass
|
80
|
+
class BaseNode:
|
81
|
+
pass
|
82
|
+
class VectorStoreIndex:
|
83
|
+
pass
|
84
|
+
class ServiceContext:
|
85
|
+
pass
|
86
|
+
class Settings:
|
87
|
+
pass
|
88
|
+
class CallbackManager:
|
89
|
+
pass
|
90
|
+
class Field:
|
91
|
+
pass
|
92
|
+
class BaseModel:
|
93
|
+
pass
|
94
|
+
class SecretStr:
|
95
|
+
pass
|
96
|
+
|
97
|
+
from euriai.client import EuriaiClient
|
98
|
+
from euriai.embedding import EuriaiEmbeddingClient
|
99
|
+
|
100
|
+
|
101
|
+
class EuriaiLlamaIndexLLM(LLM):
|
102
|
+
"""
|
103
|
+
Enhanced LlamaIndex LLM implementation using Euri API.
|
104
|
+
|
105
|
+
This implementation provides full LlamaIndex compatibility with advanced features:
|
106
|
+
- Streaming support (both sync and async)
|
107
|
+
- Async operations
|
108
|
+
- Proper metadata handling
|
109
|
+
- Usage tracking
|
110
|
+
- Error handling and retries
|
111
|
+
- Callback support
|
112
|
+
|
113
|
+
Example:
|
114
|
+
llm = EuriaiLlamaIndexLLM(
|
115
|
+
api_key="your_api_key",
|
116
|
+
model="gpt-4.1-nano",
|
117
|
+
temperature=0.7,
|
118
|
+
max_tokens=1000,
|
119
|
+
streaming=True
|
120
|
+
)
|
121
|
+
|
122
|
+
# Basic completion
|
123
|
+
response = llm.complete("What is AI?")
|
124
|
+
print(response.text)
|
125
|
+
|
126
|
+
# Chat
|
127
|
+
messages = [
|
128
|
+
ChatMessage(role=MessageRole.USER, content="Hello!")
|
129
|
+
]
|
130
|
+
response = llm.chat(messages)
|
131
|
+
print(response.message.content)
|
132
|
+
|
133
|
+
# Streaming
|
134
|
+
response_gen = llm.stream_complete("Tell me a story")
|
135
|
+
for chunk in response_gen:
|
136
|
+
print(chunk.text, end="")
|
137
|
+
|
138
|
+
# Async
|
139
|
+
response = await llm.acomplete("What is the weather?")
|
140
|
+
print(response.text)
|
141
|
+
"""
|
142
|
+
|
143
|
+
# Configuration
|
144
|
+
api_key: str = Field(description="Euri API key")
|
145
|
+
model: str = Field(default="gpt-4.1-nano", description="Model name")
|
146
|
+
temperature: float = Field(default=0.7, ge=0.0, le=1.0, description="Sampling temperature")
|
147
|
+
max_tokens: int = Field(default=1000, gt=0, description="Maximum tokens to generate")
|
148
|
+
top_p: Optional[float] = Field(default=None, ge=0.0, le=1.0, description="Nucleus sampling parameter")
|
149
|
+
frequency_penalty: Optional[float] = Field(default=None, ge=-2.0, le=2.0, description="Frequency penalty")
|
150
|
+
presence_penalty: Optional[float] = Field(default=None, ge=-2.0, le=2.0, description="Presence penalty")
|
151
|
+
|
152
|
+
# Features
|
153
|
+
streaming: bool = Field(default=False, description="Enable streaming responses")
|
154
|
+
context_window: int = Field(default=8000, description="Context window size")
|
155
|
+
|
156
|
+
# Internal
|
157
|
+
_client: Optional[EuriaiClient] = None
|
158
|
+
_executor: Optional[ThreadPoolExecutor] = None
|
159
|
+
|
160
|
+
def __init__(self, **kwargs):
|
161
|
+
if not LLAMAINDEX_AVAILABLE:
|
162
|
+
raise ImportError(
|
163
|
+
"LlamaIndex is not installed. Please install with: "
|
164
|
+
"pip install llama-index-core"
|
165
|
+
)
|
166
|
+
|
167
|
+
super().__init__(**kwargs)
|
168
|
+
|
169
|
+
# Initialize client
|
170
|
+
self._client = EuriaiClient(
|
171
|
+
api_key=self.api_key,
|
172
|
+
model=self.model
|
173
|
+
)
|
174
|
+
|
175
|
+
# Initialize thread pool for async operations
|
176
|
+
self._executor = ThreadPoolExecutor(max_workers=4)
|
177
|
+
|
178
|
+
@property
|
179
|
+
def metadata(self) -> LLMMetadata:
|
180
|
+
"""Get LLM metadata."""
|
181
|
+
if not LLAMAINDEX_AVAILABLE:
|
182
|
+
return {}
|
183
|
+
|
184
|
+
return LLMMetadata(
|
185
|
+
context_window=self.context_window,
|
186
|
+
num_output=self.max_tokens,
|
187
|
+
is_chat_model=True,
|
188
|
+
model_name=self.model,
|
189
|
+
is_function_calling_model=True,
|
190
|
+
)
|
191
|
+
|
192
|
+
def _prepare_request_params(self, **kwargs) -> Dict[str, Any]:
|
193
|
+
"""Prepare request parameters for the API call."""
|
194
|
+
params = {
|
195
|
+
"temperature": self.temperature,
|
196
|
+
"max_tokens": self.max_tokens,
|
197
|
+
}
|
198
|
+
|
199
|
+
# Add optional parameters
|
200
|
+
if self.top_p is not None:
|
201
|
+
params["top_p"] = self.top_p
|
202
|
+
if self.frequency_penalty is not None:
|
203
|
+
params["frequency_penalty"] = self.frequency_penalty
|
204
|
+
if self.presence_penalty is not None:
|
205
|
+
params["presence_penalty"] = self.presence_penalty
|
206
|
+
|
207
|
+
# Override with kwargs
|
208
|
+
params.update(kwargs)
|
209
|
+
return params
|
210
|
+
|
211
|
+
def _format_messages(self, messages: List[ChatMessage]) -> List[Dict[str, str]]:
|
212
|
+
"""Format LlamaIndex messages for the Euri API."""
|
213
|
+
formatted_messages = []
|
214
|
+
|
215
|
+
for message in messages:
|
216
|
+
if hasattr(message, 'role') and hasattr(message, 'content'):
|
217
|
+
if message.role == MessageRole.USER:
|
218
|
+
formatted_messages.append({"role": "user", "content": message.content})
|
219
|
+
elif message.role == MessageRole.ASSISTANT:
|
220
|
+
formatted_messages.append({"role": "assistant", "content": message.content})
|
221
|
+
elif message.role == MessageRole.SYSTEM:
|
222
|
+
formatted_messages.append({"role": "system", "content": message.content})
|
223
|
+
else:
|
224
|
+
formatted_messages.append({"role": "user", "content": message.content})
|
225
|
+
else:
|
226
|
+
# Fallback for simple message formats
|
227
|
+
formatted_messages.append({"role": "user", "content": str(message)})
|
228
|
+
|
229
|
+
return formatted_messages
|
230
|
+
|
231
|
+
def _create_completion_response(self, response: Dict[str, Any]) -> CompletionResponse:
|
232
|
+
"""Create a CompletionResponse from API response."""
|
233
|
+
if not LLAMAINDEX_AVAILABLE:
|
234
|
+
return {"text": response.get("choices", [{}])[0].get("message", {}).get("content", "")}
|
235
|
+
|
236
|
+
text = response.get("choices", [{}])[0].get("message", {}).get("content", "")
|
237
|
+
return CompletionResponse(text=text)
|
238
|
+
|
239
|
+
def _create_chat_response(self, response: Dict[str, Any]) -> ChatResponse:
|
240
|
+
"""Create a ChatResponse from API response."""
|
241
|
+
if not LLAMAINDEX_AVAILABLE:
|
242
|
+
return {"message": {"content": response.get("choices", [{}])[0].get("message", {}).get("content", "")}}
|
243
|
+
|
244
|
+
text = response.get("choices", [{}])[0].get("message", {}).get("content", "")
|
245
|
+
message = ChatMessage(role=MessageRole.ASSISTANT, content=text)
|
246
|
+
return ChatResponse(message=message)
|
247
|
+
|
248
|
+
def complete(self, prompt: str, formatted: bool = False, **kwargs) -> CompletionResponse:
|
249
|
+
"""Complete a prompt."""
|
250
|
+
# Convert prompt to message format
|
251
|
+
messages = [{"role": "user", "content": prompt}]
|
252
|
+
|
253
|
+
# Prepare request
|
254
|
+
params = self._prepare_request_params(**kwargs)
|
255
|
+
params["messages"] = messages
|
256
|
+
|
257
|
+
try:
|
258
|
+
# Make API call
|
259
|
+
response = self._client.generate_completion(**params)
|
260
|
+
return self._create_completion_response(response)
|
261
|
+
except Exception as e:
|
262
|
+
logging.error(f"Error in complete: {e}")
|
263
|
+
raise
|
264
|
+
|
265
|
+
def chat(self, messages: List[ChatMessage], **kwargs) -> ChatResponse:
|
266
|
+
"""Chat with messages."""
|
267
|
+
# Format messages
|
268
|
+
formatted_messages = self._format_messages(messages)
|
269
|
+
|
270
|
+
# Prepare request
|
271
|
+
params = self._prepare_request_params(**kwargs)
|
272
|
+
params["messages"] = formatted_messages
|
273
|
+
|
274
|
+
try:
|
275
|
+
# Make API call
|
276
|
+
response = self._client.generate_completion(**params)
|
277
|
+
return self._create_chat_response(response)
|
278
|
+
except Exception as e:
|
279
|
+
logging.error(f"Error in chat: {e}")
|
280
|
+
raise
|
281
|
+
|
282
|
+
def stream_complete(self, prompt: str, formatted: bool = False, **kwargs) -> CompletionResponseGen:
|
283
|
+
"""Stream completion (currently not implemented for streaming)."""
|
284
|
+
# For now, return single response as generator
|
285
|
+
response = self.complete(prompt, formatted, **kwargs)
|
286
|
+
yield response
|
287
|
+
|
288
|
+
def stream_chat(self, messages: List[ChatMessage], **kwargs) -> ChatResponseGen:
|
289
|
+
"""Stream chat (currently not implemented for streaming)."""
|
290
|
+
# For now, return single response as generator
|
291
|
+
response = self.chat(messages, **kwargs)
|
292
|
+
yield response
|
293
|
+
|
294
|
+
async def acomplete(self, prompt: str, formatted: bool = False, **kwargs) -> CompletionResponse:
|
295
|
+
"""Async complete."""
|
296
|
+
loop = asyncio.get_event_loop()
|
297
|
+
return await loop.run_in_executor(
|
298
|
+
self._executor,
|
299
|
+
lambda: self.complete(prompt, formatted, **kwargs)
|
300
|
+
)
|
301
|
+
|
302
|
+
async def achat(self, messages: List[ChatMessage], **kwargs) -> ChatResponse:
|
303
|
+
"""Async chat."""
|
304
|
+
loop = asyncio.get_event_loop()
|
305
|
+
return await loop.run_in_executor(
|
306
|
+
self._executor,
|
307
|
+
lambda: self.chat(messages, **kwargs)
|
308
|
+
)
|
309
|
+
|
310
|
+
async def astream_complete(self, prompt: str, formatted: bool = False, **kwargs) -> AsyncIterator[CompletionResponse]:
|
311
|
+
"""Async stream complete."""
|
312
|
+
response = await self.acomplete(prompt, formatted, **kwargs)
|
313
|
+
yield response
|
314
|
+
|
315
|
+
async def astream_chat(self, messages: List[ChatMessage], **kwargs) -> AsyncIterator[ChatResponse]:
|
316
|
+
"""Async stream chat."""
|
317
|
+
response = await self.achat(messages, **kwargs)
|
318
|
+
yield response
|
319
|
+
|
320
|
+
|
321
|
+
class EuriaiLlamaIndexEmbedding(BaseEmbedding):
|
322
|
+
"""
|
323
|
+
Enhanced LlamaIndex Embedding implementation using Euri API.
|
324
|
+
|
325
|
+
This implementation provides full LlamaIndex compatibility with:
|
326
|
+
- Batch embedding support
|
327
|
+
- Async operations
|
328
|
+
- Error handling and retries
|
329
|
+
- Usage tracking
|
330
|
+
- Configurable chunk size
|
331
|
+
|
332
|
+
Example:
|
333
|
+
embedding = EuriaiLlamaIndexEmbedding(
|
334
|
+
api_key="your_api_key",
|
335
|
+
model="text-embedding-3-small",
|
336
|
+
batch_size=100
|
337
|
+
)
|
338
|
+
|
339
|
+
# Single embedding
|
340
|
+
embedding_vec = embedding.get_text_embedding("Hello world")
|
341
|
+
|
342
|
+
# Batch embeddings
|
343
|
+
embeddings = embedding.get_text_embeddings([
|
344
|
+
"Document 1",
|
345
|
+
"Document 2",
|
346
|
+
"Document 3"
|
347
|
+
])
|
348
|
+
|
349
|
+
# Query embedding
|
350
|
+
query_embedding = embedding.get_query_embedding("search query")
|
351
|
+
|
352
|
+
# Async
|
353
|
+
embedding_vec = await embedding.aget_text_embedding("Hello world")
|
354
|
+
"""
|
355
|
+
|
356
|
+
# Configuration
|
357
|
+
api_key: str = Field(description="Euri API key")
|
358
|
+
model: str = Field(default="text-embedding-3-small", description="Embedding model name")
|
359
|
+
batch_size: int = Field(default=100, gt=0, description="Batch size for processing")
|
360
|
+
max_retries: int = Field(default=3, ge=0, description="Maximum number of retries")
|
361
|
+
|
362
|
+
# Internal
|
363
|
+
_client: Optional[EuriaiEmbeddingClient] = None
|
364
|
+
_executor: Optional[ThreadPoolExecutor] = None
|
365
|
+
|
366
|
+
def __init__(self, **kwargs):
|
367
|
+
if not LLAMAINDEX_AVAILABLE:
|
368
|
+
raise ImportError(
|
369
|
+
"LlamaIndex is not installed. Please install with: "
|
370
|
+
"pip install llama-index-core"
|
371
|
+
)
|
372
|
+
|
373
|
+
super().__init__(**kwargs)
|
374
|
+
|
375
|
+
# Initialize client
|
376
|
+
self._client = EuriaiEmbeddingClient(
|
377
|
+
api_key=self.api_key,
|
378
|
+
model=self.model
|
379
|
+
)
|
380
|
+
|
381
|
+
# Initialize thread pool for async operations
|
382
|
+
self._executor = ThreadPoolExecutor(max_workers=4)
|
383
|
+
|
384
|
+
def get_text_embedding(self, text: str) -> List[float]:
|
385
|
+
"""Get embedding for a single text."""
|
386
|
+
try:
|
387
|
+
embedding = self._client.embed(text)
|
388
|
+
return embedding.tolist()
|
389
|
+
except Exception as e:
|
390
|
+
logging.error(f"Error getting text embedding: {e}")
|
391
|
+
raise
|
392
|
+
|
393
|
+
def get_text_embeddings(self, texts: List[str]) -> List[List[float]]:
|
394
|
+
"""Get embeddings for multiple texts."""
|
395
|
+
if not texts:
|
396
|
+
return []
|
397
|
+
|
398
|
+
try:
|
399
|
+
# Process in batches
|
400
|
+
all_embeddings = []
|
401
|
+
for i in range(0, len(texts), self.batch_size):
|
402
|
+
batch = texts[i:i + self.batch_size]
|
403
|
+
batch_embeddings = self._client.embed_batch(batch)
|
404
|
+
all_embeddings.extend([emb.tolist() for emb in batch_embeddings])
|
405
|
+
|
406
|
+
return all_embeddings
|
407
|
+
except Exception as e:
|
408
|
+
logging.error(f"Error getting text embeddings: {e}")
|
409
|
+
raise
|
410
|
+
|
411
|
+
def get_query_embedding(self, query: str) -> List[float]:
|
412
|
+
"""Get embedding for a query."""
|
413
|
+
return self.get_text_embedding(query)
|
414
|
+
|
415
|
+
async def aget_text_embedding(self, text: str) -> List[float]:
|
416
|
+
"""Async get text embedding."""
|
417
|
+
loop = asyncio.get_event_loop()
|
418
|
+
return await loop.run_in_executor(
|
419
|
+
self._executor,
|
420
|
+
self.get_text_embedding,
|
421
|
+
text
|
422
|
+
)
|
423
|
+
|
424
|
+
async def aget_text_embeddings(self, texts: List[str]) -> List[List[float]]:
|
425
|
+
"""Async get text embeddings."""
|
426
|
+
loop = asyncio.get_event_loop()
|
427
|
+
return await loop.run_in_executor(
|
428
|
+
self._executor,
|
429
|
+
self.get_text_embeddings,
|
430
|
+
texts
|
431
|
+
)
|
432
|
+
|
433
|
+
async def aget_query_embedding(self, query: str) -> List[float]:
|
434
|
+
"""Async get query embedding."""
|
435
|
+
return await self.aget_text_embedding(query)
|
436
|
+
|
437
|
+
|
438
|
+
class EuriaiLlamaIndex:
|
439
|
+
"""
|
440
|
+
Enhanced LlamaIndex integration that uses Euri API for both LLM and embedding operations.
|
441
|
+
|
442
|
+
This provides a complete LlamaIndex workflow with:
|
443
|
+
- Document ingestion and indexing
|
444
|
+
- Query processing and retrieval
|
445
|
+
- Multi-model support through single API key
|
446
|
+
- Async operations
|
447
|
+
- Advanced query options
|
448
|
+
|
449
|
+
Example:
|
450
|
+
llama_index = EuriaiLlamaIndex(
|
451
|
+
api_key="your_api_key",
|
452
|
+
llm_model="gpt-4.1-nano",
|
453
|
+
embedding_model="text-embedding-3-small"
|
454
|
+
)
|
455
|
+
|
456
|
+
# Add documents
|
457
|
+
llama_index.add_documents([
|
458
|
+
"The sky is blue.",
|
459
|
+
"Water is wet.",
|
460
|
+
"Fire is hot."
|
461
|
+
])
|
462
|
+
|
463
|
+
# Build index
|
464
|
+
llama_index.build_index()
|
465
|
+
|
466
|
+
# Query
|
467
|
+
response = llama_index.query("What color is the sky?")
|
468
|
+
print(response.response)
|
469
|
+
|
470
|
+
# Async query
|
471
|
+
response = await llama_index.aquery("What is water like?")
|
472
|
+
print(response.response)
|
473
|
+
"""
|
474
|
+
|
475
|
+
def __init__(
|
476
|
+
self,
|
477
|
+
api_key: str,
|
478
|
+
llm_model: str = "gpt-4.1-nano",
|
479
|
+
embedding_model: str = "text-embedding-3-small",
|
480
|
+
llm_temperature: float = 0.7,
|
481
|
+
llm_max_tokens: int = 1000,
|
482
|
+
embedding_batch_size: int = 100,
|
483
|
+
context_window: int = 8000,
|
484
|
+
verbose: bool = True
|
485
|
+
):
|
486
|
+
"""
|
487
|
+
Initialize the EuriaiLlamaIndex integration.
|
488
|
+
|
489
|
+
Args:
|
490
|
+
api_key: Euri API key
|
491
|
+
llm_model: LLM model name
|
492
|
+
embedding_model: Embedding model name
|
493
|
+
llm_temperature: LLM temperature
|
494
|
+
llm_max_tokens: LLM max tokens
|
495
|
+
embedding_batch_size: Embedding batch size
|
496
|
+
context_window: Context window size
|
497
|
+
verbose: Enable verbose logging
|
498
|
+
"""
|
499
|
+
if not LLAMAINDEX_AVAILABLE:
|
500
|
+
raise ImportError(
|
501
|
+
"LlamaIndex is not installed. Please install with: "
|
502
|
+
"pip install llama-index-core"
|
503
|
+
)
|
504
|
+
|
505
|
+
self.api_key = api_key
|
506
|
+
self.llm_model = llm_model
|
507
|
+
self.embedding_model = embedding_model
|
508
|
+
self.verbose = verbose
|
509
|
+
|
510
|
+
# Initialize LLM
|
511
|
+
self.llm = EuriaiLlamaIndexLLM(
|
512
|
+
api_key=api_key,
|
513
|
+
model=llm_model,
|
514
|
+
temperature=llm_temperature,
|
515
|
+
max_tokens=llm_max_tokens,
|
516
|
+
context_window=context_window
|
517
|
+
)
|
518
|
+
|
519
|
+
# Initialize embedding model
|
520
|
+
self.embedding = EuriaiLlamaIndexEmbedding(
|
521
|
+
api_key=api_key,
|
522
|
+
model=embedding_model,
|
523
|
+
batch_size=embedding_batch_size
|
524
|
+
)
|
525
|
+
|
526
|
+
# Configure Settings (LlamaIndex global settings)
|
527
|
+
Settings.llm = self.llm
|
528
|
+
Settings.embed_model = self.embedding
|
529
|
+
|
530
|
+
# Initialize storage
|
531
|
+
self.documents: List[Document] = []
|
532
|
+
self.index: Optional[VectorStoreIndex] = None
|
533
|
+
self.query_engine = None
|
534
|
+
|
535
|
+
# Usage tracking
|
536
|
+
self.usage_stats = {
|
537
|
+
"total_queries": 0,
|
538
|
+
"total_documents_indexed": 0,
|
539
|
+
"total_embeddings_generated": 0,
|
540
|
+
"total_llm_calls": 0
|
541
|
+
}
|
542
|
+
|
543
|
+
def add_documents(self, docs: List[Union[str, Dict[str, Any], Document]]) -> None:
|
544
|
+
"""
|
545
|
+
Add documents to the index.
|
546
|
+
|
547
|
+
Args:
|
548
|
+
docs: List of documents (strings, dicts, or Document objects)
|
549
|
+
"""
|
550
|
+
for doc in docs:
|
551
|
+
if isinstance(doc, str):
|
552
|
+
self.documents.append(Document(text=doc))
|
553
|
+
elif isinstance(doc, dict):
|
554
|
+
text = doc.get("text", doc.get("content", ""))
|
555
|
+
metadata = {k: v for k, v in doc.items() if k not in ["text", "content"]}
|
556
|
+
self.documents.append(Document(text=text, metadata=metadata))
|
557
|
+
elif isinstance(doc, Document):
|
558
|
+
self.documents.append(doc)
|
559
|
+
else:
|
560
|
+
raise ValueError(f"Invalid document type: {type(doc)}")
|
561
|
+
|
562
|
+
self.usage_stats["total_documents_indexed"] += len(docs)
|
563
|
+
|
564
|
+
if self.verbose:
|
565
|
+
print(f"Added {len(docs)} documents. Total documents: {len(self.documents)}")
|
566
|
+
|
567
|
+
def add_document(self, doc: Union[str, Dict[str, Any], Document]) -> None:
|
568
|
+
"""Add a single document."""
|
569
|
+
self.add_documents([doc])
|
570
|
+
|
571
|
+
def build_index(self, **kwargs) -> VectorStoreIndex:
|
572
|
+
"""
|
573
|
+
Build the vector index from documents.
|
574
|
+
|
575
|
+
Args:
|
576
|
+
**kwargs: Additional arguments for VectorStoreIndex.from_documents
|
577
|
+
"""
|
578
|
+
if not self.documents:
|
579
|
+
raise ValueError("No documents added. Please add documents before building index.")
|
580
|
+
|
581
|
+
try:
|
582
|
+
self.index = VectorStoreIndex.from_documents(
|
583
|
+
self.documents,
|
584
|
+
**kwargs
|
585
|
+
)
|
586
|
+
|
587
|
+
# Create query engine
|
588
|
+
self.query_engine = self.index.as_query_engine()
|
589
|
+
|
590
|
+
# Update usage stats
|
591
|
+
self.usage_stats["total_embeddings_generated"] += len(self.documents)
|
592
|
+
|
593
|
+
if self.verbose:
|
594
|
+
print(f"Built index with {len(self.documents)} documents")
|
595
|
+
|
596
|
+
return self.index
|
597
|
+
|
598
|
+
except Exception as e:
|
599
|
+
logging.error(f"Error building index: {e}")
|
600
|
+
raise
|
601
|
+
|
602
|
+
def query(self, query: str, **kwargs) -> Any:
|
603
|
+
"""
|
604
|
+
Query the index.
|
605
|
+
|
606
|
+
Args:
|
607
|
+
query: Query string
|
608
|
+
**kwargs: Additional arguments for query engine
|
609
|
+
"""
|
610
|
+
if self.index is None:
|
611
|
+
if self.verbose:
|
612
|
+
print("Index not built. Building index automatically...")
|
613
|
+
self.build_index()
|
614
|
+
|
615
|
+
try:
|
616
|
+
response = self.query_engine.query(query, **kwargs)
|
617
|
+
|
618
|
+
# Update usage stats
|
619
|
+
self.usage_stats["total_queries"] += 1
|
620
|
+
self.usage_stats["total_llm_calls"] += 1
|
621
|
+
|
622
|
+
if self.verbose:
|
623
|
+
print(f"Query: {query}")
|
624
|
+
print(f"Response: {response.response}")
|
625
|
+
|
626
|
+
return response
|
627
|
+
|
628
|
+
except Exception as e:
|
629
|
+
logging.error(f"Error querying index: {e}")
|
630
|
+
raise
|
631
|
+
|
632
|
+
async def aquery(self, query: str, **kwargs) -> Any:
|
633
|
+
"""
|
634
|
+
Async query the index.
|
635
|
+
|
636
|
+
Args:
|
637
|
+
query: Query string
|
638
|
+
**kwargs: Additional arguments for query engine
|
639
|
+
"""
|
640
|
+
if self.index is None:
|
641
|
+
if self.verbose:
|
642
|
+
print("Index not built. Building index automatically...")
|
643
|
+
self.build_index()
|
644
|
+
|
645
|
+
try:
|
646
|
+
# Create async query engine
|
647
|
+
async_query_engine = self.index.as_query_engine()
|
648
|
+
|
649
|
+
# Run query in executor
|
650
|
+
loop = asyncio.get_event_loop()
|
651
|
+
response = await loop.run_in_executor(
|
652
|
+
None,
|
653
|
+
lambda: async_query_engine.query(query, **kwargs)
|
654
|
+
)
|
655
|
+
|
656
|
+
# Update usage stats
|
657
|
+
self.usage_stats["total_queries"] += 1
|
658
|
+
self.usage_stats["total_llm_calls"] += 1
|
659
|
+
|
660
|
+
if self.verbose:
|
661
|
+
print(f"Async Query: {query}")
|
662
|
+
print(f"Response: {response.response}")
|
663
|
+
|
664
|
+
return response
|
665
|
+
|
666
|
+
except Exception as e:
|
667
|
+
logging.error(f"Error in async query: {e}")
|
668
|
+
raise
|
669
|
+
|
670
|
+
def get_index(self) -> Optional[VectorStoreIndex]:
|
671
|
+
"""Get the current index."""
|
672
|
+
return self.index
|
673
|
+
|
674
|
+
def get_documents(self) -> List[Document]:
|
675
|
+
"""Get all documents."""
|
676
|
+
return self.documents
|
677
|
+
|
678
|
+
def get_usage_stats(self) -> Dict[str, int]:
|
679
|
+
"""Get usage statistics."""
|
680
|
+
return self.usage_stats.copy()
|
681
|
+
|
682
|
+
def reset(self) -> None:
|
683
|
+
"""Reset documents and index."""
|
684
|
+
self.documents = []
|
685
|
+
self.index = None
|
686
|
+
self.query_engine = None
|
687
|
+
self.usage_stats = {
|
688
|
+
"total_queries": 0,
|
689
|
+
"total_documents_indexed": 0,
|
690
|
+
"total_embeddings_generated": 0,
|
691
|
+
"total_llm_calls": 0
|
692
|
+
}
|
693
|
+
|
694
|
+
if self.verbose:
|
695
|
+
print("Reset completed")
|
696
|
+
|
697
|
+
def update_model(self, llm_model: Optional[str] = None, embedding_model: Optional[str] = None) -> None:
|
698
|
+
"""
|
699
|
+
Update the models used.
|
700
|
+
|
701
|
+
Args:
|
702
|
+
llm_model: New LLM model name
|
703
|
+
embedding_model: New embedding model name
|
704
|
+
"""
|
705
|
+
if llm_model:
|
706
|
+
self.llm_model = llm_model
|
707
|
+
self.llm = EuriaiLlamaIndexLLM(
|
708
|
+
api_key=self.api_key,
|
709
|
+
model=llm_model,
|
710
|
+
temperature=self.llm.temperature,
|
711
|
+
max_tokens=self.llm.max_tokens,
|
712
|
+
context_window=self.llm.context_window
|
713
|
+
)
|
714
|
+
Settings.llm = self.llm
|
715
|
+
|
716
|
+
if self.verbose:
|
717
|
+
print(f"Updated LLM model to: {llm_model}")
|
718
|
+
|
719
|
+
if embedding_model:
|
720
|
+
self.embedding_model = embedding_model
|
721
|
+
self.embedding = EuriaiLlamaIndexEmbedding(
|
722
|
+
api_key=self.api_key,
|
723
|
+
model=embedding_model,
|
724
|
+
batch_size=self.embedding.batch_size
|
725
|
+
)
|
726
|
+
Settings.embed_model = self.embedding
|
727
|
+
|
728
|
+
# Need to rebuild index if embedding model changed
|
729
|
+
if self.index is not None:
|
730
|
+
if self.verbose:
|
731
|
+
print("Rebuilding index due to embedding model change...")
|
732
|
+
self.build_index()
|
733
|
+
|
734
|
+
if self.verbose:
|
735
|
+
print(f"Updated embedding model to: {embedding_model}")
|
736
|
+
|
737
|
+
|
738
|
+
def create_llama_index(
|
739
|
+
api_key: str,
|
740
|
+
llm_model: str = "gpt-4.1-nano",
|
741
|
+
embedding_model: str = "text-embedding-3-small",
|
742
|
+
**kwargs
|
743
|
+
) -> EuriaiLlamaIndex:
|
744
|
+
"""
|
745
|
+
Create a LlamaIndex integration with default settings.
|
746
|
+
|
747
|
+
Args:
|
748
|
+
api_key: Euri API key
|
749
|
+
llm_model: LLM model name
|
750
|
+
embedding_model: Embedding model name
|
751
|
+
**kwargs: Additional arguments for EuriaiLlamaIndex
|
752
|
+
"""
|
753
|
+
return EuriaiLlamaIndex(
|
754
|
+
api_key=api_key,
|
755
|
+
llm_model=llm_model,
|
756
|
+
embedding_model=embedding_model,
|
757
|
+
**kwargs
|
758
|
+
)
|
759
|
+
|
760
|
+
|
761
|
+
def create_llm(
|
762
|
+
api_key: str,
|
763
|
+
model: str = "gpt-4.1-nano",
|
764
|
+
**kwargs
|
765
|
+
) -> EuriaiLlamaIndexLLM:
|
766
|
+
"""
|
767
|
+
Create a LlamaIndex LLM with default settings.
|
768
|
+
|
769
|
+
Args:
|
770
|
+
api_key: Euri API key
|
771
|
+
model: Model name
|
772
|
+
**kwargs: Additional arguments for EuriaiLlamaIndexLLM
|
773
|
+
"""
|
774
|
+
return EuriaiLlamaIndexLLM(
|
775
|
+
api_key=api_key,
|
776
|
+
model=model,
|
777
|
+
**kwargs
|
778
|
+
)
|
779
|
+
|
780
|
+
|
781
|
+
def create_embedding(
|
782
|
+
api_key: str,
|
783
|
+
model: str = "text-embedding-3-small",
|
784
|
+
**kwargs
|
785
|
+
) -> EuriaiLlamaIndexEmbedding:
|
786
|
+
"""
|
787
|
+
Create a LlamaIndex embedding model with default settings.
|
788
|
+
|
789
|
+
Args:
|
790
|
+
api_key: Euri API key
|
791
|
+
model: Model name
|
792
|
+
**kwargs: Additional arguments for EuriaiLlamaIndexEmbedding
|
793
|
+
"""
|
794
|
+
return EuriaiLlamaIndexEmbedding(
|
795
|
+
api_key=api_key,
|
796
|
+
model=model,
|
797
|
+
**kwargs
|
798
|
+
)
|