YARP
Yet Another Robot Platform
 
Loading...
Searching...
No Matches
GPTDevice.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2023-2024 Istituto Italiano di Tecnologia (IIT)
3 * SPDX-License-Identifier: BSD-3-Clause
4 */
5
6#include <GPTDevice.h>
7#include <yarp/os/LogStream.h>
9#include <fstream>
10#include <string_view>
11
13
14using json = nlohmann::json;
15
17{
18 if (!parseParams(config))
19 {
20 yCError(GPTDEVICE) << "Failed to parse parameters";
21 return false;
22 }
23
24 // Azure settings
25 azure_deployment_id = std::getenv("DEPLOYMENT_ID");
26 azure_resource = std::getenv("AZURE_RESOURCE");
27
28 if (!azure_resource)
29 {
30 yCWarning(GPTDEVICE) << "Could not read env variable AZURE_RESOURCE. Device set in offline mode";
31 m_offline = true;
32 return true;
33 }
34
35 if (!oai.auth.SetAzureKeyEnv("AZURE_API_KEY"))
36 {
37 yCWarning(GPTDEVICE) << "Invalid or no azure key provided. Device set in offline mode.";
38 m_offline = true;
39 }
40
41 // Prompt and functions file
42 bool has_prompt_file{m_prompt_file != ""};
43 yarp::os::ResourceFinder resource_finder;
44 resource_finder.setDefaultContext(m_prompt_context);
45
46 if(has_prompt_file)
47 {
48 std::string prompt_file_fullpath = resource_finder.findFile(m_prompt_file);
49 auto stream = std::ifstream(prompt_file_fullpath);
50 if (!stream)
51 {
52 yCWarning(GPTDEVICE) << "File:" << prompt_file_fullpath << "does not exist or path is invalid";
53 }
54 else
55 {
56 std::ostringstream sstr;
57 sstr << stream.rdbuf(); //Reads the entire file into the stringstream
58 if(!setPrompt(sstr.str()))
59 {
60 return false;
61 }
62 }
63 }
64
65 bool has_function_file{m_function_file != ""};
66 resource_finder.setDefaultContext(m_json_context);
67 if(has_function_file)
68 {
69 std::string functions_file_fullpath = resource_finder.findFile(m_function_file);
70 auto stream = std::ifstream(functions_file_fullpath);
71 if (!stream)
72 {
73 yCWarning(GPTDEVICE) << "File: " << functions_file_fullpath << "does not exist or path is invalid.";
74 }
75 else
76 {
77 // Read the function file into json format
78 // yCDebug(GPTDEVICE) << functions_file_fullpath;
79 json function_js = json::parse(stream);
80 if (!setFunctions(function_js))
81 {
82 return false;
83 }
84 }
85 }
86
87 return true;
88}
89
90yarp::dev::ReturnValue GPTDevice::ask(const std::string &question, yarp::dev::LLM_Message &oAnswer)
91{
92 // Adding prompt to conversation
93 m_convo->AddUserData(question);
94 m_convo_length += 1;
95
96 if (m_offline)
97 {
98 yCWarning(GPTDEVICE) << "Device in offline mode";
100 }
101
102 // Asking gpt for an answer
103 try
104 {
105 liboai::Response res = oai.Azure->create_chat_completion(
106 azure_resource, azure_deployment_id, m_api_version,
107 *m_convo);
108 m_convo->Update(res);
109 }
110 catch (const std::exception &e)
111 {
112 yCError(GPTDEVICE) << e.what() << '\n';
114 }
115
116 if(m_convo->LastResponseIsFunctionCall())
117 {
118 yCDebug(GPTDEVICE) << "Last answer was function call";
119 auto str_args = m_convo->GetLastFunctionCallArguments();
120 std::string function_call_name = m_convo->GetLastFunctionCallName();
121 auto j_args = json::parse(str_args);
122
123 std::vector<std::string> parameters_list;
124 std::vector<std::string> arguments_list;
125
126 for(const auto&[key,val]: j_args.items())
127 {
128 parameters_list.push_back(key);
129 arguments_list.push_back(val);
130 }
131
132 auto function_call_message = yarp::dev::LLM_Message{"function",
133 function_call_name,
134 parameters_list,
135 arguments_list};
136
137 m_function_called.insert({m_convo_length,function_call_message});
138
139 oAnswer = function_call_message;
140 }
141 else
142 {
143 oAnswer = yarp::dev::LLM_Message{"assistant",
144 m_convo->GetLastResponse(),
145 std::vector<std::string>(),
146 std::vector<std::string>()};
147 }
148
149 m_convo_length+=1;
150 return yarp::dev::ReturnValue_ok;
151}
152
154{
155
156 std::string aPrompt;
157
158 if(readPrompt(aPrompt))
159 {
160 yCError(GPTDEVICE) << "A prompt is already set. You must delete conversation first";
162 }
163
164 try
165 {
166 m_convo->SetSystemData(prompt);
167 }
168 catch (const std::exception &e)
169 {
170 yCError(GPTDEVICE) << e.what() << '\n';
172 }
173
174 return yarp::dev::ReturnValue_ok;
175}
176
178{
179 auto &convo_json = m_convo->GetJSON();
180 for (auto &message : convo_json["messages"])
181 {
182 if (message["role"] == "system")
183 {
184 oPrompt = message["content"];
185 return yarp::dev::ReturnValue_ok;
186 }
187 }
188
190}
191
192yarp::dev::ReturnValue GPTDevice::getConversation(std::vector<yarp::dev::LLM_Message> &oConversation)
193{
194 std::vector<yarp::dev::LLM_Message> conversation;
195
196 auto &convo_json = m_convo->GetJSON();
197
198
199 if (convo_json["messages"].empty())
200 {
201 yCWarning(GPTDEVICE) << "Conversation is empty!";
203 }
204
205 for (auto &message : convo_json["messages"])
206 {
207 std::string type = message["role"].get<std::string>();
208 std::string content = message["content"].get<std::string>();
209
210 conversation.push_back(yarp::dev::LLM_Message{type, content,std::vector<std::string>(),std::vector<std::string>()});
211 }
212
213 // Adding function calls to the conversation
214 for(const auto& [i,answer]: m_function_called)
215 {
216 auto conv_it = conversation.begin();
217 conversation.insert(std::next(conv_it,i),answer);
218 }
219
220 oConversation = conversation;
221 return yarp::dev::ReturnValue_ok;
222}
223
225{
226 // Api does not provide a method to empty the conversation: we are better off if we rebuild an object from scratch
227 m_convo.reset(new liboai::Conversation());
228 m_convo_length = 0;
229 m_function_called.clear();
230 return yarp::dev::ReturnValue_ok;
231}
232
234{
235 std::string current_prompt = "";
236 this->readPrompt(current_prompt);
237 this->deleteConversation();
238 this->setPrompt(current_prompt);
239 return yarp::dev::ReturnValue_ok;
240}
241
243{
244 return true;
245}
246
247bool GPTDevice::setFunctions(const json& function_json)
248{
249
250 for (auto& function: function_json.items())
251 {
252 if(!function.value().contains("name") || !function.value().contains("description"))
253 {
254 yCError(GPTDEVICE) << "Function missing mandatory parameters <name> and/or <description>";
255 return false;
256 }
257
258 std::string function_name = function.value()["name"].template get<std::string>();
259 std::string function_desc = function.value()["description"].template get<std::string>();
260
261 if(!m_functions->AddFunction(function_name))
262 {
263 yCError(GPTDEVICE) << module_name + "::setFunctions(). Cannot add function.";
264 return false;
265 }
266
267 if(!m_functions->SetDescription(function_name,function_desc))
268 {
269 yCError(GPTDEVICE) << module_name + "::setFunctions(). Cannot set description";
270 return false;
271 }
272
273 if(function.value().contains("parameters"))
274 {
275 auto parameters = function.value()["parameters"]["properties"];
276 std::vector<liboai::Functions::FunctionParameter> parameters_vec;
277 for(auto& params: parameters.items())
278 {
279 liboai::Functions::FunctionParameter param;
280 param.name = params.key();
281 param.description = params.value()["description"];
282 param.type = params.value()["type"];
283 parameters_vec.push_back(param);
284 }
285 if(!m_functions->SetParameters(function_name,parameters_vec))
286 {
287 yCError(GPTDEVICE) << module_name + "::setFunction(). Cannot set parameters";
288 return false;
289 }
290 }
291 }
292
293 if(!m_convo->SetFunctions(*m_functions))
294 {
295 yCError(GPTDEVICE) << module_name + "::setFunction(). Cannot set function";
296 return false;
297 }
298
299 return true;
300}
const yarp::os::LogComponent & GPTDEVICE()
Definition GPTDevice.cpp:12
nlohmann::json json
Definition GPTDevice.cpp:14
nlohmann::json json
Definition GPTDevice.h:19
bool parseParams(const yarp::os::Searchable &config) override
Parse the DeviceDriver parameters.
yarp::dev::ReturnValue deleteConversation() noexcept override
Delete the conversation and clear the system context from any internally stored context.
yarp::dev::ReturnValue setPrompt(const std::string &prompt) override
Performs a question.
yarp::dev::ReturnValue getConversation(std::vector< yarp::dev::LLM_Message > &oConversation) override
Retrieves the whole conversation.
yarp::dev::ReturnValue refreshConversation() noexcept override
Refresh the conversation.
yarp::dev::ReturnValue readPrompt(std::string &oPrompt) override
Retrieves the provided prompt.
yarp::dev::ReturnValue ask(const std::string &question, yarp::dev::LLM_Message &oAnswer) override
Performs a question.
Definition GPTDevice.cpp:90
bool close() override
Close the DeviceDriver.
bool open(yarp::os::Searchable &config) override
Open the DeviceDriver.
Definition GPTDevice.cpp:16
@ return_value_error_method_failed
Method is deprecated.
@ TraceType
Definition Log.h:92
Helper class for finding config files and other external resources.
bool setDefaultContext(const std::string &contextName)
Sets the context for the current ResourceFinder object.
std::string findFile(const std::string &name)
Find the full path to a file.
A base class for nested structures that can be searched.
Definition Searchable.h:31
#define yCError(component,...)
#define yCWarning(component,...)
#define yCDebug(component,...)
#define YARP_LOG_COMPONENT(name,...)