Skip to content
··8 min basahin

Paano Ako Nakaahon sa Coding Quicksand Gamit ang isang AI Agent

Sumabak ako sa pagbuo ng chatbot na walang coding skills at puro sigasig lang — para malaman na ang v0.1 ko ay isang sakuna ng CSV databases at primitive chunking, hanggang sa iniligtas ako ng mga AI agents.

Update (2026): Ang chatbot na ito ay naging Sydney! Pagkatapos ng maraming iterations, si Sydney ay nasa /ask/ na ngayon at nakatuon sa blog content at products.


Noong unang bahagi ng Nov noong nakaraang taon, ini-ship ko ang aking DIY chatbot version 0.1. Noong panahong iyon, sumulat ako, "Habang ang v0.1 ay kumakatawan sa isang malaking unang hakbang bilang coding novice, mayroon itong malaking limitasyon." Well, gaya ng nangyari, understatement pala iyon. Hindi ko na-appreciate kung gaano ka-clunky ang buong proseso at build ko. Ang katotohanan, ang unang pagtatangka ko, bagaman sinsero, ay mas isang prototype na pinagsama-sama ng sigasig pero limitadong kaalaman.

Ang post na ito ay hindi lang isang follow-up; ito ay isang malalim na pagsisid sa journey na nagbukas mula sa puntong iyon — isang journey na puno ng pagsubok, pagkakamali, at napakahalang aral. Inilalantad ko ang nuts and bolts ng adventure na ito, hindi lang para sa transparency kundi sa pag-asa na ang mga karanasan ko, detalyado man, ay maaaring ma-relate o makatulong sa iba na nasa katulad na landas. (Para sa karagdagang context, isa akong middle-aged advertising professional na walang dating coding experience.)

Gaya ng nabanggit, para ma-ship ang v0.1 ng aking chatbot, pangunahing sinunod ko ang mga instructions mula sa short course na ito "Building Systems with the ChatGPT API" at dalawang cookbooks mula sa OpenAI: Question answering using embeddings-based search at How to count tokens with tiktoken.

Ito ang dahilan kung bakit terrible ang chatbot v0.1 ko:

  • Chunking: Simpleng pag-split lang ang ginawa ko sa mahabang blog posts sa mas maliliit na chunks batay sa token length aka simple static character chunk of data. Ang pinaka-primitive na paraan ng paghahati :D. Kung gusto mong maintindihan kung bakit ito ay isang terrible idea, basahin ang tungkol sa 5 levels of text splitting mula kay Greg Kamradt dito.
  • Embedding: Nahirapan ako sa embeddings. Ginamit ko ang OpenAI's embedding model, pero patuloy na nahaharang sa API request limits, na nagdudulot na mag-fail ang embedding process sa kalagitnaan. Natutunan ko pagkatapos na mag-batch ng requests at magdagdag ng timeouts sa pagitan ng batches para maiwasan ang limits. Sa huli, ini-save ko ang mga na-generate na embeddings sa isang simpleng .csv file bilang aking makeshift "database".
  • Database: Alam ko na hindi optimal ang CSV para sa database, pero kulang ang skills ko para sa mas magandang alternatibo.
  • Metadata: Hindi ko una-unang na-realize na importante ang pagsasama ng metadata tulad ng publish dates at post URLs para sa chatbots na sumagot nang tama sa mga tanong ng user. Kinailangan kong ulitin ang embedding at pag-save para maisama ang relevant metadata.
  • Retriever: Hindi ko alam ang iba't ibang uri at algorithm ng retriever. Simpleng ginamit ko lang ang OpenAI's relevance search para kumuha ng hardcoded na bilang ng mga resulta.
  • Memory: Para magkaroon ng usapan, kailangan ng chatbot na matandaan ang sinabi ng user dati. At dito, sa limitadong context window length ng gpt-3.5 (noong panahong iyon), may malinaw na trade off sa pagitan ng chunk size at kung gaano karaming resulta ang gusto mong kunin.
    • Halimbawa, kung ang chunk size mo ay 800 tokens at ang retriever ay nagbabalik ng top 8 results, iyon ay 6,400 tokens o mahigit 50% ng lumang model limit.
    • Ang nasa itaas ay 1 tanong lang kaya maiisip mo kung gaano kabilis mapupuno ang memory sa isang multi-turn conversation.
    • Isang paraan para malutas ang isyu na ito ay ang magkaroon ng mas maliit na chunk size at para ang retriever ay magbalik ng mas kaunting resulta pero sa isang basic retriever (sa itaas), ibig sabihin nito na ang model ay walang komprehensibong impormasyon para sagutin ang tanong.
  • Hindi ko nga ginamit ang kahit anong IDE. Lahat ng codes ay ni-edit gamit ang TextEdit sa Mac :D (Sinabi ko na ba na noob ako dati? :P)
  • Marami pa akong mababanggit pero sa tingin ko naintindihan mo na ang picture.

