[{"data":1,"prerenderedAt":1449},["ShallowReactive",2],{"blog-post-/blogs/openai-serp-api-web-search-ai-summary":3},{"id":4,"title":5,"body":6,"description":1433,"extension":1434,"meta":1435,"navigation":119,"ogImage":1444,"path":1445,"seo":1446,"stem":1447,"__hash__":1448},"en/blogs/en/10.openai-serp-api-web-search-ai-summary.md","Building a Web Search + AI Summary Tool",{"type":7,"value":8,"toc":1415},"minimal",[9,14,18,21,25,28,57,60,75,79,82,87,90,139,143,146,298,302,305,498,502,505,764,768,771,934,938,941,955,958,1303,1307,1310,1314,1317,1328,1332,1335,1346,1349,1363,1367,1370,1381,1385,1388,1391,1405,1408,1411],[10,11,13],"h2",{"id":12},"introduction","Introduction",[15,16,17],"p",{},"In today's fast-paced financial markets, analysts need to process vast amounts of information quickly to make informed decisions. Traditional methods of manually searching the web, reading articles, and synthesizing information are time-consuming and prone to missing critical insights. This is where combining AI with web search capabilities can create a powerful tool for stock analysis.",[15,19,20],{},"In this blog post, I'll share how I built a web search + AI summary tool for a company's experimental stock analysis project. This tool helped analysts quickly gather and synthesize information about stocks they were researching, saving hours of manual work and providing more comprehensive insights.",[10,22,24],{"id":23},"the-power-of-combining-web-search-with-ai","The Power of Combining Web Search with AI",[15,26,27],{},"Before diving into the implementation, let's understand why this combination is particularly powerful:",[29,30,31,39,45,51],"ol",{},[32,33,34,38],"li",{},[35,36,37],"strong",{},"Real-time information access",": SERP (Search Engine Results Page) APIs provide access to the latest information from across the web",[32,40,41,44],{},[35,42,43],{},"Contextual understanding",": Large language models like GPT-4 can understand the context and relevance of information",[32,46,47,50],{},[35,48,49],{},"Synthesis capabilities",": AI can summarize, extract key points, and identify trends across multiple sources",[32,52,53,56],{},[35,54,55],{},"Customizable analysis",": The system can be tailored to focus on specific aspects of stock analysis (financials, news sentiment, market trends)",[15,58,59],{},"For stock analysis specifically, this combination allows analysts to:",[61,62,63,66,69,72],"ul",{},[32,64,65],{},"Quickly gather the latest news and developments about a company",[32,67,68],{},"Analyze market sentiment across multiple sources",[32,70,71],{},"Identify potential risks or opportunities that might be buried in various articles",[32,73,74],{},"Generate comprehensive research summaries in seconds rather than hours",[10,76,78],{"id":77},"implementation-building-the-tool","Implementation: Building the Tool",[15,80,81],{},"Let's walk through how to build this tool step by step.",[83,84,86],"h3",{"id":85},"_1-setting-up-the-environment","1. Setting Up the Environment",[15,88,89],{},"First, we need to set up our environment with the necessary dependencies:",[91,92,97],"pre",{"className":93,"code":94,"language":95,"meta":96,"style":96},"language-javascript shiki shiki-themes dracula","// Install required packages\nnpm install axios openai dotenv\n\n// Create a .env file for API keys\nOPENAI_API_KEY=your_openai_api_key\nSERP_API_KEY=your_serp_api_key\n","javascript","",[98,99,100,108,114,121,127,133],"code",{"__ignoreMap":96},[101,102,105],"span",{"class":103,"line":104},"line",1,[101,106,107],{},"// Install required packages\n",[101,109,111],{"class":103,"line":110},2,[101,112,113],{},"npm install axios openai dotenv\n",[101,115,117],{"class":103,"line":116},3,[101,118,120],{"emptyLinePlaceholder":119},true,"\n",[101,122,124],{"class":103,"line":123},4,[101,125,126],{},"// Create a .env file for API keys\n",[101,128,130],{"class":103,"line":129},5,[101,131,132],{},"OPENAI_API_KEY=your_openai_api_key\n",[101,134,136],{"class":103,"line":135},6,[101,137,138],{},"SERP_API_KEY=your_serp_api_key\n",[83,140,142],{"id":141},"_2-configuring-the-serp-api","2. Configuring the SERP API",[15,144,145],{},"We'll use a SERP API to fetch search results. There are several providers available, but for this project, I used SerpAPI which provides structured data from search engines:",[91,147,149],{"className":93,"code":148,"language":95,"meta":96,"style":96},"const axios = require('axios')\nrequire('dotenv').config()\n\nasync function searchWeb(query, numResults = 5) {\n  try {\n    const response = await axios.get('https://serpapi.com/search', {\n      params: {\n        q: query,\n        api_key: process.env.SERP_API_KEY,\n        num: numResults,\n      },\n    })\n\n    // Extract the organic search results\n    const searchResults = response.data.organic_results.map((result) => ({\n      title: result.title,\n      link: result.link,\n      snippet: result.snippet,\n    }))\n\n    return searchResults\n  } catch (error) {\n    console.error('Error searching the web:', error)\n    throw error\n  }\n}\n",[98,150,151,156,161,165,170,175,180,186,192,198,204,210,216,221,227,233,239,245,251,257,262,268,274,280,286,292],{"__ignoreMap":96},[101,152,153],{"class":103,"line":104},[101,154,155],{},"const axios = require('axios')\n",[101,157,158],{"class":103,"line":110},[101,159,160],{},"require('dotenv').config()\n",[101,162,163],{"class":103,"line":116},[101,164,120],{"emptyLinePlaceholder":119},[101,166,167],{"class":103,"line":123},[101,168,169],{},"async function searchWeb(query, numResults = 5) {\n",[101,171,172],{"class":103,"line":129},[101,173,174],{},"  try {\n",[101,176,177],{"class":103,"line":135},[101,178,179],{},"    const response = await axios.get('https://serpapi.com/search', {\n",[101,181,183],{"class":103,"line":182},7,[101,184,185],{},"      params: {\n",[101,187,189],{"class":103,"line":188},8,[101,190,191],{},"        q: query,\n",[101,193,195],{"class":103,"line":194},9,[101,196,197],{},"        api_key: process.env.SERP_API_KEY,\n",[101,199,201],{"class":103,"line":200},10,[101,202,203],{},"        num: numResults,\n",[101,205,207],{"class":103,"line":206},11,[101,208,209],{},"      },\n",[101,211,213],{"class":103,"line":212},12,[101,214,215],{},"    })\n",[101,217,219],{"class":103,"line":218},13,[101,220,120],{"emptyLinePlaceholder":119},[101,222,224],{"class":103,"line":223},14,[101,225,226],{},"    // Extract the organic search results\n",[101,228,230],{"class":103,"line":229},15,[101,231,232],{},"    const searchResults = response.data.organic_results.map((result) => ({\n",[101,234,236],{"class":103,"line":235},16,[101,237,238],{},"      title: result.title,\n",[101,240,242],{"class":103,"line":241},17,[101,243,244],{},"      link: result.link,\n",[101,246,248],{"class":103,"line":247},18,[101,249,250],{},"      snippet: result.snippet,\n",[101,252,254],{"class":103,"line":253},19,[101,255,256],{},"    }))\n",[101,258,260],{"class":103,"line":259},20,[101,261,120],{"emptyLinePlaceholder":119},[101,263,265],{"class":103,"line":264},21,[101,266,267],{},"    return searchResults\n",[101,269,271],{"class":103,"line":270},22,[101,272,273],{},"  } catch (error) {\n",[101,275,277],{"class":103,"line":276},23,[101,278,279],{},"    console.error('Error searching the web:', error)\n",[101,281,283],{"class":103,"line":282},24,[101,284,285],{},"    throw error\n",[101,287,289],{"class":103,"line":288},25,[101,290,291],{},"  }\n",[101,293,295],{"class":103,"line":294},26,[101,296,297],{},"}\n",[83,299,301],{"id":300},"_3-fetching-content-from-search-results","3. Fetching Content from Search Results",[15,303,304],{},"Once we have the search results, we need to fetch the actual content from the web pages:",[91,306,308],{"className":93,"code":307,"language":95,"meta":96,"style":96},"const axios = require('axios')\nconst cheerio = require('cheerio')\n\nasync function fetchContent(url) {\n  try {\n    const response = await axios.get(url)\n    const $ = cheerio.load(response.data)\n\n    // Remove script tags, style tags, and other non-content elements\n    $('script, style, meta, link').remove()\n\n    // Extract the main content (this is a simplified approach)\n    // For production, you might want to use more sophisticated content extraction\n    const content = $('body').text().replace(/\\s+/g, ' ').trim()\n\n    return content\n  } catch (error) {\n    console.error(`Error fetching content from ${url}:`, error)\n    return '' // Return empty string if content can't be fetched\n  }\n}\n\nasync function fetchContentsFromSearchResults(searchResults) {\n  const contents = []\n\n  for (const result of searchResults) {\n    const content = await fetchContent(result.link)\n    if (content) {\n      contents.push({\n        title: result.title,\n        url: result.link,\n        content: content.substring(0, 8000), // Limit content length\n      })\n    }\n  }\n\n  return contents\n}\n",[98,309,310,314,319,323,328,332,337,342,346,351,356,360,365,370,375,379,384,388,393,398,402,406,410,415,420,424,429,435,441,447,453,459,465,471,477,482,487,493],{"__ignoreMap":96},[101,311,312],{"class":103,"line":104},[101,313,155],{},[101,315,316],{"class":103,"line":110},[101,317,318],{},"const cheerio = require('cheerio')\n",[101,320,321],{"class":103,"line":116},[101,322,120],{"emptyLinePlaceholder":119},[101,324,325],{"class":103,"line":123},[101,326,327],{},"async function fetchContent(url) {\n",[101,329,330],{"class":103,"line":129},[101,331,174],{},[101,333,334],{"class":103,"line":135},[101,335,336],{},"    const response = await axios.get(url)\n",[101,338,339],{"class":103,"line":182},[101,340,341],{},"    const $ = cheerio.load(response.data)\n",[101,343,344],{"class":103,"line":188},[101,345,120],{"emptyLinePlaceholder":119},[101,347,348],{"class":103,"line":194},[101,349,350],{},"    // Remove script tags, style tags, and other non-content elements\n",[101,352,353],{"class":103,"line":200},[101,354,355],{},"    $('script, style, meta, link').remove()\n",[101,357,358],{"class":103,"line":206},[101,359,120],{"emptyLinePlaceholder":119},[101,361,362],{"class":103,"line":212},[101,363,364],{},"    // Extract the main content (this is a simplified approach)\n",[101,366,367],{"class":103,"line":218},[101,368,369],{},"    // For production, you might want to use more sophisticated content extraction\n",[101,371,372],{"class":103,"line":223},[101,373,374],{},"    const content = $('body').text().replace(/\\s+/g, ' ').trim()\n",[101,376,377],{"class":103,"line":229},[101,378,120],{"emptyLinePlaceholder":119},[101,380,381],{"class":103,"line":235},[101,382,383],{},"    return content\n",[101,385,386],{"class":103,"line":241},[101,387,273],{},[101,389,390],{"class":103,"line":247},[101,391,392],{},"    console.error(`Error fetching content from ${url}:`, error)\n",[101,394,395],{"class":103,"line":253},[101,396,397],{},"    return '' // Return empty string if content can't be fetched\n",[101,399,400],{"class":103,"line":259},[101,401,291],{},[101,403,404],{"class":103,"line":264},[101,405,297],{},[101,407,408],{"class":103,"line":270},[101,409,120],{"emptyLinePlaceholder":119},[101,411,412],{"class":103,"line":276},[101,413,414],{},"async function fetchContentsFromSearchResults(searchResults) {\n",[101,416,417],{"class":103,"line":282},[101,418,419],{},"  const contents = []\n",[101,421,422],{"class":103,"line":288},[101,423,120],{"emptyLinePlaceholder":119},[101,425,426],{"class":103,"line":294},[101,427,428],{},"  for (const result of searchResults) {\n",[101,430,432],{"class":103,"line":431},27,[101,433,434],{},"    const content = await fetchContent(result.link)\n",[101,436,438],{"class":103,"line":437},28,[101,439,440],{},"    if (content) {\n",[101,442,444],{"class":103,"line":443},29,[101,445,446],{},"      contents.push({\n",[101,448,450],{"class":103,"line":449},30,[101,451,452],{},"        title: result.title,\n",[101,454,456],{"class":103,"line":455},31,[101,457,458],{},"        url: result.link,\n",[101,460,462],{"class":103,"line":461},32,[101,463,464],{},"        content: content.substring(0, 8000), // Limit content length\n",[101,466,468],{"class":103,"line":467},33,[101,469,470],{},"      })\n",[101,472,474],{"class":103,"line":473},34,[101,475,476],{},"    }\n",[101,478,480],{"class":103,"line":479},35,[101,481,291],{},[101,483,485],{"class":103,"line":484},36,[101,486,120],{"emptyLinePlaceholder":119},[101,488,490],{"class":103,"line":489},37,[101,491,492],{},"  return contents\n",[101,494,496],{"class":103,"line":495},38,[101,497,297],{},[83,499,501],{"id":500},"_4-integrating-with-openai-api","4. Integrating with OpenAI API",[15,503,504],{},"Now, we'll use OpenAI's API to summarize and analyze the content:",[91,506,508],{"className":93,"code":507,"language":95,"meta":96,"style":96},"const { OpenAI } = require('openai')\nrequire('dotenv').config()\n\nconst openai = new OpenAI({\n  apiKey: process.env.OPENAI_API_KEY,\n})\n\nasync function summarizeWithAI(stockSymbol, contents) {\n  // Prepare the content for the AI\n  const contentText = contents\n    .map((item) => `Source: ${item.title} (${item.url})\\n${item.content}\\n\\n`)\n    .join('')\n\n  // Create the prompt for the AI\n  const prompt = `\n    You are a financial analyst assistant. Below are web search results about the stock ${stockSymbol}.\n    Please analyze these results and provide:\n    \n    1. A summary of key recent developments\n    2. Analysis of market sentiment (positive, negative, neutral)\n    3. Potential impact on stock price\n    4. Key financial metrics mentioned\n    5. Any risks or opportunities identified\n    \n    Make your analysis concise, factual, and focused on information that would be relevant to investors.\n    \n    Search Results:\n    ${contentText}\n  `\n\n  try {\n    const response = await openai.chat.completions.create({\n      model: 'gpt-4',\n      messages: [\n        {\n          role: 'system',\n          content:\n            'You are a financial analyst assistant that helps analyze stock information from web search results.',\n        },\n        { role: 'user', content: prompt },\n      ],\n      temperature: 0.2, // Lower temperature for more factual responses\n      max_tokens: 1500,\n    })\n\n    return response.choices[0].message.content\n  } catch (error) {\n    console.error('Error generating AI summary:', error)\n    throw error\n  }\n}\n",[98,509,510,515,519,523,528,533,538,542,547,552,557,562,567,571,576,581,586,591,596,601,606,611,616,621,625,630,634,639,644,649,653,657,662,667,672,677,682,687,692,698,704,710,716,722,727,732,738,743,749,754,759],{"__ignoreMap":96},[101,511,512],{"class":103,"line":104},[101,513,514],{},"const { OpenAI } = require('openai')\n",[101,516,517],{"class":103,"line":110},[101,518,160],{},[101,520,521],{"class":103,"line":116},[101,522,120],{"emptyLinePlaceholder":119},[101,524,525],{"class":103,"line":123},[101,526,527],{},"const openai = new OpenAI({\n",[101,529,530],{"class":103,"line":129},[101,531,532],{},"  apiKey: process.env.OPENAI_API_KEY,\n",[101,534,535],{"class":103,"line":135},[101,536,537],{},"})\n",[101,539,540],{"class":103,"line":182},[101,541,120],{"emptyLinePlaceholder":119},[101,543,544],{"class":103,"line":188},[101,545,546],{},"async function summarizeWithAI(stockSymbol, contents) {\n",[101,548,549],{"class":103,"line":194},[101,550,551],{},"  // Prepare the content for the AI\n",[101,553,554],{"class":103,"line":200},[101,555,556],{},"  const contentText = contents\n",[101,558,559],{"class":103,"line":206},[101,560,561],{},"    .map((item) => `Source: ${item.title} (${item.url})\\n${item.content}\\n\\n`)\n",[101,563,564],{"class":103,"line":212},[101,565,566],{},"    .join('')\n",[101,568,569],{"class":103,"line":218},[101,570,120],{"emptyLinePlaceholder":119},[101,572,573],{"class":103,"line":223},[101,574,575],{},"  // Create the prompt for the AI\n",[101,577,578],{"class":103,"line":229},[101,579,580],{},"  const prompt = `\n",[101,582,583],{"class":103,"line":235},[101,584,585],{},"    You are a financial analyst assistant. Below are web search results about the stock ${stockSymbol}.\n",[101,587,588],{"class":103,"line":241},[101,589,590],{},"    Please analyze these results and provide:\n",[101,592,593],{"class":103,"line":247},[101,594,595],{},"    \n",[101,597,598],{"class":103,"line":253},[101,599,600],{},"    1. A summary of key recent developments\n",[101,602,603],{"class":103,"line":259},[101,604,605],{},"    2. Analysis of market sentiment (positive, negative, neutral)\n",[101,607,608],{"class":103,"line":264},[101,609,610],{},"    3. Potential impact on stock price\n",[101,612,613],{"class":103,"line":270},[101,614,615],{},"    4. Key financial metrics mentioned\n",[101,617,618],{"class":103,"line":276},[101,619,620],{},"    5. Any risks or opportunities identified\n",[101,622,623],{"class":103,"line":282},[101,624,595],{},[101,626,627],{"class":103,"line":288},[101,628,629],{},"    Make your analysis concise, factual, and focused on information that would be relevant to investors.\n",[101,631,632],{"class":103,"line":294},[101,633,595],{},[101,635,636],{"class":103,"line":431},[101,637,638],{},"    Search Results:\n",[101,640,641],{"class":103,"line":437},[101,642,643],{},"    ${contentText}\n",[101,645,646],{"class":103,"line":443},[101,647,648],{},"  `\n",[101,650,651],{"class":103,"line":449},[101,652,120],{"emptyLinePlaceholder":119},[101,654,655],{"class":103,"line":455},[101,656,174],{},[101,658,659],{"class":103,"line":461},[101,660,661],{},"    const response = await openai.chat.completions.create({\n",[101,663,664],{"class":103,"line":467},[101,665,666],{},"      model: 'gpt-4',\n",[101,668,669],{"class":103,"line":473},[101,670,671],{},"      messages: [\n",[101,673,674],{"class":103,"line":479},[101,675,676],{},"        {\n",[101,678,679],{"class":103,"line":484},[101,680,681],{},"          role: 'system',\n",[101,683,684],{"class":103,"line":489},[101,685,686],{},"          content:\n",[101,688,689],{"class":103,"line":495},[101,690,691],{},"            'You are a financial analyst assistant that helps analyze stock information from web search results.',\n",[101,693,695],{"class":103,"line":694},39,[101,696,697],{},"        },\n",[101,699,701],{"class":103,"line":700},40,[101,702,703],{},"        { role: 'user', content: prompt },\n",[101,705,707],{"class":103,"line":706},41,[101,708,709],{},"      ],\n",[101,711,713],{"class":103,"line":712},42,[101,714,715],{},"      temperature: 0.2, // Lower temperature for more factual responses\n",[101,717,719],{"class":103,"line":718},43,[101,720,721],{},"      max_tokens: 1500,\n",[101,723,725],{"class":103,"line":724},44,[101,726,215],{},[101,728,730],{"class":103,"line":729},45,[101,731,120],{"emptyLinePlaceholder":119},[101,733,735],{"class":103,"line":734},46,[101,736,737],{},"    return response.choices[0].message.content\n",[101,739,741],{"class":103,"line":740},47,[101,742,273],{},[101,744,746],{"class":103,"line":745},48,[101,747,748],{},"    console.error('Error generating AI summary:', error)\n",[101,750,752],{"class":103,"line":751},49,[101,753,285],{},[101,755,757],{"class":103,"line":756},50,[101,758,291],{},[101,760,762],{"class":103,"line":761},51,[101,763,297],{},[83,765,767],{"id":766},"_5-putting-it-all-together","5. Putting It All Together",[15,769,770],{},"Finally, let's create the main function that ties everything together:",[91,772,774],{"className":93,"code":773,"language":95,"meta":96,"style":96},"async function analyzeStock(stockSymbol) {\n  try {\n    console.log(`Analyzing stock: ${stockSymbol}...`)\n\n    // Step 1: Search for recent information about the stock\n    const searchQuery = `${stockSymbol} stock news financial analysis recent developments`\n    const searchResults = await searchWeb(searchQuery, 8)\n\n    // Step 2: Fetch content from search results\n    const contents = await fetchContentsFromSearchResults(searchResults)\n\n    // Step 3: Generate AI summary and analysis\n    const analysis = await summarizeWithAI(stockSymbol, contents)\n\n    return {\n      stockSymbol,\n      searchResults,\n      analysis,\n    }\n  } catch (error) {\n    console.error(`Error analyzing stock ${stockSymbol}:`, error)\n    throw error\n  }\n}\n\n// Example usage\nanalyzeStock('AAPL')\n  .then((result) => {\n    console.log('Analysis complete:')\n    console.log(result.analysis)\n  })\n  .catch((error) => {\n    console.error('Analysis failed:', error)\n  })\n",[98,775,776,781,785,790,794,799,804,809,813,818,823,827,832,837,841,846,851,856,861,865,869,874,878,882,886,890,895,900,905,910,915,920,925,930],{"__ignoreMap":96},[101,777,778],{"class":103,"line":104},[101,779,780],{},"async function analyzeStock(stockSymbol) {\n",[101,782,783],{"class":103,"line":110},[101,784,174],{},[101,786,787],{"class":103,"line":116},[101,788,789],{},"    console.log(`Analyzing stock: ${stockSymbol}...`)\n",[101,791,792],{"class":103,"line":123},[101,793,120],{"emptyLinePlaceholder":119},[101,795,796],{"class":103,"line":129},[101,797,798],{},"    // Step 1: Search for recent information about the stock\n",[101,800,801],{"class":103,"line":135},[101,802,803],{},"    const searchQuery = `${stockSymbol} stock news financial analysis recent developments`\n",[101,805,806],{"class":103,"line":182},[101,807,808],{},"    const searchResults = await searchWeb(searchQuery, 8)\n",[101,810,811],{"class":103,"line":188},[101,812,120],{"emptyLinePlaceholder":119},[101,814,815],{"class":103,"line":194},[101,816,817],{},"    // Step 2: Fetch content from search results\n",[101,819,820],{"class":103,"line":200},[101,821,822],{},"    const contents = await fetchContentsFromSearchResults(searchResults)\n",[101,824,825],{"class":103,"line":206},[101,826,120],{"emptyLinePlaceholder":119},[101,828,829],{"class":103,"line":212},[101,830,831],{},"    // Step 3: Generate AI summary and analysis\n",[101,833,834],{"class":103,"line":218},[101,835,836],{},"    const analysis = await summarizeWithAI(stockSymbol, contents)\n",[101,838,839],{"class":103,"line":223},[101,840,120],{"emptyLinePlaceholder":119},[101,842,843],{"class":103,"line":229},[101,844,845],{},"    return {\n",[101,847,848],{"class":103,"line":235},[101,849,850],{},"      stockSymbol,\n",[101,852,853],{"class":103,"line":241},[101,854,855],{},"      searchResults,\n",[101,857,858],{"class":103,"line":247},[101,859,860],{},"      analysis,\n",[101,862,863],{"class":103,"line":253},[101,864,476],{},[101,866,867],{"class":103,"line":259},[101,868,273],{},[101,870,871],{"class":103,"line":264},[101,872,873],{},"    console.error(`Error analyzing stock ${stockSymbol}:`, error)\n",[101,875,876],{"class":103,"line":270},[101,877,285],{},[101,879,880],{"class":103,"line":276},[101,881,291],{},[101,883,884],{"class":103,"line":282},[101,885,297],{},[101,887,888],{"class":103,"line":288},[101,889,120],{"emptyLinePlaceholder":119},[101,891,892],{"class":103,"line":294},[101,893,894],{},"// Example usage\n",[101,896,897],{"class":103,"line":431},[101,898,899],{},"analyzeStock('AAPL')\n",[101,901,902],{"class":103,"line":437},[101,903,904],{},"  .then((result) => {\n",[101,906,907],{"class":103,"line":443},[101,908,909],{},"    console.log('Analysis complete:')\n",[101,911,912],{"class":103,"line":449},[101,913,914],{},"    console.log(result.analysis)\n",[101,916,917],{"class":103,"line":455},[101,918,919],{},"  })\n",[101,921,922],{"class":103,"line":461},[101,923,924],{},"  .catch((error) => {\n",[101,926,927],{"class":103,"line":467},[101,928,929],{},"    console.error('Analysis failed:', error)\n",[101,931,932],{"class":103,"line":473},[101,933,919],{},[10,935,937],{"id":936},"real-world-application-stock-analysis-dashboard","Real-World Application: Stock Analysis Dashboard",[15,939,940],{},"For our company's experimental project, we integrated this functionality into a dashboard that allowed analysts to:",[29,942,943,946,949,952],{},[32,944,945],{},"Input multiple stock symbols for analysis",[32,947,948],{},"Customize the search parameters (time range, focus areas)",[32,950,951],{},"Compare AI-generated summaries side by side",[32,953,954],{},"Save and track analyses over time to identify trends",[15,956,957],{},"The dashboard looked something like this:",[91,959,961],{"className":93,"code":960,"language":95,"meta":96,"style":96},"// React component example (simplified)\nfunction StockAnalysisDashboard() {\n  const [stocks, setStocks] = useState([])\n  const [loading, setLoading] = useState({})\n  const [analyses, setAnalyses] = useState({})\n\n  const addStock = (symbol) => {\n    if (!stocks.includes(symbol)) {\n      setStocks([...stocks, symbol])\n      analyzeStockAndUpdateState(symbol)\n    }\n  }\n\n  const analyzeStockAndUpdateState = async (symbol) => {\n    setLoading((prev) => ({ ...prev, [symbol]: true }))\n    try {\n      const result = await analyzeStock(symbol)\n      setAnalyses((prev) => ({ ...prev, [symbol]: result }))\n    } catch (error) {\n      console.error(`Error analyzing ${symbol}:`, error)\n    } finally {\n      setLoading((prev) => ({ ...prev, [symbol]: false }))\n    }\n  }\n\n  return (\n    \u003Cdiv className=\"dashboard\">\n      \u003Ch1>Stock Analysis Dashboard\u003C/h1>\n\n      \u003Cdiv className=\"stock-input\">\n        \u003Cinput\n          type=\"text\"\n          placeholder=\"Enter stock symbol (e.g., AAPL)\"\n          onKeyPress={(e) => e.key === 'Enter' && addStock(e.target.value)}\n        />\n      \u003C/div>\n\n      \u003Cdiv className=\"stock-analyses\">\n        {stocks.map((symbol) => (\n          \u003Cdiv key={symbol} className=\"stock-card\">\n            \u003Ch2>{symbol}\u003C/h2>\n            {loading[symbol] ? (\n              \u003Cp>Loading analysis...\u003C/p>\n            ) : analyses[symbol] ? (\n              \u003Cdiv>\n                \u003Ch3>AI Analysis\u003C/h3>\n                \u003Cdiv className=\"analysis-content\">{analyses[symbol].analysis}\u003C/div>\n                \u003Ch3>Sources\u003C/h3>\n                \u003Cul>\n                  {analyses[symbol].searchResults.map((result, i) => (\n                    \u003Cli key={i}>\n                      \u003Ca href={result.link} target=\"_blank\" rel=\"noopener noreferrer\">\n                        {result.title}\n                      \u003C/a>\n                    \u003C/li>\n                  ))}\n                \u003C/ul>\n              \u003C/div>\n            ) : (\n              \u003Cp>No analysis available\u003C/p>\n            )}\n          \u003C/div>\n        ))}\n      \u003C/div>\n    \u003C/div>\n  )\n}\n",[98,962,963,968,973,978,983,988,992,997,1002,1007,1012,1016,1020,1024,1029,1034,1039,1044,1049,1054,1059,1064,1069,1073,1077,1081,1086,1091,1096,1100,1105,1110,1115,1120,1125,1130,1135,1139,1144,1149,1154,1159,1164,1169,1174,1179,1184,1189,1194,1199,1204,1209,1215,1221,1227,1233,1239,1245,1251,1257,1263,1269,1275,1281,1286,1292,1298],{"__ignoreMap":96},[101,964,965],{"class":103,"line":104},[101,966,967],{},"// React component example (simplified)\n",[101,969,970],{"class":103,"line":110},[101,971,972],{},"function StockAnalysisDashboard() {\n",[101,974,975],{"class":103,"line":116},[101,976,977],{},"  const [stocks, setStocks] = useState([])\n",[101,979,980],{"class":103,"line":123},[101,981,982],{},"  const [loading, setLoading] = useState({})\n",[101,984,985],{"class":103,"line":129},[101,986,987],{},"  const [analyses, setAnalyses] = useState({})\n",[101,989,990],{"class":103,"line":135},[101,991,120],{"emptyLinePlaceholder":119},[101,993,994],{"class":103,"line":182},[101,995,996],{},"  const addStock = (symbol) => {\n",[101,998,999],{"class":103,"line":188},[101,1000,1001],{},"    if (!stocks.includes(symbol)) {\n",[101,1003,1004],{"class":103,"line":194},[101,1005,1006],{},"      setStocks([...stocks, symbol])\n",[101,1008,1009],{"class":103,"line":200},[101,1010,1011],{},"      analyzeStockAndUpdateState(symbol)\n",[101,1013,1014],{"class":103,"line":206},[101,1015,476],{},[101,1017,1018],{"class":103,"line":212},[101,1019,291],{},[101,1021,1022],{"class":103,"line":218},[101,1023,120],{"emptyLinePlaceholder":119},[101,1025,1026],{"class":103,"line":223},[101,1027,1028],{},"  const analyzeStockAndUpdateState = async (symbol) => {\n",[101,1030,1031],{"class":103,"line":229},[101,1032,1033],{},"    setLoading((prev) => ({ ...prev, [symbol]: true }))\n",[101,1035,1036],{"class":103,"line":235},[101,1037,1038],{},"    try {\n",[101,1040,1041],{"class":103,"line":241},[101,1042,1043],{},"      const result = await analyzeStock(symbol)\n",[101,1045,1046],{"class":103,"line":247},[101,1047,1048],{},"      setAnalyses((prev) => ({ ...prev, [symbol]: result }))\n",[101,1050,1051],{"class":103,"line":253},[101,1052,1053],{},"    } catch (error) {\n",[101,1055,1056],{"class":103,"line":259},[101,1057,1058],{},"      console.error(`Error analyzing ${symbol}:`, error)\n",[101,1060,1061],{"class":103,"line":264},[101,1062,1063],{},"    } finally {\n",[101,1065,1066],{"class":103,"line":270},[101,1067,1068],{},"      setLoading((prev) => ({ ...prev, [symbol]: false }))\n",[101,1070,1071],{"class":103,"line":276},[101,1072,476],{},[101,1074,1075],{"class":103,"line":282},[101,1076,291],{},[101,1078,1079],{"class":103,"line":288},[101,1080,120],{"emptyLinePlaceholder":119},[101,1082,1083],{"class":103,"line":294},[101,1084,1085],{},"  return (\n",[101,1087,1088],{"class":103,"line":431},[101,1089,1090],{},"    \u003Cdiv className=\"dashboard\">\n",[101,1092,1093],{"class":103,"line":437},[101,1094,1095],{},"      \u003Ch1>Stock Analysis Dashboard\u003C/h1>\n",[101,1097,1098],{"class":103,"line":443},[101,1099,120],{"emptyLinePlaceholder":119},[101,1101,1102],{"class":103,"line":449},[101,1103,1104],{},"      \u003Cdiv className=\"stock-input\">\n",[101,1106,1107],{"class":103,"line":455},[101,1108,1109],{},"        \u003Cinput\n",[101,1111,1112],{"class":103,"line":461},[101,1113,1114],{},"          type=\"text\"\n",[101,1116,1117],{"class":103,"line":467},[101,1118,1119],{},"          placeholder=\"Enter stock symbol (e.g., AAPL)\"\n",[101,1121,1122],{"class":103,"line":473},[101,1123,1124],{},"          onKeyPress={(e) => e.key === 'Enter' && addStock(e.target.value)}\n",[101,1126,1127],{"class":103,"line":479},[101,1128,1129],{},"        />\n",[101,1131,1132],{"class":103,"line":484},[101,1133,1134],{},"      \u003C/div>\n",[101,1136,1137],{"class":103,"line":489},[101,1138,120],{"emptyLinePlaceholder":119},[101,1140,1141],{"class":103,"line":495},[101,1142,1143],{},"      \u003Cdiv className=\"stock-analyses\">\n",[101,1145,1146],{"class":103,"line":694},[101,1147,1148],{},"        {stocks.map((symbol) => (\n",[101,1150,1151],{"class":103,"line":700},[101,1152,1153],{},"          \u003Cdiv key={symbol} className=\"stock-card\">\n",[101,1155,1156],{"class":103,"line":706},[101,1157,1158],{},"            \u003Ch2>{symbol}\u003C/h2>\n",[101,1160,1161],{"class":103,"line":712},[101,1162,1163],{},"            {loading[symbol] ? (\n",[101,1165,1166],{"class":103,"line":718},[101,1167,1168],{},"              \u003Cp>Loading analysis...\u003C/p>\n",[101,1170,1171],{"class":103,"line":724},[101,1172,1173],{},"            ) : analyses[symbol] ? (\n",[101,1175,1176],{"class":103,"line":729},[101,1177,1178],{},"              \u003Cdiv>\n",[101,1180,1181],{"class":103,"line":734},[101,1182,1183],{},"                \u003Ch3>AI Analysis\u003C/h3>\n",[101,1185,1186],{"class":103,"line":740},[101,1187,1188],{},"                \u003Cdiv className=\"analysis-content\">{analyses[symbol].analysis}\u003C/div>\n",[101,1190,1191],{"class":103,"line":745},[101,1192,1193],{},"                \u003Ch3>Sources\u003C/h3>\n",[101,1195,1196],{"class":103,"line":751},[101,1197,1198],{},"                \u003Cul>\n",[101,1200,1201],{"class":103,"line":756},[101,1202,1203],{},"                  {analyses[symbol].searchResults.map((result, i) => (\n",[101,1205,1206],{"class":103,"line":761},[101,1207,1208],{},"                    \u003Cli key={i}>\n",[101,1210,1212],{"class":103,"line":1211},52,[101,1213,1214],{},"                      \u003Ca href={result.link} target=\"_blank\" rel=\"noopener noreferrer\">\n",[101,1216,1218],{"class":103,"line":1217},53,[101,1219,1220],{},"                        {result.title}\n",[101,1222,1224],{"class":103,"line":1223},54,[101,1225,1226],{},"                      \u003C/a>\n",[101,1228,1230],{"class":103,"line":1229},55,[101,1231,1232],{},"                    \u003C/li>\n",[101,1234,1236],{"class":103,"line":1235},56,[101,1237,1238],{},"                  ))}\n",[101,1240,1242],{"class":103,"line":1241},57,[101,1243,1244],{},"                \u003C/ul>\n",[101,1246,1248],{"class":103,"line":1247},58,[101,1249,1250],{},"              \u003C/div>\n",[101,1252,1254],{"class":103,"line":1253},59,[101,1255,1256],{},"            ) : (\n",[101,1258,1260],{"class":103,"line":1259},60,[101,1261,1262],{},"              \u003Cp>No analysis available\u003C/p>\n",[101,1264,1266],{"class":103,"line":1265},61,[101,1267,1268],{},"            )}\n",[101,1270,1272],{"class":103,"line":1271},62,[101,1273,1274],{},"          \u003C/div>\n",[101,1276,1278],{"class":103,"line":1277},63,[101,1279,1280],{},"        ))}\n",[101,1282,1284],{"class":103,"line":1283},64,[101,1285,1134],{},[101,1287,1289],{"class":103,"line":1288},65,[101,1290,1291],{},"    \u003C/div>\n",[101,1293,1295],{"class":103,"line":1294},66,[101,1296,1297],{},"  )\n",[101,1299,1301],{"class":103,"line":1300},67,[101,1302,297],{},[10,1304,1306],{"id":1305},"challenges-and-considerations","Challenges and Considerations",[15,1308,1309],{},"While building this tool, we encountered several challenges worth noting:",[83,1311,1313],{"id":1312},"_1-api-rate-limits-and-costs","1. API Rate Limits and Costs",[15,1315,1316],{},"Both SERP APIs and OpenAI's API have rate limits and usage costs. For a production system, you'll need to:",[61,1318,1319,1322,1325],{},[32,1320,1321],{},"Implement caching to avoid redundant searches",[32,1323,1324],{},"Set up usage monitoring and alerts",[32,1326,1327],{},"Consider batch processing for multiple stocks",[83,1329,1331],{"id":1330},"_2-content-extraction-quality","2. Content Extraction Quality",[15,1333,1334],{},"Extracting meaningful content from web pages can be challenging due to:",[61,1336,1337,1340,1343],{},[32,1338,1339],{},"Paywalls on financial news sites",[32,1341,1342],{},"Dynamic content loaded via JavaScript",[32,1344,1345],{},"Varied page structures across different sites",[15,1347,1348],{},"We improved our content extraction by:",[61,1350,1351,1357,1360],{},[32,1352,1353,1354],{},"Using more sophisticated libraries like ",[98,1355,1356],{},"mozilla/readability",[32,1358,1359],{},"Implementing site-specific extractors for common financial news sources",[32,1361,1362],{},"Falling back to meta descriptions when full content wasn't available",[83,1364,1366],{"id":1365},"_3-ensuring-analysis-quality","3. Ensuring Analysis Quality",[15,1368,1369],{},"To improve the quality of AI-generated analyses:",[61,1371,1372,1375,1378],{},[32,1373,1374],{},"We fine-tuned prompts based on feedback from financial analysts",[32,1376,1377],{},"Implemented fact-checking by cross-referencing key claims",[32,1379,1380],{},"Added source attribution to make it clear where information came from",[10,1382,1384],{"id":1383},"conclusion","Conclusion",[15,1386,1387],{},"Combining web search capabilities with AI summarization creates a powerful tool for stock analysis that can save hours of research time and provide more comprehensive insights. The implementation we've outlined here is just a starting point—there are many ways to extend and improve this system.",[15,1389,1390],{},"Some potential extensions include:",[61,1392,1393,1396,1399,1402],{},[32,1394,1395],{},"Adding sentiment analysis specifically tuned for financial news",[32,1397,1398],{},"Incorporating historical stock price data for correlation analysis",[32,1400,1401],{},"Expanding to include social media sentiment from platforms like Twitter/X",[32,1403,1404],{},"Creating alerts for significant news that might impact stock prices",[15,1406,1407],{},"As AI capabilities continue to advance, tools like this will become increasingly sophisticated and valuable for financial analysis and decision-making.",[15,1409,1410],{},"Have you built similar tools or have ideas for improvements? I'd love to hear about your experiences in the comments!",[1412,1413,1414],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":96,"searchDepth":110,"depth":110,"links":1416},[1417,1418,1419,1426,1427,1432],{"id":12,"depth":110,"text":13},{"id":23,"depth":110,"text":24},{"id":77,"depth":110,"text":78,"children":1420},[1421,1422,1423,1424,1425],{"id":85,"depth":116,"text":86},{"id":141,"depth":116,"text":142},{"id":300,"depth":116,"text":301},{"id":500,"depth":116,"text":501},{"id":766,"depth":116,"text":767},{"id":936,"depth":110,"text":937},{"id":1305,"depth":110,"text":1306,"children":1428},[1429,1430,1431],{"id":1312,"depth":116,"text":1313},{"id":1330,"depth":116,"text":1331},{"id":1365,"depth":116,"text":1366},{"id":1383,"depth":110,"text":1384},"How to combine OpenAI and SERP API to create a powerful web search and AI summary tool for stock analysis and research","md",{"date":1436,"tags":1437,"category":1440,"topics":1441,"published":119,"featured":119},"4th May 2025",[1438,1439],"AI","Engineering","ai-native-systems",[1442,1443],"ai-native","execution",null,"/blogs/en/openai-serp-api-web-search-ai-summary",{"title":5,"description":1433},"blogs/en/10.openai-serp-api-web-search-ai-summary","Ay_c7-5AsRA5SMXw-8dNHUt0vTmqy1QCth1jmpAL8aA",1781532113946]