File size: 20,002 Bytes
315ee1d
 
 
 
 
 
704b955
315ee1d
 
 
3c9b5b0
315ee1d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b2e2264
3c9b5b0
c8713eb
315ee1d
b2e2264
 
 
 
 
 
 
 
 
315ee1d
c8713eb
315ee1d
 
 
 
 
 
 
 
 
 
 
 
 
 
d5ca187
 
 
315ee1d
 
 
 
 
f5a061d
315ee1d
f5a061d
 
315ee1d
 
 
 
 
 
 
 
 
 
 
 
d5ca187
 
 
 
315ee1d
 
 
 
f5a061d
315ee1d
f5a061d
 
315ee1d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3c9b5b0
315ee1d
 
 
 
704b955
 
315ee1d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
eefd585
 
315ee1d
 
 
 
 
 
 
 
 
 
 
eefd585
b2e2264
 
f5a061d
eefd585
315ee1d
 
 
 
704b955
315ee1d
 
704b955
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
315ee1d
 
 
 
 
 
b2e2264
 
 
 
315ee1d
 
b2e2264
332bfd8
f5a061d
b2e2264
32720a3
 
 
 
 
69ac52d
32720a3
b2e2264
315ee1d
f9e33a9
79d488d
 
 
e6f851f
6393e7a
332bfd8
4707343
 
 
a8f544c
d292141
 
4707343
a8f544c
 
3a03172
 
a8f544c
9338748
79d488d
 
 
421706b
79d488d
 
a8f544c
79d488d
 
 
e6f851f
 
79d488d
82589dc
 
32720a3
332bfd8
 
 
b2e2264
1918520
b907305
b2e2264
 
 
315ee1d
 
b2e2264
315ee1d
b2e2264
 
 
315ee1d
 
 
b2e2264
315ee1d
 
 
 
b2e2264
315ee1d
f5a061d
 
 
b2e2264
f9e33a9
32720a3
 
 
 
 
 
 
 
 
b2e2264
315ee1d
f9e33a9
 
 
d292141
315ee1d
a8f544c
b2e2264
a8f544c
 
3a03172
 
a8f544c
 
b2e2264
 
332bfd8
 
 
b2e2264
a8f544c
b2e2264
 
 
 
 
 
 
 
 
 
 
1918520
b2e2264
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
315ee1d
 
 
 
 
 
 
 
 
b2e2264
 
 
f5a061d
32720a3
 
 
 
 
 
b2e2264
315ee1d
f9e33a9
 
332bfd8
315ee1d
332bfd8
 
 
 
b2e2264
315ee1d
 
a8f544c
 
 
 
 
332bfd8
a8f544c
 
 
 
332bfd8
 
b2e2264
1918520
332bfd8
b2e2264
 
 
315ee1d
 
 
 
 
a8f544c
 
 
 
 
332bfd8
a8f544c
 
 
 
332bfd8
 
b2e2264
1918520
332bfd8
b2e2264
 
 
315ee1d
b2e2264
 
315ee1d
 
 
b2e2264
 
 
315ee1d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
import random
import uuid
import datetime
import json
import os
from typing import Dict, List, Any, Tuple
import concurrent.futures  # Add this import for multithreading

# Import from danielson.py
from danielson import generate_single_component_evaluation
from elo import update_ratings

# Import from lowInferenceNotes.py
from lowInferenceNotes import lowInferenceNotes

# Import Argilla
import argilla as rg

# Global variables to store data temporarily
all_outputs = []  # Will be populated with outputs
current_cycle_note = None  # The current note being used for this cycle

# Initialize Argilla client
def init_argilla():
    """Initialize Argilla client"""
    try:
        print("[DEBUG] Initializing Argilla client")
        # Updated initialization method
        client = rg.Argilla(api_url=os.getenv("ARGILLA_API_URL", "http://localhost:6900"), 
                api_key=os.getenv("ARGILLA_API_KEY", "admin.apikey"))
        print(f"[DEBUG] Argilla client initialized with URL: {os.getenv('ARGILLA_API_URL', 'http://localhost:6900')}")
        
        # Test connection to verify client is working
        try:
            me = client.me
            print(f"[DEBUG] Connected to Argilla as user: {me.username}")
        except Exception as conn_error:
            print(f"[ERROR] Failed to connect to Argilla: {str(conn_error)}")
            raise conn_error
        
        
        # Create datasets if they don't exist
        try:
            # Dataset for outputs
            outputs_settings = rg.Settings(
                fields=[
                    rg.TextField(name="low_inference_notes"),
                    rg.TextField(name="output_summary"),
                    rg.TextField(name="component_id")
                ],
                questions=[
                    rg.LabelQuestion(name="quality", labels=["good", "fine", "bad"]),
                    rg.TextQuestion(name="explanation"),
                    rg.TextField(name="elo_rating", required=False)
                ],
                metadata=[
                    rg.TermsMetadataProperty(name="output_id"),
                    rg.TermsMetadataProperty(name="timestamp")
                ]
            )
            
            # Try to get dataset, create if it doesn't exist
            try:
                outputs_dataset = rg.Dataset.from_argilla("danielson_outputs")
            except:
                outputs_dataset = rg.Dataset(name="danielson_outputs", settings=outputs_settings)
                outputs_dataset.create()
                
            # Dataset for comparisons
            comparisons_settings = rg.Settings(
                fields=[
                    rg.TextField(name="low_inference_notes"),
                    rg.TextField(name="output_a"),
                    rg.TextField(name="output_b")
                ],
                questions=[
                    rg.LabelQuestion(name="preference", labels=["output_a", "output_b"]),
                    rg.TextQuestion(name="explanation")
                ],
                metadata=[
                    rg.TermsMetadataProperty(name="output_id_a"),
                    rg.TermsMetadataProperty(name="output_id_b"),
                    rg.TermsMetadataProperty(name="timestamp")
                ]
            )
            
            try:
                comparisons_dataset = rg.Dataset.from_argilla("danielson_comparisons")
            except:
                comparisons_dataset = rg.Dataset(name="danielson_comparisons", settings=comparisons_settings)
                comparisons_dataset.create()
                
        except Exception as e:
            print(f"Error creating Argilla datasets: {str(e)}")
            
    except Exception as e:
        print(f"Error initializing Argilla: {str(e)}")

def load_low_inference_notes():
    """Get the list of low inference notes"""
    return lowInferenceNotes

def generate_outputs(note, num_outputs=10):
    """Generate outputs using danielson.py for a specific note"""
    outputs = []
    
    # Component IDs for Danielson framework
    component_ids = ["1a", "1b", "1c", "1d", "1e", "1f",
                    "2a", "2b", "2c", "2d", "2e",
                    "3a", "3b", "3c", "3d", "3e"]
    
    # Select random component IDs
    selected_components = random.sample(component_ids, min(num_outputs, len(component_ids)))
    
    # Function to process a single component and return the result
    def process_component(component_id):
        try:
            # Generate output using danielson.py
            output = generate_single_component_evaluation(note, component_id)
            
            # Create output object with metadata
            output_id = str(uuid.uuid4())
            output_obj = {
                "id": output_id,
                "component_id": component_id,
                "low_inference_notes": note,
                "output": output,
                "timestamp": datetime.datetime.now().isoformat()
            }
            
            # Log to Argilla
            try:
                # Updated to use Record instead of FeedbackRecord
                record = rg.Record(
                    fields={
                        "low_inference_notes": note,
                        "output_summary": output["summary"],
                        "component_id": component_id
                    },
                    metadata={
                        "output_id": output_id,
                        "timestamp": datetime.datetime.now().isoformat()
                    }
                )
                
                # Add to Argilla dataset using the records.log() method
                client = rg.Argilla(api_url=os.getenv("ARGILLA_API_URL", "http://localhost:6900"), 
                api_key=os.getenv("ARGILLA_API_KEY", "admin.apikey"))
                dataset = client.datasets("danielson_outputs")
                dataset.records.log([record])
                
            except Exception as e:
                print(f"Error logging to Argilla: {str(e)}")
            
            return output_obj
        except Exception as e:
            print(f"Error generating output for component {component_id}: {str(e)}")
            return None
    
    # Use ThreadPoolExecutor to parallelize the process
    with concurrent.futures.ThreadPoolExecutor() as executor:
        # Submit all tasks and get future objects
        future_to_component = {executor.submit(process_component, component_id): component_id 
                              for component_id in selected_components}
        
        # Process results as they complete
        for future in concurrent.futures.as_completed(future_to_component):
            component_id = future_to_component[future]
            try:
                output_obj = future.result()
                if output_obj:
                    outputs.append(output_obj)
            except Exception as e:
                print(f"Exception for component {component_id}: {str(e)}")
    
    return outputs