Ang aking "valley of death"

Sabik na mag-improve lampas sa mga limitasyon ng v0.1, sinubukan ko ang ilang online courses, umaasang bibigyan nila ako ng mga nawawalang piraso para i-level up ang skills ko. Pero ang progreso ay patuloy na nag-lead sa dead ends lang.

Nahirapan ako sa mga exercises sa vector databases (Vector Databases: from Embeddings to Applications with Weaviate), evaluative RAG methods (Building and Evaluating Advanced RAG Applications kasama ang Llama-Index at Truera), at advanced retrieval techniques (Advanced Retrieval for AI with Chroma.) Gaano man ako kapilit, hindi ko ma-connect ang theory sa practical application gamit ang aking sariling blog data.

Masama ba ang pagkakagawa ng mga courses? Hindi — ang kakulangan ay sa sarili kong kawalan ng underlying knowledge. Gayunpaman, ang sunud-sunod na kabiguan ay sobrang nakaka-frustrate, hindi pa banggitin ang nakakawalang-gana. Natagpuan ko ang sarili ko sa isang figurative valley, hindi sigurado kung paano magpatuloy.

Incremental Wins Sa Paglipas ng Panahon

Gayunpaman, may lumabas na nuggets of value mula sa paulit-ulit na pagkabigo:

  • Paggamit ng VS Code imbes na TextEdit
  • Paggamit ng GitHub Copilot extensions
  • Pag-appreciate sa Jupyter Notebook para sa development environments

Ang huling course ay binanggit ang LangChain, isang popular na bagong framework para sa pagbuo ng chatbots. Sinubukan ko talaga ang LangChain tutorials ilang buwan na ang nakakaraan ("LangChain: Chat with Your Data" at "Functions, Tools and Agents with LangChain") nang walang gaanong tagumpay. Pero ngayon, kasama ang hard-won knowledge sa ilalim ng aking belt, ang muling pagbisita sa docs nito ay nagbigay liwanag. Ang mga konsepto ay nag-click nang magkasama at ang modular architecture nito ay naging intuitive.

Naisip ko na ang pag-adapt ng robust capabilities ng LangChain sa aking passion project. Sa wakas, may paraan na paabante! Isa-isang hakbang, nag-orient ako sa mga pipelines nito para sa data ingestion, embedding, storage at retrieval.

Lumaki ang kumpiyansa ko sa bawat piraso na nagawa kong i-implement. Nagsimulang mangyari ang V2...

Muling Pagtatayo ng Foundation

Sa LangChain bilang gabay ko, nagsimula akong i-reconstruct ang aking chatbot mula sa ground up:

Pag-ingest ng WordPress Exports papuntang JSON

Pagkatapos ng ilang pagsubok at pagkakamali sa pag-tweak ng ingestion parameters, maayos na na-parse ng JSONLoader ng LangChain ang aking na-export na posts. Ngayon, validated input na ang maaaring magpagana ng downstream pipelines. (Ang code para sa step na ito ay nasa file na "DataIngestionAndIndexing.ipynb" sa pampublikong Github repo na ito.)

