Using the Brave Search API

You need to sign up for a free or paid account on the Brave search page and set an environment variable to your assigned API key:

export BRAVE_SEARCH_API_KEY = BSAgQ-Nc5.....

The Brave Search API allows you to access Brave Search results directly within your applications or services. It provides developers with the ability to harness the privacy-focused and independent search capabilities of Brave, returning results for web searches, news, videos, and more. To obtain an API key, simply create an account on the Brave Search API website and subscribe to either the free or one of the paid plans. The Brave Search API offers flexible pricing tiers, including a free option for testing and development, making it accessible to a wide range of users and projects. Currently you can call the API 2000 times a month on the free tier.

The library developed in this chapter is implemented in a single file BraveSearch.hs:

 1 {-# LANGUAGE OverloadedStrings #-}
 2 {-# LANGUAGE RecordWildCards #-}
 3 
 4 module BraveSearch
 5   ( getSearchSuggestions
 6   ) where
 7 
 8 import Network.HTTP.Simple
 9 import Data.Aeson
10 import qualified Data.Text as T
11 import Control.Exception (try)
12 import Network.HTTP.Client (HttpException)
13 import qualified Data.ByteString.Char8 as BS
14 import qualified Data.ByteString.Lazy.Char8 as LBS
15 
16 data SearchResponse = SearchResponse
17   { query :: QueryInfo
18   , web :: WebResults
19   } deriving (Show)
20 
21 data QueryInfo = QueryInfo
22   { original :: T.Text
23   } deriving (Show)
24 
25 data WebResults = WebResults
26   { results :: [WebResult]
27   } deriving (Show)
28 
29 data WebResult = WebResult
30   { type_ :: T.Text
31   , index :: Maybe Int
32   , all :: Maybe Bool
33   , title :: Maybe T.Text
34   , url :: Maybe T.Text
35   , description :: Maybe T.Text
36   } deriving (Show)
37 
38 instance FromJSON SearchResponse where
39   parseJSON = withObject "SearchResponse" $ \v -> SearchResponse
40     <$> v .: "query"
41     <*> v .: "web"
42 
43 instance FromJSON QueryInfo where
44   parseJSON = withObject "QueryInfo" $ \v -> QueryInfo
45     <$> v .: "original"
46 
47 instance FromJSON WebResults where
48   parseJSON = withObject "WebResults" $ \v -> WebResults
49     <$> v .: "results"
50 
51 instance FromJSON WebResult where
52   parseJSON = withObject "WebResult" $ \v -> WebResult
53     <$> v .: "type"
54     <*> v .:? "index"
55     <*> v .:? "all"
56     <*> v .:? "title"
57     <*> v .:? "url"
58     <*> v .:? "description"
59 
60 getSearchSuggestions :: String -> String -> IO (Either String [T.Text])
61 getSearchSuggestions apiKey query = do
62   let url = "https://api.search.brave.com/res/v1/web/search?q=" ++
63             query ++ "&country=US&count=5"
64   
65   request <- parseRequest url
66   let requestWithHeaders = setRequestHeader "Accept" ["application/json"]
67                          $ setRequestHeader "X-Subscription-Token" [BS.pack apiKey]
68                          $ request
69   
70   result <- try $ httpLBS requestWithHeaders
71   
72   case result of
73     Left e -> return $ Left $ "Network error: " ++ show (e :: HttpException)
74     Right response -> do
75       let statusCode = getResponseStatusCode response
76       if statusCode /= 200
77         then return $ Left $ "HTTP error: " ++ show statusCode
78         else do
79           let body = getResponseBody response
80           case eitherDecode body of
81             Left err -> return $ Left $ "JSON parsing error: " ++ err
82             Right searchResponse@SearchResponse{..} -> do
83               let originalQuery = original query
84                   webResults = results web
85               let suggestions = "Original Query: " <>
86                   originalQuery : map formatResult webResults
87               return $ Right suggestions
88 
89 formatResult :: WebResult -> T.Text
90 formatResult WebResult{..} =
91   let titleText = maybe "N/A" ("Title: " <>) title
92       urlText = maybe "N/A" ("URL: " <>) url
93       descText = maybe "N/A" ("Description: " <>) (fmap (T.take 100) description)
94   in T.intercalate " | " [titleText, urlText, descText]

Haskell Code Description for Brave Search Suggestions

This Haskell code implements a function, getSearchSuggestions, that fetches search suggestions from the Brave Search API.

Functionality:

  • getSearchSuggestions:
    • Takes an API key and a search query as input.
    • Constructs a URL to send a request to the Brave Search API, specifying the query, country, and result count.
    • Sets up the HTTP request with necessary headers, including the API key.
    • Makes the request and handles potential network errors.
    • Checks the response status code. If it’s 200 (OK), proceeds to parse the JSON response.
    • Extracts search results and formats them, including the original query.
    • Returns either an error message (if something went wrong) or a list of formatted search suggestions.

Key Features:

  • Data Types:
    • Defines data types to model the JSON structure of the Brave Search API response, including SearchResponse, QueryInfo, WebResults, and WebResult.
  • JSON Parsing:
    • Uses the aeson library to parse the JSON response into the defined data types.
  • Error Handling:
    • Employs try from the Control.Exception module to gracefully handle potential network errors during the HTTP request.
  • HTTP Request:
    • Utilizes the Network.HTTP.Simple library to make the HTTP request to the Brave Search API.
  • Formatting:
    • The formatResult function formats each search result into a user-friendly string, including the title, URL, and a shortened description.

Libraries Used:

  • Network.HTTP.Simple - For making HTTP requests.
  • Data.Aeson - For JSON parsing and encoding.
  • Data.Text - For efficient text handling.
  • Control.Exception - For error handling.
  • Network.HTTP.Client - For additional HTTP functionalities.
  • Data.ByteString.Char8 and Data.ByteString.Lazy.Char8 - For working with byte strings.

Language Extensions:

  • OverloadedStrings - Allows the use of string literals as Text values.
  • RecordWildCards - Enables convenient access to record fields using wildcards.

Overall:

This code provides a basic but functional way to interact with the Brave Search API to retrieve and format search suggestions. It demonstrates good practices in Haskell programming, including data modeling, error handling, and the use of relevant libraries.

Here is an example Main.hs file to use this library:

 1 {-# LANGUAGE OverloadedStrings #-}
 2 
 3 module Main where
 4 
 5 import BraveSearch (getSearchSuggestions)
 6 import System.Environment (getEnv)
 7 import qualified Data.Text as T
 8 import qualified Data.Text.IO as TIO
 9 
10 main :: IO ()
11 main = do
12   -- Get the API key from the environment variable
13   apiKey <- getEnv "BRAVE_SEARCH_API_KEY"
14   
15   -- Prompt the user for a search query
16   TIO.putStrLn "Enter a search query:"
17   query <- TIO.getLine
18   
19   -- Call the function to get search suggestions
20   result <- getSearchSuggestions apiKey (T.unpack query)
21   
22   case result of
23     Left err -> TIO.putStrLn $ "Error: " <> T.pack err
24     Right suggestions -> do
25       TIO.putStrLn "Search suggestions:"
26       mapM_ (TIO.putStrLn . ("- " <>)) suggestions

Test Code Explanation

The code interacts with the BraveSearch module to demonstrate how to fetch and display search suggestions from the Brave Search API.

Code Breakdown

  1. Imports

    • It imports:
      • BraveSearch to use the getSearchSuggestions function.
      • System.Environment to get the API key from an environment variable.
      • Data.Text and Data.Text.IO for working with text input and output.
  2. main Function

    1. Get API Key

      • It uses getEnv "BRAVE_SEARCH_API_KEY" to retrieve the Brave Search API key from an environment variable named BRAVE_SEARCH_API_KEY. This assumes you have set this environment variable in your system before running the code.
    2. Prompt for Query

      • It prints the message “Enter a search query:” to the console, prompting the user to input a search term.
      • It reads the user’s input using TIO.getLine and stores it in the query variable.
    3. Fetch Search Suggestions

      • It calls the getSearchSuggestions function from the BraveSearch module, passing the API key and the user’s query (converted from Text to String using T.unpack).
      • It stores the result of this call in the result variable.
    4. Handle Result

      • It uses a case expression to handle the two possible outcomes of the getSearchSuggestions call:

        • Left err:

          • If there was an error (e.g., network issue, HTTP error, JSON parsing error), it prints the error message prefixed with “Error:”.
        • Right suggestions:

          • If the call was successful and returned a list of search suggestions:
            • Prints “Search suggestions:” to the console.
            • Uses mapM_ to iterate over the suggestions list and print each suggestion in the following format: - suggestion text

This test code provides a basic example of how to use the getSearchSuggestions function from the BraveSearch module.

Here is the output:

$ cabal run     
Enter a search query:
find a consultant for AI and common lisp, and the semantic web
Search suggestions:
- Original Query: find a consultant for AI and common lisp, and the semantic web
- Title: Mark Watson: AI Practitioner and Lisp Hacker | URL: https://markwatson.com/\
 | Description: I am the author of 20+ books on Artificial Intelligence, <strong>Com
mon</strong> <strong>Lisp</stron
- Title: Lisp (programming language) - Wikipedia | URL: https://en.wikipedia.org/wik\
i/Lisp_(programming_language) | Description: Scheme is a statically scoped and prope
rly tail-recursive dialect of <strong>the</strong> <strong>Li
- Title: The Lisp approach to AI (Part 1). If you are a programmer that reads… | by \
Sebastian Valencia | AI Society | Medium | URL: https://medium.com/ai-society/the-li
sp-approach-to-ai-part-1-a48c7385a913 | Description: If you are a programmer that re
ads about the history and random facts of this lovely craft