-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.py
More file actions
905 lines (759 loc) · 38.6 KB
/
Copy pathapp.py
File metadata and controls
905 lines (759 loc) · 38.6 KB
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
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
from flask import Flask, request, render_template, redirect, url_for, flash, session, jsonify
import os
import tempfile
import shutil
import json
import time
import pandas as pd
from werkzeug.utils import secure_filename
from dotenv import load_dotenv
import markdown
# Matplotlib backend ayarını en başta yap
import matplotlib
matplotlib.use('Agg')
from flask_session import Session
from src.core.state import RunState
from src.core.run_io import make_run_dir
from src.agent.agent import AnalyticsAgent
from src.agent.plan_renderer import plan_to_markdown
from src.tools.load_data import tool_load_data
# .env'yi yükle
load_dotenv()
app = Flask(__name__)
app.secret_key = 'your_secret_key_here_change_this_in_production'
# Session config
app.config['SESSION_TYPE'] = 'filesystem'
app.config['SESSION_FILE_DIR'] = os.path.join(os.getcwd(), 'flask_sessions')
Session(app)
# Yüklenen dosyalar için klasör
UPLOAD_FOLDER = 'uploads'
if not os.path.exists(UPLOAD_FOLDER):
os.makedirs(UPLOAD_FOLDER)
# Static figures klasörü
STATIC_FIGURES = os.path.join('static', 'figures')
if not os.path.exists(STATIC_FIGURES):
os.makedirs(STATIC_FIGURES)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024 # 100MB limit
ALLOWED_EXTENSIONS = {'csv', 'xlsx'}
def allowed_file(filename):
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
def add_log(message):
"""Terminal loglarına mesaj ekle"""
if 'terminal_logs' not in session:
session['terminal_logs'] = []
session['terminal_logs'].append(message)
print(f"LOG: {message}")
def make_llm():
"""
.env içindeki LLM_PROVIDER'a göre LLM seçer.
Default: ollama (çünkü gemini quota, openai kredi 0)
"""
provider = os.getenv("LLM_PROVIDER", "ollama").lower()
if provider == "ollama":
from src.llm.ollama_client import OllamaClient
model = os.getenv("OLLAMA_MODEL", "mistral")
base_url = os.getenv("OLLAMA_BASE_URL", "http://localhost:11434")
return OllamaClient(model=model, base_url=base_url)
if provider == "gemini":
from src.llm.gemini_client import GeminiClient
return GeminiClient()
if provider == "openai":
from src.llm.openai_client import OpenAIClient
return OpenAIClient()
raise ValueError(
f"Unknown LLM_PROVIDER: {provider}. Use 'ollama', 'gemini' or 'openai'."
)
class CallbackCalledException(Exception):
pass
def ask_callback_web(question):
"""Web için ask callback - exception throw eder"""
session['pending_question'] = question
session['waiting_for_answer'] = True
raise CallbackCalledException("Question asked")
def choose_callback_web(candidates):
"""Web için choose callback - exception throw eder"""
session['pending_choice'] = candidates
session['waiting_for_choice'] = True
raise CallbackCalledException("Choice asked")
@app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'POST':
# AJAX request mi kontrol et
if request.is_json:
return handle_ajax_request()
# Dosya kontrolü
if 'file' not in request.files:
return jsonify({'error': 'Dosya seçilmedi'}), 400
file = request.files['file']
if file.filename == '':
return jsonify({'error': 'Dosya seçilmedi'}), 400
if file and allowed_file(file.filename):
# ÖNEMLİ: Yeni analiz başlarken eski session'ı tamamen temizle
session.clear()
filename = secure_filename(file.filename)
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(filepath)
# Intent al
intent = request.form.get('intent', 'analyze')
print(f"DEBUG: Received intent from form: {intent}")
add_log(f"Analiz türü seçildi: {intent}")
# State oluştur
state = RunState(input_path=filepath, intent=intent)
state.run_dir = make_run_dir()
# Agent oluştur (web callbacks ile)
agent = AnalyticsAgent(llm=make_llm(), ask_callback=ask_callback_web, choose_callback=choose_callback_web)
# Session'a state'i sakla
session['state'] = state.to_dict()
session['agent_step'] = 'plan' # İlk adım: plan oluştur
session['terminal_logs'] = [] # Terminal logları için
session['plan_approved'] = False # Plan henüz onaylanmadı
session['start_time'] = time.time() # Çalışma süresini takip et
# İlk adımı çalıştır ve response dön
return handle_analysis_ajax()
else:
return jsonify({'error': 'Geçersiz dosya türü. Sadece CSV ve XLSX dosyaları kabul edilir.'}), 400
# GET request için normal template render
return render_template('index.html')
@app.route('/action', methods=['POST'])
def action():
"""AJAX action handler"""
print("DEBUG: /action route called")
if not request.is_json:
print("DEBUG: Request is not JSON")
return jsonify({'error': 'JSON request expected'}), 400
return handle_ajax_action()
def handle_ajax_request():
"""AJAX request handler for initial POST"""
print("DEBUG: handle_ajax_request called")
return handle_analysis_ajax()
def handle_ajax_action():
"""AJAX action isteklerini yönetir"""
print("DEBUG: handle_ajax_action called")
data = request.get_json()
print(f"DEBUG: Received data: {data}")
action = data.get('action')
print(f"DEBUG: Action: {action}")
# plan_approval action'ını answer_question olarak işle
if action == 'plan_approval':
action = 'answer_question'
if action == 'answer_question':
print("DEBUG: Answering question")
answer = data.get('answer', '')
print(f"DEBUG: Answer: {answer}")
session['waiting_for_answer'] = False
session['last_answer'] = answer
# Plan onayı sorusuysa plan_approved işaretle
if session.get('pending_question') and 'Planı uygulayayım mı' in session.get('pending_question', ''):
if answer:
print("DEBUG: Plan approval question answered yes")
session['plan_approved'] = True
session['current_step_index'] = 0
else:
print("DEBUG: Plan rejected by user")
add_log("❌ Analiz planı kullanıcı tarafından reddedildi")
# Session'ı temizle ve ana sayfaya yönlendirme için flag ekle
terminal_logs = session.get('terminal_logs', [])
session.clear()
flash('Analiz planı reddedildi. İsterseniz yeni bir analiz başlatabilirsiniz.', 'info')
return jsonify({
'redirect': url_for('index'),
'message': 'Analiz iptal edildi',
'terminal_logs': terminal_logs
})
session['pending_question'] = ''
# Agent'i devam ettir
return handle_analysis_ajax()
elif action == 'select_choice' or action == 'submit_choice':
print("DEBUG: Selecting choice")
choice = data.get('choice', '')
print(f"DEBUG: Choice: {choice}")
session['waiting_for_choice'] = False
session['last_choice'] = choice
session['pending_choice'] = []
# Agent'i devam ettir
return handle_analysis_ajax()
print("DEBUG: handle_ajax_action returning current state")
return get_current_state_json()
def run_next_step(state):
"""Bir sonraki adımı çalıştırır"""
print("DEBUG: run_next_step called")
# State'te df yoksa tekrar load et
if state.df is None:
print("DEBUG: DF not in state, reloading data")
state = tool_load_data(state)
step_index = session.get('current_step_index', 0)
steps = state.plan.get("steps", [])
print(f"DEBUG: step_index={step_index}, total_steps={len(steps)}")
if step_index < len(steps):
current_step = steps[step_index]
tool = current_step.get("tool")
params = current_step.get("params", {}) or {}
print(f"DEBUG: Executing step {step_index}: tool={tool}, params={params}")
# Tool çalıştır
add_log(f"Çalıştırılan adım: {tool}")
from src.agent.agent import TOOLS
if tool in TOOLS:
print(f"DEBUG: Running tool {tool}")
state = TOOLS[tool](state, **params)
print(f"DEBUG: Tool {tool} completed")
add_log(f"Adım tamamlandı: {tool}")
else:
print(f"DEBUG: Tool {tool} not found in TOOLS")
add_log(f"Uyarı: {tool} aracı bulunamadı")
# Tool çalıştırıldıktan sonra state kontrol et - soru sorulmuş olabilir
print("DEBUG: Checking for questions after tool execution")
session['state'] = state.to_dict()
if session.get('waiting_for_answer') or session.get('waiting_for_choice'):
print("DEBUG: Question found, stopping here")
return
print(f"DEBUG: No questions, moving to next step")
session['current_step_index'] = step_index + 1
session['state'] = state.to_dict()
# Recursive olarak bir sonraki adımı çalıştır
run_next_step(state)
else:
print("DEBUG: All steps completed")
# Tüm adımlar bitti, rapor oluştur
add_log("Rapor oluşturuluyor...")
from src.tools.report import tool_report
state = tool_report(state)
add_log("Rapor tamamlandı")
session['agent_step'] = 'finished'
session['state'] = state.to_dict()
def handle_analysis_ajax():
"""Terminal versiyonu gibi çalışır"""
print("DEBUG: handle_analysis_ajax called")
if 'state' not in session:
return jsonify({'error': 'No session'}), 400
state_dict = session['state']
state = RunState.from_dict(state_dict)
# Plan zaten oluşturulmuş ve onaylanmış mı kontrol et
plan_approved = session.get('plan_approved', False)
print(f"DEBUG: plan_approved={plan_approved}, state.plan={state.plan is not None}")
if plan_approved and state.plan:
# Plan onaylandı, adımları çalıştır
print("DEBUG: Plan already approved, executing steps")
add_log("Plan onaylandı, adımlar çalıştırılıyor...")
# DataFrame session'da saklanmıyor, tekrar yükle
if state.df is None:
print("DEBUG: DataFrame is None, reloading data")
add_log("Veri yükleniyor...")
state = tool_load_data(state)
session['state'] = state.to_dict()
from src.agent.agent import TOOLS
steps = state.plan.get("steps", [])
current_step = session.get('current_step_index', 0)
print(f"DEBUG: Executing step {current_step} of {len(steps)}")
while current_step < len(steps):
# Her adımda DataFrame'in yüklendiğinden emin ol
if state.df is None:
print("DEBUG: DataFrame became None, reloading")
state = tool_load_data(state)
step = steps[current_step]
tool = step.get("tool")
params = step.get("params", {}) or {}
# Modeling için özel işlem: target_col boşsa kullanıcıya sor
if tool == "modeling":
target_col = params.get("target_col", "").strip()
# Daha kapsamlı placeholder kontrolü (agent.py'deki gibi)
tc_l = target_col.lower()
is_placeholder = (
not target_col
or target_col.startswith("(")
or "user specified" in tc_l
or "the user specified" in tc_l
or "specified target" in tc_l
or target_col in ["", "()", "...", "tbd", "none", "null", "<specified_target_column>"]
)
# target_col boş veya placeholder ise
if is_placeholder:
# Önce choice yanıtı alındı mı kontrol et
if 'last_choice' in session:
chosen = session['last_choice']
if chosen:
params["target_col"] = chosen
state.target_col = chosen
del session['last_choice']
else:
# Kullanıcı iptal etti, modeling atla
add_log("⊘ Modeling atlandı (hedef sütun seçilmedi)")
state.warnings.append("Modeling atlandı: hedef sütun seçilmedi")
current_step += 1
session['current_step_index'] = current_step
session['state'] = state.to_dict()
continue
else:
# Kullanıcıya hedef sütun seçtir
add_log("❓ Modeling için hedef sütun seçilmesi gerekiyor...")
# Tüm sütunları aday olarak göster
candidates = list(state.df.columns)
# Callback ile kullanıcıdan seçim iste
try:
choose_callback_web(candidates)
except CallbackCalledException:
pass # Expected, kullanıcıya seçim gösteriliyor
session['state'] = state.to_dict()
session['current_step_index'] = current_step
return get_current_state_json()
# task_type yoksa veya geçersizse otomatik tespit et
task_type_param = params.get("task_type")
# Check for override: If LLM said "classification" but data looks like regression (ordinal/numeric with >2 values)
# Wine quality example: 3-9 values (7 unique) -> should be regression, not classification
if params.get("target_col"):
target_col_name = params.get("target_col")
if target_col_name and target_col_name in state.df.columns:
y = state.df[target_col_name].dropna()
try:
# If numeric
if pd.api.types.is_numeric_dtype(y):
nunique = int(y.nunique())
# Heuristic: If numeric and >2 unique values, prefer regression (unless explicitly binary)
# 3 unique values could be borderline, but usually 1-5, 1-10 rankings are better as regression
if nunique > 2:
if task_type_param == "classification":
add_log(f"ℹ️ Plan 'classification' önerdi ama '{target_col_name}' numeric ve {nunique} farklı değer içeriyor. Regression'a çevriliyor.")
params["task_type"] = "regression"
task_type_param = "regression"
except Exception as e:
pass
if not task_type_param or task_type_param not in ["classification", "regression"]:
default_tt = "classification"
try:
# Hedef sütuna bak
target_col_name = params.get("target_col")
if target_col_name and target_col_name in state.df.columns:
y = state.df[target_col_name].dropna()
nunique = int(y.nunique())
total = len(y)
unique_ratio = nunique / total if total > 0 else 0
# Otomatik tespit kuralları:
# - Numeric ve 10+ unique value → regression (wine quality gibi)
# - Numeric ve unique_ratio > 0.05 → regression (çok fazla farklı değer)
# - Numeric ve 3-9 unique → regression (ordinal değerler)
# - 2 unique veya categorical → classification
if pd.api.types.is_numeric_dtype(y):
if nunique >= 10 or unique_ratio > 0.05:
default_tt = "regression"
add_log(f"ℹ️ '{target_col_name}': {nunique} farklı değer ({unique_ratio*100:.1f}% benzersiz) → regression")
elif nunique >= 3:
# 3-9 arası: ordinal olabilir, regression daha mantıklı
default_tt = "regression"
add_log(f"ℹ️ '{target_col_name}': {nunique} sıralı değer → regression (ordinal)")
else:
# 2 veya daha az: binary classification
add_log(f"ℹ️ '{target_col_name}': {nunique} sınıf → classification")
else:
# Kategorik
add_log(f"ℹ️ '{target_col_name}': kategorik, {nunique} sınıf → classification")
except Exception as e:
add_log(f"⚠️ task_type otomatik tespit edilemedi: {e}")
params["task_type"] = default_tt
# LLM bazen metadata parametreleri ekliyor, bunları temizle
params.pop("conditional", None)
params.pop("why", None)
params.pop("visualization", None) # EDA için geçersiz parametre
print(f"DEBUG: Running tool {tool} with params {params}")
add_log(f"Adım {current_step + 1}/{len(steps)}: {tool} çalıştırılıyor...")
if tool in TOOLS:
try:
state = TOOLS[tool](state, **params)
add_log(f"✓ {tool} tamamlandı")
except CallbackCalledException:
# Tool içinde soru soruldu, kaydet ve döndür
print(f"DEBUG: Tool {tool} asked a question")
session['state'] = state.to_dict()
session['current_step_index'] = current_step
return get_current_state_json()
except Exception as e:
print(f"DEBUG: Tool {tool} failed: {e}")
add_log(f"✗ {tool} hatası: {str(e)}")
state.warnings.append(f"{tool} hatası: {str(e)}")
else:
add_log(f"✗ Bilinmeyen araç: {tool}")
state.warnings.append(f"Bilinmeyen araç: {tool}")
current_step += 1
session['current_step_index'] = current_step
session['state'] = state.to_dict()
# Tüm adımlar tamamlandı
print("DEBUG: All steps completed")
add_log("Tüm adımlar tamamlandı!")
session['agent_step'] = 'finished'
# Çalışma süresini hesapla
if 'start_time' in session:
execution_time = time.time() - session['start_time']
state.execution_time = execution_time
print(f"DEBUG: Execution time: {execution_time:.2f} seconds")
session['state'] = state.to_dict()
# Rapor oluştur
from src.tools.report import tool_report
state = tool_report(state)
session['state'] = state.to_dict()
# Sonuçları göster
report_path = os.path.join(state.run_dir, 'report.md')
if os.path.exists(report_path):
with open(report_path, 'r', encoding='utf-8') as f:
report_content = f.read()
# Figures'ları kopyala
figures_dir = os.path.join(state.run_dir, 'figures')
figures = []
if os.path.exists(figures_dir):
for fig_file in os.listdir(figures_dir):
if fig_file.endswith(('.png', '.jpg', '.jpeg', '.svg')):
shutil.copy2(
os.path.join(figures_dir, fig_file),
os.path.join(STATIC_FIGURES, fig_file)
)
figures.append(fig_file)
# Çalışma süresini log'a ekle
if hasattr(state, 'execution_time') and state.execution_time:
minutes = int(state.execution_time // 60)
seconds = state.execution_time % 60
if minutes > 0:
add_log(f"⏱️ Toplam çalışma süresi: {minutes} dakika {seconds:.2f} saniye")
else:
add_log(f"⏱️ Toplam çalışma süresi: {seconds:.2f} saniye")
return jsonify({
'report': markdown.markdown(report_content),
'figures': figures,
'terminal_logs': session.get('terminal_logs', [])
})
return get_current_state_json()
# Plan henüz oluşturulmadı, agent.run() çalıştır
agent = AnalyticsAgent(llm=make_llm(), ask_callback=ask_callback_web, choose_callback=choose_callback_web)
try:
# Agent.run() çalıştır - callback çağrılırsa exception throw eder
state = agent.run(state)
# Tamamlandı
session['agent_step'] = 'finished'
# Çalışma süresini hesapla (agent.run zaten hesaplıyor ama session'dan da ekleyelim)
if 'start_time' in session and not state.execution_time:
state.execution_time = time.time() - session['start_time']
session['state'] = state.to_dict()
# Sonuçları göster
report_path = os.path.join(state.run_dir, 'report.md')
if os.path.exists(report_path):
with open(report_path, 'r', encoding='utf-8') as f:
report_content = f.read()
# Çalışma süresini log'a ekle
if hasattr(state, 'execution_time') and state.execution_time:
minutes = int(state.execution_time // 60)
seconds = state.execution_time % 60
if minutes > 0:
add_log(f"⏱️ Toplam çalışma süresi: {minutes} dakika {seconds:.2f} saniye")
else:
add_log(f"⏱️ Toplam çalışma süresi: {seconds:.2f} saniye")
return jsonify({
'report': markdown.markdown(report_content),
'terminal_logs': session.get('terminal_logs', [])
})
except CallbackCalledException:
# Callback çağrıldı, soru göster
print("DEBUG: Callback called, showing question")
session['state'] = state.to_dict()
# Plan onayı sorusuysa, plan markdown'ını sakla
if session.get('waiting_for_answer') and state.plan:
session['plan_markdown'] = plan_to_markdown(state.plan)
print("DEBUG: Stored plan markdown")
return get_current_state_json()
except Exception as e:
# LLM hatası - basit plan oluştur
print(f"DEBUG: Agent run failed: {e}, creating simple plan")
add_log(f"LLM hatası: {str(e)}")
add_log("Basit plan kullanılıyor...")
# Basit plan oluştur
simple_plan = {
"plan_name": "Simple Analysis Plan",
"need_user_approval": True,
"questions": [],
"steps": [
{"tool": "profile_data", "why": "Dataset overview", "params": {}},
{"tool": "eda", "why": "Exploratory data analysis", "params": {}},
{"tool": "report", "why": "Generate analysis report", "params": {}}
]
}
state.plan = simple_plan
session['state'] = state.to_dict()
session['plan_markdown'] = plan_to_markdown(simple_plan)
# Plan onayını sor
ask_callback_web("Basit planı uygulayayım mı? (y/n): ")
session['state'] = state.to_dict()
return get_current_state_json()
return get_current_state_json()
# Adım adım ilerle
step = session.get('agent_step', 'plan')
print(f"DEBUG: Current step: {step}")
try:
if step == 'plan':
add_log("DEBUG: Starting plan creation...")
# Plan oluştur
state = tool_load_data(state)
add_log("DEBUG: Data loaded")
from src.tools.profile_data import make_dataset_brief
brief = make_dataset_brief(state)
add_log("DEBUG: Dataset brief created")
from src.agent.planner import llm_make_plan
add_log("DEBUG: Calling LLM for plan...")
try:
plan = llm_make_plan(agent.llm, dataset_brief=brief, intent=state.intent)
add_log(f"DEBUG: Plan received: {plan}")
except Exception as e:
add_log(f"DEBUG: LLM call failed: {e}")
# Fallback plan
plan = {
"plan_name": "Fallback Analysis Plan",
"need_user_approval": True,
"questions": [],
"steps": [
{"tool": "profile_data", "why": "Dataset özeti oluştur", "params": {}},
{"tool": "eda", "why": "Keşifsel veri analizi", "params": {}},
{"tool": "report", "why": "Analiz raporu oluştur", "params": {}}
]
}
add_log("DEBUG: Using fallback plan")
state.warnings.append("LLM yanıt vermediği için basit bir analiz planı kullanılıyor.")
add_log("DEBUG: Plan received from LLM")
agent._validate_plan(plan)
state.plan = plan
add_log("DEBUG: Plan validated and saved")
# Plan'da sorular varsa önce onları sor
questions = plan.get("questions", [])
if questions:
add_log(f"DEBUG: Plan has {len(questions)} questions to ask")
session['pending_questions'] = questions
session['current_question_index'] = 0
session['agent_step'] = 'ask_plan_questions'
else:
session['agent_step'] = 'plan_approval'
session['state'] = state.to_dict()
session['plan_markdown'] = plan_to_markdown(plan)
add_log("DEBUG: Plan step completed")
elif step == 'ask_plan_questions':
add_log("DEBUG: In ask_plan_questions")
questions = session.get('pending_questions', [])
question_index = session.get('current_question_index', 0)
if question_index < len(questions):
current_question = questions[question_index]
add_log(f"DEBUG: Asking question {question_index}: {current_question}")
if 'last_answer' not in session:
ask_callback_web(current_question)
session['state'] = state.to_dict()
return get_current_state_json()
return get_current_state_json()
else:
# Cevap alındı, bir sonraki soruya geç
answer = session['last_answer']
add_log(f"DEBUG: Answer received: {answer}")
# Cevabı state'e kaydet (şimdilik basitçe)
state.notes.append(f"Q{question_index}: {current_question} -> {answer}")
del session['last_answer']
session['current_question_index'] = question_index + 1
session['state'] = state.to_dict()
else:
# Tüm sorular soruldu, plan onayına geç
add_log("DEBUG: All questions asked, moving to plan approval")
del session['pending_questions']
del session['current_question_index']
session['agent_step'] = 'plan_approval'
session['state'] = state.to_dict()
elif step == 'plan_approval':
add_log("DEBUG: In plan_approval step")
if 'last_answer' in session:
add_log(f"DEBUG: User answered: {session['last_answer']}")
approved = session['last_answer']
state.approvals["plan"] = approved
if not approved:
state.notes.append("User declined plan execution.")
session['agent_step'] = 'finished'
else:
session['agent_step'] = 'execute_steps'
session['current_step_index'] = 0
del session['last_answer']
session['state'] = state.to_dict()
else:
add_log("DEBUG: Waiting for user approval")
elif step == 'execute_steps':
add_log("DEBUG: In execute_steps")
steps = state.plan.get("steps", [])
step_index = session.get('current_step_index', 0)
add_log(f"DEBUG: step_index={step_index}, total_steps={len(steps)}")
if step_index < len(steps):
current_step = steps[step_index]
tool = current_step.get("tool")
params = current_step.get("params", {}) or {}
add_log(f"DEBUG: Executing step {step_index}: tool={tool}, params={params}")
# Modeling için özel işlemler
if tool == "modeling":
# Target column kontrolü
tc_raw = str(params.get("target_col") or "").strip()
tc_l = tc_raw.lower()
is_placeholder = (
tc_raw.startswith("(")
or "user specified" in tc_l
or "the user specified" in tc_l
or "specified target" in tc_l
or tc_raw in ("...", "tbd", "none", "null")
)
if (not tc_raw) or is_placeholder:
if state.target_col:
params["target_col"] = state.target_col
else:
params["target_col"] = ""
if not params.get("target_col"):
if 'last_choice' in session:
chosen = session['last_choice']
if chosen:
params["target_col"] = chosen
state.target_col = chosen
else:
state.warnings.append("Modeling skipped: no target chosen.")
session['current_step_index'] = step_index + 1
session['state'] = state.to_dict()
return get_current_state_json()
del session['last_choice']
else:
# Target seçimi sor
from src.agent.planner import llm_suggest_targets
candidates = llm_suggest_targets(agent.llm, brief=make_dataset_brief(state))
choose_callback_web(candidates)
session['state'] = state.to_dict()
return get_current_state_json()
# Modeling onayı sor
if 'last_answer' not in session:
ask_callback_web(f"[Step {step_index+1}] Modeling çalıştırılsın mı? (y/n): ")
session['state'] = state.to_dict()
return get_current_state_json()
approved = session['last_answer']
del session['last_answer']
if not approved:
state.notes.append("User skipped modeling step.")
session['current_step_index'] = step_index + 1
session['state'] = state.to_dict()
return get_current_state_json()
# Tool çalıştır
add_log(f"Çalıştırılan adım: {tool}")
from src.agent.agent import TOOLS
if tool in TOOLS:
add_log(f"DEBUG: Running tool {tool}")
state = TOOLS[tool](state, **params)
add_log(f"DEBUG: Tool {tool} completed")
add_log(f"Adım tamamlandı: {tool}")
else:
add_log(f"DEBUG: Tool {tool} not found in TOOLS")
# Tool çalıştırıldıktan sonra state kontrol et - soru sorulmuş olabilir
add_log("DEBUG: Checking for questions after tool execution")
session['state'] = state.to_dict()
if session.get('waiting_for_answer') or session.get('waiting_for_choice'):
add_log("DEBUG: Question found, returning state")
return get_current_state_json()
add_log(f"DEBUG: No questions, moving to next step")
session['current_step_index'] = step_index + 1
session['state'] = state.to_dict()
else:
add_log("DEBUG: All steps completed")
# Tüm adımlar bitti, rapor sor
if 'last_answer' not in session:
add_log("DEBUG: Asking for report")
ask_callback_web("Rapor oluşturalım mı? (y/n): ")
session['state'] = state.to_dict()
return get_current_state_json()
if session['last_answer']:
add_log("Rapor oluşturuluyor...")
from src.tools.report import tool_report
state = tool_report(state)
add_log("Rapor tamamlandı")
del session['last_answer']
session['agent_step'] = 'finished'
session['state'] = state.to_dict()
elif step == 'finished':
# Sonuçları hazırla
report_path = os.path.join(state.run_dir, 'report.md')
with open(report_path, 'r', encoding='utf-8') as f:
report_content = f.read()
# Figures'ları static klasörüne kopyala
figures_dir = os.path.join(state.run_dir, 'figures')
figures = []
if os.path.exists(figures_dir):
for fig_file in os.listdir(figures_dir):
if fig_file.endswith(('.png', '.jpg', '.jpeg', '.svg')):
shutil.copy2(
os.path.join(figures_dir, fig_file),
os.path.join(STATIC_FIGURES, fig_file)
)
figures.append(fig_file)
# Session temizle ama logları tut
terminal_logs = session.get('terminal_logs', [])
# Çalışma süresini ekle
if hasattr(state, 'execution_time') and state.execution_time:
minutes = int(state.execution_time // 60)
seconds = state.execution_time % 60
if minutes > 0:
terminal_logs.append(f"⏱️ Toplam çalışma süresi: {minutes} dakika {seconds:.2f} saniye")
else:
terminal_logs.append(f"⏱️ Toplam çalışma süresi: {seconds:.2f} saniye")
session.clear()
session['terminal_logs'] = terminal_logs
return jsonify({
'report': markdown.markdown(report_content),
'figures': figures,
'terminal_logs': terminal_logs
})
except Exception as e:
add_log(f'Hata oluştu: {str(e)}')
return jsonify({'error': f'Hata oluştu: {str(e)}'}), 500
# Current state'i JSON olarak dön
return get_current_state_json()
def get_current_state_json():
"""Current state'i JSON olarak döner"""
print("DEBUG: get_current_state_json called")
step = session.get('agent_step', 'plan')
print(f"DEBUG: get_current_state_json step: {step}")
response_data = {
'terminal_logs': session.get('terminal_logs', [])
}
print(f"DEBUG: Terminal logs count: {len(response_data['terminal_logs'])}")
if step == 'plan_approval':
response_data['show_plan'] = True
response_data['plan'] = session.get('plan_markdown', '')
print("DEBUG: Returning plan approval state")
if session.get('waiting_for_answer'):
response_data['waiting_for_answer'] = True
response_data['question'] = session.get('pending_question', '')
print(f"DEBUG: Waiting for answer: {response_data['question']}")
if session.get('waiting_for_choice'):
response_data['waiting_for_choice'] = True
response_data['candidates'] = session.get('pending_choice', [])
print(f"DEBUG: Waiting for choice: {len(response_data['candidates'])} candidates")
if step in ['plan', 'execute_steps'] and not session.get('waiting_for_answer') and not session.get('waiting_for_choice'):
# State'i yükle
state_dict = session['state']
state = RunState.from_dict(state_dict)
processing_message = "Analiz hazırlanıyor..."
sub_message = "Lütfen bekleyin."
if step == 'plan':
processing_message = "Verileriniz yükleniyor ve analiz planı hazırlanıyor..."
sub_message = "Yapay zeka verilerinizi inceliyor."
elif step == 'execute_steps':
step_index = session.get('current_step_index', 0)
steps = state.plan.get("steps", [])
if step_index < len(steps):
current_tool = steps[step_index].get("tool", "unknown")
processing_message = f"Adım {step_index + 1}/{len(steps)}: {current_tool} çalıştırılıyor..."
sub_message = "Verileriniz işleniyor."
else:
processing_message = "Tüm adımlar tamamlandı, rapor hazırlanıyor..."
sub_message = "Sonuçlar derleniyor."
response_data['processing'] = True
response_data['processing_message'] = processing_message
response_data['sub_message'] = sub_message
print(f"DEBUG: Returning processing state: {processing_message}")
print(f"DEBUG: Final response_data keys: {list(response_data.keys())}")
return jsonify(response_data)
def add_log(message):
"""Terminal loglarını session'a ekler"""
if 'terminal_logs' not in session:
session['terminal_logs'] = []
session['terminal_logs'].append(message)
session.modified = True
if __name__ == '__main__':
app.run(debug=True)