Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chatbot1.2 #240

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 158 additions & 0 deletions Examples/chatbot-planner/ChatBot.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package com.edgechain;

import com.edgechain.lib.endpoint.impl.OpenAiEndpoint;
import com.edgechain.lib.jsonnet.JsonnetArgs;
import com.edgechain.lib.jsonnet.JsonnetLoader;
import com.edgechain.lib.jsonnet.enums.DataType;
import com.edgechain.lib.jsonnet.impl.FileJsonnetLoader;
import com.edgechain.lib.openai.request.ChatMessage;
import com.edgechain.lib.request.ArkRequest;
import com.edgechain.lib.rxjava.retry.impl.ExponentialDelay;
import com.edgechain.lib.rxjava.transformer.observable.EdgeChain;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.TimeUnit;

import static com.edgechain.lib.constants.EndpointConstants.OPENAI_CHAT_COMPLETION_API;

@SpringBootApplication
public class ChatBot {

private static final String OPENAI_AUTH_KEY = ""; // YOUR OPENAI KEY
private static final String OPENAI_ORG_ID = ""; // YOUR OPENAI KEY
private static OpenAiEndpoint gpt3Endpoint;
private static PostgresEndpoint postgresEndpoint;
private static PostgreSQLHistoryContextEndpoint contextEndpoint;
private static JsonnetLoader loader = new FileJsonnetLoader("./chatbot-planner/store-planner.jsonnet"); // JSONNET FILE PATH


public static void main(String[] args) {
System.setProperty("server.port", "8080");


Properties properties = new Properties();

properties.setProperty("spring.jpa.show-sql", "true");
properties.setProperty("spring.jpa.properties.hibernate.format_sql", "true");

// Adding Cors ==> You can configure multiple cors w.r.t your urls.;
properties.setProperty("cors.origins", "http://localhost:4200");

properties.setProperty("postgres.db.host", "");
properties.setProperty("postgres.db.username", "");
properties.setProperty("postgres.db.password", "");


new SpringApplicationBuilder(ChatBot.class).run(args);

gpt3Endpoint = new OpenAiEndpoint(
OPENAI_CHAT_COMPLETION_API,
OPENAI_AUTH_KEY,
OPENAI_ORG_ID,
"gpt-3.5-turbo",
"user",
0.7,
new ExponentialDelay(3, 5, 2, TimeUnit.SECONDS)
);

// Defining tablename and namespace...
postgresEndpoint =
new PostgresEndpoint(
"pg_vectors", "movie",
new ExponentialDelay(5, 5, 2, TimeUnit.SECONDS));

contextEndpoint = new PostgreSQLHistoryContextEndpoint(new FixedDelay(2, 3, TimeUnit.SECONDS));
}


@RestController
@RequestMapping
public class Bot {
Logger logger = LoggerFactory.getLogger(getClass());

@PostMapping("/planner")
public ResponseEntity<String> chatBotPlanner(ArkRequest arkRequest) {
String resourceURL = "";
boolean delete = false;
String contextId = arkRequest.getQueryParam("id");
String prompt = arkRequest.getBody().getString("prompt");

HistoryContext historyContext = contextEndpoint.get(contextId);

plannerLoader.put("query", new JsonnetArgs(DataType.STRING, prompt))
.put("gptResponse", new JsonnetArgs(DataType.STRING, ""))
.put("keepHistory", new JsonnetArgs(DataType.BOOLEAN, "false"))
.loadOrReload();

String chatPrompt = chatFn(historyContext.getResponse(), prompt);

contextEndpoint.put(historyContext.getId(), prompt + historyContext.getResponse());

String gptResponse = getGptResponse(chatPrompt, arkRequest);

if (gptResponse.contains("delete")) {
delete = true;
}

logger.info("GPT Response {} ", gptResponse);

plannerLoader.put("gptResponse", new JsonnetArgs(DataType.STRING, gptResponse)).loadOrReload();

String gptResult = plannerLoader.get("result");
logger.info("Extracted result from GPT result {} ", gptResult);
if (!gptResult.contains("NOT_APPLICABLE")) {
return doApiCalls(resourceURL, gptResult, delete);
}

return ResponseEntity.ok(gptResult);
}

private ResponseEntity<String> doApiCalls(String resourceURL, String result, boolean delete) {
String finalURL = resourceURL + result;

logger.info("GPT response final URI {} ", finalURL);

RestTemplate restTemplate = new RestTemplate();

if (!delete) {
return restTemplate
.getForEntity(finalURL, String.class);
} else {
restTemplate.delete(finalURL);
return ResponseEntity.ok("Deleted");
}
}

private String getGptResponse(String prompt, ArkRequest arkRequest) {
return new EdgeChain<>(gpt3Endpoint.chatCompletion(prompt, "planner", arkRequest))
.get()
.getChoices()
.get(0)
.getMessage()
.getContent();
}

public String chatFn(String chatHistory, String queries) {
plannerLoader
.put("keepHistory", new JsonnetArgs(DataType.BOOLEAN, "true"))
.put("history", new JsonnetArgs(DataType.STRING, chatHistory)) // Getting ChatHistory from Mapper
.put("keepContext", new JsonnetArgs(DataType.BOOLEAN, "true"))
.put("context", new JsonnetArgs(DataType.STRING, queries)) // Getting Queries from Mapper
.loadOrReload(); // Step 5: Pass the Args & Reload Jsonnet

return plannerLoader.get("prompt");
}
}
}
127 changes: 127 additions & 0 deletions Examples/chatbot-planner/movie-planner.jsonnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
local examples = |||
Example 1:

