01. Chain of Tables for Multiple Tables
CYQIQ Chain of Table for Multiple Tables
CYQIQ Chain of Table adalah teknik untuk memproses beberapa tabel.
Teknik ini digunakan untuk mengekstraksi dan mengintegrasikan informasi dari berbagai tabel untuk menghasilkan jawaban atas pertanyaan pengguna.
CYQIQ Chain of Table bertujuan untuk memahami hubungan antar tabel dan mencari informasi yang diperlukan secara efektif untuk disajikan kepada pengguna.
Melalui teknik ini, pengguna dapat memanfaatkan data yang tersebar di beberapa tabel secara terpadu, sehingga memperoleh informasi yang lebih akurat dan bermanfaat.
Teknologi CYQIQ Chain of Table dapat diterapkan dalam berbagai bidang seperti manajemen basis data, pencarian informasi, dan pemrosesan bahasa alami.
Penulis: Richárd Hruby (opens in a new tab) and Dániel Márk Szalai (opens in a new tab) from CYQIQ Inc. (opens in a new tab)
Coba secara online (opens in a new tab)
Chain of table
Teknologi Chain of Table adalah teknik baru yang dikembangkan oleh peneliti dari Google dan Universitas California, San Diego.
Teknik ini melibatkan operasi SQL seperti select, group, sort dan join pada tabel yang tidak terstrukturisasi dengan cara yang lebih efisien dan efektif.
CoT juga menggunakan teknik pengambilan data dari tabel yang tidak terstrukturisasi untuk membuat tabel baru dengan struktur yang lebih sederhana dan mudah dipahami oleh LLM.
Teknik ini memungkinkan LLM untuk menjawab pertanyaan dengan cara yang lebih sederhana dan efektif, membuat tabel baru yang lebih mudah dipahami oleh LLM.
Untuk informasi lebih lanjut tentang CoT, silakan lihat di bawah ini:
Wang, Zilong et al. Chain-of-Table: Evolving Tables in the Reasoning Chain for Table Understanding. 2024. arXiv: 2401.04398 (opens in a new tab) [cs.CL].
Analisis CoT
CoT beroperasi melalui rantai tabel perantara.
menyebabkan kesalahan yang tidak terduga seperti overflow konteks atau menghasilkan informasi yang tidak ada pada kolom baru.
Dua masalah ini semakin serius ketika ukuran tabel yang digunakan besar.
Dalam mengembangkan CoT untuk lingkungan produksi, mungkin ada masalah tambahan yang ditemukan.
Metode ini tidak dapat memproses beberapa tabel secara bersamaan, dan pengembang harus membuat parser untuk perintah yang dihasilkan oleh LLM sendiri. (Namun, ada perdebatan tentang hal ini. LLM dapat membuat tabel secara keseluruhan, tetapi lagi-lagi, masalah konteks jendela dan kepercayaan (kesalahan) dapat terjadi, sehingga menjalankan kode manipulasi tabel di luar ruang lingkup LLM mungkin lebih baik.)
CYQIQ - CoT for multiple tables
CYQIQ adalah sistem yang menyediakan fungsi Chain-of-Thought (CoT) untuk beberapa tabel.
CoT adalah metode yang memecah proses inferensi yang kompleks menjadi langkah-langkah kecil, kemudian menghasilkan hasil akhir berdasarkan hasil antara.
CYQIQ menggunakan pendekatan CoT untuk mengintegrasikan informasi yang terdistribusi di beberapa tabel dan menghasilkan jawaban yang akurat untuk pertanyaan.
Teknologi ini dapat digunakan dalam bidang database query, pencarian informasi, atau pemrosesan bahasa alami.
Dalam notebook ini, kami akan memperkenalkan konsep CoT terbaru untuk beberapa tabel dan implementasi yang lebih praktis menggunakan LangChain (opens in a new tab) dan LangGraph (opens in a new tab).
Kami akan menggunakan Pandas sebagai tabel manajer.
Seperti CoT, kami akan meminta LLM untuk membuat serangkaian tugas untuk memanipulasi dataframe, kemudian melakukan tugas pertama dan memberikan tabel antara kepada LLM untuk membuat tugas berikutnya dalam rantai.
1. Mengimpor library yang diperlukan
Ini adalah tahap untuk mengimpor pustaka yang diperlukan.
Pada tahap ini, berbagai pustaka yang diperlukan untuk pengembangan program dimuat menggunakan perintah import
.
Mengimpor pustaka adalah proses menyiapkan agar fungsi, kelas, modul, dan elemen lain yang disediakan oleh pustaka tersebut dapat digunakan.
Untuk menginstal pustaka yang diperlukan, jalankan perintah berikut: pip install langchain langchain_openai langgraph langchainhub pandas tabulate
Atau, Anda juga dapat menjalankannya dengan cara berikut: pip install -r requirements.txt
Kode ini menggunakan pustaka LangChain dan LangGraph untuk mengimplementasikan sistem AI interaktif.
Library dan modul yang diperlukan diimpor sebagai berikut:
- Library dasar:
json
,pandas
,traceback
- Modul untuk tipe hint:
TypedDict
,Annotated
,Sequence
darityping
- Modul operator
- Library kustom:
get_last_chains
,save_new_chain
- Library LangChain:
ChatOpenAI
,BaseMessage
,FunctionMessage
,HumanMessage
,tool
,convert_to_openai_function
,ChatPromptTemplate
,MessagesPlaceholder
,hub
- Library LangGraph:
ToolExecutor
,ToolInvocation
,StateGraph
,END
Kode ini memanfaatkan LangChain dan LangGraph untuk mengatur struktur dasar sistem AI interaktif. Fungsi kustom dan tipe hint digunakan untuk meningkatkan keterbacaan dan pemeliharaan kode.
# Impor pustaka dasar
import json
import pandas as pd
import traceback
# Impor pustaka pydantic
from typing import TypedDict, Annotated, Sequence
import operator
# Impor fungsi kustom
from util_functions import get_last_chains, save_new_chain
# Impor pustaka langchain
from langchain_openai import ChatOpenAI
from langchain_core.messages import BaseMessage, FunctionMessage, HumanMessage
from langchain_core.tools import tool
from langchain_core.utils.function_calling import convert_to_openai_function
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain import hub
# Impor pustaka langgraph
from langgraph.prebuilt import ToolExecutor, ToolInvocation
from langgraph.graph import StateGraph, END
2. Environment Variable:
Untuk melakukan tugas ini, pastikan file [.env] harus ada di lokasi yang sama dengan file notebook ini, Isi file dalam format berikut:
OPENAI_API_KEY="..."
LANGCHAIN_API_KEY="..."
LANGCHAIN_TRACING_V2=true
LANGCHAIN_ENDPOINT="https://api.smith.langchain.com"
LANGCHAIN_PROJECT="..."
- Impor fungsi
load_dotenv
dari moduldotenv
. - Panggil fungsi
load_dotenv()
untuk memuat variabel lingkungan.
from dotenv import load_dotenv
#### Environment ####
load_dotenv() # Muat file variabel lingkungan (.env).
3. Mengambil daftar DataFrame
Di sini, tabel dibaca dan dikonversi menjadi dictionary untuk memudahkan eksekusi kode.
Selain itu, daftar string pertanyaan dibuat, yang merupakan deskripsi tentang tabel yang dapat diberikan ke LLM.
Deskripsi ini digunakan dalam prompt untuk membantu LLM memilih tabel yang relevan dengan pertanyaan.
Dalam lingkungan operasional sebenarnya, deskripsi tabel ini juga dihasilkan melalui panggilan ke LLM.
Di sini ditunjukkan contoh sederhana yang mencakup 6 tabel dan deskripsi yang di-hardcode.
- 6 file CSV (dari coworker0.csv hingga coworker5.csv) dibaca, masing-masing menghasilkan DataFrame, dan disimpan dalam
df_list
. - Dictionary
df_dic
dibuat dan diberikan pasangan kunci-DataFrame, di mana kunci adalahdf1
hinggadf6
. df_dic
digunakan dalam fungsievaluate_pandas_chain
untuk secara dinamis merujuk DataFrame dengan menggunakan fungsieval()
.
# Mempersiapkan input untuk template prompt.
df_list = []
for i in range(6):
# Membaca file 'data/coworker{i}.csv' dan mengonversinya menjadi DataFrame, lalu membuat salinannya.
df_list.append(pd.read_csv(f"data/coworker{i}.csv", index_col=0).copy())
# Membuat df_dic yang akan digunakan dalam fungsi eval() di evaluate_pandas_chain.
df_dic = {}
for i, dataframe in enumerate(df_list):
# Menyimpan setiap DataFrame ke dalam df_dic dengan kunci dalam format 'df1', 'df2', ...
df_dic[f"df{i + 1}"] = dataframe
- Variabel
questions_str
berisi string yang menjelaskan beberapa dataframe. - Setiap dataframe diberi nomor dari
df1
hinggadf6
. df1
berisi tentang pemilihan rekan kerja yang paling berharga (MVP) yang telah memberikan kontribusi luar biasa terhadap kinerja dan produktivitas perusahaan.df2
berisi tentang pemilihan rekan kerja yang memiliki potensi terbesar.df3
berisi tentang pemilihan rekan kerja yang paling diinginkan untuk diajak berkolaborasi.df4
berisi tentang pemilihan rekan kerja yang paling menegangkan atau sering menimbulkan konflik.df5
berisi tentang pemilihan rekan kerja yang paling sulit untuk diajak berkolaborasi.df6
berisi metadata tentang orang-orang yang berpartisipasi dalam survei.
# df1: Pilih rekan kerja yang diakui sebagai pemain paling berharga (MVP) yang telah memberikan kontribusi luar biasa terhadap kinerja dan produktivitas perusahaan.
# df2: Pilih rekan kerja yang Anda anggap memiliki potensi yang belum berkembang.
# df3: Dengan siapa Anda paling ingin berkolaborasi?
# df4: Dengan rekan kerja mana Anda merasa paling tegang atau mengalami banyak konflik?
# df5: Siapa rekan kerja yang paling sulit diajak berkolaborasi?
# df6: Metadata tentang orang-orang yang berpartisipasi dalam survei
# Membuat string yang menjelaskan setiap DataFrame.
questions_str = """
df1: Pilih rekan kerja yang Anda akui sebagai pemain paling berharga (MVP) di perusahaan karena kontribusi mereka yang luar biasa terhadap kinerja dan produktivitas perusahaan.
df2: Pilih rekan kerja yang menurut Anda memiliki potensi terbesar yang belum dimanfaatkan.
df3: Dengan siapa Anda paling suka berkolaborasi?
df4: Dengan rekan kerja mana Anda merasa paling tegang atau mengalami konflik terbanyak?
df5: Siapa rekan kerja yang paling sulit untuk diajak berkolaborasi?
df6: Metadata tentang orang-orang yang berpartisipasi dalam survei
"""
4. Ambil aksi berikut:
Seperti yang telah disebutkan sebelumnya, kita akan melakukan hanya satu aksi pada setiap siklus generasi.
Fungsi ini membantu dalam mencari aksi dari output LLM.
Fungsi get_action
mem-parsing rantai aksi untuk mengekstrak aksi individu.
Jika string actions
mengandung "":
- Pisahkan
actions
berdasarkan "->" dan ambil bagian kedua. - Hapus spasi di depan dan di belakang bagian yang diambil dan alokasikan ke variabel
a
.
Jika string actions
tidak mengandung "":
- Pisahkan
actions
berdasarkan "->" dan ambil bagian pertama. - Hapus spasi di depan dan di belakang bagian yang diambil dan alokasikan ke variabel
a
. - Kembalikan aksi yang diekstrak
a
.
# Parser untuk rantai aksi
def get_action(actions):
if "<BEGIN>" in actions: # Jika aksi mengandung "<BEGIN>"
# Membagi berdasarkan tanda "->" dan mengambil elemen kedua, lalu menghapus spasi.
a = actions.split("->")[1].strip()
else: # Jika aksi tidak mengandung "<BEGIN>"
# Membagi berdasarkan tanda "->" dan mengambil elemen pertama, lalu menghapus spasi.
a = actions.split("->")[0].strip()
return a # Mengembalikan aksi yang diekstraksi.
5. Definisi alat yang dapat digunakan oleh LLM (Large Language Model)
Definisikan dua alat yang dapat digunakan oleh LLM untuk melakukan aksi dan berinteraksi dengan dunia (atau dalam hal ini, data).
Alat view_pandas_dataframes
bertugas memberikan informasi tentang data frame kepada LLM, seperti nama kolom dan beberapa baris data pertama.
Perlu diperhatikan bahwa tabel yang sangat besar dapat sepenuhnya mengisi konteks LLM.
Namun, ini bukanlah batasan besar karena tabel-tabel tersebut dapat dibagi menjadi beberapa tabel kecil yang secara logis terkait, dan memerlukan ratusan kolom untuk menimbulkan masalah seperti itu berdasarkan isi tabel.
Jika pembagian tidak memungkinkan, disarankan untuk mengurangi jumlah baris yang dimasukkan ke LLM.
Alat evaluate_pandas_chain
bertugas menjalankan aksi yang dihasilkan dan mengembalikan kepala dari data frame sementara yang dihasilkan, serta aksi yang telah dilakukan dan data frame lengkap.
Untuk informasi lebih lanjut tentang cara kerja alat-alat tersebut, lihat dokumentasi.
- Fungsi
evaluate_pandas_chain
digunakan untuk menjalankan rantai aksi pandas. - Parameter
chain
adalah string yang menunjukkan rantai aksi pandas yang akan dijalankan. - Parameter
inter
digunakan untuk menyimpan hasil sementara selama eksekusi rantai. - Gunakan fungsi
get_action
untuk mengekstrak aksi berikutnya dari rantai. - Gunakan fungsi
eval
untuk menjalankan aksi yang diekstrak dan simpan hasilnya diinter
. - Jika hasilnya adalah DataFrame, kembalikan 50 baris pertama dalam format markdown menggunakan
.head(50)
. - Jika terjadi pengecualian, kembalikan informasi pengecualian.
- Fungsi
view_pandas_dataframes
digunakan untuk memeriksa hingga 3 DataFrame. - Parameter
df_list
adalah daftar nama DataFrame yang akan diperiksa. - Ambil DataFrame yang diminta dari
df_dic
dan kembalikan 10 baris pertama dalam format markdown menggunakan.head(10)
. - Daftar
tools
mencakup fungsievaluate_pandas_chain
danview_pandas_dataframes
. tool_executor
adalah instance dari kelasToolExecutor
yang dibuat dengan menerima daftartools
sebagai argumen.- Daftar
functions
menyimpan hasil konversi setiap fungsi dalam daftartools
ke format fungsi OpenAI.
# Fungsi untuk mengevaluasi aksi berikutnya dalam rantai
@tool
def evaluate_pandas_chain(
chain: Annotated[
str,
"Rantai aksi pandas. Contoh: df1.groupby('age').mean() -> df1.sort_values() -> <END>",
],
inter=None,
):
"""Gunakan fungsi ini untuk menjalankan rantai kode pandas pada DataFrame"""
name = "evaluate_pandas_chain"
try:
action = get_action(chain)
print("\n\naction: ", action)
inter = eval(action, {"inter": inter, "df_dic": df_dic})
if isinstance(inter, pd.DataFrame):
intermediate = inter.head(50).to_markdown()
else:
intermediate = inter
return intermediate, action, inter
except Exception as e:
return f"Terjadi pengecualian: {traceback.format_exc()}", action, None
# Fungsi untuk melihat DataFrame
@tool
def view_pandas_dataframes(
df_list: Annotated[
Sequence[str],
"Daftar hingga 3 DataFrame pandas yang ingin dilihat. Contoh: [df1, df2, df3]",
]
):
"""Gunakan fungsi ini untuk melihat head(10) dari DataFrame untuk menjawab pertanyaan"""
name = "view_pandas_dataframes"
markdown_str = "Berikut adalah .head(10) dari DataFrame yang diminta:\n"
for df in df_list:
df_head = df_dic[df].head(10).to_markdown()
markdown_str += f"{df}:\n{df_head}\n"
markdown_str = markdown_str.strip()
return markdown_str
tools = [evaluate_pandas_chain, view_pandas_dataframes]
tool_executor = ToolExecutor(tools)
functions = [convert_to_openai_function(t) for t in tools]
Berikut ini adalah hasil koreksi konten yang diberikan seperti yang diminta.
6. Generate Prompt
Gunakan pesan sistem untuk menjelaskan kepada LLM pekerjaan yang perlu dilakukan saat ini.
Beritahu jumlah data frame yang dapat diakses oleh LLM dan minta LLM untuk menggunakan alat yang telah didefinisikan.
Jelaskan bahwa tujuan utamanya adalah untuk membuat data frame yang akan membantu menjawab pertanyaan spesifik pengguna.
Selanjutnya, jelaskan harapan terkait rantai aksi (action chain) yang dihasilkan, yang akan berguna untuk eksekusi kode atau penggunaan alat evaluate_pandas_chain
.
Berikan beberapa contoh untuk menjelaskan logika apa yang harus diikuti untuk pertanyaan contoh tertentu.
Tunjukkan juga contoh atau "resep" yang benar dari masa lalu sebagai referensi di masa depan.
Terakhir, tunjukkan alat yang baru saja dibuat, deskriptor data frame, dan log pesan terbaru antara agen dan pengguna.
Setelah mendefinisikan template prompt, lengkapi dengan informasi yang diperlukan.
- Ambil template prompt "hrubyonrails/multi-cot" dari Langchain Hub ke dalam variabel
SYSTEM_PROMPT
. - Template prompt yang diambil dapat diakses melalui
messages[0].prompt.template
. - Gunakan
print(SYSTEM_PROMPT)
untuk mencetak template prompt yang diformat.
# Mengambil prompt dari langchain hub.
SYSTEM_PROMPT = hub.pull("hrubyonrails/multi-cot").messages[0].prompt.template
# Mencetak template prompt yang telah diformat.
print(SYSTEM_PROMPT)
- Gunakan
ChatPromptTemplate
untuk membuat template prompt yang mencakup sistem prompt (SYSTEM_PROMPT
) danMessagesPlaceholder
. - Gunakan metode
partial
untuk menerapkan variabel berikut secara parsial pada template prompt: num_dfs
: Panjang daridf_list
tool_names
: String yang memisahkan nama-nama alat dalam daftartools
dengan komaquestions_str
: String pertanyaan- Jika hasil dari fungsi
get_last_chains()
adalah tipepd.core.frame.DataFrame
, buat stringchain_examples
dengan menelusuri kolom "query" dan "chain" dari data frame tersebut. Setiap contoh terdiri dari format "Question: [query]\nChain: [chain]\n\n". - Gunakan metode
partial
untuk menerapkan variabelchain_examples
secara parsial pada template prompt.
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
SYSTEM_PROMPT, # Mengatur sistem prompt.
),
MessagesPlaceholder(
variable_name="messages"
), # Mengatur placeholder pesan.
]
)
# Menetapkan panjang daftar DataFrame ke variabel num_dfs.
prompt = prompt.partial(num_dfs=len(df_list))
# Menetapkan nama alat yang dipisahkan dengan koma ke variabel tool_names.
prompt = prompt.partial(tool_names=", ".join([tool.name for tool in tools]))
# Menetapkan string pertanyaan ke variabel questions_str.
prompt = prompt.partial(questions_str=questions_str)
# Mengirimkan kueri yang berhasil sebelumnya.
chain_examples = ""
# Memeriksa apakah chain terakhir dalam format DataFrame.
if type(get_last_chains()) == pd.core.frame.DataFrame:
# Mengulangi kueri dan kolom chain.
for index, row in get_last_chains().iterrows():
# Membuat contoh chain.
chain_examples += f'Question: {row["query"]}\nChain: {row["chain"]}\n\n'
# Menetapkan contoh chain ke variabel chain_examples.
prompt = prompt.partial(chain_examples=chain_examples)
7. Tentang LangGraph
Secara sederhana, LangGraph memungkinkan kita untuk membuat state machine.
Mesin ini akan menjalankan chain (rantai) pada setiap status.
Perbedaan utama antara LangGraph dan membuat serta menjalankan beberapa chain secara berurutan adalah bahwa LangGraph memungkinkan perulangan atau eksekusi berulang.
Ini berarti, berdasarkan kriteria transisi tertentu, state machine kita dapat beralih antar status secara tak terbatas hingga kondisi berhenti (stop condition) terpenuhi.
Ini dicapai dengan mengirimkan objek status (state object) antar node pada grafik.
Setiap node dapat melakukan tindakan yang diperlukan dan memperbarui objek status.
Untuk informasi lebih lanjut tentang LangGraph, Anda dapat melihatnya di dokumentasi.
Pertama, kita memastikan bahwa model dapat mengakses dua alat yang dapat dipanggil dengan melakukan binding alat pada model.
Kemudian, objek status AgentState didefinisikan.
Objek ini memiliki 5 field berikut:
- messages: Beranotasi dengan operator tambahan sebagai BaseMessage, di mana setiap node akan menambahkannya.
- actions: Mirip dengan messages tetapi dengan tipe string.
- inter: DataFrame sementara, yang diperbarui pada setiap siklus berdasarkan ide CoT.
- question: Pertanyaan yang perlu dijawab.
- memory: Percakapan sebelumnya antara pengguna dan LLM.
Variabel model dibuat dengan melakukan binding antara prompt dan model ChatOpenAI, menggunakan model gpt-4-0125-preview, dan fungsi di-bind melalui argumen functions.
Kelas AgentState adalah kelas tipe hint yang mewakili status agent, dengan properti sebagai berikut:
- messages: Tipe BaseMessage sebagai urutan, menyimpan pesan yang dikirim dan diterima oleh agent. Anotasi operator.add memungkinkan penggabungan urutan ini.
- actions: Urutan bertipe string, menyimpan tindakan yang dilakukan oleh agent. Anotasi operator.add memungkinkan penggabungan urutan ini.
- inter: Tipe pandas.DataFrame, menyimpan hasil sementara dari agent.
- question: Tipe string, menyimpan pertanyaan yang diberikan kepada agent.
- memory: Tipe string, menyimpan memori agent
# Binding model
model = prompt | ChatOpenAI(
model="gpt-4-0125-preview").bind_functions(functions)
# Membuat status grafis
class AgentState(TypedDict):
messages: Annotated[Sequence[BaseMessage], operator.add] # Urutan pesan
actions: Annotated[Sequence[str], operator.add] # Urutan aksi
inter: pd.DataFrame # DataFrame sementara
question: str # Pertanyaan
memory: str # Memori
8. Mendefinisikan Elemen Grafik
Pertama, buat fungsi should_continue
yang berfungsi sebagai kondisi cabang antar node. Berdasarkan status saat ini, beberapa hal dapat terjadi.
- Jika model menentukan bahwa langkah tambahan diperlukan, lanjutkan eksekusi. Jika tidak, hentikan eksekusi.
Selanjutnya, buat node.
Dalam LangGraph, node adalah fungsi atau objek yang dapat dieksekusi. Di sini, kita akan menggunakan fungsi sebagai node.
Node call_model
berfungsi sebagai titik masuk dan digunakan untuk menentukan tindakan berikutnya.
Jika cabang kondisional memutuskan untuk berhenti, keluar dari node ini.
Jika harus melanjutkan, pindah ke node call_tool
untuk melakukan tindakan berdasarkan fungsi yang ditentukan oleh LLM.
Beberapa data frame dapat diperiksa (tindakan aman) atau perintah yang dihasilkan dapat dijalankan (jika perintah tidak ditulis dengan benar, bisa terjadi kesalahan; oleh karena itu, tambahkan penanganan pengecualian dan mekanisme perbaikan sendiri. Jika eksekusi kode gagal, beri tahu LLM dan minta untuk memperbaiki perintah yang salah).
Kode ini mendefinisikan fungsi yang digunakan dalam sistem interaktif.
- Fungsi
should_continue
memutuskan apakah percakapan harus dilanjutkan berdasarkan status percakapan.- Jika pesan terakhir tidak memiliki
function_call
, kembalikan "end" untuk mengakhiri percakapan. - Jika tidak, kembalikan "continue" untuk melanjutkan ke node
call_tool
.
- Jika pesan terakhir tidak memiliki
- Fungsi
call_model
memanggil model untuk menghasilkan respons.- Gunakan
model.invoke
untuk mendapatkan respons dari model berdasarkan status saat ini. - Kembalikan respons dalam bentuk daftar dan tambahkan ke daftar pesan yang ada.
- Gunakan
- Fungsi
call_tool
berfungsi untuk menjalankan alat.- Ekstrak informasi
function_call
dari pesan terakhir. - Ubah
tool_input
menjadi bentuk kamus dan tambahkanstate['inter']
. - Berdasarkan nama alat yang dipanggil, lakukan tindakan berbeda:
- Jika alat
view_pandas_dataframes
:- Buat
ToolInvocation
dan panggiltool_executor.invoke
untuk mendapatkan respons. - Ubah respons menjadi
FunctionMessage
dan kembalikan.
- Buat
- Jika alat
evaluate_pandas_chain
:- Buat
ToolInvocation
dan panggiltool_executor.invoke
untuk mendapatkan respons, tindakan yang dicoba, dan informasi interaksi. - Jika respons menghasilkan pengecualian, buat informasi kesalahan dan kembalikan sebagai
FunctionMessage
. - Jika respons berhasil, buat informasi sukses dan kembalikan sebagai
FunctionMessage
, bersama dengan tindakan yang dicoba dan informasi interaksi.
- Buat
- Jika alat
- Ekstrak informasi
# Mendefinisikan fungsi untuk memutuskan apakah harus melanjutkan: edge kondisional
def should_continue(state):
messages = state['messages']
last_message = messages[-1]
# Jika tidak ada pemanggilan fungsi, hentikan
if "function_call" not in last_message.additional_kwargs:
return "end"
# Jika ada pemanggilan fungsi, lanjutkan ke node call_tool
else:
return "continue"
# Mendefinisikan fungsi untuk memanggil model
def call_model(state):
response = model.invoke(state)
# Mengembalikan dalam bentuk daftar, yang akan ditambahkan ke daftar yang ada
return {"messages": [response]}
# Mendefinisikan fungsi untuk menjalankan alat
def call_tool(state):
messages = state['messages']
# Berdasarkan kondisi continue
# Dapat dilihat bahwa pesan terakhir mencakup pemanggilan fungsi
last_message = messages[-1]
tool_input = last_message.additional_kwargs["function_call"]["arguments"]
tool_input_dict = json.loads(tool_input)
tool_input_dict['inter'] = state['inter']
if last_message.additional_kwargs["function_call"]["name"] == 'view_pandas_dataframes':
# Membuat ToolInvocation dari function_call
action = ToolInvocation(
tool=last_message.additional_kwargs["function_call"]["name"],
tool_input=tool_input_dict,
)
# Memanggil tool_executor dan mendapatkan respons
response = tool_executor.invoke(action)
function_message = FunctionMessage(content=str(response), name=action.tool)
return {"messages": [function_message]} # ,"actions": [attempted_action]}
# Jika alat mengevaluasi rantai
elif last_message.additional_kwargs["function_call"]["name"] == 'evaluate_pandas_chain':
# Membuat ToolInvocation dari function_call
action = ToolInvocation(
tool=last_message.additional_kwargs["function_call"]["name"],
tool_input=tool_input_dict,
)
# Memanggil tool_executor dan mendapatkan respons
response, attempted_action, inter = tool_executor.invoke(action)
if "An exception occured:" in str(response):
error_info = f"""
Tindakan yang telah dilakukan sebelumnya:
{state['actions']}
Tindakan saat ini:
{attempted_action}
Hasil .head(50):
{response}
Anda harus memperbaiki pendekatan dan melanjutkan hingga Anda dapat menjawab pertanyaan berikutnya:
{state['question']}
Teruskan rantai dengan format berikut: action_i -> action_i+1 ... -> <END>
"""
print(error_info)
function_message = FunctionMessage(content=str(error_info), name=action.tool)
return {"messages": [function_message]}
else:
success_info = f"""
Tindakan yang telah dilakukan sebelumnya:
{state['actions']}
Tindakan saat ini:
{attempted_action}
Hasil .head(50):
{response}
Anda harus melanjutkan hingga Anda dapat menjawab pertanyaan berikutnya:
{state['question']}
Teruskan rantai dengan format berikut: action_i -> action_i
"""
9. Menyiapkan elemen graf dan membuat struktur grafik yang sesuai
Sekarang, kita mendefinisikan mesin status (StateGraph) kita, menambahkan node, menentukan titik awal, dan menambahkan kondisi antara.
Sampai saat ini, kita belum menyebutkan bahwa setelah node call_tool
selesai, kita selalu kembali ke node call_model
.
Jika struktur grafik sudah didefinisikan dengan benar, maka pekerjaan yang tersisa hanya kompilasi, yang akan mengembalikan LangChain Runnable.
- Kita menggunakan kelas
StateGraph
untuk mendefinisikan grafikworkflow
baru. - Kita menambahkan dua node,
agent
danaction
, keworkflow
dan mengatur agar masing-masing node memanggil fungsicall_model
dancall_tool
. - Kita menentukan titik awal
workflow
sebagai nodeagent
, sehingga nodeagent
akan dipanggil pertama kali saat grafik dijalankan. - Kita menambahkan kondisi antara ke node
agent
: - Kita menggunakan fungsi
should_continue
untuk menentukan node berikutnya yang akan dipanggil. - Berdasarkan output fungsi, jika
continue
maka nodeaction
akan dipanggil, jikaend
maka grafik akan dihentikan. - Kita menambahkan kondisi antara biasa ke node
action
yang mengarah ke nodeagent
, sehingga nodeagent
akan dipanggil setelah nodeaction
selesai. - Akhirnya, kita mengkompilasi
workflow
dan membuat objekRunnable
yang dapat dijalankan oleh LangChain, yang kita sebutapp
.
# Mendefinisikan graf baru.
workflow = StateGraph(AgentState)
# Mendefinisikan dua node yang akan berputar.
workflow.add_node("agent", call_model)
workflow.add_node("action", call_tool)
# Menetapkan titik masuk ke `agent`.
# Ini berarti node ini akan dipanggil pertama kali.
workflow.set_entry_point("agent")
# Sekarang tambahkan tepi bersyarat.
workflow.add_conditional_edges(
# Pertama, mendefinisikan node awal. Gunakan `agent`.
# Ini berarti tepi ini dilakukan setelah node `agent` dipanggil.
"agent",
# Kemudian, sediakan fungsi untuk menentukan node yang akan dipanggil berikutnya.
should_continue,
# Terakhir, berikan pemetaan.
# Kunci adalah string dan nilai adalah node lain.
# END adalah node khusus yang menunjukkan graf harus diakhiri.
# Output dari `should_continue` kemudian dibandingkan dengan kunci pemetaan ini
# untuk memanggil node yang cocok.
{
# Jika `continue`, panggil node `action`.
"continue": "action",
# Jika tidak, akhiri.
"end": END,
},
)
# Sekarang tambahkan tepi umum dari `tools` ke `agent`.
# Ini berarti setelah `tools` dipanggil, node `agent` akan dipanggil berikutnya.
workflow.add_edge("action", "agent")
# Terakhir, kompilasi!
# Ini dikompilasi menjadi LangChain Runnable,
# sehingga dapat digunakan dengan cara yang sama seperti hal lainnya.
app = workflow.compile()
10. Menentukan pertanyaan
Kode yang diberikan adalah contoh menangani pertanyaan yang melibatkan beberapa langkah dan berbagai tabel.
- Variabel
user_query
menetapkan pertanyaan pengguna. - Contoh pertanyaan pertama adalah "Tampilkan berapa kali Steven Rollins terpilih sebagai MVP, dan tunjukkan jumlah peringkat yang diterima oleh karyawan ini untuk setiap alasan MVP."
- Contoh pertanyaan kedua (dalam komentar) adalah "Tim yang menerima suara terbanyak sebagai tim yang sulit diajak bekerja sama adalah tim mana?"
Kode ini tidak menyertakan logika pemrosesan pertanyaan yang sebenarnya, melainkan menunjukkan langkah-langkah persiapan untuk menangani berbagai jenis pertanyaan.
# Mari coba beberapa pertanyaan yang melibatkan beberapa langkah dan tabel yang berbeda.
user_query = "Tampilkan berapa kali Steven Rollins terpilih sebagai MVP. Juga, tunjukkan jumlah peringkat yang diterima oleh karyawan ini untuk setiap alasan MVP."
# user_query = "Tim mana yang menerima suara terbanyak sebagai tim yang sulit diajak bekerja sama?"
10. Memanggil model
Langkah untuk memanggil model.
Di langkah ini, model yang telah dilatih dipanggil menggunakan data input yang telah disiapkan dan hasil prediksi diperoleh.
Panggilan model biasanya dilakukan menggunakan metode predict atau evaluate dari objek model.
Penting untuk memastikan bentuk data input sesuai dengan format input yang diminta oleh model.
Hasil prediksi dapat dikembalikan dalam berbagai format tergantung pada bentuk output model.
Hasil prediksi yang dikembalikan kemudian diproses lebih lanjut atau digunakan untuk perhitungan metrik evaluasi.
- Definisikan dictionary inputs untuk mengatur kueri pengguna, token mulai aksi, memori, dll.
- Gunakan fungsi
app.stream()
untuk melakukan streaming input, dan atur batas rekursi menjadi 40. - Iterasi melalui dictionary output yang telah di-stream dan proses sesuai dengan nama node masing-masing:
- Node "agent": Tampilkan bahwa agen sedang bekerja.
- Node "action":
- Jika aksi adalah "view_pandas_dataframes", tampilkan "viewing dataframes".
- Jika tidak, tampilkan aksi saat ini dan output, atau tampilkan pesan retry jika terjadi kesalahan.
- Node lainnya: Tampilkan output akhir dan rantai aksi.
- Ekstrak respons agen, tabel akhir, dan pesan akhir dari dictionary output.
- Hapus token '' dari respons agen untuk menghasilkan pesan akhir.
# Mengatur data input dalam bentuk dictionary. Termasuk kueri pengguna, aksi, memori, dll.
inputs = {"messages": [HumanMessage(content=user_query)], "actions": [
"<BEGIN>"], "question": user_query, "memory": ""}
# Menggunakan fungsi app.stream() untuk melakukan streaming data input, dan atur batas rekursi menjadi 40.
for output in app.stream(inputs, {"recursion_limit": 40}):
for key, value in output.items(): # Mengulangi setiap pasangan kunci-nilai dalam dictionary output.
if key == "agent": # Jika kunci adalah "agent"
print("🤖 Agen sedang bekerja...") # Menampilkan pesan "Agen sedang bekerja..."
elif key == "action": # Jika kunci adalah "action"
# Jika nama pesan aksi adalah "view_pandas_dataframes"
if value["messages"][0].name == "view_pandas_dataframes":
print("🛠️ Aksi saat ini:") # Menampilkan pesan "Aksi saat ini:"
# Menampilkan pesan "viewing dataframes"
print("`viewing dataframes`")
else: # Jika tidak
if "actions" in value.keys(): # Jika kunci "actions" ada
# Menampilkan pesan "Aksi saat ini:"
print(f"🛠️ Aksi saat ini:")
print(f"`{value['actions']}`") # Menampilkan aksi saat ini
print(f"Output saat ini:") # Menampilkan pesan "Output saat ini:"
print(value["inter"]) # Menampilkan output saat ini
else: # Jika kunci "actions" tidak ada
# Menampilkan pesan kesalahan dan mencoba lagi
print(f"⚠️ Terjadi kesalahan, mencoba lagi...")
else: # Jika kunci lainnya
print("🏁 Menyelesaikan...") # Menampilkan pesan "Menyelesaikan..."
print(f"Output akhir:") # Menampilkan pesan "Output akhir:"
print(value["inter"]) # Menampilkan output akhir
print(f"Rantai aksi akhir:") # Menampilkan pesan "Rantai aksi akhir:"
# Menampilkan rantai aksi akhir
print(" -> ".join(value["actions"]) + ' -> <END>')
print("---") # Menampilkan garis pemisah
pass # Tidak melakukan apa-apa
output_dict = output["__end__"] # Mengambil nilai untuk kunci "__end__" dari dictionary output
agent_response = output_dict["messages"][-1].content # Mengambil respons agen
final_table = output_dict["inter"] # Mengambil tabel akhir
# Menghapus tag '<END>' dari respons agen untuk menghasilkan pesan akhir
final_message = agent_response.replace('<END>', '')
Gunakan fungsi print()
untuk mencetak isi variabel final_message
.
# Mencetak pesan terakhir.
print(final_message)
Mencetak isi dari variabel final_table
.
- Gunakan fungsi
print()
untuk mencetak nilai variabelfinal_table
ke konsol.
print(final_table) # Keluarkan tabel akhir.