def store_feedback(output_id, choice, explanation, stage):
    """Store user feedback in Argilla"""
    try:
        print(f"[DEBUG] Attempting to store feedback for output_id={output_id}, choice={choice}, stage={stage}")
        client = rg.Argilla(api_url=os.getenv("ARGILLA_API_URL", "http://localhost:6900"), 
                api_key=os.getenv("ARGILLA_API_KEY", "admin.apikey"))
        print(f"[DEBUG] Argilla client initialized: {client}")
        
        if stage == "individual":
            print(f"[DEBUG] Processing individual feedback for output_id={output_id}")
            # Get the dataset
            dataset = client.datasets("danielson_outputs")
            print(f"[DEBUG] Retrieved dataset: {dataset.name}")
            
            # Use rg.Filter for proper filtering
            filter_condition = rg.Filter(("metadata.output_id", "==", output_id))
            print(f"[DEBUG] Querying for records with filter: metadata.output_id == {output_id}")
            records_iterator = dataset.records(query=rg.Query(filter=filter_condition))
            print(f"[DEBUG] Records iterator: {records_iterator}")
            records_list = records_iterator.to_list(flatten=True)
            print(f"[DEBUG] Found {len(records_list)} records matching output_id={output_id}")
            
            if records_list:
                note = records_list[0]["low_inference_notes"]
                output = records_list[0]["output_summary"]
                component_id = records_list[0]["component_id"]
                id = records_list[0]["id"]

                # Create a record with responses we want to update
                print("Choice: ", choice)
                print("Explanation: ", explanation)
                print("Records list: ", records_list)
                print("ID: ", id)
                current_user = client.me
                print("Current user: ", current_user.id)
                
                # Create Response objects with proper format
                responses = [
                    rg.Response(question_name="quality", value=choice, user_id=current_user.id),
                    rg.Response(question_name="explanation", value=explanation, user_id=current_user.id)
                ]

                updated_record = rg.Record(
                    fields={
                        "low_inference_notes": note,
                        "output_summary": output,
                        "component_id": component_id
                    },
                    responses=responses,
                    metadata={
                        "output_id": output_id,
                        "timestamp": datetime.datetime.now().isoformat()
                    },
                    id=id
                )

                
                
                print(f"[DEBUG] Prepared update record: {updated_record}")
                
                # Log the update using the correct format
                try:
                    dataset.records.log([updated_record])
                    print(f"[DEBUG] Successfully updated record {output_id}")
                except Exception as update_error:
                    print(f"[ERROR] Failed to update record: {str(update_error)}")
                    raise update_error
                
                # Also update ELO rating
                print(f"[DEBUG] Updating ELO rating for output_id={output_id}")
                get_or_update_elo_rating(output_id, initial_rating=1500.0)
            else:
                print(f"[ERROR] No records found for output_id={output_id}")
                return False
        elif stage == "comparison":
            # For comparison feedback, we need to extract the IDs
            # Assuming choice format is "preferred_over_<other_id>"
            print(f"[DEBUG] Processing comparison feedback with choice: {choice}")
            parts = choice.split("preferred_over_")
            if len(parts) == 2:
                preferred_id = output_id
                other_id = parts[1]
                print(f"[DEBUG] Parsed preferred_id={preferred_id}, other_id={other_id}")
                
                # Create a comparison record
                # First, get the original records to extract the outputs
                dataset_outputs = client.datasets("danielson_outputs")
                print(f"[DEBUG] Retrieved outputs dataset for comparisons")
                
                # Use proper filtering for both records
                preferred_filter = rg.Filter(("metadata.output_id", "==", preferred_id))
                other_filter = rg.Filter(("metadata.output_id", "==", other_id))
                
                preferred_records_iterator = dataset_outputs.records(query=rg.Query(filter=preferred_filter))
                other_records_iterator = dataset_outputs.records(query=rg.Query(filter=other_filter))
                
                preferred_records_list = preferred_records_iterator.to_list(flatten=True)
                other_records_list = other_records_iterator.to_list(flatten=True)
                print(f"[DEBUG] Found {len(preferred_records_list)} preferred records and {len(other_records_list)} other records")
                
                if preferred_records_list and other_records_list:
                    preferred_record = preferred_records_list[0]
                    other_record = other_records_list[0]
                    current_user = client.me
                    
                    # Create comparison record with proper Response objects
                    try:
                        # Create Response objects with proper format
                        responses = [
                            rg.Response(question_name="preference", value="output_a" if preferred_id == output_id else "output_b", user_id=current_user.id),
                            rg.Response(question_name="explanation", value=explanation, user_id=current_user.id)
                        ]
                        
                        comparison_record = rg.Record(
                            fields={
                                "low_inference_notes": preferred_record["fields"]["low_inference_notes"],
                                "output_a": preferred_record["fields"]["output_summary"],
                                "output_b": other_record["fields"]["output_summary"]
                            },
                            responses=responses,
                            metadata={
                                "output_id_a": preferred_id,
                                "output_id_b": other_id,
                                "timestamp": datetime.datetime.now().isoformat()
                            }
                        )
                        print(f"[DEBUG] Created comparison record")
                        
                        # Add to comparisons dataset
                        comparisons_dataset = client.datasets("danielson_comparisons")
                        print(f"[DEBUG] Retrieved comparisons dataset")
                        comparisons_dataset.records.log([comparison_record])
                        print(f"[DEBUG] Successfully logged comparison record")
                        
                        # Update ELO ratings
                        print(f"[DEBUG] Updating ELO ratings for comparison")
                        a_wins = (preferred_id == output_id)
                        update_ratings(preferred_id, other_id, a_wins)
                        
                        return True
                    except Exception as e:
                        print(f"[ERROR] Failed to create or log comparison record: {str(e)}")
                        import traceback
                        print(f"[ERROR] Traceback: {traceback.format_exc()}")
                        return False
                else:
                    print(f"[ERROR] Could not find both records for comparison: preferred={len(preferred_records_list)}, other={len(other_records_list)}")
                    return False
            else:
                print(f"[ERROR] Invalid comparison choice format: {choice}")
                return False
        
        return True
    except Exception as e:
        print(f"Error storing feedback in Argilla: {str(e)}")
        return False

