The SEC Agent is a specialized retrieval agent optimized for extracting information from SEC 10-K annual filings. It uses planning-driven parallel retrieval with intelligent section routing and table selection.
Current scope: 10-K filings only (annual reports). Support for 10-Q (quarterly) and 8-K (current events) is under development.
Generates targeted sub-questions instead of repeating the original question.Why this matters: Using targeted sub-questions retrieves different, specific information for each information need.Example:
Question: "What is AMD's inventory turnover ratio for FY2022?"Sub-questions generated:1. "What is the cost of goods sold (COGS)?"2. "What is the ending inventory balance?"3. "What is the beginning inventory balance?"4. "How is inventory valued and managed?"
Parallel Execution
Executes multiple searches concurrently using ThreadPoolExecutor with 6 workers.Before (Sequential): ~170s per question
After (Parallel): ~10s per questionAll sub-question searches run simultaneously, dramatically reducing latency.
Dynamic Replanning
Adjusts search strategy based on evaluation feedback.Process:
Evaluate answer quality
Identify missing information
Generate new targeted searches
Loop back to retrieval (max 5 iterations)
Example:
Evaluation says: "Missing prior year inventory for average calculation"↓New search plan: [{"query": "FY2021 ending inventory", "type": "table"}]
Early Termination
Stops when confidence ≥ 90% (typically 1-3 iterations).Average: 2.4 iterations out of max 5Avoids unnecessary searches when answer is already comprehensive.
Hybrid Search
Combines semantic + TF-IDF with cross-encoder reranking.Weights:
Semantic (vector): 70%
TF-IDF (keyword): 30%
Reranking: ms-marco cross-encoder for better precision
Executes all searches concurrently with 6 workers.
TABLE Queries
TEXT Queries
LLM-based table selection from financial statements:
def select_tables_by_llm(self, question, available_tables, iteration): """ LLM sees all available tables and selects 1-2 most relevant. Prioritizes core financial statements: - Income Statement - Balance Sheet - Cash Flow Statement Avoids selecting same tables as previous iterations. """
Available tables:
Consolidated Statements of Operations (Income Statement)
Uses ALL accumulated chunks to generate comprehensive answer.
def _generate_answer(self, question, sub_questions, chunks, previous_answer): """ Generates answer addressing: - Original question - Each sub-question - Calculations where needed Citations: [10K1], [10K2], etc. """ prompt = f""" Question: {question} Sub-questions to address: {chr(10).join(f'{i+1}. {q}' for i, q in enumerate(sub_questions))} Available information: {format_chunks_with_citations(chunks)} {'Previous answer: ' + previous_answer if previous_answer else ''} Generate a comprehensive answer that: 1. Addresses the main question 2. Answers each sub-question 3. Performs any necessary calculations 4. Cites sources as [10K1], [10K2] 5. Includes specific numbers and quotes """
Problem: Iterative approach used same query repeatedly, getting same results.Solution: Generate targeted sub-questions for specific information needs.Impact:
Before: Same chunks retrieved each iteration
After: Different, targeted chunks for each sub-question
Result: Better coverage, fewer iterations
2. Parallel Over Sequential
Problem: Sequential iterations were slow (~170s/question).Solution: Execute all searches concurrently with ThreadPoolExecutor.Impact:
Before: 170s average
After: 10s average
Speedup: 17x faster
3. Table-First for Numeric Questions
Problem: Text search missed structured financial data in tables.Solution: Prioritize table retrieval for financial questions.
FINANCIAL_KEYWORDS = [ 'revenue', 'income', 'profit', 'assets', 'liabilities', 'earnings', 'sales', 'expenses', 'equity', 'cash flow', 'ratio', 'margin', 'million', 'billion', 'percent', 'eps']if any(kw in question.lower() for kw in FINANCIAL_KEYWORDS): prioritize_tables = True
Impact:
Accuracy on numeric questions: 78% → 94%
Financial statement data properly retrieved
4. LLM-Based Table Selection
Problem: Retrieving all tables was slow and added noise.Solution: LLM selects 1-2 most relevant tables per query.
def select_tables_by_llm(self, question, available_tables, iteration): """ LLM sees all available tables and selects 1-2 most relevant. Prioritizes core financial statements: - Income Statement - Balance Sheet - Cash Flow Statement Avoids selecting same tables as previous iterations. """
Impact:
Precision: Higher relevance, less noise
Speed: Fewer tokens to process
Iterations: Better table diversity across iterations
5. Cross-Encoder Reranking
Problem: Hybrid search alone sometimes ranked less relevant chunks higher.Solution: Rerank top-K chunks using cross-encoder.
def rerank_chunks(self, query, chunks, top_k=10): """ Uses cross-encoder (ms-marco-MiniLM-L-6-v2) to rerank hybrid search results for better precision. """ scores = cross_encoder.predict( [(query, chunk.text) for chunk in chunks] ) reranked = sorted(zip(chunks, scores), key=lambda x: x[1], reverse=True) return [chunk for chunk, _ in reranked[:top_k]]
Impact:
Relevance: 12% improvement in precision@10
Reduces evaluation iteration due to better initial retrieval