Prompt Tools 0.3.2
Loading...
Searching...
No Matches
rest_base_class.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <sstream>
4#include <string>
5
6// include poco json and net/netssl
7#include <Poco/JSON/Object.h>
8#include <Poco/JSON/Parser.h>
9#include <Poco/Net/Context.h>
10#include <Poco/Net/HTMLForm.h>
11#include <Poco/Net/HTTPRequest.h>
12#include <Poco/Net/HTTPResponse.h>
13#include <Poco/Net/HTTPSClientSession.h>
14#include <Poco/Net/NetException.h>
15#include <Poco/URI.h>
16
19#include <prompt_msgs/msg/prompt.hpp>
20
21namespace prompt
22{
31{
32public:
39 {
40 }
41
47 virtual ~RestBaseClass() = default;
48
59 virtual void initialize_rest_base(rclcpp::Node::SharedPtr node, std::string plugin_name = "RestBaseClass",
60 std::string api_key_name = "")
61 {
62 // initialize base class
63 initialize_base(node, plugin_name);
64
65 // declare parameters only if not already declared (idempotent init)
66 if (!node_->has_parameter(plugin_name_ + ".rest.method"))
67 {
68 node_->declare_parameter(plugin_name_ + ".rest.method", "POST");
69 }
70 if (!node_->has_parameter(plugin_name_ + ".rest.ssl_verify"))
71 {
72 node_->declare_parameter(plugin_name_ + ".rest.ssl_verify", true);
73 }
74 if (!node_->has_parameter(plugin_name_ + ".rest.auth_type"))
75 {
76 node_->declare_parameter(plugin_name_ + ".rest.auth_type", "Bearer");
77 }
78
79 // get parameters from the parameter server
80 method_ = node_->get_parameter(plugin_name_ + ".rest.method").as_string();
81 ssl_verify_ = node_->get_parameter(plugin_name_ + ".rest.ssl_verify").as_bool();
82 auth_type_ = node_->get_parameter(plugin_name_ + ".rest.auth_type").as_string();
83
84 // get api key from environment
85 if (!api_key_name.empty())
86 {
87 const char* api_key_env = std::getenv(api_key_name.c_str());
88 if (api_key_env)
89 {
90 api_key_ = api_key_env;
91 RCLCPP_INFO(node_->get_logger(), "API key loaded from environment variables");
92 }
93 else
94 {
95 RCLCPP_WARN(node_->get_logger(), "missing env variable: %s", api_key_name.c_str());
96 throw prompt::PromptException("missing env variable: " + api_key_name);
97 api_key_ = "";
98 }
99 }
100 else
101 {
102 RCLCPP_INFO(node_->get_logger(), "An API key is not used for this plugin, using empty string");
103 api_key_ = "";
104 }
105
106 // log the parameters
107 RCLCPP_INFO(node_->get_logger(), "%s Method: %s", plugin_name_.c_str(), method_.c_str());
108 RCLCPP_INFO(node_->get_logger(), "%s SSL Verify: %s", plugin_name_.c_str(), ssl_verify_ ? "true" : "false");
109 RCLCPP_INFO(node_->get_logger(), "%s Auth Type: %s", plugin_name_.c_str(), auth_type_.c_str());
110 RCLCPP_INFO(node_->get_logger(), "%s Plugin initialized", plugin_name_.c_str());
111
112 RCLCPP_INFO(node_->get_logger(), "Loading default model options from parameters.");
114 }
115
116protected:
117 static std::string extract_error_description(const std::string& response_body)
118 {
119 if (response_body.empty())
120 {
121 return "";
122 }
123
124 try
125 {
126 Poco::JSON::Parser parser;
127 Poco::Dynamic::Var parsed = parser.parse(response_body);
128 const auto object = parsed.extract<Poco::JSON::Object::Ptr>();
129
130 if (object->has("error"))
131 {
132 const auto error_var = object->get("error");
133 if (error_var.type() == typeid(Poco::JSON::Object::Ptr))
134 {
135 const auto error_object = error_var.extract<Poco::JSON::Object::Ptr>();
136 if (error_object->has("message"))
137 {
138 return error_object->getValue<std::string>("message");
139 }
140 }
141 else if (error_var.type() == typeid(std::string))
142 {
143 return error_var.convert<std::string>();
144 }
145 }
146
147 if (object->has("message"))
148 {
149 return object->getValue<std::string>("message");
150 }
151 }
152 catch (const std::exception&)
153 {
154 }
155
156 return response_body;
157 }
158
170 Poco::JSON::Object::Ptr process(Poco::JSON::Object& body_json, std::string& uri)
171 {
172 // convert the uri
173 Poco::URI uri_obj(uri);
174
175 // calculate body length
176 std::ostringstream body_stream;
177 body_json.stringify(body_stream);
178
179 // create request object
180 Poco::Net::HTTPRequest request(method_, uri_obj.getPath());
181 // set headers
182 request.setContentType("application/json");
183 request.setContentLength(body_stream.str().size());
184
185 // if bearer token
186 if (auth_type_ == "Bearer")
187 {
188 request.setCredentials(auth_type_, api_key_);
189 }
190
191 // Todo: support other auth types
192 // else if (auth_type_ == "Token")
193 // {
194 // request.setCredentials(auth_type_, api_key_);
195 // }
196 else
197 {
198 RCLCPP_WARN(node_->get_logger(), "unsupported auth type: %s", auth_type_.c_str());
199 }
200
201 // RCLCPP_INFO(node_->get_logger(), "Port %d", uri.getPort());
202
203 std::unique_ptr<Poco::Net::HTTPClientSession> session_ptr;
204
205 // is the session secure?
206 if (uri_obj.getScheme() == "https")
207 {
208 // context without certificate verification
209 Poco::Net::Context::Params params;
210
211 if (!ssl_verify_)
212 {
213 params.verificationMode = Poco::Net::Context::VERIFY_NONE;
214 }
215 else
216 {
217 params.verificationMode = Poco::Net::Context::VERIFY_STRICT;
218 params.caLocation = "/etc/ssl/certs"; // Update this path if needed
219 }
220
221 Poco::Net::Context::Ptr context = new Poco::Net::Context(Poco::Net::Context::CLIENT_USE, params);
222
223 // create secure session
224 session_ptr = std::make_unique<Poco::Net::HTTPSClientSession>(uri_obj.getHost(), uri_obj.getPort(), context);
225 RCLCPP_DEBUG(node_->get_logger(), "secure session created");
226 }
227 else
228 {
229 // create insecure session
230 session_ptr = std::make_unique<Poco::Net::HTTPClientSession>(uri_obj.getHost(), uri_obj.getPort());
231
232 RCLCPP_WARN(node_->get_logger(), "insecure session created");
233 }
234
235 try
236 {
237 // send request
238 std::ostream& os = session_ptr->sendRequest(request);
239 // complete request body
240 body_json.stringify(os);
241 }
242 catch (const Poco::Net::NetException& e)
243 {
244 RCLCPP_ERROR(node_->get_logger(), "network error: %s", e.what());
245 throw prompt::PromptException("network error: " + std::string(e.what()));
246 }
247
248 // get response
249 Poco::Net::HTTPResponse response;
250 std::istream& rs = session_ptr->receiveResponse(response);
251
252 // check for errors
253 if (response.getStatus() != Poco::Net::HTTPResponse::HTTP_OK)
254 {
255 std::ostringstream error_stream;
256 error_stream << rs.rdbuf();
257 const std::string response_body = error_stream.str();
258 const std::string description = extract_error_description(response_body);
259
260 if (!description.empty())
261 {
262 RCLCPP_ERROR(node_->get_logger(), "HTTP Error: %i %s. Description: %s", response.getStatus(),
263 response.getReason().c_str(), description.c_str());
264 }
265 else
266 {
267 RCLCPP_ERROR(node_->get_logger(), "HTTP Error: %i %s", response.getStatus(), response.getReason().c_str());
268 }
269
270 std::string message = "HTTP Error: " + std::to_string(response.getStatus()) + " " + response.getReason();
271 if (!description.empty())
272 {
273 message += " - " + description;
274 }
275
276 throw prompt::PromptException(message);
277 }
278
279 // check content type is 'text/event-stream' or 'application/x-ndjson'
280 // https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events
281 // these types are used for server-sent events or newline delimited JSON
282 if (response.getContentType() == "text/event-stream" || response.getContentType() == "application/x-ndjson")
283 {
284 // TODO: handle event stream
285 // pass to stream parsing
286 // SinglePromptProvider::handle_event_stream(rs, chunck_cb);
287 RCLCPP_ERROR(node_->get_logger(), "HTTP streaming not supported");
288 throw prompt::PromptException("HTTP stream not supported");
289 }
290
291 // is the response chunked even though it is not server-sent event?
292 if (response.getChunkedTransferEncoding())
293 {
294 // TODO: handle chunked responses
295 RCLCPP_ERROR(node_->get_logger(), "HTTP Chunked Transfer Encoding not supported");
296 throw prompt::PromptException("HTTP Chunked Transfer Encoding not supported");
297 }
298
299 // parse response
300 Poco::JSON::Parser parser;
301 Poco::Dynamic::Var result = parser.parse(rs);
302 return result.extract<Poco::JSON::Object::Ptr>();
303 }
304
314 virtual const Poco::JSON::Object handle_options(std::vector<prompt::PromptOption>& options)
315 {
316 // flatten options into object
317 Poco::JSON::Object result;
318
319 for (const prompt::PromptOption& option : options)
320 {
321 // try cast the value if there is a type hint
322 if (option.type == prompt_msgs::msg::ModelOption::STRING_TYPE)
323 {
324 result.set(option.key, option.value);
325 continue;
326 }
327
328 // try cast the value if there is a type hint
329 if (option.type == prompt_msgs::msg::ModelOption::BOOL_TYPE)
330 {
331 result.set(option.key, (option.value == "true") ? true : false);
332 continue;
333 }
334
335 // try cast the value if there is a type hint
336 if (option.type == prompt_msgs::msg::ModelOption::INT_TYPE)
337 {
338 result.set(option.key, std::stoi(option.value));
339 continue;
340 }
341
342 // try cast the value if there is a type hint
343 if (option.type == prompt_msgs::msg::ModelOption::REAL_TYPE)
344 {
345 result.set(option.key, std::stod(option.value));
346 continue;
347 }
348
349 // just set the value if there is no type hint
350 result.set(option.key, option.value);
351 }
352
353 return result;
354 }
355
356 std::string method_;
358 std::string auth_type_;
359
363 std::string api_key_;
364
371 std::vector<prompt::PromptOption> required_options_;
372};
373
374} // namespace prompt
PromptProviderBase.
Definition base_class.hpp:17
virtual void initialize_base(rclcpp::Node::SharedPtr node, std::string plugin_name="BaseClass")
Initialize the prompt base class.
Definition base_class.hpp:91
rclcpp::Node::SharedPtr node_
Node shared pointer.
Definition base_class.hpp:105
std::string plugin_name_
Plugin name.
Definition base_class.hpp:112
Definition exceptions.hpp:10
RestBaseClass.
Definition rest_base_class.hpp:31
std::string auth_type_
Definition rest_base_class.hpp:358
Poco::JSON::Object::Ptr process(Poco::JSON::Object &body_json, std::string &uri)
Process the HTTP request and return the response as a JSON object.
Definition rest_base_class.hpp:170
bool ssl_verify_
Definition rest_base_class.hpp:357
std::vector< prompt::PromptOption > required_options_
Model options.
Definition rest_base_class.hpp:371
virtual const Poco::JSON::Object handle_options(std::vector< prompt::PromptOption > &options)
Process options from the prompt request.
Definition rest_base_class.hpp:314
std::string method_
Definition rest_base_class.hpp:356
RestBaseClass()
Constructor.
Definition rest_base_class.hpp:38
virtual ~RestBaseClass()=default
Destructor.
std::string api_key_
API key.
Definition rest_base_class.hpp:363
virtual void initialize_rest_base(rclcpp::Node::SharedPtr node, std::string plugin_name="RestBaseClass", std::string api_key_name="")
Initialize the REST base class.
Definition rest_base_class.hpp:59
static std::string extract_error_description(const std::string &response_body)
Definition rest_base_class.hpp:117
Definition base_class.hpp:8
std::vector< prompt::PromptOption > load_from_parameters(rclcpp::Node::SharedPtr node, std::string plugin_name)
Load prompt options from ROS2 parameters.
Definition prompt_options.hpp:23
Definition structs.hpp:9