Automated Text Splitting

Ang aking naive token length chunking ay pinalitan ng SentenceTransformers ng LangChain, gumagamit ng advanced NLP para maghati ng semantic units. Wala nang disjointed sentences na pinutol sa gitna! Ang mga configuration ay nagpanatili ng tamang laki ng chunks para sa memory-constrained models.

from langchain.text_splitter import SentenceTransformersTokenTextSplitter
# Define the token splitter with specific configurations
token_splitter = SentenceTransformersTokenTextSplitter(
    chunk_overlap=0,  # Overlap between chunks
    tokens_per_chunk=256  # Number of tokens per chunk
)
# Split the documents into chunks based on tokens
all_splits = token_splitter.split_documents(documents)
print(f"Total document splits: \{len(all_splits)\}")

Pag-generate ng Embeddings at Indexes

Ang dating mga pakikibaka sa OpenAI API limits ay nawala gamit ang wrapper ng LangChain para sa OpenAI embeddings. Nakapaloob sa dalawang linya ng code, malinis na na-extract ng embeddings ang mga salient features mula sa na-split na text.

Para sa vector store, pinili ko ang FAISS (Facebook AI Similarity Search) kaysa Weaviate o Chroma. Ang industry-tested FAISS ay tamang balanse ng capability versus complexity para sa aking mga pangangailangan. Ang CPU version nito ay mabilis na nag-index ng blog chunks, na nag-output ng compact searchable database. Hindi ko na kailangang mag-alala tungkol sa batching o pag-hit sa API request sa OpenAI.

Ang LangChain ay sumusuporta ng multiple vector stores kaya maaari mo silang i-check dito.

# Initialize embeddings and FAISS vector store
embeddings = OpenAIEmbeddings()
db = FAISS.from_documents(all_splits, embeddings)

# Save the vector store locally
db.save_local("path/to/save/faiss_index")  # Placeholder for save path/ index name

Dalawang linya ng code lang! Iyon lang.

Ang napansin ko rin ay kung hindi maliit ang content mo (mayroon akong 400+ blog posts), hindi mo dapat subukang gamitin agad ang retriever pagkatapos ng embedding dahil kailangan ng kaunting panahon para makumpleto/maging stable ang mga indexes.

Pag-evaluate ng Retrievers

Ang pag-setup ng retriever na ito ay 1 linya lang ng code :D, gamit ang FAISS bilang vector store.

retriever = db.as_retriever(search_type="mmr")

Sa aking index at retriever na nakalagay na, may data pipelines na akong handa para magpagana ng isang intelligent chatbot!

Pag-arkitekto ng Conversational Agents

Nagdesisyon akong gamitin ang agent framework mula sa LangChain para gawin ang chatbot na ito. Overkill ba ito sa puntong ito? Oo. Pero ang pag-asa ko ay sa paglipas ng panahon, ma-evolve ko ang chatbot na ito at mabigyan ng mas maraming "tools" aka functionalities. Ginagawa ng LangChain na napakadali ang pag-setup ng agent at pagbigay ng tools na gagamitin.

embeddings = OpenAIEmbeddings()
db = FAISS.load_local("path/to/your/faiss_index_file", embeddings)  # Replace the path with your actual FAISS index file path
retriever = db.as_retriever(search_type="mmr")
tool = create_retriever_tool(
    retriever,
    "search_your_blog",  # Replace "search_your_blog" with a descriptive name for your tool
    "Your tool description here"  # Provide a brief description of what your tool does
)

tools = [tool]
prompt_template = ChatPromptTemplate.from_messages([
    # Customize the prompt template according to your chatbot's persona and requirements
])

