11
Text-to-SQL dan Analitik Data
Tiga berkas di repository ini menjawab pertanyaan analitik atas data industri di bursa saham dengan tiga bentuk sumber data. 15_sql.py memuat industry-leaders-full.csv ke SQLite lalu membiarkan LLM menulis SQL. 16_repl.py melewati SQL dan menyerahkan sebuah DataFrame pandas agar model mengeksekusi kode Python. workshop/ai-search-engine.py membuang CSV sama sekali dan mengambil data langsung dari API Sectors. Ketiganya dirujuk di README sebagai materi kursus "Building search engines in the age of AI". README.md:57-601
Dua jalur dari satu CSV
graph LR
Q["Pertanyaan bahasa alami"] --> C{"Butuh agregasi SQL?"}
C -->|"ya"| A["create_sql_agent"]
C -->|"tidak"| B["PythonAstREPLTool"]
CSV["industry-leaders-full.csv"] --> L["pandas.read_csv"]
L --> DB[("industry.db · tabel industry")]
L --> DF["industry = df.loc[:, 4 kolom]"]
DB --> A
DF --> B
A --> R1["Hasil query SQL"]
B --> R2["stdout eksekusi pandas"]
style CSV fill:#8b5cf6,color:#fff
style L fill:#8b5cf6,color:#fff
style DF fill:#8b5cf6,color:#fff
style DB fill:#336791,color:#fff
style C fill:#f59e0b,color:#000
style A fill:#f59e0b,color:#000
style B fill:#f59e0b,color:#000
style R1 fill:#2d7d46,color:#fff
style R2 fill:#2d7d46,color:#fff
%% fig: Dua jalur dari satu dataset ke jawaban
%% desc: CSV yang sama masuk ke SQLite untuk jalur SQL, atau ke DataFrame pandas untuk jalur eksekusi kode; keduanya berakhir di hasil berbentuk tabel.15_sql.py membangun industry.db hanya kalau berkas itu belum ada: CSV dibaca, engine sqlite:///industry.db disiapkan, lalu seluruh baris ditulis ke tabel industry dengan if_exists='replace'. Kalau database sudah ada, skrip hanya membuka koneksi dan tidak menimpa isinya. 15_sql.py:17-252
⚠️ Kedua skrip membaca
./datasets/industry-leaders-full.csv, padahal repository tidak punya direktoridatasets; berkas yang tersedia ada diworkshop/industry-leaders-full.csv. Tanpa penyesuaian path,pd.read_csvgagal sebelum agent sempat dipanggil. 15_sql.py:193 workshop/industry-leaders-full.csv:14
Artefak industry.db juga tidak ikut ter-commit karena .gitignore memblokir *.db, jadi setiap mesin membangun ulang database dari CSV pada eksekusi pertama. .gitignore:6-6
Jalur SQL dengan create_sql_agent
SQLDatabase(engine=engine) membungkus engine tadi, dan db.get_usable_table_names() mencetak daftar tabel yang boleh dipakai agent sebagai konteks skema. 15_sql.py:27-305
Sebelum LLM dilibatkan, skrip sudah lebih dulu menjalankan SQL tulis tangan, SELECT * FROM industry WHERE total_market_cap > 1e14, lewat db.run. Baris ini berfungsi sebagai baseline: db.run menerima string SQL apa pun dan mengeksekusinya langsung ke SQLite tanpa pemeriksaan tambahan. 15_sql.py:33-346
Agent-nya dibuat dengan agent_type="tool-calling" dan verbose=True, memakai ChatGroq bermodel llama-3.3-70b-versatile. 15_sql.py:36-447 Satu pemanggilan invoke kemudian mengirim pertanyaan bisnis dan meminta hasil berupa tabel markdown. 15_sql.py:45-478
Parameter di create_sql_agent | Nilai pada 15_sql.py |
|---|---|
llm | ChatGroq(model_name="llama-3.3-70b-versatile") 15_sql.py:36-389 |
db | SQLDatabase(engine=engine) atas industry.db 15_sql.py:2710 |
agent_type | "tool-calling" 15_sql.py:4411 |
verbose | True, sehingga langkah antara SQL yang dihasilkan ikut tercetak 15_sql.py:4411 |
| tabel target | industry 15_sql.py:17-2112 |
Jalur pandas dengan PythonAstREPLTool
16_repl.py tidak membuat database. CSV dibaca, empat kolom dipilih ke variabel industry, lalu tool dibuat dengan locals={"df": industry}. Perhatikan penamaannya: kunci lokalnya df, tetapi isinya DataFrame hasil seleksi kolom, bukan CSV mentah. 16_repl.py:30-3413
Kolom di industry-leaders-full.csv | Masuk ke tool |
|---|---|
industry | ya 16_repl.py:3214 |
sub_industry | ya 16_repl.py:3214 |
total_market_cap | ya 16_repl.py:3214 |
num_of_companies | ya 16_repl.py:3214 |
companies | tidak; berisi JSON daftar emiten workshop/industry-leaders-full.csv:14 |
top_mcap_gainers | tidak workshop/industry-leaders-full.csv:14 |
top_price_change | tidak workshop/industry-leaders-full.csv:14 |
System prompt menyisipkan industry.head().to_markdown() sehingga model melihat nama kolom dan bentuk nilai tanpa perlu menebak skema. Prompt itu juga menegaskan arti top_mcap_gainers dan meminta keluaran tabular. 16_repl.py:40-4515
Berkas ini tidak memakai agent executor. Ia merangkai LCEL: prompt, llm.bind_tools(tools, tool_choice=python_repl.name), lalu JsonOutputKeyToolsParser(key_name=python_repl.name, first_tool_only=True) yang hanya mengambil panggilan tool pertama. 16_repl.py:54-5916 Rantai penuh menambahkan python_repl di ujungnya sehingga kode dari model langsung dieksekusi dan stdout-nya dikembalikan sebagai jawaban. 16_repl.py:61-6817
sequenceDiagram
participant U as Pengguna
participant P as ChatPromptTemplate
participant M as ChatGroq
participant J as JsonOutputKeyToolsParser
participant T as PythonAstREPLTool
U->>P: query_string dari input
P->>M: system prompt + industry.head().to_markdown
rect rgb(245,158,11)
M->>J: tool_call bernama python_repl
end
rect rgb(45,125,70)
J->>T: kode Python, first_tool_only=True
T-->>U: stdout berisi hasil pandas
end
rect rgb(220,38,38)
J--xU: rantai dipotong sebelum tool, kode tidak dieksekusi
end
%% fig: Siklus satu pertanyaan pada rantai REPL
%% desc: Model hanya menghasilkan panggilan tool; eksekusi terjadi di PythonAstREPLTool dan rantai bisa dipotong tepat sebelum langkah itu untuk sekadar membaca kode.Komentar di berkas menyatakan cara aman mencoba prompt: buang python_repl dari ujung chain untuk melihat kode yang akan dijalankan tanpa benar-benar menjalankannya. 16_repl.py:63-6618
⚠️
PythonAstREPLTooldi16_repl.pydibuat tanpaallow_dangerous_code=True, sedangkan04_csv.pymenambahkan flag itu karena LangChain 1.x mewajibkannya untuk eksekusi kode.langchain-experimental==0.4.1yang terpasang membuat tool menolak dipakai sampai flag tersebut diberikan, dan sekali diberikan, kode dari model berjalan di proses Python Anda tanpa sandbox. 16_repl.py:3419 04_csv.py:1620 requirements.txt:63-6421
| Aspek | 15_sql.py | 16_repl.py |
|---|---|---|
| Sumber data | tabel industry di industry.db 15_sql.py:17-2112 | CSV langsung ke pandas 16_repl.py:3022 |
| Alat | create_sql_agent(agent_type="tool-calling") 15_sql.py:4411 | PythonAstREPLTool(locals=...) 16_repl.py:3419 |
| Bahasa eksekusi | SQL | Python dan pandas |
| Model | llama-3.3-70b-versatile 15_sql.py:3723 | llama-3.3-70b-versatile 16_repl.py:3724 |
| Bentuk keluaran | hasil db.run dari SQL yang dihasilkan 15_sql.py:33-346 | stdout hasil print 16_repl.py:6825 |
| Titik gagal utama | path CSV dan skema tabel | skema kolom di prompt dan izin eksekusi kode |
Jalur API: workshop/ai-search-engine.py
Versi workshop mengganti CSV dengan endpoint Sectors. retrieve_from_endpoint menyusun header Authorization dari SECTORS_API_KEY, memanggil requests.get, memeriksa raise_for_status, dan menghentikan program lewat SystemExit kalau terjadi HTTPError. 18-27 workshop/ai-search-engine.py:18-2726
Dua fungsi diberi dekorator @tool sehingga bisa dipanggil model: get_company_overview(stock) dan get_sector_overview(sector). Keduanya hanya menyusun URL lalu menyerahkan pekerjaan ke retrieve_from_endpoint. 29-47 workshop/ai-search-engine.py:29-4727
| Tool | Endpoint yang dipanggil | Argumen |
|---|---|---|
get_company_overview | https://api.sectors.app/v1/company/report/{stock}/?sections=overview | kode saham, misalnya BBRI, TLKM workshop/ai-search-engine.py:29-3728 |
get_sector_overview | https://api.sectors.app/v1/subsector/report/{sector}/ | nama subsector, misalnya banks workshop/ai-search-engine.py:39-4729 |
Perakitan agent memakai create_react_agent dari LangGraph dengan system_message khusus analis data finansial dan checkpointer=MemorySaver(), lalu chat() mengirim pesan dengan session_id dan config berisi thread_id tetap bernilai "supertype". 74-87 workshop/ai-search-engine.py:74-8730 90-102 workshop/ai-search-engine.py:90-10231
graph LR
UI["user_input"] --> AG["create_react_agent"]
AG --> T1["get_company_overview"]
AG --> T2["get_sector_overview"]
T1 --> RT["retrieve_from_endpoint"]
T2 --> RT
RT --> API["api.sectors.app"]
AG --> MEM[("MemorySaver")]
style RT fill:#8b5cf6,color:#fff
style API fill:#2d7d46,color:#fff
style AG fill:#f59e0b,color:#000
style MEM fill:#336791,color:#fff
%% fig: Loop tool-use ke API Sectors
%% desc: Agent memilih satu dari dua tool, keduanya lewat retrieve_from_endpoint mentah ke api.sectors.app, sementara MemorySaver menyimpan riwayat percakapan per thread.⚠️ Jalur pencocokan nama di workshop belum jalan.
match_input_to_valid_subsector_slugmengembalikangood_approximationyang masih list kosong,fuzzbaru diimpor di dalam fungsi, dan__main__menyisakan dua penandaChallenge (1)sertaChallenge (2)untuk bagian ini. Tanpa implementasi itu, input pengguna tidak pernah dipetakan ke slug subsector yang valid. workshop/ai-search-engine.py:58-7132 workshop/ai-search-engine.py:104-11333
⚠️ Model di berkas workshop masih
llama3-8b-8192, sedangkan15_sql.pydan16_repl.pysudah dimigrasi kellama-3.3-70b-versatilekarena model lama dihentikan. Jalankan ulang ketiga skrip dengan model yang masih aktif. workshop/ai-search-engine.py:1634 CHANGES.md:129-13335
Yang perlu disiapkan sebelum menjalankan
langchain-groq dan langchain-experimental sama-sama dibutuhkan: yang pertama untuk ChatGroq di 15_sql.py dan 16_repl.py, yang kedua untuk PythonAstREPLTool. requirements.txt:63-6421 Kunci API sendiri dibaca lewat load_dotenv(); 16_repl.py bahkan mengambil SECTORS_API_KEY walaupun jalur REPL tidak memakainya. 16_repl.py:27-2836 Tata cara mengisi .env dan variabel mana yang wajib dibahas di Persiapan Lingkungan dan Konfigurasi API Key.
Pilihan antara dua jalur pertama sederhana: pakai 15_sql.py ketika pertanyaan berbentuk agregasi atas kolom skalar dan SQL lebih mudah diaudit, dan pakai 16_repl.py ketika jawabannya butuh manipulasi bentuk DataFrame. Pola agent yang lebih panjang, termasuk pembagian peran antar agent, dibahas di Pola Multi-Agent dengan OpenAI Agent SDK.