Back to architectures
6 min read
Architecture

Dynamic Structured Output

Cara supaya structured output bersifat dinamis.

Dynamic Structured Output

Dalam membuat sebuah project yang berkaitan dengan generative AI terutama untuk AI Agent, Saya selalu menggunakan framework langgraph. Langgraph merupakan framework untuk membangun sebuah sistem AI berbasis node-node yang dapat kita rancang sesuai dengan kebutuhan. Alasan saya selalu menggunakan langgraph dibanding langchain, karena menggunakan langgraph jauh lebih fleksibel dibandingkan menggunakan langchain. Dengan kata lain, langgraph lebih bekerja secara low level dibandingkan dengan langchain. Walaupun, secara tingkat kesulitan menurut saya, langgraph jauh lebih sulit dibandingkan dengan langchain karena kita harus mengontrol secara manual mulai dari state, workflow, dan lain sebagainya. Namun, menurut saya, itu menjadi kelebihan langgraph untuk membangun sistem AI yang lebih fleksibel.

Untuk menyusun sebuah sistem AI agent yang dapat bekerja sesuai dengan keinginan kita, maka kita harus bisa memprediksi kemungkinan output/response yang dikeluarkan oleh LLM. Terkadang dalam sistem AI Agent, kita hanya memerlukan LLM supaya memberikan output secara singkat (to the point) dan spesifik berdasarkan yang kita inginkan. Dengan kata lain, kita perlu mengontrol output yang dihasilkan oleh LLM. Namun, Dikarenakan LLM bersifat probabilitas yaitu men-generate token selanjutnya berdasarkan nilai probabilitas yang tinggi, maka terkadang dia menghasilkan output yang berbeda dengan hasil sebelumnya. Walaupun hal tersebut bisa diatasi dengan memberikan rule secara eksplisit pada sistem promptnya. Namun, jika aturan output yang kita harapkan semakin kompleks atau banyak, maka itu dapat mempersulit LLM dan berpotensi berhalusinasi. Oleh karena itu, diperlukan sebuah method untuk mengontrol output yang dihasilkan LLM yaitu dengan menggunakan structured output.

Dengan menggunakan langgraph/langchain, kita bisa mengontrol output yang dihasilkan LLM. Dengan cara menggunakan schema output yang dapat kita buat dengan menggunakan pydantic. Pastikan ketika membuat schema output dari pydantic, dapat dengan jelas mendeklasarikan tipe data dan deskripsi field (jika ada). Dengan mendeklarasikan secara jelas, maka LLM dapat dengan mudah untuk memahaminya dan menghasilkan output sesuai dengan format schema. Kemudian, setelah schema tersebut dibuat, maka bisa langsung diberikan kepada LLM supaya menghasilkan output berdasarkan schema pydantic tadi. Berikut adalah contoh implementasi kodenya dalam python:

from pydantic import BaseModel, Field

class ContohStructuredOutput(BaseModel):
    name: str = Field(description="Your name")

Field digunakan untuk mempermudah LLM supaya menghasilkan output sesuai dengan yang kita inginkan. LLM akan membaca description dari masing-masing attribute (jika ada) untuk menentukan output yang diperintahkan. Oleh karena itu, pastikan memberikan deskripsi secara jelas supaya mudah dipahami oleh LLM.

Dengan begitu, maka LLM hanya akan menghasilkan output berdasarkan schema dari pydantic yang telah dibuat.

Namun, dalam real world sistem perlu adanya penyesuaian dalam membuat structured output. Terlebih lagi jika sistem yang akan diimplementasikan merupakan sistem yang terdapat banyak pengguna (multi tenant) dan setiap pengguna memiliki data yang berbeda. Dikarenakan schema pydantic yang dibuat sebelumnya bersifat statis (tidak berubah), maka diperlukan sedikit tambahan algoritma untuk membuatnya jadi lebih dinamis.

Dalam sistem yang sedang saya kembangkan sekarang contohnya. Saya membuat supaya schema pydantic dapat berubah menjadi schema yang dinamis. Dalam sistem tersebut, saya membuat sebuah agent customer service untuk bisnis yang membutuhkan. Setiap pemilik bisnis dapat membuat AI Agent customer service mereka dan menambahkan pengetahuan terkait dengan bisnis mereka. Pengetahuan tersebut akan digunakan AI nya dalam menjawab pertanyaan customer. Namun, dikarenakan sistem tersebut bersifat multi tenant atau dapat mengelola banyak bisnis. Sehingga pengetahuan tentang bisnis yang dapat digunakan AI sebagai referensi untuk menjawab pertanyaan, untuk setiap bisnis berbeda-beda pula. Selain itu, untuk setiap pengetahuan yang dimiliki oleh bisnis, LLM perlu menghasilkan output untuk menentukan key yang sesuai. Key tersebut dapat digunakan untuk mengambil pengetahuan melalui database. Dengan demikian, perlu adanya konsep structured output yang dinamis dalam arsitektur AI Agentnya, supaya dapat menghasilkan output yang sesuai dengan bisnis terkait.

Untuk membuatnya saya masih menggunakan pydantic sebagai base model schemanya. Tetapi ada sedikit tambahan yaitu dengan menggunakan method create_model dari pydantic, maka dapat membuat sebuah pydantic model schema secara custom. Untuk penggunaanya cukup mudah, kita hanya membuat sebuah function untuk membungkus schema custom. Kemudian, fungsi tersebut kita bisa atur untuk mengembalikan tipe base model schema pydantic. Berikut adalah untuk implementasi dalam kodenya:

from pydantic import BaseModel, Field, create_model

def create_structured_output_model(category: list[str]):
    category_type = Literal[Tuple(category)]
    return create_model(
        "ContohDynamicStructuredOutput",
        category=list[category_type],Field(description="List category"),
        __base__ = BaseModel
    )

Dengan begitu kita dapat mengatur value tipe data secara custom dan bersifat dinamis.

Untuk contoh penggunaannya dalam kasus saya yaitu tipe data literal yang dinamis. Ketika kita mendeklarasikan tipe data literal dalam schema, maka secara default masih bersifat statis. Tetapi dengan menggunakan metode yang sebelumnya saya pakai, maka value dari tipe data Literal menjadi dinamis sesuai dengan keinginan kita.

from pydantic import BaseModel, Field, create_model

def create_call_preparation_tool_model(business_knowladge: list[str]):
    business_knowladge_type = Literal[tuple(business_knowladge)]
    return create_model(
        "CallPreparationToolOutput",
        rag_query=(
            Optional[str],  # Gunakan Optional
            Field(
                default=None,
                description="Query untuk RAG. Isi hanya jika butuh mencari di dokumen. Kosongkan jika tidak butuh.",
            ),
        ),
        business_knowladge=(
            List[business_knowladge_type],
            Field(
                default_factory=list,  # Default list kosong
                description="List category key. Isi hanya yang relevan. Kosongkan jika tidak ada yang cocok.",
            ),
        ),
        decision_summary=(
            str,
            Field(
                description=(
                    "Ringkasan reasoning"
                    "Jelaskan apa yang sudah kamu lakukan dan mengapa"
                )
            ),
        ),
        __base__=BaseModel,
    )

Schema structured output tersebut dapat kita langsung berikan kepada LLM nya. Maka, LLM akan menghasilkan output berdasarkan schema tersebut yang telah kita berikan sebelumnya.

Dengan cara tersebut, kita bisa mendesign sebuah structured output yang bersifat dinamis. Hal tersebut cukup penting jika kita memiliki sebuah sistem multi tenant dan LLM harus menyesuaikan output yang dihasilkan untuk setiap tenant secara spesifik.