add humanus_server (mcp)
parent
7c797864dd
commit
26c45d966e
11
agent/base.h
11
agent/base.h
|
@ -157,7 +157,7 @@ struct BaseAgent : std::enable_shared_from_this<BaseAgent> {
|
||||||
memory->add_message(Message::user_message(stuck_prompt));
|
memory->add_message(Message::user_message(stuck_prompt));
|
||||||
}
|
}
|
||||||
|
|
||||||
// O(nm) LCS algorithm, could basically handle current LLM context
|
// O(n * m) LCS algorithm, could basically handle current LLM context
|
||||||
size_t get_lcs_length(const std::string& s1, const std::string& s2) {
|
size_t get_lcs_length(const std::string& s1, const std::string& s2) {
|
||||||
std::vector<std::vector<size_t>> dp(s1.size() + 1, std::vector<size_t>(s2.size() + 1));
|
std::vector<std::vector<size_t>> dp(s1.size() + 1, std::vector<size_t>(s2.size() + 1));
|
||||||
for (size_t i = 1; i <= s1.size(); i++) {
|
for (size_t i = 1; i <= s1.size(); i++) {
|
||||||
|
@ -209,10 +209,19 @@ struct BaseAgent : std::enable_shared_from_this<BaseAgent> {
|
||||||
void reset(bool reset_memory = true) {
|
void reset(bool reset_memory = true) {
|
||||||
current_step = 0;
|
current_step = 0;
|
||||||
state = AgentState::IDLE;
|
state = AgentState::IDLE;
|
||||||
|
llm->reset_tokens();
|
||||||
if (reset_memory) {
|
if (reset_memory) {
|
||||||
memory->clear();
|
memory->clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t get_prompt_tokens() {
|
||||||
|
return llm->get_prompt_tokens();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t get_completion_tokens() {
|
||||||
|
return llm->get_completion_tokens();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace humanus
|
} // namespace humanus
|
||||||
|
|
|
@ -35,11 +35,13 @@ struct ReActAgent : BaseAgent {
|
||||||
// Execute a single step: think and act.
|
// Execute a single step: think and act.
|
||||||
virtual std::string step() {
|
virtual std::string step() {
|
||||||
bool should_act = think();
|
bool should_act = think();
|
||||||
logger->info("Prompt tokens: " + std::to_string(llm->total_prompt_tokens()) + ", Completion tokens: " + std::to_string(llm->total_completion_tokens()));
|
|
||||||
if (!should_act) {
|
if (!should_act) {
|
||||||
return "Thinking complete - no action needed";
|
return "Thinking complete - no action needed";
|
||||||
}
|
}
|
||||||
return act();
|
if (state == AgentState::RUNNING) {
|
||||||
|
return act();
|
||||||
|
}
|
||||||
|
return "Agent is not running";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,10 @@ bool ToolCallAgent::think() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state != AgentState::RUNNING) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Handle different tool_choices modes
|
// Handle different tool_choices modes
|
||||||
if (tool_choice == "none") {
|
if (tool_choice == "none") {
|
||||||
|
@ -76,6 +80,10 @@ std::string ToolCallAgent::act() {
|
||||||
|
|
||||||
std::vector<std::string> results;
|
std::vector<std::string> results;
|
||||||
for (const auto& tool_call : tool_calls) {
|
for (const auto& tool_call : tool_calls) {
|
||||||
|
if (state != AgentState::RUNNING) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
auto result = execute_tool(tool_call);
|
auto result = execute_tool(tool_call);
|
||||||
logger->info(
|
logger->info(
|
||||||
"🎯 Tool `" + tool_call.function.name + "` completed its mission! Result: " + result.substr(0, 500) + (result.size() > 500 ? "..." : "")
|
"🎯 Tool `" + tool_call.function.name + "` completed its mission! Result: " + result.substr(0, 500) + (result.size() > 500 ? "..." : "")
|
||||||
|
@ -94,6 +102,10 @@ std::string ToolCallAgent::act() {
|
||||||
result_str += result + "\n\n";
|
result_str += result + "\n\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state != AgentState::RUNNING) {
|
||||||
|
result_str += "Agent is not running, so no more tool calls will be executed.\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
return result_str;
|
return result_str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,8 @@
|
||||||
# examples/CMakeLists.txt
|
|
||||||
# 构建所有examples目录
|
|
||||||
|
|
||||||
# 获取examples目录下的所有子目录
|
|
||||||
file(GLOB EXAMPLE_DIRS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/*)
|
file(GLOB EXAMPLE_DIRS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/*)
|
||||||
|
|
||||||
# 遍历所有子目录
|
|
||||||
foreach(EXAMPLE_DIR ${EXAMPLE_DIRS})
|
foreach(EXAMPLE_DIR ${EXAMPLE_DIRS})
|
||||||
# 检查是否是目录
|
|
||||||
if(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${EXAMPLE_DIR})
|
if(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${EXAMPLE_DIR})
|
||||||
# 检查子目录中是否有CMakeLists.txt文件
|
|
||||||
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${EXAMPLE_DIR}/CMakeLists.txt")
|
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${EXAMPLE_DIR}/CMakeLists.txt")
|
||||||
# 添加子目录
|
|
||||||
add_subdirectory(${EXAMPLE_DIR})
|
add_subdirectory(${EXAMPLE_DIR})
|
||||||
message(STATUS "Added example: ${EXAMPLE_DIR}")
|
message(STATUS "Added example: ${EXAMPLE_DIR}")
|
||||||
endif()
|
endif()
|
||||||
|
|
|
@ -2,5 +2,4 @@ set(target humanus_chat)
|
||||||
|
|
||||||
add_executable(${target} humanus_chat.cpp)
|
add_executable(${target} humanus_chat.cpp)
|
||||||
|
|
||||||
# 链接到核心库
|
|
||||||
target_link_libraries(${target} PRIVATE humanus)
|
target_link_libraries(${target} PRIVATE humanus)
|
|
@ -2,11 +2,4 @@ set(target humanus_cli)
|
||||||
|
|
||||||
add_executable(${target} main.cpp)
|
add_executable(${target} main.cpp)
|
||||||
|
|
||||||
# 链接到核心库
|
|
||||||
target_link_libraries(${target} PRIVATE humanus)
|
target_link_libraries(${target} PRIVATE humanus)
|
||||||
|
|
||||||
# 设置输出目录
|
|
||||||
set_target_properties(${target}
|
|
||||||
PROPERTIES
|
|
||||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
|
|
||||||
)
|
|
|
@ -2,11 +2,4 @@ set(target humanus_cli_mcp)
|
||||||
|
|
||||||
add_executable(${target} humanus_mcp.cpp)
|
add_executable(${target} humanus_mcp.cpp)
|
||||||
|
|
||||||
# 链接到核心库
|
|
||||||
target_link_libraries(${target} PRIVATE humanus)
|
target_link_libraries(${target} PRIVATE humanus)
|
||||||
|
|
||||||
# 设置输出目录
|
|
||||||
set_target_properties(${target}
|
|
||||||
PROPERTIES
|
|
||||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
|
|
||||||
)
|
|
|
@ -2,11 +2,4 @@ set(target humanus_cli_plan)
|
||||||
|
|
||||||
add_executable(${target} humanus_plan.cpp)
|
add_executable(${target} humanus_plan.cpp)
|
||||||
|
|
||||||
# 链接到核心库
|
|
||||||
target_link_libraries(${target} PRIVATE humanus)
|
target_link_libraries(${target} PRIVATE humanus)
|
||||||
|
|
||||||
# 设置输出目录
|
|
||||||
set_target_properties(${target}
|
|
||||||
PROPERTIES
|
|
||||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
|
|
||||||
)
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
set(target humanus_server)
|
||||||
|
|
||||||
|
add_executable(${target} server.cpp)
|
||||||
|
|
||||||
|
target_link_libraries(${target} PRIVATE humanus)
|
|
@ -0,0 +1,222 @@
|
||||||
|
#include "httplib.h"
|
||||||
|
#include "agent/humanus.h"
|
||||||
|
#include "logger.h"
|
||||||
|
#include "mcp_server.h"
|
||||||
|
#include "mcp_tool.h"
|
||||||
|
#include <vector>
|
||||||
|
#include <mutex>
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <memory>
|
||||||
|
#include <atomic>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <random>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
using namespace humanus;
|
||||||
|
|
||||||
|
static auto session_sink = SessionSink::get_instance();
|
||||||
|
|
||||||
|
class SessionManager {
|
||||||
|
public:
|
||||||
|
std::shared_ptr<Humanus> get_agent(const std::string& session_id) {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
|
||||||
|
auto it = agents_.find(session_id);
|
||||||
|
if (it != agents_.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto agent = std::make_shared<Humanus>();
|
||||||
|
agents_[session_id] = agent;
|
||||||
|
|
||||||
|
return agent;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<std::string> get_logs_buffer(const std::string& session_id) {
|
||||||
|
return session_sink->get_buffer(session_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<std::string> get_logs_history(const std::string& session_id) {
|
||||||
|
return session_sink->get_history(session_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_result(const std::string& session_id, const std::string& result) {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
results_[session_id] = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string get_result(const std::string& session_id) {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
|
||||||
|
auto it = results_.find(session_id);
|
||||||
|
if (it != results_.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear_result(const std::string& session_id) {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
results_.erase(session_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool has_session(const std::string& session_id) {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
return agents_.find(session_id) != agents_.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void close_session(const std::string& session_id) {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
agents_.erase(session_id);
|
||||||
|
results_.erase(session_id);
|
||||||
|
session_sink->cleanup_session(session_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> get_all_sessions() {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
std::vector<std::string> sessions;
|
||||||
|
for (const auto& pair : agents_) {
|
||||||
|
sessions.push_back(pair.first);
|
||||||
|
}
|
||||||
|
return sessions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::mutex mutex_;
|
||||||
|
std::unordered_map<std::string, std::shared_ptr<Humanus>> agents_;
|
||||||
|
std::unordered_map<std::string, std::string> results_;
|
||||||
|
std::unordered_map<std::string, std::shared_ptr<SessionSink>> session_sinks_;
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
mcp::set_log_level(mcp::log_level::warning);
|
||||||
|
|
||||||
|
int port = 8896;
|
||||||
|
|
||||||
|
if (argc == 2) {
|
||||||
|
try {
|
||||||
|
port = std::stoi(argv[1]);
|
||||||
|
} catch (...) {
|
||||||
|
std::cerr << "Invalid port number: " << argv[1] << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and configure server
|
||||||
|
mcp::server server("localhost", port, "HumanusServer", "0.0.1");
|
||||||
|
|
||||||
|
// Set server capabilities
|
||||||
|
mcp::json capabilities = {
|
||||||
|
{"tools", mcp::json::object()}
|
||||||
|
};
|
||||||
|
server.set_capabilities(capabilities);
|
||||||
|
|
||||||
|
auto session_manager = std::make_shared<SessionManager>();
|
||||||
|
|
||||||
|
auto run_tool = mcp::tool_builder("humanus_run")
|
||||||
|
.with_description("Request to start a new task. Best to give clear and concise prompts.")
|
||||||
|
.with_string_param("prompt", "The prompt text to process", true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
server.register_tool(run_tool, [session_manager](const json& args, const std::string& session_id) -> json {
|
||||||
|
if (!args.contains("prompt")) {
|
||||||
|
throw mcp::mcp_exception(mcp::error_code::invalid_params, "Missing `prompt` parameter");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string prompt = args["prompt"].get<std::string>();
|
||||||
|
|
||||||
|
auto agent = session_manager->get_agent(session_id);
|
||||||
|
|
||||||
|
if (agent->state != AgentState::IDLE) {
|
||||||
|
throw mcp::mcp_exception(mcp::error_code::invalid_request, "The agent is busy, please wait for the current task to complete or terminate the current task.");
|
||||||
|
}
|
||||||
|
|
||||||
|
agent->reset();
|
||||||
|
|
||||||
|
std::thread([agent, session_manager, prompt, session_id]() {
|
||||||
|
try {
|
||||||
|
session_sink->set_session_id(session_id);
|
||||||
|
logger->info("Processing your request: " + prompt);
|
||||||
|
auto result = agent->run(prompt);
|
||||||
|
logger->info("Task completed.");
|
||||||
|
session_manager->set_result(session_id, result);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
logger->error("Session {} error: {}", session_id, e.what());
|
||||||
|
}
|
||||||
|
}).detach();
|
||||||
|
|
||||||
|
return {{
|
||||||
|
{"type", "text"},
|
||||||
|
{"text", "Task started, call `humanus_status` to check the status."}
|
||||||
|
}};
|
||||||
|
});
|
||||||
|
|
||||||
|
auto terminate_tool = mcp::tool_builder("humanus_terminate")
|
||||||
|
.with_description("Terminate the current task")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
server.register_tool(terminate_tool, [session_manager](const json& args, const std::string& session_id) -> json {
|
||||||
|
if (!session_manager->has_session(session_id)) {
|
||||||
|
throw mcp::mcp_exception(mcp::error_code::invalid_request, "Session not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto agent = session_manager->get_agent(session_id);
|
||||||
|
|
||||||
|
if (agent->state == AgentState::IDLE) {
|
||||||
|
return {{
|
||||||
|
{"type", "text"},
|
||||||
|
{"text", "The agent is idle, no task to terminate."}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
agent->update_memory("user", "User interrupted the interaction. Consider rescheduling the previous task or switching to a different task according to the user's request.");
|
||||||
|
agent->state = AgentState::IDLE;
|
||||||
|
|
||||||
|
logger->info("Task terminated by user.");
|
||||||
|
|
||||||
|
return {{
|
||||||
|
{"type", "text"},
|
||||||
|
{"text", "Task terminated."}
|
||||||
|
}};
|
||||||
|
});
|
||||||
|
|
||||||
|
auto status_tool = mcp::tool_builder("humanus_status")
|
||||||
|
.with_description("Get the status of the current task.")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
server.register_tool(status_tool, [session_manager](const json& args, const std::string& session_id) -> json {
|
||||||
|
if (!session_manager->has_session(session_id)) {
|
||||||
|
throw mcp::mcp_exception(mcp::error_code::invalid_request, "Session not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto agent = session_manager->get_agent(session_id);
|
||||||
|
auto result = session_manager->get_result(session_id);
|
||||||
|
|
||||||
|
json status = {
|
||||||
|
{"state", agent_state_map[agent->state]},
|
||||||
|
{"current_step", agent->current_step},
|
||||||
|
{"max_steps", agent->max_steps},
|
||||||
|
{"prompt_tokens", agent->get_prompt_tokens()},
|
||||||
|
{"completion_tokens", agent->get_completion_tokens()},
|
||||||
|
{"logs_buffer", session_sink->get_buffer(session_id)},
|
||||||
|
{"result", result}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {{
|
||||||
|
{"type", "text"},
|
||||||
|
{"text", status.dump(2)}
|
||||||
|
}};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start server
|
||||||
|
std::cout << "Starting Humanus server at http://localhost:" << port << "..." << std::endl;
|
||||||
|
std::cout << "Press Ctrl+C to stop server" << std::endl;
|
||||||
|
server.start(true); // Blocking mode
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
|
@ -120,13 +120,18 @@ public:
|
||||||
int max_retries = 3
|
int max_retries = 3
|
||||||
);
|
);
|
||||||
|
|
||||||
size_t total_prompt_tokens() const {
|
size_t get_prompt_tokens() const {
|
||||||
return total_prompt_tokens_;
|
return total_prompt_tokens_;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t total_completion_tokens() const {
|
size_t get_completion_tokens() const {
|
||||||
return total_completion_tokens_;
|
return total_completion_tokens_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void reset_tokens() {
|
||||||
|
total_prompt_tokens_ = 0;
|
||||||
|
total_completion_tokens_ = 0;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace humanus
|
} // namespace humanus
|
||||||
|
|
173
include/logger.h
173
include/logger.h
|
@ -1,33 +1,180 @@
|
||||||
#ifndef HUMANUSlogger_H
|
#ifndef HUMANUSlogger_H
|
||||||
#define HUMANUSlogger_H
|
#define HUMANUSlogger_H
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "spdlog/spdlog.h"
|
#include "spdlog/spdlog.h"
|
||||||
#include "spdlog/sinks/stdout_color_sinks.h"
|
#include "spdlog/sinks/stdout_color_sinks.h"
|
||||||
#include "spdlog/sinks/basic_file_sink.h"
|
#include "spdlog/sinks/basic_file_sink.h"
|
||||||
#include "spdlog/sinks/rotating_file_sink.h"
|
#include "spdlog/sinks/rotating_file_sink.h"
|
||||||
#include "spdlog/sinks/daily_file_sink.h"
|
#include "spdlog/sinks/daily_file_sink.h"
|
||||||
#include "spdlog/sinks/dist_sink.h"
|
#include "spdlog/sinks/dist_sink.h"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include "config.h"
|
#include <mutex>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
namespace humanus {
|
namespace humanus {
|
||||||
|
|
||||||
static spdlog::level::level_enum _print_level = spdlog::level::info;
|
static spdlog::level::level_enum _print_level = spdlog::level::info;
|
||||||
|
static spdlog::level::level_enum _logfile_level = spdlog::level::debug;
|
||||||
|
|
||||||
/**
|
extern std::shared_ptr<spdlog::logger> set_log_level(spdlog::level::level_enum print_level, spdlog::level::level_enum logfile_level);
|
||||||
* @brief Adjust the log level
|
|
||||||
* @param print_level The console output log level
|
|
||||||
* @param logfile_level The file record log level
|
|
||||||
* @param name The log file name prefix
|
|
||||||
* @return The log record instance
|
|
||||||
*/
|
|
||||||
extern std::shared_ptr<spdlog::logger> define_log_level(spdlog::level::level_enum print_level = spdlog::level::info,
|
|
||||||
spdlog::level::level_enum logfile_level = spdlog::level::debug,
|
|
||||||
std::string name = "");
|
|
||||||
|
|
||||||
static std::shared_ptr<spdlog::logger> logger = define_log_level();
|
class SessionSink : public spdlog::sinks::base_sink<std::mutex> {
|
||||||
|
private:
|
||||||
|
inline static std::unordered_map<std::string, std::vector<std::string>> buffers_; // session_id -> buffer
|
||||||
|
inline static std::unordered_map<std::string, std::vector<std::string>> histories_; // session_id -> history
|
||||||
|
inline static std::unordered_map<std::string, std::string> sessions_; // thread_id -> session_id
|
||||||
|
inline static std::mutex mutex_;
|
||||||
|
|
||||||
|
SessionSink() = default;
|
||||||
|
SessionSink(const SessionSink&) = delete;
|
||||||
|
SessionSink& operator=(const SessionSink&) = delete;
|
||||||
|
|
||||||
|
public:
|
||||||
|
~SessionSink() = default;
|
||||||
|
|
||||||
|
static std::shared_ptr<SessionSink> get_instance() {
|
||||||
|
static SessionSink instance;
|
||||||
|
static std::shared_ptr<SessionSink> shared_instance(&instance, [](SessionSink*){});
|
||||||
|
return shared_instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sink_it_(const spdlog::details::log_msg& msg) override {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
|
||||||
|
if (sessions_.find(get_thread_id()) == sessions_.end()) { // Ignore messages if session_id is not set
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto session_id = sessions_[get_thread_id()];
|
||||||
|
|
||||||
|
auto time_t = std::chrono::system_clock::to_time_t(msg.time);
|
||||||
|
auto tm = fmt::localtime(time_t);
|
||||||
|
std::string log_message = fmt::format("[{:%Y-%m-%d %H:%M:%S}] {}", tm, msg.payload);
|
||||||
|
|
||||||
|
buffers_[session_id].push_back(log_message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void flush_() override {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
|
||||||
|
if (sessions_.find(get_thread_id()) == sessions_.end()) { // Ignore messages if session_id is not set
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto session_id = sessions_[get_thread_id()];
|
||||||
|
|
||||||
|
if (!buffers_[session_id].empty()) {
|
||||||
|
histories_[session_id].insert(histories_[session_id].end(),
|
||||||
|
buffers_[session_id].begin(),
|
||||||
|
buffers_[session_id].end());
|
||||||
|
buffers_[session_id].clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string get_thread_id() {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << std::this_thread::get_id();
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_session_id(std::string session_id) {
|
||||||
|
if (session_id.empty()) {
|
||||||
|
throw std::invalid_argument("session_id is empty");
|
||||||
|
}
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
sessions_[get_thread_id()] = session_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Messages in buffer are flushed to the history and cleared from the buffer.
|
||||||
|
std::vector<std::string> get_buffer(const std::string& session_id) {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
|
||||||
|
if (buffers_.find(session_id) == buffers_.end()) {
|
||||||
|
throw std::invalid_argument("Invalid session_id: " + session_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> buffer = buffers_[session_id];
|
||||||
|
if (!buffers_[session_id].empty()) {
|
||||||
|
histories_[session_id].insert(histories_[session_id].end(),
|
||||||
|
buffers_[session_id].begin(),
|
||||||
|
buffers_[session_id].end());
|
||||||
|
buffers_[session_id].clear();
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> get_history(const std::string& session_id) {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
|
||||||
|
if (histories_.find(session_id) == histories_.end()) {
|
||||||
|
throw std::invalid_argument("Invalid session_id: " + session_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return histories_[session_id];
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear_buffer() {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
|
||||||
|
if (sessions_.find(get_thread_id()) == sessions_.end()) { // Ignore messages if session_id is not set
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto session_id = sessions_[get_thread_id()];
|
||||||
|
|
||||||
|
if (!buffers_[session_id].empty()) {
|
||||||
|
histories_[session_id].insert(histories_[session_id].end(),
|
||||||
|
buffers_[session_id].begin(),
|
||||||
|
buffers_[session_id].end());
|
||||||
|
buffers_[session_id].clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear_history() {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
|
||||||
|
if (sessions_.find(get_thread_id()) == sessions_.end()) { // Ignore messages if session_id is not set
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto session_id = sessions_[get_thread_id()];
|
||||||
|
|
||||||
|
histories_[session_id].clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void cleanup_session(const std::string& session_id) {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
|
||||||
|
for (auto it = sessions_.begin(); it != sessions_.end();) {
|
||||||
|
if (it->second == session_id) {
|
||||||
|
it = sessions_.erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buffers_.erase(session_id);
|
||||||
|
histories_.erase(session_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> get_active_sessions() {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
std::vector<std::string> result;
|
||||||
|
std::unordered_set<std::string> unique_sessions;
|
||||||
|
|
||||||
|
for (const auto& [thread_id, session_id] : sessions_) {
|
||||||
|
if (unique_sessions.insert(session_id).second) {
|
||||||
|
result.push_back(session_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::shared_ptr<spdlog::logger> logger = set_log_level(_print_level, _logfile_level);
|
||||||
|
|
||||||
} // namespace humanus
|
} // namespace humanus
|
||||||
|
|
||||||
|
|
2
mcp
2
mcp
|
@ -1 +1 @@
|
||||||
Subproject commit 21dc5cb1448923abc2566568403d837f0a9da64a
|
Subproject commit 0dbcd1e6d5bef3443c9d4b3cff6c6e3adef72264
|
|
@ -14,6 +14,7 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
// Check if Python is found
|
// Check if Python is found
|
||||||
#ifdef PYTHON_FOUND
|
#ifdef PYTHON_FOUND
|
||||||
|
@ -22,7 +23,7 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class python_interpreter
|
* @class python_interpreter
|
||||||
* @brief Python interpreter class for executing Python code
|
* @brief Python interpreter class for executing Python code with session support
|
||||||
*/
|
*/
|
||||||
class python_interpreter {
|
class python_interpreter {
|
||||||
private:
|
private:
|
||||||
|
@ -30,6 +31,9 @@ private:
|
||||||
mutable std::mutex py_mutex;
|
mutable std::mutex py_mutex;
|
||||||
bool is_initialized;
|
bool is_initialized;
|
||||||
|
|
||||||
|
// Map to store Python thread states for each session
|
||||||
|
mutable std::unordered_map<std::string, PyThreadState*> session_states;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief Constructor, initializes Python interpreter
|
* @brief Constructor, initializes Python interpreter
|
||||||
|
@ -40,7 +44,9 @@ public:
|
||||||
Py_Initialize();
|
Py_Initialize();
|
||||||
if (Py_IsInitialized()) {
|
if (Py_IsInitialized()) {
|
||||||
is_initialized = true;
|
is_initialized = true;
|
||||||
PyThreadState *_save = PyEval_SaveThread();
|
// Create main thread state
|
||||||
|
PyThreadState* main_thread_state = PyThreadState_Get();
|
||||||
|
PyThreadState_Swap(NULL);
|
||||||
} else {
|
} else {
|
||||||
std::cerr << "Failed to initialize Python interpreter" << std::endl;
|
std::cerr << "Failed to initialize Python interpreter" << std::endl;
|
||||||
}
|
}
|
||||||
|
@ -57,6 +63,14 @@ public:
|
||||||
#ifdef PYTHON_FOUND
|
#ifdef PYTHON_FOUND
|
||||||
if (is_initialized) {
|
if (is_initialized) {
|
||||||
std::lock_guard<std::mutex> lock(py_mutex);
|
std::lock_guard<std::mutex> lock(py_mutex);
|
||||||
|
// Clean up all session states
|
||||||
|
for (auto& pair : session_states) {
|
||||||
|
PyThreadState_Swap(pair.second);
|
||||||
|
PyThreadState_Clear(pair.second);
|
||||||
|
PyThreadState_Delete(pair.second);
|
||||||
|
}
|
||||||
|
session_states.clear();
|
||||||
|
|
||||||
Py_Finalize();
|
Py_Finalize();
|
||||||
is_initialized = false;
|
is_initialized = false;
|
||||||
}
|
}
|
||||||
|
@ -64,19 +78,53 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Execute Python code
|
* @brief Get or create a thread state for a session
|
||||||
|
* @param session_id The session identifier
|
||||||
|
* @return PyThreadState for the session
|
||||||
|
*/
|
||||||
|
#ifdef PYTHON_FOUND
|
||||||
|
PyThreadState* get_session_state(const std::string& session_id) const {
|
||||||
|
std::lock_guard<std::mutex> lock(py_mutex);
|
||||||
|
|
||||||
|
// Check if session already exists
|
||||||
|
auto it = session_states.find(session_id);
|
||||||
|
if (it != session_states.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new thread state for this session
|
||||||
|
PyThreadState* new_state = Py_NewInterpreter();
|
||||||
|
if (!new_state) {
|
||||||
|
throw std::runtime_error("Failed to create new Python interpreter for session" + session_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store and return the new state
|
||||||
|
session_states[session_id] = new_state;
|
||||||
|
return new_state;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Execute Python code in the context of a specific session
|
||||||
* @param input JSON object containing Python code
|
* @param input JSON object containing Python code
|
||||||
|
* @param session_id The session identifier
|
||||||
* @return JSON object with execution results
|
* @return JSON object with execution results
|
||||||
*/
|
*/
|
||||||
mcp::json forward(const mcp::json& input) const {
|
mcp::json forward(const mcp::json& input, const std::string& session_id) const {
|
||||||
#ifdef PYTHON_FOUND
|
#ifdef PYTHON_FOUND
|
||||||
if (!is_initialized) {
|
if (!is_initialized) {
|
||||||
return mcp::json{{"error", "Python interpreter not properly initialized"}};
|
return mcp::json{{"error", "Python interpreter not properly initialized"}};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Acquire GIL lock
|
// Get or create session thread state
|
||||||
std::lock_guard<std::mutex> lock(py_mutex);
|
PyThreadState* tstate = nullptr;
|
||||||
PyGILState_STATE gstate = PyGILState_Ensure();
|
try {
|
||||||
|
tstate = get_session_state(session_id);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
return mcp::json{{"error", e.what()}};
|
||||||
|
}
|
||||||
|
|
||||||
|
PyThreadState* old_state = PyThreadState_Swap(tstate);
|
||||||
|
|
||||||
mcp::json result_json;
|
mcp::json result_json;
|
||||||
|
|
||||||
|
@ -87,13 +135,13 @@ public:
|
||||||
// Get main module and dictionary
|
// Get main module and dictionary
|
||||||
PyObject *main_module = PyImport_AddModule("__main__");
|
PyObject *main_module = PyImport_AddModule("__main__");
|
||||||
if (!main_module) {
|
if (!main_module) {
|
||||||
PyGILState_Release(gstate);
|
PyThreadState_Swap(old_state);
|
||||||
return mcp::json{{"error", "Failed to get Python main module"}};
|
return mcp::json{{"error", "Failed to get Python main module"}};
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject *main_dict = PyModule_GetDict(main_module);
|
PyObject *main_dict = PyModule_GetDict(main_module);
|
||||||
if (!main_dict) {
|
if (!main_dict) {
|
||||||
PyGILState_Release(gstate);
|
PyThreadState_Swap(old_state);
|
||||||
return mcp::json{{"error", "Failed to get Python main module dictionary"}};
|
return mcp::json{{"error", "Failed to get Python main module dictionary"}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +149,7 @@ public:
|
||||||
PyObject *sys_module = PyImport_ImportModule("sys");
|
PyObject *sys_module = PyImport_ImportModule("sys");
|
||||||
if (!sys_module) {
|
if (!sys_module) {
|
||||||
PyErr_Print();
|
PyErr_Print();
|
||||||
PyGILState_Release(gstate);
|
PyThreadState_Swap(old_state);
|
||||||
return mcp::json{{"error", "Failed to import sys module"}};
|
return mcp::json{{"error", "Failed to import sys module"}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +157,7 @@ public:
|
||||||
if (!io_module) {
|
if (!io_module) {
|
||||||
Py_DECREF(sys_module);
|
Py_DECREF(sys_module);
|
||||||
PyErr_Print();
|
PyErr_Print();
|
||||||
PyGILState_Release(gstate);
|
PyThreadState_Swap(old_state);
|
||||||
return mcp::json{{"error", "Failed to import io module"}};
|
return mcp::json{{"error", "Failed to import io module"}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,7 +167,7 @@ public:
|
||||||
Py_DECREF(io_module);
|
Py_DECREF(io_module);
|
||||||
Py_DECREF(sys_module);
|
Py_DECREF(sys_module);
|
||||||
PyErr_Print();
|
PyErr_Print();
|
||||||
PyGILState_Release(gstate);
|
PyThreadState_Swap(old_state);
|
||||||
return mcp::json{{"error", "Failed to get StringIO class"}};
|
return mcp::json{{"error", "Failed to get StringIO class"}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,7 +178,7 @@ public:
|
||||||
Py_DECREF(io_module);
|
Py_DECREF(io_module);
|
||||||
Py_DECREF(sys_module);
|
Py_DECREF(sys_module);
|
||||||
PyErr_Print();
|
PyErr_Print();
|
||||||
PyGILState_Release(gstate);
|
PyThreadState_Swap(old_state);
|
||||||
return mcp::json{{"error", "Failed to create stdout StringIO object"}};
|
return mcp::json{{"error", "Failed to create stdout StringIO object"}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,7 +189,7 @@ public:
|
||||||
Py_DECREF(io_module);
|
Py_DECREF(io_module);
|
||||||
Py_DECREF(sys_module);
|
Py_DECREF(sys_module);
|
||||||
PyErr_Print();
|
PyErr_Print();
|
||||||
PyGILState_Release(gstate);
|
PyThreadState_Swap(old_state);
|
||||||
return mcp::json{{"error", "Failed to create stderr StringIO object"}};
|
return mcp::json{{"error", "Failed to create stderr StringIO object"}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,7 +209,7 @@ public:
|
||||||
Py_DECREF(io_module);
|
Py_DECREF(io_module);
|
||||||
Py_DECREF(sys_module);
|
Py_DECREF(sys_module);
|
||||||
PyErr_Print();
|
PyErr_Print();
|
||||||
PyGILState_Release(gstate);
|
PyThreadState_Swap(old_state);
|
||||||
return mcp::json{{"error", "Failed to set stdout/stderr redirection"}};
|
return mcp::json{{"error", "Failed to set stdout/stderr redirection"}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,11 +285,32 @@ public:
|
||||||
result_json["error"] = std::string("Python execution exception: ") + e.what();
|
result_json["error"] = std::string("Python execution exception: ") + e.what();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release GIL
|
// Restore previous thread state
|
||||||
PyGILState_Release(gstate);
|
PyThreadState_Swap(old_state);
|
||||||
|
|
||||||
return result_json;
|
return result_json;
|
||||||
#else
|
#else
|
||||||
return mcp::json{{"error", "Python interpreter not available"}};
|
return mcp::json{{"error", "Python interpreter not available"}};
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Clean up a session and remove its thread state
|
||||||
|
* @param session_id The session identifier to clean up
|
||||||
|
*/
|
||||||
|
void cleanup_session(const std::string& session_id) {
|
||||||
|
#ifdef PYTHON_FOUND
|
||||||
|
std::lock_guard<std::mutex> lock(py_mutex);
|
||||||
|
|
||||||
|
auto it = session_states.find(session_id);
|
||||||
|
if (it != session_states.end()) {
|
||||||
|
PyThreadState* old_state = PyThreadState_Swap(it->second);
|
||||||
|
PyThreadState_Clear(it->second);
|
||||||
|
PyThreadState_Delete(it->second);
|
||||||
|
PyThreadState_Swap(old_state);
|
||||||
|
|
||||||
|
session_states.erase(it);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -250,14 +319,14 @@ public:
|
||||||
static python_interpreter interpreter;
|
static python_interpreter interpreter;
|
||||||
|
|
||||||
// Python execution tool handler function
|
// Python execution tool handler function
|
||||||
mcp::json python_execute_handler(const mcp::json& args) {
|
mcp::json python_execute_handler(const mcp::json& args, const std::string& session_id) {
|
||||||
if (!args.contains("code")) {
|
if (!args.contains("code")) {
|
||||||
throw mcp::mcp_exception(mcp::error_code::invalid_params, "Missing 'code' parameter");
|
throw mcp::mcp_exception(mcp::error_code::invalid_params, "Missing 'code' parameter");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Use Python interpreter to execute code
|
// Use Python interpreter to execute code with session context
|
||||||
mcp::json result = interpreter.forward(args);
|
mcp::json result = interpreter.forward(args, session_id);
|
||||||
|
|
||||||
return {{
|
return {{
|
||||||
{"type", "text"},
|
{"type", "text"},
|
||||||
|
@ -277,4 +346,9 @@ void register_python_execute_tool(mcp::server& server) {
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
server.register_tool(python_tool, python_execute_handler);
|
server.register_tool(python_tool, python_execute_handler);
|
||||||
|
|
||||||
|
// Register session cleanup handler
|
||||||
|
server.register_session_cleanup("python_execute", [](const std::string& session_id) {
|
||||||
|
interpreter.cleanup_session(session_id);
|
||||||
|
});
|
||||||
}
|
}
|
|
@ -6,20 +6,16 @@
|
||||||
|
|
||||||
namespace humanus {
|
namespace humanus {
|
||||||
|
|
||||||
std::shared_ptr<spdlog::logger> define_log_level(spdlog::level::level_enum print_level,
|
std::shared_ptr<spdlog::logger> set_log_level(spdlog::level::level_enum print_level, spdlog::level::level_enum logfile_level) {
|
||||||
spdlog::level::level_enum logfile_level,
|
|
||||||
std::string name) {
|
|
||||||
_print_level = print_level;
|
|
||||||
|
|
||||||
auto current_date = std::chrono::system_clock::now();
|
auto current_date = std::chrono::system_clock::now();
|
||||||
auto in_time_t = std::chrono::system_clock::to_time_t(current_date);
|
auto in_time_t = std::chrono::system_clock::to_time_t(current_date);
|
||||||
|
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
std::tm tm_info = *std::localtime(&in_time_t);
|
std::tm tm_info = *std::localtime(&in_time_t);
|
||||||
ss << std::put_time(&tm_info, "%Y%m%d");
|
ss << std::put_time(&tm_info, "%Y-%m-%d");
|
||||||
std::string formatted_date = ss.str(); // YYYYMMDD
|
std::string formatted_date = ss.str(); // YYYY-MM-DD
|
||||||
|
|
||||||
std::string log_name = name.empty() ? formatted_date : name + "_" + formatted_date;
|
std::string log_name = formatted_date;
|
||||||
std::string log_file_path = (PROJECT_ROOT / "logs" / (log_name + ".log")).string();
|
std::string log_file_path = (PROJECT_ROOT / "logs" / (log_name + ".log")).string();
|
||||||
|
|
||||||
// Ensure the log directory exists
|
// Ensure the log directory exists
|
||||||
|
@ -29,13 +25,18 @@ std::shared_ptr<spdlog::logger> define_log_level(spdlog::level::level_enum print
|
||||||
std::shared_ptr<spdlog::logger> _logger = std::make_shared<spdlog::logger>(log_name);
|
std::shared_ptr<spdlog::logger> _logger = std::make_shared<spdlog::logger>(log_name);
|
||||||
|
|
||||||
auto stderr_sink = std::make_shared<spdlog::sinks::stderr_color_sink_mt>();
|
auto stderr_sink = std::make_shared<spdlog::sinks::stderr_color_sink_mt>();
|
||||||
stderr_sink->set_level(print_level);
|
stderr_sink->set_level(_print_level);
|
||||||
_logger->sinks().push_back(stderr_sink);
|
_logger->sinks().push_back(stderr_sink);
|
||||||
|
|
||||||
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(log_file_path, false);
|
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(log_file_path, false);
|
||||||
file_sink->set_level(logfile_level);
|
file_sink->set_level(_logfile_level);
|
||||||
_logger->sinks().push_back(file_sink);
|
_logger->sinks().push_back(file_sink);
|
||||||
|
|
||||||
|
auto session_sink = SessionSink::get_instance();
|
||||||
|
session_sink->set_level(_print_level);
|
||||||
|
_logger->sinks().push_back(session_sink);
|
||||||
|
|
||||||
|
// Reset the log output
|
||||||
return _logger;
|
return _logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue