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 and Dániel Márk Szalai from CYQIQ Inc.

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 [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 dan LangGraph.

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 dari typing

  • 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 modul dotenv.

  • 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 adalah df1 hingga df6.

  • df_dic digunakan dalam fungsi evaluate_pandas_chain untuk secara dinamis merujuk DataFrame dengan menggunakan fungsi eval().

# 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 hingga df6.

  • 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 di inter.

  • 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 fungsi evaluate_pandas_chain dan view_pandas_dataframes.

  • tool_executor adalah instance dari kelas ToolExecutor yang dibuat dengan menerima daftar tools sebagai argumen.

  • Daftar functions menyimpan hasil konversi setiap fungsi dalam daftar tools 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) dan MessagesPlaceholder.

  • Gunakan metode partial untuk menerapkan variabel berikut secara parsial pada template prompt:

  • num_dfs: Panjang dari df_list

  • tool_names: String yang memisahkan nama-nama alat dalam daftar tools dengan koma

  • questions_str: String pertanyaan

  • Jika hasil dari fungsi get_last_chains() adalah tipe pd.core.frame.DataFrame, buat string chain_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 variabel chain_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.

  • 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.

  • Fungsi call_tool berfungsi untuk menjalankan alat.

    • Ekstrak informasi function_call dari pesan terakhir.

    • Ubah tool_input menjadi bentuk kamus dan tambahkan state['inter'].

    • Berdasarkan nama alat yang dipanggil, lakukan tindakan berbeda:

      • Jika alat view_pandas_dataframes:

        • Buat ToolInvocation dan panggil tool_executor.invoke untuk mendapatkan respons.

        • Ubah respons menjadi FunctionMessage dan kembalikan.

      • Jika alat evaluate_pandas_chain:

        • Buat ToolInvocation dan panggil tool_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.

# 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 grafik workflow baru.

  • Kita menambahkan dua node, agent dan action, ke workflow dan mengatur agar masing-masing node memanggil fungsi call_model dan call_tool.

  • Kita menentukan titik awal workflow sebagai node agent, sehingga node agent 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 node action akan dipanggil, jika end maka grafik akan dihentikan.

  • Kita menambahkan kondisi antara biasa ke node action yang mengarah ke node agent, sehingga node agent akan dipanggil setelah node action selesai.

  • Akhirnya, kita mengkompilasi workflow dan membuat objek Runnable yang dapat dijalankan oleh LangChain, yang kita sebut app.

# 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 variabel final_table ke konsol.

print(final_table)  # Keluarkan tabel akhir.

Last updated