File size: 11,804 Bytes
8f577ad
 
 
 
 
 
 
 
 
 
3fabb74
3c2faf0
 
2611e90
8f577ad
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0152a60
8f577ad
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
de501d4
 
310bb10
de501d4
 
d0a369d
 
310bb10
 
d0a369d
 
 
 
 
 
28eae7b
 
3c2faf0
 
 
 
8f577ad
 
 
 
310bb10
b45dd8f
8f577ad
0152a60
 
 
 
8f577ad
 
0152a60
8f577ad
 
 
 
 
 
 
 
 
 
 
0152a60
8f577ad
3c2faf0
 
 
 
 
 
 
 
8f577ad
3c2faf0
 
 
de501d4
3c2faf0
 
 
 
8f577ad
 
498881e
de501d4
310bb10
 
 
 
 
 
 
8f577ad
310bb10
2611e90
310bb10
 
 
 
 
 
 
 
 
 
 
 
 
2611e90
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
04fbc84
2611e90
04fbc84
 
2611e90
 
 
8f577ad
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12c6dfc
8f577ad
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0152a60
 
 
 
 
 
 
498881e
 
 
f7831d4
0152a60
 
8f577ad
 
0152a60
8f577ad
0152a60
 
 
f7831d4
0152a60
 
 
498881e
0152a60
 
8f577ad
 
 
 
 
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
import gradio as gr
import os
from git import Repo
from datetime import datetime
from typing import List
from pydantic import BaseModel
import instructor
from litellm import completion
import json
import getpass
import shutil
import tempfile
import subprocess
import stat

# Initialize instructor client
client = instructor.from_litellm(completion)

class Phrases(BaseModel):
    """Response structure for the assistant"""
    phrases: List[str]

