Overview
The GTM Research Engine uses Google Gemini to power two critical AI components:
Query Strategy Generation - Converts research goals into intelligent search strategies
Evidence Analysis - Extracts technologies and calculates confidence scores from collected evidence
Unlike traditional keyword-based systems, this fully dynamic approach adapts to any research goal without hardcoded rules.
Query Strategy Generation
How It Works
The QueryGenerator service uses LLM prompting to create optimized search strategies for each data source:
# From routes.py:29-35
query_generator = QueryGenerator()
strategies = await query_generator.generate_strategies(
research_goal = payload.research_goal,
search_depth = payload.search_depth,
)
LLM Configuration
The generator uses Gemini 2.5 Flash with a comprehensive system instruction:
# From query_generation.py:156-160
self .model = genai.GenerativeModel(
model_name = 'gemini-2.5-flash' ,
system_instruction = system_instruction,
generation_config = genai.GenerationConfig( temperature = 0.7 )
)
The LLM generates structured query strategies:
{
"strategies" : [
{
"channel" : "google_search" ,
"query_template" : "site:{DOMAIN} AND (fraud detection OR risk analysis)" ,
"relevance_score" : 0.95
},
{
"channel" : "news_search" ,
"query_template" : "{COMPANY_NAME} AND (AI OR machine learning) AND fraud" ,
"relevance_score" : 0.85
},
{
"channel" : "jobs_search" ,
"query_template" : "machine learning engineer fraud detection" ,
"relevance_score" : 0.80
}
]
}
Search Depth Optimization
The number of strategies adapts to search depth:
# From query_generation.py:81-84 (system instruction)
SEARCH DEPTH LEVELS :
- quick: 4 - 6 strategies ( 1 per major source)
- standard: 7 - 10 strategies ( 1 - 2 per major source)
- comprehensive: 11 - 13 strategies ( 2 - 3 per major source)
Relevance Scoring
Strategies are scored and prioritized:
# From query_generation.py:217-218
strategies.sort( key = lambda s : s.relevance_score, reverse = True )
return strategies
High relevance scores (0.9-1.0) indicate direct, specific searches most likely to find strong evidence. Lower scores (0.5-0.7) represent broader exploratory queries.
Two-Stage LLM Analysis
The Extractor service performs dynamic analysis in two stages:
Analyze the research goal to identify relevant signals:
# From extractor.py:139-171
async def generate_extraction_strategy ( self , research_goal : str ) -> ExtractionStrategy:
prompt = f """
RESEARCH GOAL: { research_goal }
"""
response = self .keyword_model.generate_content(prompt)
response_text = response.text.strip()
result = json.loads(response_text)
return ExtractionStrategy(
target_keywords = result.get( "target_keywords" , []),
context_phrases = result.get( "context_phrases" , []),
confidence_boosters = result.get( "confidence_boosters" , [])
)
Example output for “Find companies using AI for fraud detection”:
{
"target_keywords" : [
"chatbot" , "conversational ai" , "fraud detection" ,
"machine learning" , "risk analysis" , "anomaly detection"
],
"context_phrases" : [
"AI-powered fraud prevention" ,
"automated risk assessment" ,
"real-time transaction monitoring"
],
"confidence_boosters" : [
"AI fraud detection platform" ,
"machine learning risk engine"
]
}
Analyze collected evidence using the dynamic strategy:
# From extractor.py:205-221
prompt = f """
RESEARCH GOAL: { research_goal }
RELEVANT KEYWORDS TO LOOK FOR:
{ ', ' .join(strategy.target_keywords) }
CONTEXT PHRASES THAT INDICATE RELEVANCE:
{ ', ' .join(strategy.context_phrases) }
COMPANY EVIDENCE:
{ combined_evidence }
Based on the research goal and keyword strategy, analyze this
company's evidence and extract relevant technologies.
"""
Output format:
{
"relevant_technologies" : [ "TensorFlow" , "Python" , "Kubernetes" ],
"goal_match_signals" : [
"fraud detection algorithms" ,
"AI-powered fraud prevention"
],
"confidence_score" : 0.85
}
Complete Analysis Pipeline
# From extractor.py:249-270
async def analyze_company (
self , research_goal : str , evidences : List[Evidence]
) -> Dict[ str , any ]:
# Step 1: Generate dynamic keyword strategy
strategy = await self .generate_extraction_strategy(research_goal)
# Step 2: Extract technologies using the strategy
extraction_result = await self .extract_technologies_from_evidence(
research_goal, evidences, strategy
)
# Step 3: Return comprehensive result
return {
"technologies" : extraction_result[ "relevant_technologies" ],
"goal_match_signals" : extraction_result[ "goal_match_signals" ],
"confidence_score" : extraction_result[ "confidence_score" ],
}
Integration with Pipeline
Analysis happens after evidence collection:
# From pipeline.py:166-173
domain_tasks = [
asyncio.create_task( self .analyze_domain(domain, evidences))
for domain, evidences in domain_to_evidence.items()
]
results: List[CompanyResearchResult] = []
for coro in asyncio.as_completed(domain_tasks):
result = await coro
results.append(result)
Building Results
# From pipeline.py:183-206
def _build_company_result (
self , domain : str , evidences : List[Evidence], analysis_result : Dict
) -> CompanyResearchResult:
technologies = analysis_result.get( "technologies" , [])
goal_match_signals = analysis_result.get( "goal_match_signals" , [])
confidence_score = analysis_result.get( "confidence_score" , 0.0 )
evidence_sources = len ( set (evidence.source_name for evidence in evidences))
findings = Findings(
technologies = technologies,
evidence = evidences,
signals_found = len (goal_match_signals)
)
return CompanyResearchResult(
domain = domain,
confidence_score = round (confidence_score, 2 ),
evidence_sources = evidence_sources,
findings = findings
)
Rate Limiting
AI operations are rate-limited to prevent API exhaustion:
# From query_generation.py:176
@rate_limited ( "gemini" )
async def _generate_with_llm (
self , research_goal : str , search_depth : str
) -> List[QueryStrategy]:
# ... LLM call logic
# From extractor.py:139
@rate_limited ( "gemini" )
async def generate_extraction_strategy (
self , research_goal : str
) -> ExtractionStrategy:
# ... strategy generation logic
Gemini API has rate limits based on your plan tier. Monitor usage to avoid exhausting quotas during large batch operations.
Caching
Extraction strategies are cached per research goal:
# From extractor.py:137-145
self ._strategy_cache: Dict[ str , ExtractionStrategy] = {}
async def generate_extraction_strategy ( self , research_goal : str ):
# Check cache first
if research_goal in self ._strategy_cache:
return self ._strategy_cache[research_goal]
# ... generate and cache strategy
self ._strategy_cache[research_goal] = strategy
Configuration
Environment Variables
GEMINI_API_KEY = your_gemini_api_key_here
Model Selection
Different models for different tasks:
# From extractor.py:125-134
self .keyword_model = genai.GenerativeModel(
model_name = 'gemini-2.5-flash-lite' , # Fast, cost-effective
system_instruction = keyword_system_prompt,
generation_config = genai.GenerationConfig( temperature = 0.7 )
)
self .evidence_model = genai.GenerativeModel(
model_name = 'gemini-2.5-flash-lite' ,
system_instruction = evidence_system_prompt,
generation_config = genai.GenerationConfig( temperature = 0.7 )
)
Confidence Scoring
Confidence scores range from 0.0 to 1.0 and reflect:
Evidence quality - Direct mentions vs. indirect signals
Signal strength - Number and relevance of matching keywords
Source diversity - Evidence from multiple independent sources
Context alignment - How well evidence matches the research goal
Thresholding
Filter results by confidence:
curl -X POST http://localhost:8000/research/batch \
-H "Content-Type: application/json" \
-d '{
"research_goal": "Find AI companies",
"company_domains": ["openai.com"],
"search_depth": "standard",
"max_parallel_searches": 20,
"confidence_threshold": 0.7
}'
Best Practices
Write Specific Research Goals
Better prompts produce better strategies: ✅ Good: “Find fintech companies using machine learning for fraud detection” ❌ Poor: “Find AI companies”
Adjust Confidence Thresholds
Recommended thresholds by use case:
Lead generation : 0.6-0.7 (broader results)
Sales qualification : 0.75-0.85 (medium precision)
Partnership vetting : 0.85-0.95 (high precision)
Gemini charges per token. Optimize evidence length: # From extractor.py:196-202
for i, evidence in enumerate (evidences[: 3 ]): # Limit to 3
combined_evidence += f """
Evidence { i + 1 } :
Title: { evidence.title }
Content: { evidence.snippet }
"""
For repeated research with the same goal, strategies are automatically cached: # First call: generates strategy via LLM
await query_generator.generate_strategies(
research_goal = "Find AI companies" ,
search_depth = "standard"
)
# Second call with same goal: uses cache (instant)
await query_generator.generate_strategies(
research_goal = "Find AI companies" ,
search_depth = "standard"
)
Example: End-to-End Flow
# 1. Generate search strategies
query_generator = QueryGenerator()
strategies = await query_generator.generate_strategies(
research_goal = "Find companies using Kubernetes in production" ,
search_depth = "standard"
)
# Output: 7-10 strategies across google_search, news_search, jobs_search
# 2. Execute searches (handled by pipeline)
pipeline = ResearchPipeline( ... , strategies = strategies)
results, total_searches = await pipeline.run()
# 3. Analyze evidence
extractor = Extractor()
for domain, evidences in domain_to_evidence.items():
analysis = await extractor.analyze_company(
research_goal = "Find companies using Kubernetes in production" ,
evidences = evidences
)
# Output: {
# "technologies": ["Kubernetes", "Docker", "AWS"],
# "goal_match_signals": ["production kubernetes cluster"],
# "confidence_score": 0.89
# }
Multi-Source Research Learn about data source integration
Real-Time Streaming See how AI analysis streams in real-time