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

feat: add messages api #1720

Merged
merged 1 commit into from
Dec 3, 2024
Merged
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
3 changes: 2 additions & 1 deletion engine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ else()
endif()

aux_source_directory(controllers CTL_SRC)
aux_source_directory(repositories REPO_SRC)
aux_source_directory(services SERVICES_SRC)
aux_source_directory(common COMMON_SRC)
aux_source_directory(models MODEL_SRC)
Expand All @@ -180,7 +181,7 @@ aux_source_directory(utils UTILS_SRC)

target_include_directories(${TARGET_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} )

target_sources(${TARGET_NAME} PRIVATE ${UTILS_SRC} ${CONFIG_SRC} ${CTL_SRC} ${COMMON_SRC} ${SERVICES_SRC} ${DB_SRC} ${MIGR_SRC})
target_sources(${TARGET_NAME} PRIVATE ${UTILS_SRC} ${CONFIG_SRC} ${CTL_SRC} ${COMMON_SRC} ${SERVICES_SRC} ${DB_SRC} ${MIGR_SRC} ${REPO_SRC})

set_target_properties(${TARGET_NAME} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}
Expand Down
1 change: 0 additions & 1 deletion engine/cli/commands/engine_install_cmd.cc
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,6 @@ bool EngineInstallCmd::Exec(const std::string& engine,
auto response = curl_utils::SimplePostJson(install_url.ToFullPath(),
body.toStyledString());
if (response.has_error()) {
// TODO: namh refactor later
Json::Value root;
Json::Reader reader;
if (!reader.parse(response.error(), root)) {
Expand Down
19 changes: 19 additions & 0 deletions engine/common/api-dto/messages/delete_message_response.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#pragma once

#include "common/json_serializable.h"

namespace api_response {
struct DeleteMessageResponse : JsonSerializable {
std::string id;
std::string object;
bool deleted;

cpp::result<Json::Value, std::string> ToJson() override {
Json::Value json;
json["id"] = id;
json["object"] = object;
json["deleted"] = deleted;
return json;
}
};
} // namespace api_response
11 changes: 11 additions & 0 deletions engine/common/json_serializable.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#pragma once

#include <json/value.h>
#include "utils/result.hpp"

struct JsonSerializable {

virtual cpp::result<Json::Value, std::string> ToJson() = 0;

virtual ~JsonSerializable() = default;
};
213 changes: 213 additions & 0 deletions engine/common/message.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
#pragma once

#include <json/reader.h>
#include <json/value.h>
#include <json/writer.h>
#include <cstdint>
#include <string>
#include "common/message_attachment.h"
#include "common/message_attachment_factory.h"
#include "common/message_content.h"
#include "common/message_content_factory.h"
#include "common/message_incomplete_detail.h"
#include "common/message_role.h"
#include "common/message_status.h"
#include "common/variant_map.h"
#include "json_serializable.h"
#include "utils/logging_utils.h"
#include "utils/result.hpp"

namespace ThreadMessage {

// Represents a message within a thread.
struct Message : JsonSerializable {
Message() = default;

Message(Message&&) = default;

Message& operator=(Message&&) = default;

Message(const Message&) = delete;

Message& operator=(const Message&) = delete;

// The identifier, which can be referenced in API endpoints.
std::string id;

// The object type, which is always thread.message.
std::string object = "thread.message";

// The Unix timestamp (in seconds) for when the message was created.
uint32_t created_at;

// The thread ID that this message belongs to.
std::string thread_id;

// The status of the message, which can be either in_progress, incomplete, or completed.
Status status;

// On an incomplete message, details about why the message is incomplete.
std::optional<IncompleteDetail> incomplete_details;

// The Unix timestamp (in seconds) for when the message was completed.
std::optional<uint32_t> completed_at;

// The Unix timestamp (in seconds) for when the message was marked as incomplete.
std::optional<uint32_t> incomplete_at;

Role role;

// The content of the message in array of text and/or images.
std::vector<std::unique_ptr<Content>> content;
vansangpfiev marked this conversation as resolved.
Show resolved Hide resolved

// If applicable, the ID of the assistant that authored this message.
std::optional<std::string> assistant_id;

// The ID of the run associated with the creation of this message. Value is null when messages are created manually using the create message or create thread endpoints.
std::optional<std::string> run_id;

// A list of files attached to the message, and the tools they were added to.
std::optional<std::vector<Attachment>> attachments;

// Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a structured format. Keys can be a maximum of 64 characters long and values can be a maximum of 512 characters long.
Cortex::VariantMap metadata;

static cpp::result<Message, std::string> FromJsonString(
std::string&& json_str) {
Json::Value root;
Json::Reader reader;
if (!reader.parse(json_str, root)) {
return cpp::fail("Failed to parse JSON: " +
reader.getFormattedErrorMessages());
}

Message message;

try {
message.id = std::move(root["id"].asString());
message.object =
std::move(root.get("object", "thread.message").asString());
message.created_at = root["created_at"].asUInt();
if (message.created_at == 0 && root["created"].asUInt64() != 0) {
message.created_at = root["created"].asUInt64() / 1000;
}
message.thread_id = std::move(root["thread_id"].asString());
message.status = StatusFromString(std::move(root["status"].asString()));

message.incomplete_details =
IncompleteDetail::FromJson(std::move(root["incomplete_details"]))
.value();
message.completed_at = root["completed_at"].asUInt();
message.incomplete_at = root["incomplete_at"].asUInt();
message.role = RoleFromString(std::move(root["role"].asString()));
message.content = ParseContents(std::move(root["content"])).value();

message.assistant_id = std::move(root["assistant_id"].asString());
message.run_id = std::move(root["run_id"].asString());
message.attachments =
ParseAttachments(std::move(root["attachments"])).value();

if (root["metadata"].isObject() && !root["metadata"].empty()) {
auto res = Cortex::ConvertJsonValueToMap(root["metadata"]);
if (res.has_error()) {
CTL_WRN("Failed to convert metadata to map: " + res.error());
} else {
message.metadata = res.value();
}
}

return message;
} catch (const std::exception& e) {
return cpp::fail(std::string("FromJsonString failed: ") + e.what());
}
}

cpp::result<std::string, std::string> ToSingleLineJsonString() {
auto json_result = ToJson();
if (json_result.has_error()) {
return cpp::fail(json_result.error());
}

Json::FastWriter writer;
try {
return writer.write(json_result.value());
} catch (const std::exception& e) {
return cpp::fail(std::string("Failed to write JSON: ") + e.what());
}
}

cpp::result<Json::Value, std::string> ToJson() override {
try {
Json::Value json;

json["id"] = id;
json["object"] = object;
json["created_at"] = created_at;
json["thread_id"] = thread_id;
json["status"] = StatusToString(status);

if (incomplete_details.has_value()) {
if (auto it = incomplete_details->ToJson(); it.has_value()) {
json["incomplete_details"] = it.value();
} else {
CTL_WRN("Failed to convert incomplete_details to json: " +
it.error());
}
}
if (completed_at.has_value() && completed_at.value() != 0) {
json["completed_at"] = *completed_at;
}
if (incomplete_at.has_value() && incomplete_at.value() != 0) {
json["incomplete_at"] = *incomplete_at;
}

json["role"] = RoleToString(role);

Json::Value content_json_arr{Json::arrayValue};
for (auto& child_content : content) {
if (auto it = child_content->ToJson(); it.has_value()) {
content_json_arr.append(it.value());
} else {
CTL_WRN("Failed to convert content to json: " + it.error());
}
}
json["content"] = content_json_arr;
if (assistant_id.has_value() && !assistant_id->empty()) {
json["assistant_id"] = *assistant_id;
}
if (run_id.has_value() && !run_id->empty()) {
json["run_id"] = *run_id;
}
if (attachments.has_value()) {
Json::Value attachments_json_arr{Json::arrayValue};
for (auto& attachment : *attachments) {
if (auto it = attachment.ToJson(); it.has_value()) {
attachments_json_arr.append(it.value());
} else {
CTL_WRN("Failed to convert attachment to json: " + it.error());
}
}
json["attachments"] = attachments_json_arr;
}

Json::Value metadata_json{Json::objectValue};
for (const auto& [key, value] : metadata) {
if (std::holds_alternative<bool>(value)) {
metadata_json[key] = std::get<bool>(value);
} else if (std::holds_alternative<uint64_t>(value)) {
metadata_json[key] = std::get<uint64_t>(value);
} else if (std::holds_alternative<double>(value)) {
metadata_json[key] = std::get<double>(value);
} else {
metadata_json[key] = std::get<std::string>(value);
}
}
json["metadata"] = metadata_json;

return json;
} catch (const std::exception& e) {
return cpp::fail(std::string("ToJson failed: ") + e.what());
}
}
};
}; // namespace ThreadMessage
50 changes: 50 additions & 0 deletions engine/common/message_attachment.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#pragma once

#include <json/reader.h>
#include "common/json_serializable.h"

namespace ThreadMessage {

// The tools to add this file to.
struct Tool {
std::string type;

Tool(const std::string& type) : type{type} {}
};

// The type of tool being defined: code_interpreter
struct CodeInterpreter : Tool {
CodeInterpreter() : Tool{"code_interpreter"} {}
};

// The type of tool being defined: file_search
struct FileSearch : Tool {
FileSearch() : Tool{"file_search"} {}
};

// A list of files attached to the message, and the tools they were added to.
struct Attachment : JsonSerializable {

// The ID of the file to attach to the message.
std::string file_id;

std::vector<Tool> tools;

cpp::result<Json::Value, std::string> ToJson() override {
try {
Json::Value json;
json["file_id"] = file_id;
Json::Value tools_json_arr{Json::arrayValue};
for (auto& tool : tools) {
Json::Value tool_json;
tool_json["type"] = tool.type;
tools_json_arr.append(tool_json);
}
json["tools"] = tools_json_arr;
return json;
} catch (const std::exception& e) {
return cpp::fail(std::string("ToJson failed: ") + e.what());
}
}
};
}; // namespace ThreadMessage
48 changes: 48 additions & 0 deletions engine/common/message_attachment_factory.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#include <optional>
#include "common/message_attachment.h"
#include "utils/result.hpp"

namespace ThreadMessage {
inline cpp::result<Attachment, std::string> ParseAttachment(
Json::Value&& json) {
if (json.empty()) {
return cpp::fail("Json string is empty");
}

Attachment attachment;
attachment.file_id = json["file_id"].asString();

std::vector<Tool> tools{};
if (json["tools"].isArray()) {
for (auto& tool_json : json["tools"]) {
Tool tool{tool_json["type"].asString()};
tools.push_back(tool);
}
}
attachment.tools = tools;

return attachment;
}

inline cpp::result<std::optional<std::vector<Attachment>>, std::string>
ParseAttachments(Json::Value&& json) {
if (json.empty()) {
// still count as success
return std::nullopt;
}
if (!json.isArray()) {
return cpp::fail("Json is not an array");
}

std::vector<Attachment> attachments;
for (auto& attachment_json : json) {
auto attachment = ParseAttachment(std::move(attachment_json));
if (attachment.has_error()) {
return cpp::fail(attachment.error());
}
attachments.push_back(attachment.value());
}

return attachments;
}
}; // namespace ThreadMessage
23 changes: 23 additions & 0 deletions engine/common/message_content.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#pragma once

#include <string>
#include "common/json_serializable.h"

namespace ThreadMessage {

struct Content : JsonSerializable {
std::string type;

Content(const std::string& type) : type{type} {}

Content(const Content&) = delete;

Content& operator=(const Content&) = delete;

Content(Content&&) noexcept = default;

Content& operator=(Content&&) noexcept = default;

virtual ~Content() = default;
};
}; // namespace ThreadMessage
Loading