- Click Deploy to TakeShape.
- Select "Create new project" from the dropdown
- Enter a name for the new project or leave the default
- Click "Add to TakeShape"
- Create an OpenAI API Key https://platform.openai.com/api-keys with the "Models" and "Model capabilities" permissions (this example uses
/v1/embeddingsand/v1/chat/completions) - Copy/paste your API key into the service configuration dialog
- Click "Save"
Follow the directions in the TakeShape Shopify documentation to connect Shopify Admin API
Once your services are connected now try out the API in the API Explorer
{
completion(text: "what shoes should I buy?")
}{
getRelatedProductList(text: "what shoes should I buy?" size: 3) {
items {
_id
title
descriptionHtml
}
}
}This example demonstrates how to use TakeShape's vector capabilities combined with indexing to enable the RAG use-case. A prerequisite for RAG is to populate a vector database, for this example we will use TakeShape's built-in index. The first step to preparing our data is to extend Shopify_Product with a vector property that contains an embedding created by OpenAI. Once we have a our extended Shopify_Product we can use API Indexing to iterate over every product and store the combined data in our Index:
sequenceDiagram
participant TakeShape
participant Shopify
participant OpenAI
participant Index
TakeShape->>Shopify: 1. Bulk query products from shopify
Shopify->>TakeShape: 2. Shopify returns all products
TakeShape->>OpenAI: 3. Create embeddings for product text fields
OpenAI->>TakeShape: 4. OpenAI returns embeddings
TakeShape->>Index: 5. Store Product + embeddings in TakeShape Index
This combined Shopify_Product will be kept up to date by listening to Shopify webhooks and will be fully re-indexed every 24hrs.
Now that our Shopify_Product data is stored in the built-in index we can perform RAG:
sequenceDiagram
actor User
participant TakeShape
participant Index
participant OpenAI
User->>TakeShape: 1. GraphQL containing user prompt
TakeShape->>OpenAI: 2. Create embedding of user prompt
OpenAI->>TakeShape: 3. OpenAI returns vector
TakeShape->>Index: 4. Use vector to search TakeShape Index
Index->>TakeShape: 5. Return related products
TakeShape->>OpenAI: 6. Combine related results with original prompt and send to GPT 3.5
OpenAI->>TakeShape: 7. OpenAI returns generated text
TakeShape->>User: 8. GraphQL response
This section explains the schema features and steps used to enable RAG. The starting point of this guide is a project created with the web client with both "Shopify Admin" and "OpenAI" services added and Shopify_Product added.
The first step is to add a vector property to our Shopify_Product shape:
{
"vector": {
"type": "array",
"items": {
"type": "number"
},
"title": "Vector",
"@tag": "vector"
}
}Vectors are arrays of floating point numbers which is easy to express using the JSON Schema syntax. Using the @tag annotation with the value of vector tells TakeShape to treat this array property as a vector and to enable vector (knn) search on it. In this property we want to store the OpenAI calculated embedding of the text content of the Shopify_Product. To specify this we use an @resolver annotation to tell TakeShape how to get the embedding from OpenAI:
{
"vector": {
"type": "array",
"items": {"type": "number"},
"title": "Vector",
"@tag": "vector",
"@dependencies": "{title description}",
"@resolver": {
"name": "ai:createEmbedding",
"service": "openai",
"model": "text-embedding-3-small",
"options": {"useDependencies": true}
}
}
}We use the ai:createEmbedding resolver to send the text content of Shopify_Product, as defined by @dependencies, to OpenAI to create a vector.
After adding the vector property to Shopify_Product the next step is to enable indexing:
{
"Shopify_Product": {
...
"cache": {
"enabled": true,
"triggers": [
{
"type": "schedule",
"loader": "list",
"interval": 1440
},
{
"type": "webhook",
"loader": "get",
"service": "shopify",
"events": [
"products/create",
"products/update",
"products/delete"
]
}
],
"fragment": {
"maxDepth": 2
}
},
"loaders": {
"list": {
"query": "shopify:Query.products"
},
"get": {
"query": "shopify:Query.product"
}
},
"schema": {...}
}
}Above is the default indexing configuration for Shopify_Product generated by checking the "Indexing" checkbox in the Shape Editor UI. This configuration will index all products every 24hrs using the the list loader and will index single products in response to a webhook.
Once indexing is enabled the next step is to create the getRelatedProductList query. This query composes OpenAI's createEmbedding mutation and TakeShape's vectorSearch resolver:
{
"queries": {
"getRelatedProductList": {
"shape": "PaginatedList<shopify:Product>",
"resolver": {
"compose": [
{
"name": "ai:createEmbedding",
"id": "createEmbedding",
"service": "openai",
...
},
{
"shapeName": "Shopify_Product",
"name": "takeshape:vectorSearch",
"service": "takeshape",
...
}
]
},
"args": {
"type": "object",
"properties": {
"text": {
"type": "string"
},
"size": {
"type": "integer"
}
},
"required": [
"text"
]
}
}
}
}This query uses a compose resolver to execute ai:createEmbedding and then pass the resulting vector to takeshape:vectorSearch which searches our indexed Shopify_Product. First let's look at the resolver to fetch the embedding:
{
"name": "ai:createEmbedding",
"id": "createEmbedding",
"service": "openai",
"model": "text-embedding-3-small",
"args": {
"ops": [{"path": "input", "mapping": "$args.text"}]
}
}This resolver looks very similar to the resolver in the previous section, but it is creating an embedding of th query you pass in the text arg. Then we take the resulting vector and pass it into the vector.value arg of the takeshape:vectorSearch resolver:
{
"shapeName": "Shopify_Product",
"name": "takeshape:vectorSearch",
"service": "takeshape",
"args": {
"ops": [
{"path": "vector.name", "value": "vector"},
{
"path": "vector.value",
"mapping": "$resolvers.createEmbedding"
},
{"path": "size", "mapping": "$args.size"}
]
}Note that the vector.name is set to "vector" to correspond with the property name on Shopify_Product. The size argument determines the number of related (k in k-nearest neighbors).
The last step is to put it all together with the chat mutation. To generate text in response to a users prompt we are composing getRelatedProductList with OpenAI's createChatCompletion:
{
"queries": {
"chat": {
"shape": "string",
"resolver": {
"compose": [
{
"name": "delegate",
"id": "getRelatedProductList",
"to": "Query.getRelatedProductList",
...
},
{
"name": "ai:generateText",
...
}
]
},
"args": {
"type": "object",
"properties": {
"text": {
"type": "string"
}
},
"required": [
"text"
]
}
}
}
}First we take the text arg and fetch top related product using getRelatedProductList:
{
"name": "delegate",
"id": "getRelatedProductList",
"to": "Query.getRelatedProductList",
"options": {
"selectionSet": "{ items {title description} }"
},
"args": {
"ops": [
{
"path": "text",
"mapping": "$args.text"
},
{
"path": "size",
"value": 1
}
]
},
"results": {
"ops": [
{
"path": "$",
"mapping": "$currentResolver.items[0]"
}
]
}
}Then we combine the text from the top related product and the user's original prompt and send it to GPT 3.5 using's ai:generateText resolver:
{
"name": "ai:generateText",
"service": "openai",
"model": "gpt-3.5-turbo",
"systemPrompt": "You are a helpful assistant.",
"inputTemplate": "Answer the following question:\n{$args.input} \n\nby using the following text:\n{$resolvers.getRelatedProductList.title} {$resolvers.getRelatedProductList.description}"
}