Skip to content

Commit

Permalink
Merge pull request #1720 from janhq/j/add-message
Browse files Browse the repository at this point in the history
feat: add messages api
  • Loading branch information
namchuai authored Dec 3, 2024
2 parents 5eda212 + 7d6199d commit 0528553
Show file tree
Hide file tree
Showing 31 changed files with 3,127 additions and 4 deletions.
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;

// 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

0 comments on commit 0528553

Please sign in to comment.