From 154712bc8180750b3bf05bf4e95c5975db6b9782 Mon Sep 17 00:00:00 2001 From: Bartosz Taudul Date: Sat, 29 Nov 2025 20:16:00 +0100 Subject: [PATCH] Keep user manual chunks in a separate object. This makes the user manual available outside of the LLM context. The code is also more readable, as splitting the manual into sections and splitting section content into chunks fit for embeddings is now separated. A bug has been fixed, where the above splits were mixed up for the last manual section, producing invalid data. The unembedded manual contents are no longer held in the memory. The only use case for this was to calculate the manual contents hash. The hash is now precalculated and cached. --- manual/tracy.md | 2 +- profiler/CMakeLists.txt | 1 + profiler/src/profiler/TracyLlm.cpp | 4 +- profiler/src/profiler/TracyLlm.hpp | 3 +- profiler/src/profiler/TracyLlmTools.cpp | 143 ++++------------------ profiler/src/profiler/TracyLlmTools.hpp | 14 +-- profiler/src/profiler/TracyManualData.cpp | 107 ++++++++++++++++ profiler/src/profiler/TracyManualData.hpp | 37 ++++++ profiler/src/profiler/TracyView.cpp | 7 +- profiler/src/profiler/TracyView.hpp | 3 + 10 files changed, 184 insertions(+), 137 deletions(-) create mode 100644 profiler/src/profiler/TracyManualData.cpp create mode 100644 profiler/src/profiler/TracyManualData.hpp diff --git a/manual/tracy.md b/manual/tracy.md index ea4c83b8..cde0663e 100644 --- a/manual/tracy.md +++ b/manual/tracy.md @@ -12,7 +12,7 @@ The user manual **Bartosz Taudul** [\](mailto:wolf@nereid.pl) -2025-11-11 +2025-11-29 ::: # Quick overview {#quick-overview .unnumbered} diff --git a/profiler/CMakeLists.txt b/profiler/CMakeLists.txt index d01731d3..7825af70 100644 --- a/profiler/CMakeLists.txt +++ b/profiler/CMakeLists.txt @@ -66,6 +66,7 @@ set(SERVER_FILES TracyFileselector.cpp TracyFilesystem.cpp TracyImGui.cpp + TracyManualData.cpp TracyMarkdown.cpp TracyMicroArchitecture.cpp TracyMouse.cpp diff --git a/profiler/src/profiler/TracyLlm.cpp b/profiler/src/profiler/TracyLlm.cpp index 3ab05ebc..e5eafb33 100644 --- a/profiler/src/profiler/TracyLlm.cpp +++ b/profiler/src/profiler/TracyLlm.cpp @@ -24,7 +24,7 @@ extern double s_time; constexpr size_t InputBufferSize = 1024; -TracyLlm::TracyLlm( Worker& worker ) +TracyLlm::TracyLlm( Worker& worker, const TracyManualData& manual ) : m_exit( false ) , m_input( nullptr ) { @@ -47,7 +47,7 @@ TracyLlm::TracyLlm( Worker& worker ) m_api = std::make_unique(); m_chatUi = std::make_unique(); - m_tools = std::make_unique( worker ); + m_tools = std::make_unique( worker, manual ); m_busy = true; QueueConnect(); diff --git a/profiler/src/profiler/TracyLlm.hpp b/profiler/src/profiler/TracyLlm.hpp index 00be91b0..6c34a19e 100644 --- a/profiler/src/profiler/TracyLlm.hpp +++ b/profiler/src/profiler/TracyLlm.hpp @@ -19,6 +19,7 @@ namespace tracy class TracyLlmApi; class TracyLlmChat; class TracyLlmTools; +class TracyManualData; class Worker; class TracyLlm @@ -40,7 +41,7 @@ class TracyLlm }; public: - TracyLlm( Worker& worker ); + TracyLlm( Worker& worker, const TracyManualData& manual ); ~TracyLlm(); [[nodiscard]] bool IsBusy() const { std::lock_guard lock( m_lock ); return m_busy; } diff --git a/profiler/src/profiler/TracyLlmTools.cpp b/profiler/src/profiler/TracyLlmTools.cpp index 52baf2fb..870848dd 100644 --- a/profiler/src/profiler/TracyLlmTools.cpp +++ b/profiler/src/profiler/TracyLlmTools.cpp @@ -9,15 +9,12 @@ #include #include "TracyConfig.hpp" -#include "TracyEmbed.hpp" #include "TracyLlmApi.hpp" #include "TracyLlmTools.hpp" +#include "TracyManualData.hpp" #include "TracyStorage.hpp" #include "TracyUtility.hpp" #include "TracyWorker.hpp" -#include "tracy_xxhash.h" - -#include "data/Manual.hpp" constexpr const char* NoNetworkAccess = "Internet access is disabled by the user. You may inform the user that he can enable it in the settings, so that you can use the tools to gather information."; @@ -81,122 +78,29 @@ static std::unique_ptr ParseHtml( const std::string& html ) return doc; } -TracyLlmTools::TracyLlmTools( Worker& worker ) - : m_manual( Unembed( Manual ) ) - , m_worker( worker ) +TracyLlmTools::TracyLlmTools( Worker& worker, const TracyManualData& manual ) + : m_worker( worker ) + , m_manual( manual ) { - std::string_view manual( m_manual->data(), m_manual->size() ); - const auto sz = (int)m_manual->size(); - - std::vector levels = { 0 }; - std::vector chapterNames = { "Title Page" }; - - int manualChunkPos = 0; - int pos = 0; - while( pos < sz ) + int idx = 0; + for( auto& chunk : m_manual.GetChunks() ) { - std::string::size_type next = pos; - for(;;) + std::string hdr; + if( !chunk.section.empty() ) hdr += "Section " + chunk.section; + if( !chunk.title.empty() ) { - next = manual.find( '\n', next ); - if( next == std::string_view::npos ) - { - next = sz; - break; - } - if( next+1 >= sz || manual[next+1] == '\n' ) break; - next++; + if( !chunk.section.empty() ) hdr += ": "; + hdr += chunk.title; } - if( next != pos ) + hdr += '\n'; + + for( auto& line : SplitLines( chunk.text.c_str(), chunk.text.size() ) ) { - std::string_view line( manual.data() + pos, next - pos ); - if( line != "---" && line != ":::" && line != "::: bclogo" ) - { - if( line[0] == '#' ) - { - if( manualChunkPos != pos ) - { - auto start = manualChunkPos; - auto end = pos; - manualChunkPos = pos; - - while( manual[start] != '\n' ) start++; - while( manual[start] == '\n' ) start++; - while( manual[end-1] == '\n' ) end--; - - if( end > start ) - { - std::string text, section, title, parents; - text = std::string( manual.data() + start, end - start ); - if( levels[0] != 0 ) - { - section = std::to_string( levels[0] ); - for( size_t i=1; idata(), m_manual->size() ); auto cache = GetCachePath( model.c_str() ); try { - m_manualEmbeddings = std::make_unique( cache, hash ); + m_manualEmbeddings = std::make_unique( cache, m_manual.GetHash() ); m_manualEmbeddingState = { .model = model, .done = true }; } catch( std::exception& ) {} @@ -315,7 +218,6 @@ void TracyLlmTools::BuildManualEmbeddings( const std::string& model, TracyLlmApi void TracyLlmTools::ManualEmbeddingsWorker( TracyLlmApi& api ) { - const uint64_t hash = XXH3_64bits( m_manual->data(), m_manual->size() ); auto cache = GetCachePath( m_manualEmbeddingState.model.c_str() ); std::unique_lock lock( m_lock ); @@ -395,7 +297,7 @@ void TracyLlmTools::ManualEmbeddingsWorker( TracyLlmApi& api ) i += bsz; } - m_manualEmbeddings->Save( cache, hash ); + m_manualEmbeddings->Save( cache, m_manual.GetHash() ); lock.lock(); m_manualEmbeddingState.inProgress = false; @@ -825,12 +727,13 @@ std::string TracyLlmTools::SearchManual( const std::string& query, TracyLlmApi& } if( chunks.size() > MaxOutputChunks ) chunks.resize( MaxOutputChunks ); + auto& manualChunks = m_manual.GetChunks(); const auto maxSize = CalcMaxSize(); int totalSize = 0; int idx; for( idx = 0; idx < chunks.size(); idx++ ) { - totalSize += m_manualChunks[chunks[idx].first].text.size(); + totalSize += manualChunks[chunks[idx].first].text.size(); if( totalSize >= maxSize ) break; } if( idx < chunks.size() ) chunks.resize( idx ); @@ -838,7 +741,7 @@ std::string TracyLlmTools::SearchManual( const std::string& query, TracyLlmApi& nlohmann::json json; for( auto& chunk : chunks ) { - auto& m = m_manualChunks[chunk.first]; + auto& m = manualChunks[chunk.first]; nlohmann::json r; r["distance"] = chunk.second; r["content"] = m.text; diff --git a/profiler/src/profiler/TracyLlmTools.hpp b/profiler/src/profiler/TracyLlmTools.hpp index e26caaf4..ba2e82c7 100644 --- a/profiler/src/profiler/TracyLlmTools.hpp +++ b/profiler/src/profiler/TracyLlmTools.hpp @@ -17,6 +17,7 @@ namespace tracy { class TracyLlmApi; +class TracyManualData; class Worker; class TracyLlmTools @@ -36,15 +37,7 @@ public: float progress = 0; }; - struct ManualChunk - { - std::string text; - std::string section; - std::string title; - std::string parents; - }; - - TracyLlmTools( Worker& worker ); + TracyLlmTools( Worker& worker, const TracyManualData& manual ); ~TracyLlmTools(); ToolReply HandleToolCalls( const nlohmann::json& json, TracyLlmApi& api, int contextSize, bool hasEmbeddingsModel ); @@ -82,11 +75,10 @@ private: EmbeddingState m_manualEmbeddingState; std::unique_ptr m_manualEmbeddings; - std::shared_ptr m_manual; - std::vector m_manualChunks; std::vector> m_chunkData; Worker& m_worker; + const TracyManualData& m_manual; }; } diff --git a/profiler/src/profiler/TracyManualData.cpp b/profiler/src/profiler/TracyManualData.cpp new file mode 100644 index 00000000..f9fcb23c --- /dev/null +++ b/profiler/src/profiler/TracyManualData.cpp @@ -0,0 +1,107 @@ +#include "TracyEmbed.hpp" +#include "TracyManualData.hpp" + +#define XXH_INLINE_ALL +#include "tracy_xxhash.h" + +#include "data/Manual.hpp" + +namespace tracy +{ + +TracyManualData::TracyManualData() +{ + auto data = Unembed( Manual ); + m_hash = XXH3_64bits( data->data(), data->size() ); + + std::string_view manual( data->data(), data->size() ); + const auto sz = (int)data->size(); + + std::vector levels = { 0 }; + std::vector chapterNames = { "Title Page" }; + + int manualChunkPos = 0; + int pos = 0; + while( pos < sz ) + { + std::string::size_type next = pos; + for(;;) + { + next = manual.find( '\n', next ); + if( next == std::string_view::npos ) + { + next = sz; + break; + } + if( next+1 >= sz || manual[next+1] == '\n' ) break; + next++; + } + if( next != pos ) + { + std::string_view line( manual.data() + pos, next - pos ); + if( line[0] == '#' ) + { + if( manualChunkPos != pos ) + { + AddManualChunk( manual, manualChunkPos, pos, levels, chapterNames ); + manualChunkPos = pos; + } + + int level = 1; + if( line.find( ".unnumbered}" ) == std::string_view::npos ) + { + while( level < line.size() && line[level] == '#' ) level++; + if( level != levels.size() ) + { + levels.resize( level, 0 ); + chapterNames.resize( level ); + } + levels[level - 1]++; + chapterNames[level - 1] = line.substr( level + 1 ); + } + } + } + pos = next + 1; + while( pos < sz && manual[pos] == '\n' ) pos++; + } + if( manualChunkPos != pos ) + { + AddManualChunk( manual, manualChunkPos, pos, levels, chapterNames ); + } +} + +void TracyManualData::AddManualChunk( const std::string_view& manual, int start, int end, const std::vector& levels, const std::vector& chapterNames ) +{ + while( manual[start] != '\n' ) start++; + while( manual[start] == '\n' ) start++; + while( manual[end-1] == '\n' ) end--; + + if( end > start ) + { + std::string text, section, title, parents; + text = std::string( manual.data() + start, end - start ); + if( levels[0] != 0 ) + { + section = std::to_string( levels[0] ); + for( size_t i=1; i +#include +#include +#include + +namespace tracy +{ + +class TracyManualData +{ +public: + struct ManualChunk + { + std::string text; + std::string section; + std::string title; + std::string parents; + }; + + TracyManualData(); + + [[nodiscard]] const std::vector& GetChunks() const { return m_manualChunks; } + [[nodiscard]] uint64_t GetHash() const { return m_hash; } + +private: + void AddManualChunk( const std::string_view& manual, int manualChunkPos, int pos, const std::vector& levels, const std::vector& chapterNames ); + + std::vector m_manualChunks; + uint64_t m_hash; +}; + +} + +#endif diff --git a/profiler/src/profiler/TracyView.cpp b/profiler/src/profiler/TracyView.cpp index 87c658a6..ceb4fd84 100644 --- a/profiler/src/profiler/TracyView.cpp +++ b/profiler/src/profiler/TracyView.cpp @@ -17,6 +17,7 @@ #include "TracyFileRead.hpp" #include "TracyFilesystem.hpp" #include "TracyImGui.hpp" +#include "TracyManualData.hpp" #include "TracyPrint.hpp" #include "TracySourceView.hpp" #include "TracyTexture.hpp" @@ -57,11 +58,12 @@ View::View( void(*cbMainThread)(const std::function&, bool), const char* , m_achievements( s_config.achievements ) , m_horizontalScrollMultiplier( s_config.horizontalScrollMultiplier ) , m_verticalScrollMultiplier( s_config.verticalScrollMultiplier ) + , m_manualData( std::make_shared() ) #ifdef __EMSCRIPTEN__ , m_td( 2, "ViewMt" ) #else , m_td( std::thread::hardware_concurrency(), "ViewMt" ) - , m_llm( m_worker ) + , m_llm( m_worker, *m_manualData ) #endif { InitTextEditor(); @@ -86,11 +88,12 @@ View::View( void(*cbMainThread)(const std::function&, bool), FileRead& f , m_achievements( s_config.achievements ) , m_horizontalScrollMultiplier( s_config.horizontalScrollMultiplier ) , m_verticalScrollMultiplier( s_config.verticalScrollMultiplier ) + , m_manualData( std::make_shared() ) #ifdef __EMSCRIPTEN__ , m_td( 2, "ViewMt" ) #else , m_td( std::thread::hardware_concurrency(), "ViewMt" ) - , m_llm( m_worker ) + , m_llm( m_worker, *m_manualData ) #endif { m_notificationTime = 4; diff --git a/profiler/src/profiler/TracyView.hpp b/profiler/src/profiler/TracyView.hpp index 23f308ed..452d0dea 100644 --- a/profiler/src/profiler/TracyView.hpp +++ b/profiler/src/profiler/TracyView.hpp @@ -63,6 +63,7 @@ struct CpuCtxDraw; struct LockDraw; struct PlotDraw; struct FlameGraphContext; +class TracyManualData; class View @@ -929,6 +930,8 @@ private: double m_horizontalScrollMultiplier = 1.0; double m_verticalScrollMultiplier = 1.0; + std::shared_ptr m_manualData; + TaskDispatch m_td; std::vector m_flameGraphData; struct