def get_or_update_elo_rating(output_id, initial_rating=None, new_rating=None):
    """Get or update ELO rating for an output"""
    try:
        print(f"[DEBUG] get_or_update_elo_rating called for output_id={output_id}, initial_rating={initial_rating}, new_rating={new_rating}")
        client = rg.Argilla(api_url=os.getenv("ARGILLA_API_URL", "http://localhost:6900"), 
                api_key=os.getenv("ARGILLA_API_KEY", "admin.apikey"))
        dataset = client.datasets("danielson_outputs")
        
        # Use proper filtering instead of string query
        filter_condition = rg.Filter(("metadata.output_id", "==", output_id))
        print(f"[DEBUG] Querying for records with filter: metadata.output_id == {output_id}")
        records_iterator = dataset.records(query=rg.Query(filter=filter_condition))
        records_list = records_iterator.to_list(flatten=True)
        print(f"[DEBUG] Found {len(records_list)} records for ELO update")
        
        if records_list:
            record = records_list[0]
            print(f"[DEBUG] Processing record ID: {record['id']}")
            
            # Get current rating if available
            current_rating = None
            if "responses" in record and record["responses"] and "elo_rating" in record["responses"]:
                current_rating = record["responses"]["elo_rating"]
            print(f"[DEBUG] Current ELO rating: {current_rating}")
            
            if new_rating is not None:
                # Create a Response object with proper format
                responses = [
                    rg.Response(question_name="elo_rating", value=str(new_rating), user_id=str(uuid.uuid4()))
                ]
                
                # Update rating with properly structured record
                elo_record = rg.Record(
                    id=record["id"],
                    responses=responses
                )
                
                print(f"[DEBUG] Updating ELO rating with record: {elo_record}")
                try:
                    dataset.records.log([elo_record])
                    print(f"[DEBUG] Successfully updated ELO rating for record {record['id']}")
                except Exception as update_error:
                    print(f"[ERROR] Failed to update ELO rating: {str(update_error)}")
                    raise update_error
                return new_rating
            elif current_rating is not None:
                # Return existing rating
                return float(current_rating)
            elif initial_rating is not None:
                # Create a Response object with proper format
                responses = [
                    rg.Response(question_name="elo_rating", value=str(initial_rating), user_id=str(uuid.uuid4()))
                ]
                
                # Set initial rating with properly structured record
                elo_record = rg.Record(
                    id=record["id"],
                    responses=responses
                )
                
                print(f"[DEBUG] Setting initial ELO rating with record: {elo_record}")
                try:
                    dataset.records.log([elo_record])
                    print(f"[DEBUG] Successfully set initial ELO rating for record {record['id']}")
                except Exception as update_error:
                    print(f"[ERROR] Failed to set initial ELO rating: {str(update_error)}")
                    raise update_error
                return initial_rating
        else:
            print(f"[ERROR] No records found for ELO update with output_id={output_id}")
        
        return None
    except Exception as e:
        print(f"[ERROR] Exception in get_or_update_elo_rating: {str(e)}")
        import traceback
        print(f"[ERROR] Traceback: {traceback.format_exc()}")
        return None

def initialize_cycle():
    """Initialize a new cycle of outputs with a new low inference note"""
    global all_outputs, current_cycle_note
    
    # Initialize Argilla if not already done
    init_argilla()
    
    # Select a random low inference note different from the current one if possible
    notes = load_low_inference_notes()
    
    if len(notes) > 1 and current_cycle_note is not None:
        # Filter out the current note to ensure we get a different one
        available_notes = [note for note in notes if note != current_cycle_note]
        current_cycle_note = random.choice(available_notes)
    else:
        # If only one note or first cycle, just choose randomly
        current_cycle_note = random.choice(notes)
    
    # Generate outputs for this note
    all_outputs = generate_outputs(current_cycle_note)
    
    return all_outputs