def process_questions(llm_model: str, openai_api_Key: str, deploy_key: str, metrics_prompt: str, dimensions_prompt: str, questions: str, customer_name: str, progress=gr.Progress()):
    # Check which fields are missing
    missing_fields = []
    if not llm_model: missing_fields.append("OpenAI Model")
    if not openai_api_Key: missing_fields.append("OpenAI API Key")
    if not deploy_key: missing_fields.append("GitHub Deploy Key")
    if not customer_name: missing_fields.append("Customer Name")
    if not metrics_prompt: missing_fields.append("Metrics Prompt")
    if not dimensions_prompt: missing_fields.append("Dimensions Prompt")
    if not questions: missing_fields.append("Questions")
    
    if missing_fields:
        return f"Error: The following fields are mandatory and missing: {', '.join(missing_fields)}", None
    
    # Set API key for LiteLLM
    os.environ["OPENAI_API_KEY"] = openai_api_Key
    
    # remove new lines from metrics and dimensions prompts
    metrics_prompt = metrics_prompt.replace('\n', ' ')
    dimensions_prompt = dimensions_prompt.replace('\n', ' ')
    
    intents = {
        'metrics': {
            'prompt': metrics_prompt,
        },
        'dimensions': {
            'prompt': dimensions_prompt,
        }
    }
    
    # Split questions into list
    question_list = [q.strip() for q in questions.split('\n') if q.strip()]
    
    results = {'metrics_prompt': metrics_prompt, 'dimensions_prompt': dimensions_prompt, 'results': []}
    results['llm_model'] = llm_model
    results['username'] = getpass.getuser()
    error_messages = []
    
    for idx, question in progress.tqdm(enumerate(question_list), desc="Processing questions", total=len(question_list)):
        result = {'question': question}
        try:
            for intent in intents:
                # Make API call using LiteLLM with Instructor
                response = client.chat.completions.create(
                    model=llm_model,
                    messages=[
                        {"role": "user", "content": intents[intent]['prompt'].format(question=question)}
                    ],
                    response_model=Phrases
                )
                # Store results
                result[intent] = response.phrases
                if idx == 0:
                    results[f'{intent}_prompt'] = intents[intent]['prompt']
            results['results'].append(result)
        except Exception as e:
            error_msg = f'Error processing question {question}: {str(e)}'
            print(error_msg)
            error_messages.append(error_msg)
            results['results'].append({
                "question": question,
                "error": str(e),
            })
    
    # Change working directory to current file's location
    script_dir = os.path.dirname(os.path.abspath(__file__))
    os.chdir(script_dir)
    
    # Save to file
    customer_name = customer_name.replace(' ', '_')
    output_dir = os.path.join(script_dir, "atomic_concepts_gradio")
    os.makedirs(output_dir, exist_ok=True)  # Create directory if it doesn't exist
    filename = f"{datetime.now().strftime('%Y%m%d_%H%M%S')}_{customer_name}.json"
    filepath = os.path.join(output_dir, filename)
    print(f'saving output to {filepath}')
    with open(filepath, 'w') as f:
        json.dump(results, f, indent=4)
    
    try:
        # Set up SSH key before git operations
        try:
            # Save the key file and get its path
            key_path = setup_ssh_key(deploy_key)
            
            # Explicitly set the SSH command with the deploy key file path and strict host key checking disabled
            ssh_command = f'ssh -i {key_path} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null'
            os.environ['GIT_SSH_COMMAND'] = ssh_command
            
            # Test SSH connection with strict host key checking disabled
            result = subprocess.run(
                ['ssh', '-T', '-i', key_path, '-o', 'StrictHostKeyChecking=no', '-o', 'UserKnownHostsFile=/dev/null', 'git@github.com'], 
                capture_output=True, 
                text=True
            )
            if result.returncode != 1:  # GitHub's SSH test always returns 1 when successful
                raise Exception(f"SSH test failed with return code {result.returncode}\nOutput: {result.stderr}")
        except Exception as e:
            raise Exception(f"SSH connection test failed: {str(e)}")

        # Rest of the git operations
        repo_url = "git@github.com:spotonix-inc/eval_results.git"
        temp_dir = tempfile.mkdtemp()
        
        try:
            # Clone using the SSH command with the key file
            repo = Repo.clone_from(repo_url, temp_dir, env={"GIT_SSH_COMMAND": ssh_command})
            
            # Copy the new file to the cloned repo under atomic_concepts_gradio directory
            repo_subdir = os.path.join(temp_dir, "atomic_concepts_gradio")
            os.makedirs(repo_subdir, exist_ok=True)
            shutil.copy2(filepath, os.path.join(repo_subdir, filename))
            
            # Add, commit and push from temporary directory
            repo.index.add([os.path.join("atomic_concepts_gradio", filename)])
            repo.index.commit(f"Add evaluation results: {filename}")
            origin = repo.remote('origin')
            push_info = origin.push()
            
            # Verify push was successful
            if push_info[0].flags & push_info[0].ERROR:
                raise Exception("Failed to push to repository")
            
            # Construct GitHub web URL from SSH URL
            github_web_url = repo_url.replace("git@github.com:", "https://github.com/")
            github_web_url = github_web_url.replace(".git", "")
            file_url = f"{github_web_url}/blob/main/atomic_concepts_gradio/{filename}"
            
            final_message = (
                f"Successfully processed {len(question_list)} questions.\n"
                f"Results saved to {filename} and pushed to repository.\n"
                f"View results at: {file_url}"
            )
            if error_messages:
                final_message += "\n\nErrors encountered:\n" + "\n".join(error_messages)
            return final_message, filepath
        
        except Exception as e:
            raise Exception(f"Git operation failed: {str(e)}")
        finally:
            # Clean up
            try:
                shutil.rmtree(temp_dir)
            except:
                pass
        
    except Exception as e:
        error_message = f"Error pushing to repository: {str(e)}\nResults were saved locally to: {filepath}"
        return error_message, filepath
    finally:
        # Clean up the key file
        try:
            if 'key_path' in locals():
                os.remove(key_path)
        except:
            pass