Background: The id of Wong Kar-Wai is 12453
User query: give me the latest movie directed by Wong Kar-Wai.
API calling 1: GET [/person/12453/movie_credits] to get the latest movie directed by Wong Kar-Wai (id 12453)
API response: The latest movie directed by Wong Kar-Wai is The Grandmaster (id 44865), ...

Example 2:

Background: No background
User query: search for movies produced by DreamWorks Animation
API calling 1: GET [/search/company] to get the id of DreamWorks Animation
API response: DreamWorks Animation's company_id is 521
Instruction: Continue. Search for the movies produced by DreamWorks Animation
API calling 2: GET [/discover/movie] to get the movies produced by DreamWorks Animation
API response: Puss in Boots: The Last Wish (id 315162), Shrek (id 808), The Bad Guys (id 629542), ...

Example 3:

Background: The id of the movie Happy Together is 18329
User query: search for the director of Happy Together
API calling 1: GET [/movie/18329/credits] to get the director for the movie Happy Together
API response: The director of Happy Together is Wong Kar-Wai (12453)

Example 4:

Background: No background
User query: search for the highest rated movie directed by Wong Kar-Wai
API calling 1: GET [/search/person] to search for Wong Kar-Wai
API response: The id of Wong Kar-Wai is 12453
Instruction: Continue. Search for the highest rated movie directed by Wong Kar-Wai (id 12453)
API calling 2: GET [/person/12453/movie_credits] to get the highest rated movie directed by Wong Kar-Wai (id 12453)
API response: The highest rated movie directed by Wong Kar-Wai is In the Mood for Love (id 843)
|||;

local api_selector_prompt = |||
You are a planner that plans a sequence of RESTful API calls to assist with user queries against an API.
Another API caller will receive your plan call the corresponding APIs and finally give you the result in natural language.
The API caller also has filtering, sorting functions to post-process the response of APIs. Therefore, if you think the API response should be post-processed, just tell the API caller to do so.
If you think you have got the final answer, do not make other API calls and just output the answer immediately. For example, the query is search for a person, you should just return the id and name of the person.

----

Here are name and description of available APIs.
Do not use APIs that are not listed here.

{endpoints}

----

Starting below, you should follow this format:

Background: background information which you can use to execute the plan, e.g., the id of a person, the id of tracks by Faye Wong. In most cases, you must use the background information instead of requesting these information again. For example, if the query is "get the poster for any other movie directed by Wong Kar-Wai (12453)", and the background includes the movies directed by Wong Kar-Wai, you should use the background information instead of requesting the movies directed by Wong Kar-Wai again.
User query: the query a User wants help with related to the API
API calling 1: the first api call you want to make. Note the API calling can contain conditions such as filtering, sorting, etc. For example, "GET /movie/18329/credits to get the director of the movie Happy Together", "GET /movie/popular to get the top-1 most popular movie". If user query contains some filter condition, such as the latest, the most popular, the highest rated, then the API calling plan should also contain the filter condition. If you think there is no need to call an API, output "No API call needed." and then output the final answer according to the user query and background information.
API response: the response of API calling 1
Instruction: Another model will evaluate whether the user query has been fulfilled. If the instruction contains "continue", then you should make another API call following this instruction.
... (this API calling n and API response can repeat N times, but most queries can be solved in 1-2 step)


{examples}


Note, if the API path contains "{{}}", it means that it is a variable and you should replace it with the appropriate value. For example, if the path is "/users/{{user_id}}/tweets", you should replace "{{user_id}}" with the user id. "{{" and "}}" cannot appear in the url. In most cases, the id value is in the background or the API response. Just copy the id faithfully. If the id is not in the background, instead of creating one, call other APIs to query the id. For example, before you call "/users/{{user_id}}/playlists", you should get the user_id via "GET /me" first. Another example is that before you call "/person/{{person_id}}", you should get the movie_id via "/search/person" first.

Begin!

Background: {background}
User query: {plan}
API calling 1: {agent_scratchpad}
|||;

local api_planner_selector = |||
You are a planner that plans a sequence of RESTful API calls to assist with user queries against an API.