llm = ChatOpenAI(model_name="gpt-3.5-turbo-1106", temperature=0)
llm_with_tools = llm.bind_tools(tools)
agent = (
    \{
        "input": lambda x: x["input"],
        "agent_scratchpad": lambda x: format_to_openai_tool_messages(x["intermediate_steps"]),
        "chat_history": lambda x: x["chat_history"],
    \}
    | prompt_template
    | llm_with_tools
    | OpenAIToolsAgentOutputParser()
)

agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

Ang buong final python code para sa agent

Sa huli, kung gusto mong subukan ang chatbot v2, ito ang link.

Kakaiba ba na walang alam ang chatbot tungkol sa iyo Chandler?

P.S: Salamat sa ilan sa inyo na nag-reach out para ipaalam sa akin na walang alam ang chatbot tungkol sa akin. At tama kayo! Ito ay dahil nakalimutan kong i-export ang "About" Page at nag-export lang ng "published posts". Ito na ang ikalawang beses na nakalimutan ko ito kaya isasama ko ang basic questions tungkol sa akin sa listahan ng eval questions. Lesson learned!

Salamat sa lahat para sa pagbabahagi ng constructive feedback ninyo. Patuloy lang kayo. At oo, alam ko na sobrang bagal ng chatbot sa simula kaya nagta-trabaho ako doon :| (Sinabi ko na ba na noob ako dati? :P)

Isang mabilisang update

Ang isyu sa chatbot na walang alam tungkol sa akin ay na-fix na. Ito ang ginawa ko at natutunan:

  • Na-export ang "About me" page mula sa Wordpress to .XML gaya ng nasa itaas.
  • Gumawa ng text splitting at nag-generate ng embeddings gamit ang FAISS gaya ng nasa itaas. Ini-save ang vector store sa ibang pangalan locally para i-test ang retriever
# save the vector store to local machine
db.save_local("faiss_index_about")
# set up the retriever to test the new vector store about Chandler
from langchain.retrievers.multi_query import MultiQueryRetriever
llm = ChatOpenAI(temperature=0)
retriever_from_llm = MultiQueryRetriever.from_llm(
    retriever=db.as_retriever(), llm=llm
)
# test retriever
question = "Who is Chandler Nguyen?"
results = retriever_from_llm.get_relevant_documents(query=question, top_k=8)
for doc in results:
    print(f"Content: {doc.page_content}, Metadata: {doc.metadata}")

  • Gaya ng lumalabas, ang proseso ng pag-merge ng dalawang FAISS vector stores ay nakakagulat na simple, ayon sa documentation dito.
# Try to merge two FAISS vector stores into 1
# load the vector store from local machine
from langchain_community.vectorstores import FAISS
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
embeddings = OpenAIEmbeddings()
db_1 = FAISS.load_local("faiss_index_about", embeddings)
db_2 = FAISS.load_local("faiss_index", embeddings)
db_2.merge_from(db_1)
# save the vector store to local machine
db_2.save_local("faiss_index_v2")
# test the new vector store to confirm correct retrieved documents
retriever = db_2.as_retriever(search_type="mmr")
results = retriever.get_relevant_documents("Who is Chandler Nguyen?")
for doc in results:
    print(f"Content: {doc.page_content}, Metadata: {doc.metadata}")
  • Pagkatapos noon, halos pareho na lang ang proseso gaya ng nasa itaas, gamit ang bagong vector store

Feb 14 update Chatbot v2.10 Inilabas

Dalawang linggo pagkatapos ng deployment ng chatbot na ito, ipinakilala ko ang version 2.10 na nag-elevate ng User Experience with Enhanced Speed, Scalability, at Simplicity. Maaari mong basahin ang higit pa dito.

Mar 25 update: Mula sa Frontend Upgrades hanggang Docker Struggles at Breakthroughs

Maaari mong basahin ang higit pa dito.

Ipagpatuloy ang Pagbasa

Ang Journey Ko
Kumonekta
Wika
Mga Preference