interactiveai 0.0.1__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.
@@ -0,0 +1,16 @@
1
+ """
2
+ Interactive Language Processing Package
3
+
4
+ A comprehensive package for managing Langfuse utilities including initialization,
5
+ observation, scoring, dataset management, and parallel processing.
6
+ """
7
+
8
+ from .interactive import Interactive, CallbackHandler
9
+
10
+ __version__ = "1.0.0"
11
+ __author__ = "Your Name"
12
+ __email__ = "your.email@example.com"
13
+
14
+ __all__ = ["Interactive", "CallbackHandler"]
15
+
16
+
@@ -0,0 +1,471 @@
1
+ import time
2
+ from typing import Dict, Any, List, Optional, Callable, Union
3
+ from concurrent.futures import ThreadPoolExecutor, as_completed
4
+ from langfuse import Langfuse
5
+ from langchain_core.prompts import ChatPromptTemplate
6
+ from langfuse.langchain import CallbackHandler
7
+ from loguru import logger
8
+ from schemas import Evaluator
9
+
10
+
11
+
12
+ class Interactive(Langfuse):
13
+ """
14
+ A comprehensive manager class for InteractiveAI utilities including initialization,
15
+ observation, scoring, dataset management, and parallel processing.
16
+ """
17
+
18
+ def __init__(self,
19
+ public_key: str,
20
+ secret_key: str,
21
+ host: Optional[str] = "https://app.interactiveai.com",
22
+ **kwargs):
23
+ """
24
+ Initialize the InteractiveAI Client
25
+
26
+ To get your keys go to https://app.interactiveai.com/settings/api-keys
27
+
28
+ Args:
29
+ public_key: InteractiveAI public key
30
+ secret_key: InteractiveAI secret key
31
+ host: InteractiveAI host (defaults to https://app.interactiveai.com)
32
+ """
33
+ # Initialize Langfuse client
34
+ super().__init__(
35
+ public_key=public_key,
36
+ secret_key=secret_key,
37
+ host=host,
38
+ **kwargs
39
+ )
40
+
41
+ def generate_thread_id(self) -> str:
42
+ """Generate a unique thread ID"""
43
+ thread_id = f"thread_{int(time.time() * 1000)}"
44
+ logger.debug(f"Generated thread ID: {thread_id}")
45
+ return thread_id
46
+
47
+ def get_prompt(self, name: str, version: Optional[int] = None, label: Optional[str] = None) -> ChatPromptTemplate:
48
+ prompt = self.get_prompt(name=name, version=version, label=label)
49
+ return ChatPromptTemplate.from_template(
50
+ prompt.get_langchain_prompt(),
51
+ metadata={"langfuse_prompt": prompt},
52
+ )
53
+
54
+
55
+ def create_or_get_dataset(self, dataset_name: str, description: str = "") -> Any:
56
+ """
57
+ Create a dataset or get existing one
58
+
59
+ Args:
60
+ dataset_name: Name of the dataset
61
+ description: Dataset description
62
+
63
+ Returns:
64
+ Dataset object
65
+ """
66
+ try:
67
+ logger.debug(f"Attempting to retrieve dataset: {dataset_name}")
68
+ dataset = self.get_dataset(dataset_name)
69
+ logger.info(f"Retrieved existing dataset: {dataset_name}")
70
+ return dataset
71
+ except Exception as e:
72
+ logger.info(f"Dataset '{dataset_name}' not found, creating new one")
73
+ try:
74
+ dataset = self.create_dataset(
75
+ name=dataset_name,
76
+ description=description or "Dataset for response evaluation",
77
+ )
78
+ logger.success(f"Successfully created dataset: {dataset_name}")
79
+ return dataset
80
+ except Exception as create_error:
81
+ logger.error(f"Error creating dataset '{dataset_name}': {create_error}")
82
+ raise
83
+
84
+ def add_dataset_item(self, dataset_name: str, item_id: str, input_data: Dict[str, Any],
85
+ expected_output: Dict[str, Any], metadata: Dict[str, Any]):
86
+ """
87
+ Add an item to a dataset
88
+
89
+ Args:
90
+ dataset_name: Name of the dataset
91
+ item_id: Unique identifier for the item
92
+ input_data: Input data
93
+ expected_output: Expected output
94
+ metadata: Additional metadata
95
+ """
96
+ try:
97
+ logger.debug(f"Adding item to dataset '{dataset_name}'")
98
+ self.create_dataset_item(
99
+ dataset_name=dataset_name,
100
+ input=input_data,
101
+ expected_output=expected_output,
102
+ metadata=metadata,
103
+ id=item_id
104
+ )
105
+ logger.debug(f"Successfully added item to dataset {dataset_name}")
106
+ except Exception as e:
107
+ logger.error(f"Error adding dataset item to '{dataset_name}': {e}")
108
+ raise
109
+
110
+ def add_dataset_items_parallel(self, dataset_name: str, data: List[tuple[str, Any]], max_workers: int = 4):
111
+ """
112
+ Add multiple items to a dataset in parallel
113
+
114
+ Args:
115
+ dataset_name: Name of the dataset
116
+ data: List of tuples containing (item_id, input_data, expected_output, metadata)
117
+ max_workers: Maximum number of parallel workers (default: 4)
118
+ """
119
+ try:
120
+ logger.debug(f"Adding {len(data)} items to dataset '{dataset_name}' in parallel")
121
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
122
+ futures = []
123
+ for item_id, input_data, expected_output, metadata in data:
124
+ futures.append(
125
+ executor.submit(
126
+ self.add_dataset_item,
127
+ dataset_name,
128
+ item_id,
129
+ input_data,
130
+ expected_output,
131
+ metadata
132
+ )
133
+ )
134
+ for future in as_completed(futures):
135
+ future.result() # Wait for all tasks to complete
136
+ logger.success(f"Successfully added {len(data)} items to dataset '{dataset_name}'")
137
+ except Exception as e:
138
+ logger.error(f"Error adding dataset items to '{dataset_name}': {e}")
139
+ raise
140
+
141
+ def process_single_item(
142
+ self,
143
+ idx: int,
144
+ item: Any,
145
+ experiment_name: str,
146
+ handler: CallbackHandler,
147
+ workflow: Any,
148
+ evaluations: List[Evaluator]
149
+ ) -> Dict[str, Union[str, Dict[str, Any]]]:
150
+ """Process a single dataset item through the workflow and evaluate results.
151
+
152
+ Args:
153
+ idx: Index of the item in the dataset
154
+ item: Dataset item containing input and expected output
155
+ experiment_name: Name of the experiment for tracking
156
+ handler: Langfuse callback handler for logging
157
+ workflow: The workflow/model to process the item
158
+ evaluations: List of evaluation functions to apply
159
+
160
+ Returns:
161
+ Dictionary containing processing status and results or error information
162
+ """
163
+ logger.debug(f"Processing item {idx} for experiment '{experiment_name}'")
164
+
165
+ try:
166
+ # Create a separate trace for each item using the item.run() context manager
167
+ with item.run(run_name=experiment_name) as root_span:
168
+ logger.debug(f"Invoking workflow for item {idx}")
169
+
170
+ # Process the item through the workflow
171
+ output = workflow.invoke(
172
+ state=item.input,
173
+ config={
174
+ "callbacks": [handler],
175
+ "configurable": {"thread_id": idx} # Use idx as thread_id for uniqueness
176
+ }
177
+ )
178
+
179
+ # Update the trace with input/output information
180
+ root_span.update_trace(
181
+ name=f"{experiment_name}_{idx}",
182
+ input=item.input,
183
+ output=output,
184
+ tags=[experiment_name],
185
+ metadata=item.metadata
186
+ )
187
+
188
+ logger.debug(f"Running {len(evaluations)} evaluations for item {idx}")
189
+
190
+ # Apply all evaluation functions to the output
191
+ for evaluation in evaluations:
192
+ try:
193
+ evaluation_result = evaluation.function(output, item.expected_output)
194
+ root_span.score_trace(
195
+ name=evaluation.name,
196
+ value=evaluation_result.score,
197
+ data_type=evaluation.data_type,
198
+ comment=evaluation_result.reasoning
199
+ )
200
+ logger.debug(
201
+ f"Evaluation '{evaluation.name}' completed for item {idx}: {evaluation_result.score}")
202
+ except Exception as eval_error:
203
+ logger.warning(f"Evaluation '{evaluation.name}' failed for item {idx}: {eval_error}")
204
+ # Continue with other evaluations even if one fails
205
+
206
+ logger.debug(f"Successfully processed item {idx}")
207
+ return {"status": "success", "output": output}
208
+
209
+ except Exception as e:
210
+ logger.error(f"Error processing item {idx}: {str(e)}")
211
+ return {"status": "error", "error": str(e)}
212
+
213
+ def run_langchain_experiment_multi(
214
+ self,
215
+ experiment_name: str,
216
+ dataset: Any,
217
+ workflow: Any,
218
+ evaluations: List[Evaluator],
219
+ max_workers: int = 4,
220
+ start_index: int = 0,
221
+ end_index: Optional[int] = None,
222
+ process_single_item_func: Optional[Callable] = None
223
+ ) -> List[Dict[str, Union[str, Dict[str, Any]]]]:
224
+ """Run an experiment with parallel processing using ThreadPool.
225
+
226
+ This function processes multiple dataset items in parallel, applies evaluations,
227
+ and tracks results using Langfuse for monitoring and analysis.
228
+
229
+ Args:
230
+ experiment_name: Name of the experiment for tracking and logging
231
+ dataset: Dataset containing items to process
232
+ workflow: The workflow/model to process items
233
+ evaluations: List of evaluation functions to apply to results
234
+ max_workers: Maximum number of parallel workers (default: 4)
235
+ start_index: Index to start processing from (default: 0)
236
+ end_index: Index to stop processing at (default: None for all items)
237
+ process_single_item_func: Optional custom processing function (default: None)
238
+
239
+ Returns:
240
+ List of result dictionaries containing status and output/error information
241
+
242
+ Raises:
243
+ ValueError: If start_index >= end_index or if indices are out of bounds
244
+ """
245
+ # Set end_index to dataset length if not provided
246
+ if end_index is None:
247
+ end_index = len(dataset.items)
248
+
249
+ # Validate indices
250
+ if start_index >= end_index:
251
+ raise ValueError(f"start_index ({start_index}) must be less than end_index ({end_index})")
252
+
253
+ if start_index < 0 or end_index > len(dataset.items):
254
+ raise ValueError(f"Indices out of bounds: dataset has {len(dataset.items)} items")
255
+
256
+ # Use provided processing function or default
257
+ if process_single_item_func:
258
+ processing_func = process_single_item_func
259
+ else:
260
+ processing_func = self.process_single_item
261
+ logger.warning(f" Using default processing function: {self.process_single_item.__name__}")
262
+
263
+ # Calculate actual number of items to process
264
+ items_to_process = end_index - start_index
265
+
266
+ logger.info(f"Starting experiment '{experiment_name}'")
267
+ logger.info(f"Processing {items_to_process} items (indices {start_index} to {end_index - 1})")
268
+ logger.info(f"Using {max_workers} parallel workers")
269
+ logger.info(f"Applying {len(evaluations)} evaluation metrics")
270
+
271
+ # Initialize Langfuse handler for tracking
272
+ handler = CallbackHandler()
273
+
274
+ # Process items in parallel using ThreadPoolExecutor
275
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
276
+ logger.debug(f"Creating thread pool with {max_workers} workers")
277
+
278
+ # Prepare arguments for each item to process
279
+ process_args = [
280
+ (idx, item, experiment_name, handler, workflow, evaluations)
281
+ for idx, item in enumerate(dataset.items[start_index:end_index], start=start_index)
282
+ ]
283
+
284
+ # Submit all tasks to the executor
285
+ future_to_args = {
286
+ executor.submit(processing_func, *args): args
287
+ for args in process_args
288
+ }
289
+
290
+ logger.info(f"Submitted {len(future_to_args)} tasks to thread pool")
291
+
292
+ # Collect results as they complete
293
+ results: List[Dict[str, Union[str, Dict[str, Any]]]] = []
294
+ completed_count = 0
295
+
296
+ for future in as_completed(future_to_args):
297
+ args = future_to_args[future]
298
+ item_idx = args[0] # First argument is the index
299
+
300
+ try:
301
+ result = future.result()
302
+ results.append(result)
303
+ completed_count += 1
304
+
305
+ # Log progress with appropriate level based on result
306
+ if result["status"] == "success":
307
+ logger.success(f"Item {item_idx} processed successfully ({completed_count}/{items_to_process})")
308
+ else:
309
+ logger.warning(
310
+ f"Item {item_idx} failed ({completed_count}/{items_to_process}): {result.get('error', 'Unknown error')}")
311
+
312
+ except Exception as e:
313
+ logger.error(
314
+ f"Exception processing item {item_idx} ({completed_count + 1}/{items_to_process}): {str(e)}")
315
+ results.append({"status": "exception", "error": str(e)})
316
+ completed_count += 1
317
+
318
+ # Calculate and log summary statistics
319
+ successful = sum(1 for r in results if r["status"] == "success")
320
+ failed = len(results) - successful
321
+ success_rate = (successful / len(results)) * 100 if results else 0
322
+
323
+ logger.info(f" Experiment '{experiment_name}' completed!")
324
+ logger.info(f" Results Summary:")
325
+ logger.info(f" • Total items processed: {len(results)}")
326
+ logger.info(f" • Successful: {successful}")
327
+ logger.info(f" • Failed: {failed}")
328
+ logger.info(f" • Success rate: {success_rate:.1f}%")
329
+
330
+ if failed > 0:
331
+ logger.warning(f"{failed} items failed processing - check logs for details")
332
+
333
+ return results
334
+
335
+ def run_langchain_experiment_sequential(
336
+ self,
337
+ experiment_name: str,
338
+ dataset: Any,
339
+ workflow: Any,
340
+ evaluations: List[Evaluator],
341
+ start_index: int = 0,
342
+ end_index: Optional[int] = None,
343
+ process_single_item_func: Optional[Callable] = None
344
+ ) -> List[Dict[str, Union[str, Dict[str, Any]]]]:
345
+ """Run an experiment with sequential processing (no parallel processing).
346
+
347
+ This function processes multiple dataset items sequentially, applies evaluations,
348
+ and tracks results using Langfuse for monitoring and analysis.
349
+
350
+ Args:
351
+ experiment_name: Name of the experiment for tracking and logging
352
+ dataset: Dataset containing items to process
353
+ workflow: The workflow/model to process items
354
+ evaluations: List of evaluation functions to apply to results
355
+ start_index: Index to start processing from (default: 0)
356
+ end_index: Index to stop processing at (default: None for all items)
357
+ process_single_item_func: Optional custom processing function (default: None)
358
+
359
+ Returns:
360
+ List of result dictionaries containing status and output/error information
361
+
362
+ Raises:
363
+ ValueError: If start_index >= end_index or if indices are out of bounds
364
+ """
365
+ # Set end_index to dataset length if not provided
366
+ if end_index is None:
367
+ end_index = len(dataset.items)
368
+
369
+ # Validate indices
370
+ if start_index >= end_index:
371
+ raise ValueError(f"start_index ({start_index}) must be less than end_index ({end_index})")
372
+
373
+ if start_index < 0 or end_index > len(dataset.items):
374
+ raise ValueError(f"Indices out of bounds: dataset has {len(dataset.items)} items")
375
+
376
+ # Use provided processing function or default
377
+ if process_single_item_func:
378
+ processing_func = process_single_item_func
379
+ else:
380
+ processing_func = self.process_single_item
381
+ logger.warning(f" Using default processing function: {self.process_single_item.__name__}")
382
+
383
+ # Calculate actual number of items to process
384
+ items_to_process = end_index - start_index
385
+
386
+ logger.info(f"Starting experiment '{experiment_name}'")
387
+ logger.info(f"Processing {items_to_process} items (indices {start_index} to {end_index - 1})")
388
+ logger.info(f"Processing sequentially (max_workers parameter ignored)")
389
+ logger.info(f"Applying {len(evaluations)} evaluation metrics")
390
+
391
+ # Initialize Langfuse handler for tracking
392
+ handler = CallbackHandler()
393
+
394
+ # Process items sequentially
395
+ results: List[Dict[str, Union[str, Dict[str, Any]]]] = []
396
+ completed_count = 0
397
+
398
+ for idx, item in enumerate(dataset.items[start_index:end_index], start=start_index):
399
+ logger.debug(f"Processing item {idx} sequentially")
400
+
401
+ try:
402
+ result = processing_func(idx, item, experiment_name, handler, workflow, evaluations)
403
+ results.append(result)
404
+ completed_count += 1
405
+
406
+ # Log progress with appropriate level based on result
407
+ if result["status"] == "success":
408
+ logger.success(f"Item {idx} processed successfully ({completed_count}/{items_to_process})")
409
+ else:
410
+ logger.warning(
411
+ f"Item {idx} failed ({completed_count}/{items_to_process}): {result.get('error', 'Unknown error')}")
412
+
413
+ except Exception as e:
414
+ logger.error(
415
+ f"Exception processing item {idx} ({completed_count + 1}/{items_to_process}): {str(e)}")
416
+ results.append({"status": "exception", "error": str(e)})
417
+ completed_count += 1
418
+
419
+ # Calculate and log summary statistics
420
+ successful = sum(1 for r in results if r["status"] == "success")
421
+ failed = len(results) - successful
422
+ success_rate = (successful / len(results)) * 100 if results else 0
423
+
424
+ logger.info(f" Experiment '{experiment_name}' completed!")
425
+ logger.info(f" Results Summary:")
426
+ logger.info(f" • Total items processed: {len(results)}")
427
+ logger.info(f" • Successful: {successful}")
428
+ logger.info(f" • Failed: {failed}")
429
+ logger.info(f" • Success rate: {success_rate:.1f}%")
430
+
431
+ if failed > 0:
432
+ logger.warning(f"{failed} items failed processing - check logs for details")
433
+
434
+ return results
435
+
436
+
437
+ def flush(self):
438
+ """Flush any pending Langfuse operations"""
439
+ try:
440
+ logger.debug("Flushing Langfuse operations...")
441
+ self.flush()
442
+ logger.success("Langfuse operations flushed successfully")
443
+ except Exception as e:
444
+ logger.error(f"Error flushing Langfuse operations: {e}")
445
+
446
+ def __enter__(self):
447
+ """Context manager entry"""
448
+ return self
449
+
450
+ def __exit__(self, exc_type, exc_val, exc_tb):
451
+ """Context manager exit - flush operations"""
452
+ if exc_type:
453
+ logger.error(f"Exiting context manager due to exception: {exc_type.__name__}: {exc_val}")
454
+ self.flush()
455
+
456
+
457
+
458
+
459
+
460
+
461
+
462
+
463
+
464
+
465
+
466
+
467
+
468
+
469
+
470
+
471
+
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.4
2
+ Name: interactiveai
3
+ Version: 0.0.1
4
+ Summary: InteractiveAI client package
5
+ Author: InteractiveAI
6
+ License: MIT License
7
+ Project-URL: Homepage, https://interactive.ai
8
+ Project-URL: App, https://app.interactive.ai
9
+ Project-URL: Repository, https://github.com/Interactive-AI-Labs/interactiveai-sdk
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.8
13
+ Description-Content-Type: text/markdown
14
+
15
+ # interactive-sdk
16
+ InteractiveAI platform SDK
@@ -0,0 +1,6 @@
1
+ interactiveai/__init__.py,sha256=0c4BSy0x0awPyM1uE0dIPVxikx-zOsIOaEB9C5HIDCQ,386
2
+ interactiveai/interactive.py,sha256=ouiqdM-fKscyIUbfr2qkZYT5XvD-kNkZQpPKRXstqcM,19274
3
+ interactiveai-0.0.1.dist-info/METADATA,sha256=QrRGF41Ofq1KzEh1KVIS7EDQBF_oCsreye-0859TuFU,515
4
+ interactiveai-0.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
5
+ interactiveai-0.0.1.dist-info/top_level.txt,sha256=GkGPBUGTyN4pdleSXMxvlfNHKNsQZpn6YBabTx-2sao,14
6
+ interactiveai-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ interactiveai