Pagbuo ng Self-Evaluating Financial Chatbot: Isang Paglalakbay sa Data, Code, at mga Pakikibaka
Gumawa ako ng financial chatbot na sumasagot sa mga tanong tungkol sa S&P 500 gamit ang SEC data—na may self-critique agent na nagpapahusay ng mga sagot bago mo makita.
Update (2026): Ang financial chatbot project sa ibaba ay kalaunan ay naging ang S&P 500 agent (MVP launched Set 2024). Pagkatapos i-ship iyon, lumipat ang aking focus sa Sydney para sa blog content at dalawang bagong produkto: STRATUM (marketing intelligence) at DIALOGUE (podcast generation). Ang SEC data pipeline at self-evaluation patterns na inilarawan dito ang nag-inform sa lahat ng mga ito.
Orihinal na post mula Abr 2024, napanatili sa ibaba para sa konteksto.
Sa blog post na ito, gusto kong ibahagi ang karanasan ko sa isang kapana-panabik na hobby project — ang pagbuo ng financial chatbot/agent na may self-evaluation capabilities. Ang layunin ng proyektong ito ay lumikha ng agent na makakapagsagot ng mga tanong tungkol sa financial conditions at trends sa mga kumpanya sa S&P 500 sa US, gamit ang data direkta mula sa opisyal na SEC filings para matiyak ang katumpakan at maiwasan ang hallucination. Ang mga pangunahing benepisyo ng agent na ito ay (o magiging :P):
- Pinapayagan kang magtanong tungkol sa financial conditions, at trends sa mga kumpanya sa S&P 500 sa US.
- Gumagamit ito ng data direkta mula sa opisyal na financial filings ng mga kumpanyang ito sa SEC, para maiwasan ang hallucination
- Nagbibigay ito ng detalyadong mga sanggunian sa ibaba ng bawat sagot para ma-fact-check mo ang sagot kung gusto mo
- Ang database ay may huling 5-10 taon ng financial filing data kaya maaari mong tanungin ang agent na mag-reason tungkol sa mga trend sa paglipas ng panahon
- Bago ibalik sa iyo ang bawat sagot, may isa pang agent, na ang trabaho ay kritisahin ang draft na sagot ng LLM at magmungkahi kung paano ito pagbutihin
- Ang mga mungkahi at buong konteksto ay ibinabahagi sa orihinal na agent. Ang agent ay maaaring magdesisyon na bumuo ng iba't ibang queries para makuha ang mas magandang impormasyon mula sa database o simpleng isama ang mga mungkahi sa final answer
- Ang final answer na ito ang ibinibigay sa user.
Obviously, wala akong access sa Bloomberg terminal kaya wala akong ideya kung magagawa na ng Bloomberg chatbot ang lahat ng nasa itaas. (Ang educated guess ko ay kaya niya — sa isang antas. Gaano kagaling ang self-critique part at ang revision, hindi ako masyadong sure pero gusto kong malaman :P)
Anyway, gusto kong subukan ito dahil pakiramdam ko ay sapat na itong complex para sa learning ko sa yugtong ito at maaaring maging kapaki-pakinabang.
Gusto kong masagot ang mga tanong tulad ng:
- Ano ang trend ng marketing spend ng Apple sa nakaraang 5-10 taon?
- Ano ang lahat ng mga major acquisitions na ginawa ng XYZ company sa nakaraang 5 taon
- Ikumpara ang R&D spend ng Nvidia at Microsoft sa nakaraang 5 taon
- atbp.
Ok kaya nasaan ako sa proyektong ito? Ano ang natutunan ko? Ano ang mga pakikibaka?
Paano ko makukuha ang data mula sa SEC?
Isa sa mga unang hamon na naranasan ko ay ang pagkuha ng kinakailangang data mula sa SEC. Bagama't nagbibigay ang SEC ng mga gabay at dokumentasyon tungkol sa pag-access ng EDGAR data (at dito), tumagal ng ilang oras bago ko maunawaan ang proseso ng pag-download ng financial filings para sa bawat kumpanya sa malaking scale.
May ibang paraan ba para ako ay makapagpatuloy sa susunod na hakbang nang hindi ko kailangang i-download at i-process ang mga filings mula sa SEC mismo?
Tiningnan ko ang Langchain documentation at natuklasan na may financial retriever na tinatawag na Kay.ai. Sinubukan ko ang retriever para makita kung paano maaaring gumana ang natitirang workflow. Gumana ang retriever ayon sa inaasahan para sa basic queries. Gayunpaman, hindi nito sinusuportahan ang asynchronous calls o advanced metadata filtering. Kaya nagdesisyon akong ipagpatuloy ang paggawa nito sa sarili ko.
Pagbabahagi ng aktwal na Python scripts
Python script para mag-download ng financial filings mula sa SEC EDGAR
Pagkatapos ng maraming pagsubok at pagkakamali, sa tulong ng chatGPT, ito ang code na awtomatikong nagda-download ng XBRL at TXT filings mula sa U.S. Securities and Exchange Commission (SEC) database. Dinisenyo ito para kunin ang mga kamakailang filings para sa mga tinukoy na kumpanya gamit ang kanilang Central Index Key (CIK).
Bakit gusto kong i-download pareho ang .zip file at ang .txt file?
Ang .txt file para sa bawat kumpanya ay sobrang komprehensibo. Marami itong mahahalagang metadata tungkol sa bawat filing tulad ng form type (10K o 10Q), ang reporting period, filed date, company CIK/name, atbp... Ito ang mga uri ng metadata na kakailanganin ko sa ibang pagkakataon para buuin ang agent. Lahat ng ito ay malinis na nakuha sa itaas ng file tulad ng:
<SEC-DOCUMENT>0000320193-19-000119.txt : 20191031
<SEC-HEADER>0000320193-19-000119.hdr.sgml : 20191031
<ACCEPTANCE-DATETIME>20191030181236
ACCESSION NUMBER: 0000320193-19-000119
CONFORMED SUBMISSION TYPE: 10-K
PUBLIC DOCUMENT COUNT: 96
CONFORMED PERIOD OF REPORT: 20190928
FILED AS OF DATE: 20191031
DATE AS OF CHANGE: 20191030
FILER:
COMPANY DATA:
COMPANY CONFORMED NAME: Apple Inc.
CENTRAL INDEX KEY: 0000320193
STANDARD INDUSTRIAL CLASSIFICATION: ELECTRONIC COMPUTERS [3571]
IRS NUMBER: 942404110
STATE OF INCORPORATION: CA
FISCAL YEAR END: 0928
FILING VALUES:
FORM TYPE: 10-K
SEC ACT: 1934 Act
SEC FILE NUMBER: 001-36743
FILM NUMBER: 191181423
BUSINESS ADDRESS:
STREET 1: ONE APPLE PARK WAY
CITY: CUPERTINO
STATE: CA
ZIP: 95014
BUSINESS PHONE: (408) 996-1010
MAIL ADDRESS:
STREET 1: ONE APPLE PARK WAY
CITY: CUPERTINO
STATE: CA
ZIP: 95014
FORMER COMPANY:
FORMER CONFORMED NAME: APPLE INC
DATE OF NAME CHANGE: 20070109
FORMER COMPANY:
FORMER CONFORMED NAME: APPLE COMPUTER INC
DATE OF NAME CHANGE: 19970808
</SEC-HEADER>
Marami pang ibang data ang .txt file, bukod sa core financial report. Dahil doon, napakalaki ng bawat file (tulad ng 10+ MB o kahit 40+ MB). At para sa isang kumpanya, gusto kong i-download ang lahat ng 10K at 10Q sa nakaraang 5 - 10 taon kaya pinag-uusapan natin ang madaling 20+ files bawat kumpanya. Ang pagsubok na i-process/linisin ang mga hindi kailangan na characters/i-chunk ang mga malalaking files na ito ay lubhang hindi produktibo dahil magtatagal ito nang mahabang panahon gamit ang normal na laptop at ang gastos ng embedding ay magiging napakalaki. Kaya kailangan kong maghanap ng ibang paraan.
Dito pumapasok ang .zip file. Sa bawat .zip files, mahahanap mo ang core financial report sa .htm format at iba pang nilalaman. Ang isyu ay hindi consistently pinangalanan ang mga core financial reports na ito sa paglipas ng mga taon, sa lahat ng kumpanya. At ang .htm report ay walang lahat ng mahahalagang meta sa malinis na format tulad ng .txt file.
Pagsasama ng metadata sa pangunahing financial report
Ito ang script na nag-automate ng proseso ng pag-extract ng financial statements at ang kanilang nauugnay na metadata mula sa filings ng maraming kumpanya. Dinisenyo ito para pangasiwaan ang mga files na na-download mula sa SEC EDGAR database, na kasama ang parehong .zip at .txt files.
Makikita mo na gumawa ako ng ilang assumptions:
- Dina-download mo pareho ang .txt at ang .zip versions para sa bawat financial report
- Ang core financial report ay ang pinakamalaking .htm file sa loob ng bawat .zip file. Ito "ay" dapat totoo dahil ang iba pang content ay na-extract mula sa pangunahing ".htm" file
Paglilinis ng hindi kailangan na content/characters bago ang chunking
Ang magandang balita ay sa paggamit ng approach sa itaas, ang bawat report (maging 10K o 10Q) ay ngayon ay may laki na mas mababa sa 3 MB lamang. Pero masyadong mahaba pa rin ito at naglalaman ng napakaraming impormasyon na hindi natin kailangan kaya kailangan nating linisin ito ng higit pa bago i-chunk. Kung hindi, ang embedding process ay tatakbo nang napakatagal at magkakahalaga ng maraming pera. Isipin mo, kung gumastos ka lang ng $0.1 bawat report para sa embedding, iyon ay halos $2.5/kumpanya para sa 10K at 10Q reports sa nakaraang 5 taon. Kung gusto mong saklawin ang karamihan ng S&P 500 o palawigin ang panahon sa huling 10 taon, mabilis itong nadaragdagan.
Kaya narito ang script para sa paglilinis. Pagkatapos ng proseso, ang bawat output ay mas mababa sa 0.2 Mb, 10 beses na mas maliit. May lahat pa rin ng mahahalagang metadata na pinag-usapan natin kanina ang bawat file.
Ngayon, handa na tayo para sa chunking/embedding step.
Aling vector store ang dapat kong gamitin?
Maraming opsyon dito (na may higit 50 vector store options). Pero dahil kailangan kong gumawa ng advanced filtering sa susunod na hakbang gamit ang metadata, ang self-querying retriever ay mukhang akma. Sinasabi ko na "mukhang" dahil may mga disadvantages ang mga ito. Nag-experiment ako sa ilang opsyon, kabilang ang Chroma, ElasticSearch at FAISS sa ngayon.
Bagama't nagbigay ang Chroma at ElasticSearch ng matibay na functionality, medyo malaki ang kanilang mga index sizes (550+ MB para sa Chroma at 800+ Mb para sa ElasticSearch). Ang mga index na ito ay para sa 5 test companies LAMANG. Hindi ito maganda dahil habang isi-scale ko ito sa natitirang S&P 500, ang final index size ay maaaring 100x na mas malaki. Hindi rin akma para sa aking local laptop :D
Ang FAISS index, sa kabilang banda, ay mga 200+ MB lamang para sa parehong 5 test companies. Kulang ang FAISS sa maraming advanced filtering/native integration sa Langchain, kaya kailangan ko pang mag-investigate. Kung may mga mungkahi kayo, sabihin niyo sa akin. (Baka mas kapaki-pakinabang ang karagdagang pag-explore ng mga alternatibong vector stores tulad ng Weaviate o Pinecone?)
Query structuring
Bagama't maganda na isinasama ko ang maraming metadata sa bawat filing/chunk at kasama ang mga ito sa embeddings, naka-store sa vector store, paano natin sasabihin sa machine kung aling mga filters ang gagamitin? Ipinaliwanag ni Lance mula sa Langchain ang "Query structuring for metadata filters" sa video na ito.
Sinundan ko ang approach at ginawa ang pydantic object sa ibaba. Ang object na ito ay may kasamang mga fields na tumutugma sa aktwal na metadata tags na matatagpuan sa financial filings, tulad ng form types (10-K, 10-Q), reporting periods, filing dates, at company identifiers.
import datetime
from typing import Optional
from pydantic import BaseModel, Field
class FinancialFilingsSearch(BaseModel):
"""Search over a database of financial filings for a company, using the actual metadata tags from the filings."""
content_search: str = Field(
...,
description="Similarity search query applied to the content of the financial filings with the SEC.",
)
conformed_submission_type: str = Field(
None,
description="Filter for the type of the SEC filing, such as 10-K (annual report) or 10-Q (quarterly report). ",
)
conformed_period_of_report: Optional[datetime.date] = Field(
None,
description= "Filter for the end date (format: YYYYMMDD) of the reporting period for the filing. For a 10-Q, it's the quarter-end date, and for a 10-K, it's the fiscal year-end date. ",
)
filed_as_of_date: Optional[datetime.date] = Field(
None,
description="Filter for the date (YYYYMMDD) on which the filing was officially submitted to the SEC. Only use if explicitly specified.",
)
company_conformed_name: str = Field(
None,
description="Filter for official name of the company as registered with the SEC",
)
central_index_key: str = Field(
None,
description="Central Index Key (CIK): A unique identifier assigned by the SEC to all entities (companies, individuals, etc.) who file with the SEC.",
)
standard_industrial_classification: Optional[str] = Field(
None,
description="he Standard Industrial Classification Codes that appear in a company's disseminated EDGAR filings indicate the company's type of business. Only use if explicitly specified.",
)
form_type: str = Field(
None,
description="Form type to filter by, such as 10-K or 10-Q.",
)
# Set up language models
llm_35 = ChatOpenAI(model="gpt-3.5-turbo-0125", temperature=0) # GPT-3.5 model
llm_4 = ChatOpenAI(model="gpt-4-turbo-2024-04-09", temperature=0) # GPT-4 model for more complex tasks
from langchain_core.prompts import ChatPromptTemplate
system = """You are an expert at converting user questions into database queries. \
You have access to a vector store of financial filings from public companies to the SEC, for building LLM-powered application. \
Given a question, return a detailed database query optimized to retrieve the most relevant results. \
Be as detailed as possible with your returned query, including all relevant fields and filters. \
Always include conformed_period_of_report. \
If there are acronyms or words you are not familiar with, do not try to rephrase them."""
prompt = ChatPromptTemplate.from_messages(
[
("system", system),
("human", "\{question\}"),
]
)
# Assuming `llm` is your already initialized LLM instance
structured_llm = llm_35.with_structured_output(FinancialFilingsSearch)
query_analyzer = prompt | structured_llm
Halimbawa, ito ang listahan ng mga tanong at ang mga sagot mula sa LLM. Makikita mo na nakakalimutan ng LLM ang "conformed period of reporting" sa ilang kaso:
Question: What was Google's advertising and marketing spending in the 10-K report for the year 2018?
\{'content_search': 'advertising and marketing spending', 'company_conformed_name': 'Alphabet Inc.', 'conformed_submission_type': '10-K', 'form_type': '10-K', 'central_index_key': '0001652044'\}
Question: What was Google's advertising and marketing spending in the 10-K report for the year 2019?
\{'content_search': 'advertising and marketing spending', 'company_conformed_name': 'Alphabet Inc.', 'conformed_submission_type': '10-K', 'form_type': '10-K', 'central_index_key': '0001652044'\}
Question: What was Google's advertising and marketing spending in the 10-K report for the year 2020?
\{'content_search': 'advertising and marketing spending', 'company_conformed_name': 'Alphabet Inc.', 'conformed_submission_type': '10-K', 'form_type': '10-K', 'conformed_period_of_report': '2020'\}
Question: What was Google's advertising and marketing spending in the 10-K report for the year 2021?
\{'content_search': 'advertising and marketing spending', 'company_conformed_name': 'Alphabet Inc.', 'conformed_submission_type': '10-K', 'form_type': '10-K', 'conformed_period_of_report': '2021'\}
Question: What was Google's advertising and marketing spending in the 10-K report for the year 2022?
\{'content_search': 'advertising and marketing spending', 'company_conformed_name': 'Alphabet Inc.', 'form_type': '10-K'\}
Question: How has Google's advertising and marketing spending trended from 2018 to 2022 according to 10-K filings?
\{'content_search': 'advertising and marketing spending', 'company_conformed_name': 'Alphabet Inc.', 'form_type': '10-K'\}
Ok kaya ano ang inaasahan ko sa mahabang (MAHABANG) post na ito?
1. Nakatulong sa inyo sa anumang paraan ang mga na-share na Python codes.
2. Maibabahagi niyo sa akin ang mga thoughts/comments o payo tungkol sa:
- Paano pagbutihin ang query structuring na ginagawa ko? FYI, ginagamit ko rin ang LLM para mag-generate ng sub-questions na nauugnay sa isang input question.
- Paano ko mapapaganda ang embedding/paggamit ng vector store, lalo na ang filtering part?
- o anumang iba pang mungkahi na naisip ko :)
Update Hul 2024
Ang chatbot na ito ay HINDI pa gumagana kaya kung susubukan mong gamitin ang kasalukuyang chatbot sa aking site at magtanong ng financial questions, hindi niya alam :D
Mind the curiosity gap :)
Nagtrabaho ka na ba sa SEC filings o sinubukang bumuo ng self-evaluating agent? Gusto kong marinig kung paano mo nilapitan ang mga hamon sa vector store at metadata filtering.
Maraming salamat,
Chandler
P.S: may bagong release na kurso na pinaplano kong kunin "Generative AI for Software Development Skill Certificate" sa Coursera.





