mirror of
https://github.com/wolfpld/tracy.git
synced 2026-09-01 16:08:30 +00:00
Move implementation of all LLM tools to a separate file.
This commit is contained in:
@@ -60,6 +60,7 @@ set(SERVER_FILES
|
||||
TracyFilesystem.cpp
|
||||
TracyImGui.cpp
|
||||
TracyLlm.cpp
|
||||
TracyLlmTools.cpp
|
||||
TracyMicroArchitecture.cpp
|
||||
TracyMouse.cpp
|
||||
TracyProtoHistory.cpp
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
#include <libbase64.h>
|
||||
#include <curl/curl.h>
|
||||
#include <ollama.hpp>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <pugixml.hpp>
|
||||
#include <ranges>
|
||||
#include <tidy.h>
|
||||
#include <tidybuffio.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "TracyConfig.hpp"
|
||||
#include "TracyImGui.hpp"
|
||||
@@ -172,6 +166,7 @@ void TracyLlm::Draw()
|
||||
{
|
||||
m_modelIdx = i;
|
||||
s_config.llmModel = model.name;
|
||||
m_tools.SetModelMaxContext( model.ctxSize );
|
||||
}
|
||||
if( m_modelIdx == i ) ImGui::SetItemDefaultFocus();
|
||||
ImGui::SameLine();
|
||||
@@ -207,7 +202,7 @@ void TracyLlm::Draw()
|
||||
ImGui::SetNextItemWidth( 40 * scale );
|
||||
if( ImGui::InputFloat( "##temperature", &m_temperature, 0, 0, "%.2f" ) ) m_temperature = std::clamp( m_temperature, 0.f, 2.f );
|
||||
|
||||
ImGui::Checkbox( ICON_FA_GLOBE " Internet access", &m_netAccess );
|
||||
ImGui::Checkbox( ICON_FA_GLOBE " Internet access", &m_tools.m_netAccess );
|
||||
|
||||
ImGui::TreePop();
|
||||
}
|
||||
@@ -583,12 +578,13 @@ void TracyLlm::UpdateModels()
|
||||
{
|
||||
m_modelIdx = std::distance( m_models.begin(), it );
|
||||
}
|
||||
if( !m_models.empty() ) m_tools.SetModelMaxContext( m_models[m_modelIdx].ctxSize );
|
||||
}
|
||||
|
||||
void TracyLlm::ResetChat()
|
||||
{
|
||||
auto systemPrompt = std::string( m_systemPrompt->data(), m_systemPrompt->size() );
|
||||
systemPrompt += "The current time is: " + GetCurrentTime() + "\n";
|
||||
systemPrompt += "The current time is: " + m_tools.GetCurrentTime() + "\n";
|
||||
|
||||
m_chat = std::make_unique<ollama::messages>();
|
||||
m_chat->emplace_back( ollama::message( "system", systemPrompt ) );
|
||||
@@ -687,7 +683,7 @@ bool TracyLlm::OnResponse( const ollama::response& response )
|
||||
auto tool = lines[0];
|
||||
lines.erase( lines.begin() );
|
||||
lock.unlock();
|
||||
const auto reply = HandleToolCalls( tool, lines );
|
||||
const auto reply = m_tools.HandleToolCalls( tool, lines );
|
||||
const auto output = "<tool_output>\n" + reply.reply;
|
||||
lock.lock();
|
||||
if( reply.image.empty() )
|
||||
@@ -822,267 +818,4 @@ void TracyLlm::CleanContext( LineContext& ctx)
|
||||
}
|
||||
}
|
||||
|
||||
static std::string UrlEncode( const std::string& str )
|
||||
{
|
||||
std::string out;
|
||||
out.reserve( str.size() * 3 );
|
||||
|
||||
constexpr char hex[] = "0123456789ABCDEF";
|
||||
|
||||
for( char c : str )
|
||||
{
|
||||
if( ( c >= 'a' && c <= 'z' ) ||
|
||||
( c >= 'A' && c <= 'Z' ) ||
|
||||
( c >= '0' && c <= '9' ) ||
|
||||
c == '-' || c == '.' || c == '_' || c == '~' )
|
||||
{
|
||||
out += c;
|
||||
}
|
||||
else
|
||||
{
|
||||
out += '%';
|
||||
out += hex[(unsigned char)c >> 4];
|
||||
out += hex[(unsigned char)c & 0x0F];
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
TracyLlm::ToolReply TracyLlm::HandleToolCalls( const std::string& name, const std::vector<std::string>& args )
|
||||
{
|
||||
if( name == "fetch_web_page" )
|
||||
{
|
||||
if( args.empty() ) return { .reply = "Missing URL argument" };
|
||||
return { .reply = FetchWebPage( args[0] ) };
|
||||
}
|
||||
if( name == "search_wikipedia" )
|
||||
{
|
||||
if( args.empty() ) return { .reply = "Missing search term argument" };
|
||||
if( args.size() < 2 ) return { .reply = "Missing language argument" };
|
||||
return SearchWikipedia( args[0], args[1] );
|
||||
}
|
||||
if( name == "get_wikipedia" )
|
||||
{
|
||||
if( args.empty() ) return { .reply = "Missing page name argument" };
|
||||
if( args.size() < 2 ) return { .reply = "Missing language argument" };
|
||||
return { .reply = GetWikipedia( args[0], args[1] ) };
|
||||
}
|
||||
if( name == "search_web" )
|
||||
{
|
||||
if( args.empty() ) return { .reply = "Missing search term argument" };
|
||||
return { .reply = SearchWeb( args[0] ) };
|
||||
}
|
||||
return { .reply = "Unknown tool call: " + name };
|
||||
}
|
||||
|
||||
std::string TracyLlm::GetCurrentTime()
|
||||
{
|
||||
auto t = time( nullptr );
|
||||
auto tm = localtime( &t );
|
||||
|
||||
char buffer[64];
|
||||
std::strftime( buffer, sizeof( buffer ), "%Y-%m-%d %H:%M:%S", tm );
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static size_t WriteFn( void* _data, size_t size, size_t num, void* ptr )
|
||||
{
|
||||
const auto data = (unsigned char*)_data;
|
||||
const auto sz = size*num;
|
||||
auto& v = *(std::string*)ptr;
|
||||
v.append( (const char*)data, sz );
|
||||
return sz;
|
||||
}
|
||||
|
||||
std::string TracyLlm::FetchWebPage( const std::string& url )
|
||||
{
|
||||
auto it = m_webCache.find( url );
|
||||
if( it != m_webCache.end() ) return it->second;
|
||||
|
||||
static bool initialized = false;
|
||||
if( !initialized )
|
||||
{
|
||||
initialized = true;
|
||||
curl_global_init( CURL_GLOBAL_ALL );
|
||||
atexit( curl_global_cleanup );
|
||||
}
|
||||
|
||||
auto curl = curl_easy_init();
|
||||
if( !curl ) return "Error: Failed to initialize cURL";
|
||||
|
||||
std::string buf;
|
||||
|
||||
curl_easy_setopt( curl, CURLOPT_URL, url.c_str() );
|
||||
curl_easy_setopt( curl, CURLOPT_CA_CACHE_TIMEOUT, 604800L );
|
||||
curl_easy_setopt( curl, CURLOPT_FOLLOWLOCATION, 1L );
|
||||
curl_easy_setopt( curl, CURLOPT_TIMEOUT, 10 );
|
||||
curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, WriteFn );
|
||||
curl_easy_setopt( curl, CURLOPT_WRITEDATA, &buf );
|
||||
curl_easy_setopt( curl, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36" );
|
||||
|
||||
auto res = curl_easy_perform( curl );
|
||||
|
||||
std::string response;
|
||||
if( res != CURLE_OK )
|
||||
{
|
||||
response = "Error: " + std::string( curl_easy_strerror( res ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
response = std::move( buf );
|
||||
}
|
||||
m_webCache.emplace( url, response );
|
||||
|
||||
curl_easy_cleanup( curl );
|
||||
return response;
|
||||
}
|
||||
|
||||
TracyLlm::ToolReply TracyLlm::SearchWikipedia( std::string query, const std::string& lang )
|
||||
{
|
||||
if( !m_netAccess ) return { .reply = "Internet access is disabled by the user." };
|
||||
|
||||
std::ranges::replace( query, ' ', '+' );
|
||||
const auto response = FetchWebPage( "https://" + lang + ".wikipedia.org/w/rest.php/v1/search/page?q=" + UrlEncode( query ) + "&limit=1" );
|
||||
|
||||
auto json = nlohmann::json::parse( response );
|
||||
if( !json.contains( "pages" ) ) return { .reply = "No results found" };
|
||||
|
||||
auto& page = json["pages"];
|
||||
if( page.size() == 0 ) return { .reply = "No results found" };
|
||||
|
||||
auto& page0 = page[0];
|
||||
if( !page0.contains( "key" ) ) return { .reply = "No results found" };
|
||||
|
||||
const auto key = page0["key"].get_ref<const std::string&>();
|
||||
|
||||
auto summary = FetchWebPage( "https://" + lang + ".wikipedia.org/api/rest_v1/page/summary/" + key );
|
||||
auto summaryJson = nlohmann::json::parse( summary );
|
||||
|
||||
if( !summaryJson.contains( "title" ) ) return { .reply = "No results found" };
|
||||
|
||||
nlohmann::json output;
|
||||
output["key"] = key;
|
||||
output["title"] = summaryJson["title"];
|
||||
if( summaryJson.contains( "description" ) ) output["description"] = summaryJson["description"];
|
||||
output["extract"] = summaryJson["extract"];
|
||||
|
||||
std::string image;
|
||||
if( summaryJson.contains( "thumbnail" ) )
|
||||
{
|
||||
auto& thumb = summaryJson["thumbnail"];
|
||||
if( thumb.contains( "source" ) )
|
||||
{
|
||||
auto imgData = FetchWebPage( thumb["source"].get_ref<const std::string&>() );
|
||||
if( !imgData.empty() && imgData[0] != '<' && strncmp( imgData.c_str(), "Error:", 6 ) != 0 )
|
||||
{
|
||||
size_t b64sz = ( ( 4 * imgData.size() / 3 ) + 3 ) & ~3;
|
||||
char* b64 = new char[b64sz+1];
|
||||
b64[b64sz] = 0;
|
||||
size_t outSz;
|
||||
base64_encode( (const char*)imgData.data(), imgData.size(), b64, &outSz, 0 );
|
||||
image = std::string( b64, outSz );
|
||||
delete[] b64;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto reply = output.dump( 2 );
|
||||
return { .reply = reply, .image = image };
|
||||
}
|
||||
|
||||
std::string TracyLlm::GetWikipedia( std::string page, const std::string& lang )
|
||||
{
|
||||
if( !m_netAccess ) return "Internet access is disabled by the user.";
|
||||
|
||||
std::ranges::replace( page, ' ', '_' );
|
||||
auto res = FetchWebPage( "https://" + lang + ".wikipedia.org/w/rest.php/v1/page/" + page );
|
||||
|
||||
// Limit the size of the response to avoid exceeding the context size
|
||||
// Assume average token size is 4 bytes. Make space for 3 articles to be retrieved.
|
||||
const auto ctxSize = std::min( m_models[m_modelIdx].ctxSize, s_config.llmContext );
|
||||
const auto maxSize = ( ctxSize * 4 ) / 3;
|
||||
|
||||
if( res.size() > maxSize ) res = res.substr( 0, maxSize );
|
||||
return res;
|
||||
}
|
||||
|
||||
static std::string RemoveNewline( std::string str )
|
||||
{
|
||||
std::erase( str, '\r' );
|
||||
std::ranges::replace( str, '\n', ' ' );
|
||||
return str;
|
||||
}
|
||||
|
||||
std::string TracyLlm::SearchWeb( std::string query )
|
||||
{
|
||||
if( !m_netAccess ) return "Internet access is disabled by the user.";
|
||||
|
||||
std::ranges::replace( query, ' ', '+' );
|
||||
const auto response = FetchWebPage( "https://lite.duckduckgo.com/lite?q=" + UrlEncode( query ) );
|
||||
|
||||
TidyBuffer err = {};
|
||||
tidyBufInit( &err );
|
||||
|
||||
TidyDoc td = tidyCreate();
|
||||
tidyOptSetBool( td, TidyXhtmlOut, yes );
|
||||
tidyOptSetBool( td, TidyLowerLiterals, yes );
|
||||
tidyOptSetBool( td, TidyMark, no );
|
||||
tidyOptSetBool( td, TidyHideComments, yes );
|
||||
tidySetErrorBuffer( td, &err );
|
||||
|
||||
if( tidyParseString( td, response.c_str() ) == 2 )
|
||||
{
|
||||
auto out = std::string( (const char*)err.bp );
|
||||
tidyBufFree( &err );
|
||||
tidyRelease( td );
|
||||
return out;
|
||||
}
|
||||
|
||||
TidyBuffer buf = {};
|
||||
tidyBufInit( &buf );
|
||||
tidyCleanAndRepair( td );
|
||||
tidySaveBuffer( td, &buf );
|
||||
|
||||
auto tidy = std::string( (const char*)buf.bp );
|
||||
|
||||
tidyBufFree( &buf );
|
||||
tidyBufFree( &err );
|
||||
tidyRelease( td );
|
||||
|
||||
auto doc = std::make_unique<pugi::xml_document>();
|
||||
if( !doc->load_string( tidy.c_str() ) )
|
||||
{
|
||||
return "Error: Failed to parse HTML";
|
||||
}
|
||||
|
||||
const auto titles = doc->select_nodes( "//a[@class='result-link']" );
|
||||
const auto snippets = doc->select_nodes( "//td[@class='result-snippet']" );
|
||||
const auto urls = doc->select_nodes( "//span[@class='link-text']" );
|
||||
|
||||
const auto sz = titles.size();
|
||||
if( sz != snippets.size() || sz != urls.size() )
|
||||
{
|
||||
return "Error: Failed to parse HTML";
|
||||
}
|
||||
|
||||
nlohmann::json json;
|
||||
|
||||
for( size_t i = 0; i < sz; i++ )
|
||||
{
|
||||
auto title = titles[i].node();
|
||||
auto snippet = snippets[i].node();
|
||||
auto url = urls[i].node();
|
||||
|
||||
nlohmann::json result;
|
||||
result["title"] = RemoveNewline( title.text().as_string() );
|
||||
result["snippet"] = RemoveNewline( snippet.text().as_string() );
|
||||
result["url"] = RemoveNewline( url.text().as_string() );
|
||||
|
||||
json[i] = result;
|
||||
}
|
||||
|
||||
return json.dump( 2 );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "TracyEmbed.hpp"
|
||||
#include "TracyLlmTools.hpp"
|
||||
#include "tracy_robin_hood.h"
|
||||
|
||||
struct ImFont;
|
||||
@@ -52,12 +53,6 @@ class TracyLlm
|
||||
bool codeBlock;
|
||||
};
|
||||
|
||||
struct ToolReply
|
||||
{
|
||||
std::string reply;
|
||||
std::string image;
|
||||
};
|
||||
|
||||
public:
|
||||
struct LlmModel
|
||||
{
|
||||
@@ -96,14 +91,6 @@ private:
|
||||
void PrintLine( LineContext& ctx, const std::string& str, int num );
|
||||
void CleanContext( LineContext& ctx);
|
||||
|
||||
ToolReply HandleToolCalls( const std::string& name, const std::vector<std::string>& args );
|
||||
|
||||
std::string GetCurrentTime();
|
||||
std::string FetchWebPage( const std::string& url );
|
||||
ToolReply SearchWikipedia( std::string query, const std::string& lang );
|
||||
std::string GetWikipedia( std::string page, const std::string& lang );
|
||||
std::string SearchWeb( std::string query );
|
||||
|
||||
std::unique_ptr<Ollama> m_ollama;
|
||||
|
||||
mutable std::mutex m_modelsLock;
|
||||
@@ -125,19 +112,18 @@ private:
|
||||
int m_usedCtx = 0;
|
||||
float m_temperature = 1.0f;
|
||||
bool m_setTemperature = false;
|
||||
bool m_netAccess = true;
|
||||
|
||||
char* m_input;
|
||||
std::unique_ptr<ollama::messages> m_chat;
|
||||
unordered_flat_map<size_t, ChatCache> m_chatCache;
|
||||
|
||||
unordered_flat_map<std::string, std::string> m_webCache;
|
||||
|
||||
ImFont* m_font;
|
||||
ImFont* m_smallFont;
|
||||
ImFont* m_bigFont;
|
||||
|
||||
std::shared_ptr<EmbedData> m_systemPrompt;
|
||||
|
||||
TracyLlmTools m_tools;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
286
profiler/src/profiler/TracyLlmTools.cpp
Normal file
286
profiler/src/profiler/TracyLlmTools.cpp
Normal file
@@ -0,0 +1,286 @@
|
||||
#include <curl/curl.h>
|
||||
#include <json.hpp>
|
||||
#include <libbase64.h>
|
||||
#include <pugixml.hpp>
|
||||
#include <tidy.h>
|
||||
#include <tidybuffio.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "TracyConfig.hpp"
|
||||
#include "TracyLlmTools.hpp"
|
||||
|
||||
extern tracy::Config s_config;
|
||||
|
||||
namespace tracy
|
||||
{
|
||||
|
||||
void TracyLlmTools::SetModelMaxContext( int modelMaxContext )
|
||||
{
|
||||
m_modelMaxContext = modelMaxContext;
|
||||
}
|
||||
|
||||
static std::string UrlEncode( const std::string& str )
|
||||
{
|
||||
std::string out;
|
||||
out.reserve( str.size() * 3 );
|
||||
|
||||
constexpr char hex[] = "0123456789ABCDEF";
|
||||
|
||||
for( char c : str )
|
||||
{
|
||||
if( ( c >= 'a' && c <= 'z' ) ||
|
||||
( c >= 'A' && c <= 'Z' ) ||
|
||||
( c >= '0' && c <= '9' ) ||
|
||||
c == '-' || c == '.' || c == '_' || c == '~' )
|
||||
{
|
||||
out += c;
|
||||
}
|
||||
else
|
||||
{
|
||||
out += '%';
|
||||
out += hex[(unsigned char)c >> 4];
|
||||
out += hex[(unsigned char)c & 0x0F];
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
TracyLlmTools::ToolReply TracyLlmTools::HandleToolCalls( const std::string& name, const std::vector<std::string>& args )
|
||||
{
|
||||
if( name == "fetch_web_page" )
|
||||
{
|
||||
if( args.empty() ) return { .reply = "Missing URL argument" };
|
||||
return { .reply = FetchWebPage( args[0] ) };
|
||||
}
|
||||
if( name == "search_wikipedia" )
|
||||
{
|
||||
if( args.empty() ) return { .reply = "Missing search term argument" };
|
||||
if( args.size() < 2 ) return { .reply = "Missing language argument" };
|
||||
return SearchWikipedia( args[0], args[1] );
|
||||
}
|
||||
if( name == "get_wikipedia" )
|
||||
{
|
||||
if( args.empty() ) return { .reply = "Missing page name argument" };
|
||||
if( args.size() < 2 ) return { .reply = "Missing language argument" };
|
||||
return { .reply = GetWikipedia( args[0], args[1] ) };
|
||||
}
|
||||
if( name == "search_web" )
|
||||
{
|
||||
if( args.empty() ) return { .reply = "Missing search term argument" };
|
||||
return { .reply = SearchWeb( args[0] ) };
|
||||
}
|
||||
return { .reply = "Unknown tool call: " + name };
|
||||
}
|
||||
|
||||
std::string TracyLlmTools::GetCurrentTime()
|
||||
{
|
||||
auto t = time( nullptr );
|
||||
auto tm = localtime( &t );
|
||||
|
||||
char buffer[64];
|
||||
strftime( buffer, sizeof( buffer ), "%Y-%m-%d %H:%M:%S", tm );
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static size_t WriteFn( void* _data, size_t size, size_t num, void* ptr )
|
||||
{
|
||||
const auto data = (unsigned char*)_data;
|
||||
const auto sz = size*num;
|
||||
auto& v = *(std::string*)ptr;
|
||||
v.append( (const char*)data, sz );
|
||||
return sz;
|
||||
}
|
||||
|
||||
std::string TracyLlmTools::FetchWebPage( const std::string& url )
|
||||
{
|
||||
auto it = m_webCache.find( url );
|
||||
if( it != m_webCache.end() ) return it->second;
|
||||
|
||||
static bool initialized = false;
|
||||
if( !initialized )
|
||||
{
|
||||
initialized = true;
|
||||
curl_global_init( CURL_GLOBAL_ALL );
|
||||
atexit( curl_global_cleanup );
|
||||
}
|
||||
|
||||
auto curl = curl_easy_init();
|
||||
if( !curl ) return "Error: Failed to initialize cURL";
|
||||
|
||||
std::string buf;
|
||||
|
||||
curl_easy_setopt( curl, CURLOPT_URL, url.c_str() );
|
||||
curl_easy_setopt( curl, CURLOPT_CA_CACHE_TIMEOUT, 604800L );
|
||||
curl_easy_setopt( curl, CURLOPT_FOLLOWLOCATION, 1L );
|
||||
curl_easy_setopt( curl, CURLOPT_TIMEOUT, 10 );
|
||||
curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, WriteFn );
|
||||
curl_easy_setopt( curl, CURLOPT_WRITEDATA, &buf );
|
||||
curl_easy_setopt( curl, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36" );
|
||||
|
||||
auto res = curl_easy_perform( curl );
|
||||
|
||||
std::string response;
|
||||
if( res != CURLE_OK )
|
||||
{
|
||||
response = "Error: " + std::string( curl_easy_strerror( res ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
response = std::move( buf );
|
||||
}
|
||||
m_webCache.emplace( url, response );
|
||||
|
||||
curl_easy_cleanup( curl );
|
||||
return response;
|
||||
}
|
||||
|
||||
TracyLlmTools::ToolReply TracyLlmTools::SearchWikipedia( std::string query, const std::string& lang )
|
||||
{
|
||||
if( !m_netAccess ) return { .reply = "Internet access is disabled by the user." };
|
||||
|
||||
std::ranges::replace( query, ' ', '+' );
|
||||
const auto response = FetchWebPage( "https://" + lang + ".wikipedia.org/w/rest.php/v1/search/page?q=" + UrlEncode( query ) + "&limit=1" );
|
||||
|
||||
auto json = nlohmann::json::parse( response );
|
||||
if( !json.contains( "pages" ) ) return { .reply = "No results found" };
|
||||
|
||||
auto& page = json["pages"];
|
||||
if( page.size() == 0 ) return { .reply = "No results found" };
|
||||
|
||||
auto& page0 = page[0];
|
||||
if( !page0.contains( "key" ) ) return { .reply = "No results found" };
|
||||
|
||||
const auto key = page0["key"].get_ref<const std::string&>();
|
||||
|
||||
auto summary = FetchWebPage( "https://" + lang + ".wikipedia.org/api/rest_v1/page/summary/" + key );
|
||||
auto summaryJson = nlohmann::json::parse( summary );
|
||||
|
||||
if( !summaryJson.contains( "title" ) ) return { .reply = "No results found" };
|
||||
|
||||
nlohmann::json output;
|
||||
output["key"] = key;
|
||||
output["title"] = summaryJson["title"];
|
||||
if( summaryJson.contains( "description" ) ) output["description"] = summaryJson["description"];
|
||||
output["extract"] = summaryJson["extract"];
|
||||
|
||||
std::string image;
|
||||
if( summaryJson.contains( "thumbnail" ) )
|
||||
{
|
||||
auto& thumb = summaryJson["thumbnail"];
|
||||
if( thumb.contains( "source" ) )
|
||||
{
|
||||
auto imgData = FetchWebPage( thumb["source"].get_ref<const std::string&>() );
|
||||
if( !imgData.empty() && imgData[0] != '<' && strncmp( imgData.c_str(), "Error:", 6 ) != 0 )
|
||||
{
|
||||
size_t b64sz = ( ( 4 * imgData.size() / 3 ) + 3 ) & ~3;
|
||||
char* b64 = new char[b64sz+1];
|
||||
b64[b64sz] = 0;
|
||||
size_t outSz;
|
||||
base64_encode( (const char*)imgData.data(), imgData.size(), b64, &outSz, 0 );
|
||||
image = std::string( b64, outSz );
|
||||
delete[] b64;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto reply = output.dump( 2 );
|
||||
return { .reply = reply, .image = image };
|
||||
}
|
||||
|
||||
std::string TracyLlmTools::GetWikipedia( std::string page, const std::string& lang )
|
||||
{
|
||||
if( !m_netAccess ) return "Internet access is disabled by the user.";
|
||||
|
||||
std::ranges::replace( page, ' ', '_' );
|
||||
auto res = FetchWebPage( "https://" + lang + ".wikipedia.org/w/rest.php/v1/page/" + page );
|
||||
|
||||
// Limit the size of the response to avoid exceeding the context size
|
||||
// Assume average token size is 4 bytes. Make space for 3 articles to be retrieved.
|
||||
assert( m_modelMaxContext != 0 );
|
||||
const auto ctxSize = std::min( m_modelMaxContext, s_config.llmContext );
|
||||
const auto maxSize = ( ctxSize * 4 ) / 3;
|
||||
|
||||
if( res.size() > maxSize ) res = res.substr( 0, maxSize );
|
||||
return res;
|
||||
}
|
||||
|
||||
static std::string RemoveNewline( std::string str )
|
||||
{
|
||||
std::erase( str, '\r' );
|
||||
std::ranges::replace( str, '\n', ' ' );
|
||||
return str;
|
||||
}
|
||||
|
||||
std::string TracyLlmTools::SearchWeb( std::string query )
|
||||
{
|
||||
if( !m_netAccess ) return "Internet access is disabled by the user.";
|
||||
|
||||
std::ranges::replace( query, ' ', '+' );
|
||||
const auto response = FetchWebPage( "https://lite.duckduckgo.com/lite?q=" + UrlEncode( query ) );
|
||||
|
||||
TidyBuffer err = {};
|
||||
tidyBufInit( &err );
|
||||
|
||||
TidyDoc td = tidyCreate();
|
||||
tidyOptSetBool( td, TidyXhtmlOut, yes );
|
||||
tidyOptSetBool( td, TidyLowerLiterals, yes );
|
||||
tidyOptSetBool( td, TidyMark, no );
|
||||
tidyOptSetBool( td, TidyHideComments, yes );
|
||||
tidySetErrorBuffer( td, &err );
|
||||
|
||||
if( tidyParseString( td, response.c_str() ) == 2 )
|
||||
{
|
||||
auto out = std::string( (const char*)err.bp );
|
||||
tidyBufFree( &err );
|
||||
tidyRelease( td );
|
||||
return out;
|
||||
}
|
||||
|
||||
TidyBuffer buf = {};
|
||||
tidyBufInit( &buf );
|
||||
tidyCleanAndRepair( td );
|
||||
tidySaveBuffer( td, &buf );
|
||||
|
||||
auto tidy = std::string( (const char*)buf.bp );
|
||||
|
||||
tidyBufFree( &buf );
|
||||
tidyBufFree( &err );
|
||||
tidyRelease( td );
|
||||
|
||||
auto doc = std::make_unique<pugi::xml_document>();
|
||||
if( !doc->load_string( tidy.c_str() ) )
|
||||
{
|
||||
return "Error: Failed to parse HTML";
|
||||
}
|
||||
|
||||
const auto titles = doc->select_nodes( "//a[@class='result-link']" );
|
||||
const auto snippets = doc->select_nodes( "//td[@class='result-snippet']" );
|
||||
const auto urls = doc->select_nodes( "//span[@class='link-text']" );
|
||||
|
||||
const auto sz = titles.size();
|
||||
if( sz != snippets.size() || sz != urls.size() )
|
||||
{
|
||||
return "Error: Failed to parse HTML";
|
||||
}
|
||||
|
||||
nlohmann::json json;
|
||||
|
||||
for( size_t i = 0; i < sz; i++ )
|
||||
{
|
||||
auto title = titles[i].node();
|
||||
auto snippet = snippets[i].node();
|
||||
auto url = urls[i].node();
|
||||
|
||||
nlohmann::json result;
|
||||
result["title"] = RemoveNewline( title.text().as_string() );
|
||||
result["snippet"] = RemoveNewline( snippet.text().as_string() );
|
||||
result["url"] = RemoveNewline( url.text().as_string() );
|
||||
|
||||
json[i] = result;
|
||||
}
|
||||
|
||||
return json.dump( 2 );
|
||||
}
|
||||
|
||||
}
|
||||
41
profiler/src/profiler/TracyLlmTools.hpp
Normal file
41
profiler/src/profiler/TracyLlmTools.hpp
Normal file
@@ -0,0 +1,41 @@
|
||||
#ifndef __TRACYLLMTOOLS_HPP__
|
||||
#define __TRACYLLMTOOLS_HPP__
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "tracy_robin_hood.h"
|
||||
|
||||
namespace tracy
|
||||
{
|
||||
|
||||
class TracyLlmTools
|
||||
{
|
||||
public:
|
||||
struct ToolReply
|
||||
{
|
||||
std::string reply;
|
||||
std::string image;
|
||||
};
|
||||
|
||||
void SetModelMaxContext( int modelMaxContext );
|
||||
|
||||
ToolReply HandleToolCalls( const std::string& name, const std::vector<std::string>& args );
|
||||
std::string GetCurrentTime();
|
||||
|
||||
bool m_netAccess = true;
|
||||
|
||||
private:
|
||||
std::string FetchWebPage( const std::string& url );
|
||||
ToolReply SearchWikipedia( std::string query, const std::string& lang );
|
||||
std::string GetWikipedia( std::string page, const std::string& lang );
|
||||
std::string SearchWeb( std::string query );
|
||||
|
||||
unordered_flat_map<std::string, std::string> m_webCache;
|
||||
|
||||
int m_modelMaxContext = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user