def setup_ssh_key(ssh_key_file):
    try:
        # Check if a file was uploaded
        if ssh_key_file is None:
            raise ValueError("No SSH key file was uploaded")
            
        # Save the key to the current working directory
        key_path = os.path.join(os.getcwd(), 'github_deploy_key')
        shutil.copy2(ssh_key_file.name, key_path)
        
        # Set correct permissions (600)
        os.chmod(key_path, stat.S_IRUSR | stat.S_IWUSR)
        
        return key_path
    except Exception as e:
        raise ValueError(f"Invalid SSH key: {str(e)}")

def handle_ssh_key(ssh_key):
    try:
        key_path = setup_ssh_key(ssh_key)
        # Test the SSH connection
        result = subprocess.run(['ssh', '-T', 'git@github.com'], 
                              capture_output=True, 
                              text=True)
        return f"SSH key setup complete. Key saved to {key_path}"
    except Exception as e:
        return f"Error setting up SSH key: {str(e)}"

def validate_ssh_key(key_content):
    # Check if it looks like a private key
    if not key_content.startswith('-----BEGIN'):
        raise ValueError("Invalid key format: Key must start with '-----BEGIN'")
    
    if not key_content.strip().endswith('-----END') or not key_content.strip().endswith('-----END OPENSSH PRIVATE KEY-----'):
        raise ValueError("Incomplete key: Key must end with '-----END' or '-----END OPENSSH PRIVATE KEY-----'")
    
    return True

# Create Gradio interface
with gr.Blocks(css="footer {visibility: hidden}") as iface:
    gr.Markdown("# Question Evaluation Tool")
    gr.Markdown("Enter your API keys and questions to evaluate. Questions should be separated by newlines.")
    
    with gr.Row():
        with gr.Column(scale=1):
            model = gr.Textbox(
                label="OpenAI Model",
                value="gpt-4o",
                info="Required",
                elem_id="model"
            )
            api_key = gr.Textbox(
                label="OpenAI API Key",
                type="password",
                info="Required",
                elem_id="api_key"
            )
            deploy_key = gr.File(
                label="GitHub Deploy Key",
                elem_id="deploy_key"
            )
            customer_name = gr.Textbox(
                label="Customer Name",
                info="Required",
                elem_id="customer_name"
            )
            metrics_prompt = gr.Textbox(
                label="Metrics Prompt",
                lines=10,
                max_lines=10,
                info="Required",
                elem_id="metrics_prompt"
            )
            dimensions_prompt = gr.Textbox(
                label="Dimensions Prompt",
                lines=10,
                max_lines=10,
                info="Required",
                elem_id="dimensions_prompt"
            )
        
        with gr.Column(scale=1):
            questions = gr.Textbox(label="Questions (one per line)", lines=10, max_lines=10, info="Required")
            result = gr.Textbox(label="Result", lines=10, max_lines=10)
            file_output = gr.State()  # Add this to store the filepath
            
            with gr.Row():
                process_btn = gr.Button("Process")
                download_btn = gr.Button("Download Results", interactive=False)
            
            def enable_download(message, filepath):
                # Only enable download if we have a valid filepath
                if filepath is None:
                    return message, None, gr.Button(value="Download Results", interactive=False)
                return message, filepath, gr.Button(value="Download Results", interactive=True)
            
            process_btn.click(
                fn=process_questions,
                inputs=[model, api_key, deploy_key, metrics_prompt, dimensions_prompt, questions, customer_name],
                outputs=[result, file_output],
                show_progress=True
            ).then(
                fn=enable_download,
                inputs=[result, file_output],
                outputs=[result, file_output, download_btn]
            )
            
            download_btn.click(
                fn=lambda filepath: gr.File(value=filepath, label="Download JSON") if filepath else None,
                inputs=[file_output],
                outputs=gr.File(label="Download JSON")
            )


if __name__ == "__main__":
    iface.launch(share=True)