|
| 1 | +## Using LLMs to Convert Natural Language Queries into Structured Queries |
| 2 | + |
| 3 | +### Context |
| 4 | + |
| 5 | +Over the past year, I have been working on building an application platform that |
| 6 | +supports searching through OpenStreetMap data via an API endpoint and an |
| 7 | +embedded JavaScript runtime into Golang. |
| 8 | + |
| 9 | +The idea behind that interface is to minimize the amount of data and round trips |
| 10 | +that need to be done from a client interface. Client-side JavaScript can be |
| 11 | +passed to server-side JavaScript for a very subset use case, which is querying |
| 12 | +and collating geospatial points of interest from OpenStreetMap. |
| 13 | + |
| 14 | +[Placeholder for a link to that blog post.] |
| 15 | + |
| 16 | +I have documentation for my query language, but what I found was that it |
| 17 | +required a lot of domain knowledge related to how OpenStreetMap tags may be used |
| 18 | +to identify a place. |
| 19 | + |
| 20 | +For example, an OpenStreetMap gas station could be marked as `store=convenience` |
| 21 | +or `amenity=gas` or might have neither or only one of them. Discovering those |
| 22 | +tags to help search led me to try to expose that information in different ways. |
| 23 | + |
| 24 | +Initially, I thought about creating an endpoint that returns information about |
| 25 | +all the tags. However, I found that there was a lot of noise in the tags, making |
| 26 | +it difficult to present the data in a human-readable and usable way. There can |
| 27 | +be tens of thousands, if not hundreds of thousands, of tags with multiple values |
| 28 | +for each. |
| 29 | + |
| 30 | +### Initial Approach |
| 31 | + |
| 32 | +I wanted to take a different approach: Could I have something that converted a |
| 33 | +user's query, such as "grocery store," and mapped it to predefined tags? |
| 34 | + |
| 35 | +I built an interface where I predetermined a categorization of tags, covering |
| 36 | +common amenities for real estate searches such as police stations, gas stations, |
| 37 | +schools, and hospitals. |
| 38 | + |
| 39 | +[Placeholder for a link to that interface.] |
| 40 | + |
| 41 | +This allowed users to input an address, which would use an element from the |
| 42 | +Mapbox SDK to return latitude and longitude. I could then use predefined |
| 43 | +categorizations to find nearby points of interest. |
| 44 | + |
| 45 | +However, this approach was limited. It only allowed predefined categories to be |
| 46 | +shown, restricting the scope of discovery. For example, what if someone wanted |
| 47 | +to exclude mainstream coffee shops to find local businesses? This would require |
| 48 | +maintaining a list of coffee shops to exclude or providing an exclusion |
| 49 | +mechanism for users—making the interface complex. |
| 50 | + |
| 51 | +### Experimenting with LLMs |
| 52 | + |
| 53 | +Given these limitations, I explored the possibility of using a Large Language |
| 54 | +Model (LLM) to process natural language queries and convert them into structured |
| 55 | +queries. |
| 56 | + |
| 57 | +I started by experimenting with ChatGPT, manually testing prompts. My initial |
| 58 | +prompt was: |
| 59 | + |
| 60 | +> "You will be given a user query in natural language and return structured JSON |
| 61 | +> representing the OpenStreetMap tags that correspond to the query." |
| 62 | +
|
| 63 | +Example query: |
| 64 | + |
| 65 | +> "Find me coffee shops in my neighborhood." |
| 66 | +
|
| 67 | +[Placeholder for what ChatGPT initially returned.] |
| 68 | + |
| 69 | +The results were promising but lacked structure. So, I defined a schema for the |
| 70 | +expected JSON output and refined my prompt: |
| 71 | + |
| 72 | +> "Given a user's query in natural language, return OpenStreetMap tags in a JSON |
| 73 | +> format. Here is an example of the expected output." |
| 74 | +
|
| 75 | +Example query: |
| 76 | + |
| 77 | +> "What coffee shops are in my neighborhood?" |
| 78 | +
|
| 79 | +[Placeholder for expected JSON output.] |
| 80 | + |
| 81 | +[Placeholder for an example with ChatGPT’s result.] |
| 82 | + |
| 83 | +### Integrating LLMs into My Application |
| 84 | + |
| 85 | +With structured JSON working, I incorporated it into my application—a text box |
| 86 | +where users could describe their desired neighborhood, and it would return |
| 87 | +points of interest on a map. |
| 88 | + |
| 89 | +However, I encountered new challenges: |
| 90 | + |
| 91 | +- **Scoping queries:** My database was structured by states and Canadian |
| 92 | + provinces. Users had to specify these areas, which broke the natural language |
| 93 | + flow. |
| 94 | +- **Teaching an LLM my custom query language:** My query language allowed |
| 95 | + substring matching, range limits, and directives to scope searches. |
| 96 | + |
| 97 | +To address these issues, I documented my query language, detailing syntax and |
| 98 | +examples. |
| 99 | + |
| 100 | +[Placeholder for link to query syntax documentation.] |
| 101 | + |
| 102 | +I then asked ChatGPT to refine my prompt: |
| 103 | + |
| 104 | +> "Here’s documentation on my query language. Help me write a prompt that |
| 105 | +> ensures you understand it." |
| 106 | +
|
| 107 | +[Placeholder for shortened example of refined prompt.] |
| 108 | + |
| 109 | +### Enhancing Query Accuracy |
| 110 | + |
| 111 | +To improve accuracy, I identified meaningful OpenStreetMap tags by researching |
| 112 | +community discussions and manually curating a list of about 50 high-value tags |
| 113 | +(e.g., shops, amenities, roads, schools, parks). I then asked ChatGPT: |
| 114 | + |
| 115 | +> "Here's a list of 50 OpenStreetMap tags for describing a neighborhood. Can you |
| 116 | +> extend it for better specificity?" |
| 117 | +
|
| 118 | +It suggested 120 tags, of which 15 were incorrect. After review, I refined the |
| 119 | +list and incorporated it into the LLM prompt: |
| 120 | + |
| 121 | +> "If looking for OpenStreetMap tags, use these as a guide, but feel free to use |
| 122 | +> others." |
| 123 | +
|
| 124 | +Testing showed that responses were now more specific. For example: |
| 125 | + |
| 126 | +> "Find coffee shops that are not mainstream." |
| 127 | +
|
| 128 | +[Placeholder for resulting query JSON.] |
| 129 | + |
| 130 | +### Handling Geographic Areas |
| 131 | + |
| 132 | +To improve area selection, I extended the JSON output schema to include multiple |
| 133 | +areas. Instead of requiring users to select one state, I allowed descriptions |
| 134 | +like "Southwestern states" to infer multiple areas. |
| 135 | + |
| 136 | +[Placeholder for a query that includes southwestern states.] |
| 137 | + |
| 138 | +### Optimizing Performance |
| 139 | + |
| 140 | +While my implementation worked, optimizations were necessary: |
| 141 | + |
| 142 | +1. **Fine-Tuning the Model** |
| 143 | + - I converted example prompts and expected outputs into fine-tuning data. |
| 144 | + - I asked ChatGPT to generate additional examples, reviewed them, and removed |
| 145 | + incorrect ones. |
| 146 | + - I fine-tuned a GPT-4 model with 30+ examples to improve performance. |
| 147 | + - Results: A slight performance boost and more accurate outputs. |
| 148 | + |
| 149 | +2. **Providing Output Prediction** |
| 150 | + - OpenAI's API allows defining an expected output structure. |
| 151 | + - I used this feature to ensure JSON output consistency. |
| 152 | + |
| 153 | +[Placeholder for OpenAI API link about this feature.] |
| 154 | + |
| 155 | +3. **Leveraging Prompt Caching** |
| 156 | + - OpenAI caches prompts for faster response times, reducing costs. |
| 157 | + - This ensured my long prompt wasn't reprocessed on every request. |
| 158 | + |
| 159 | +4. **Limiting Token Usage** |
| 160 | + - I capped JSON output to 300 tokens and limited user input to 300 |
| 161 | + characters. |
| 162 | + - This prevented excessive API costs and safeguarded against abuse. |
| 163 | + |
| 164 | +### Conclusion |
| 165 | + |
| 166 | +By integrating OpenStreetMap data with LLMs, I enabled users to query geospatial |
| 167 | +data using natural language. The system supports filtering, distance |
| 168 | +constraints, and negations, making searches more intuitive. |
| 169 | + |
| 170 | +[Placeholder for a link to the neighborhood search demo.] |
| 171 | + |
| 172 | +This approach remains **best effort**—not perfect, but continually improving. I |
| 173 | +plan to refine results through fine-tuning and ongoing user feedback to enhance |
| 174 | +accuracy and usability. |
0 commit comments