You should:
1) evaluate whether the user query can be solved by the API documentated below. If no, say NOT_APPICABLE.
2) if yes, generate a plan of API calls and say what they are doing step by step.

You should only use API endpoints documented below ("actual endpoints you can use").
Some user queries can be resolved using a single endpoint, but some will require several endpoints.
Your selected endpoints will be passed to an API planner that can look at the detailed documentation and make an execution plan.

You must always follow this format:

User query: the query from the user
Thought: you should always describe your thoughts, if you are thought contains endpoints, then endpoint always start with '[' and end with ']'
Result: a comma separated list of operation title potentially relevant for the query


Here are some examples:
Do not use APIs that are not listed here.

Fake endpoints for examples:
GET /person/{person_id}/movie_credits to Get the movie credits for a person.

User query: tell me about today's wheather
Thought: Sorry, this API's domain is Movie, not wheather.
Result: [NOT_APPICABLE]

User query: give me the latest movie directed by Wong Kar-Wai.
Thought: GET /person/{person_id}/movie_credits to get the latest movie directed by Wong Kar-Wai (id 12453)
Result: The latest movie directed by Wong Kar-Wai is The Grandmaster (id 44865)

Here are endpoints you can use. Do not reference any of the endpoints above.

{endpoint}
Begin! Remember to first describe your thoughts and then return the result list using Result or output NOT_APPLICABLE if the query can not be solved with the given endpoints:
|||;

local extractApiResponse(str) =
local apiRes = xtr.strings.substringBefore(xtr.strings.substringAfter(str, "["), "]");
apiRes;

local query = "User query:" + payload.query;
local gptResponse = payload.gptResponse;
local apiResponse = extractApiResponse(gptResponse);
local context = if(payload.keepContext == "true") then payload.context else "";
local history = "Chat History: "+ if(payload.keepHistory == "true") then payload.history else "";
local prompt = std.join("\n", [api_planner_selector, query]);
{
"apiResponse": apiResponse,
"context": context,
"history": history,
"prompt": prompt
}
79 changes: 79 additions & 0 deletions Examples/chatbot-planner/store-planner.jsonnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
local api_planner_selector = |||
You are a planner that plans a sequence of RESTful API calls to assist with user queries against an API.

You should:
1) evaluate whether the user query can be solved by the API documentated below. If no, say NOT_APPICABLE.
2) if yes, generate a plan of API calls and say what they are doing step by step.

You should only use API endpoints documented below ("actual endpoints you can use").
Some user queries can be resolved using a single endpoint, but some will require several endpoints.
Your selected endpoints will be passed to an API planner that can look at the detailed documentation and make an execution plan.

You must always follow this format:

User query: the query from the user
Thought: you should always describe your thoughts, if you are thought contains endpoints, then endpoint always start with '[' and end with ']'
Result: operation title potentially relevant for the query


Here are some examples:
Do not use APIs that are not listed here.

Fake endpoints for examples:
- getProducts (GET): to get all products list
- getProduct (GET): to get a single product
- getCategories (GET): to get all categories
- getInCategory (GET): to get products in a specific category
- deleteProduct (DELETE): to delete a specific product by it's id.

User query: tell me about today's wheather
Thought: Sorry, this API's domain is Shopping, not wheather.
Result: [NOT_APPLICABLE]

User query: Can you get me all the products.
Thought: getProducts can be used to get all products.
Result: getProducts[products]

User query: get me the product with id 1
Thought: getProduct can be used to get product by Id.
Result: getProduct[products/1]

User query: get me the all product categories
Thought: getCategories can be used to get all product categories
Result: getProduct[products/categories]

User query: get me all product in electronics category
Thought: getInCategory can be used to get all product in a specific category.
Result: getInCategory[products/category/electronics]

User query: Can you delete this product which id is 4.
Thought: deleteProduct can be used to delete a product with id.
Result: deleteProduct[products/4]

Here are endpoints you can use. Do not reference any of the endpoints above.

{endpoints}
Begin! Remember to first describe your thoughts and then return the result list using Result or output NOT_APPLICABLE if the query can not be solved with the given endpoints:

Context: {context}
User query: {query}
Chat History: {chat_history}
|||;

local extractResult(str) =
local result = xtr.strings.substringBefore(xtr.strings.substringAfter(str, "["), "]");
result;

local query = "User query:" + payload.query;
local gptResponse = payload.gptResponse;
local getResult = extractResult(gptResponse);
local context = if(payload.keepContext == "true") then payload.context else "";
local history = "Chat History: " + if(payload.keepHistory == "true") then payload.history else "";
local prompt = std.join("\n", [query , api_planner_selector, context, history]);
{
"result": getResult,
"api_planner_selector": api_planner_selector,
"context": context,
"history": history,
"prompt": prompt
}