From c996c67390fa52db117f4361955bf2e38d36a4c4 Mon Sep 17 00:00:00 2001 From: Bartosz Taudul Date: Mon, 29 Dec 2025 20:41:36 +0100 Subject: [PATCH] Take the thinking and tool use context into account in LLM chat rendering. --- profiler/src/profiler/TracyLlm.cpp | 7 +- profiler/src/profiler/TracyLlmChat.cpp | 167 ++++++++++++------------- profiler/src/profiler/TracyLlmChat.hpp | 4 +- 3 files changed, 81 insertions(+), 97 deletions(-) diff --git a/profiler/src/profiler/TracyLlm.cpp b/profiler/src/profiler/TracyLlm.cpp index 934b0292..2ba492fa 100644 --- a/profiler/src/profiler/TracyLlm.cpp +++ b/profiler/src/profiler/TracyLlm.cpp @@ -430,14 +430,11 @@ void TracyLlm::Draw() if( !line.contains( "role" ) ) break; const auto& roleStr = line["role"].get_ref(); if( roleStr == "system" ) continue; - const auto& contentNode = line["content"]; - if( !contentNode.is_string() ) continue; - const auto& content = contentNode.get_ref(); TracyLlmChat::TurnRole role = TracyLlmChat::TurnRole::None; if( roleStr == "user" ) role = TracyLlmChat::TurnRole::User; else if( roleStr == "error" ) role = TracyLlmChat::TurnRole::Error; - else if( roleStr == "assistant" ) role = TracyLlmChat::TurnRole::Assistant; + else if( roleStr == "assistant" || roleStr == "tool" ) role = TracyLlmChat::TurnRole::Assistant; else assert( false ); if( role == TracyLlmChat::TurnRole::User ) @@ -446,7 +443,7 @@ void TracyLlm::Draw() } ImGui::PushID( turnIdx++ ); - if( !m_chatUi->Turn( role, content ) ) + if( !m_chatUi->Turn( role, line ) ) { if( role == TracyLlmChat::TurnRole::Assistant ) { diff --git a/profiler/src/profiler/TracyLlmChat.cpp b/profiler/src/profiler/TracyLlmChat.cpp index bda4f3b7..44d1dc09 100644 --- a/profiler/src/profiler/TracyLlmChat.cpp +++ b/profiler/src/profiler/TracyLlmChat.cpp @@ -72,7 +72,7 @@ void TracyLlmChat::End() } } -bool TracyLlmChat::Turn( TurnRole role, const std::string& content ) +bool TracyLlmChat::Turn( TurnRole role, const nlohmann::json& json ) { bool keep = true; const auto& roleData = roles[(int)role]; @@ -166,120 +166,107 @@ bool TracyLlmChat::Turn( TurnRole role, const std::string& content ) } else if( role != TurnRole::Assistant ) { - auto pos = content.find( "" ); - if( pos == std::string::npos ) + if( json.contains( "content" ) ) { + auto& content = json["content"].get_ref(); m_markdown.Print( content.c_str(), content.size() ); } - else - { - auto str = content; - while( ( pos = str.find( "" ) ) != std::string::npos ) - { - auto pos2 = str.find( "" ); - str = str.substr( 0, pos ) + str.substr( pos2 + sizeof( "" ) - 1 ); - } - m_markdown.Print( str.c_str(), str.size() ); - } - } - else if( content.starts_with( "\n" ) ) - { - ThinkScope(); - if( m_thinkOpen ) - { - ImGui::PushStyleColor( ImGuiCol_Text, ImVec4( 0.5f, 0.5f, 0.5f, 1.f ) ); - if( content == ForgetMsg ) - { - ImGui::TextUnformatted( ICON_FA_RECYCLE " Tool response removed to save context space" ); - m_subIdx++; - } - else - { - ImGui::PushID( m_subIdx++ ); - if( ImGui::TreeNode( ICON_FA_REPLY " Tool response..." ) ) - { - ImGui::PushFont( g_fonts.mono, FontNormal ); - ImGui::TextWrapped( "%s", content.c_str() + sizeof( "\n" ) - 1 ); - ImGui::PopFont(); - ImGui::TreePop(); - } - ImGui::PopID(); - } - ImGui::PopStyleColor(); - } - else - { - m_subIdx++; - } } else { - size_t pos = 0; - size_t end = content.size(); - while( pos < end ) + if( json.contains( "reasoning_content" ) ) { - auto posThink = content.find( "", pos ); - auto posTool = content.find( "", pos ); - auto minPos = std::min( posThink, posTool ); - - if( pos != minPos ) + ThinkScope(); + if( m_thinkOpen ) { - NormalScope(); - m_markdown.Print( content.c_str() + pos, std::min( end, minPos ) - pos ); + auto& reasoning = json["reasoning_content"].get_ref(); + PrintThink( reasoning.c_str(), reasoning.size() ); } - - pos = minPos; - if( pos == std::string::npos ) break; - - if( minPos == posThink ) + } + if( json.contains( "tool_calls" ) ) + { + ThinkScope(); + if( m_thinkOpen ) { - pos += sizeof( "" ) - 1; - while( content[pos] == '\n' || content[pos] == ' ' ) pos++; - auto endThink = content.find( "", pos ); - if( endThink != pos ) + auto calls = json["tool_calls"].dump( 2 ); + PrintToolCall( calls.c_str(), calls.size() ); + } + } + if( json.contains( "content" ) ) + { + auto& content = json["content"].get_ref(); + if( json["role"].get_ref() == "tool" ) + { + ThinkScope(); + if( m_thinkOpen ) { - ThinkScope(); - if( m_thinkOpen ) PrintThink( content.c_str() + pos, std::min( end, endThink ) - pos ); + ImGui::PushStyleColor( ImGuiCol_Text, ImVec4( 0.5f, 0.5f, 0.5f, 1.f ) ); + if( content == ForgetMsg ) + { + ImGui::TextUnformatted( ICON_FA_RECYCLE " Tool response removed to save context space" ); + m_subIdx++; + } + else + { + auto& name = json["name"].get_ref(); + auto& id = json["tool_call_id"].get_ref(); + ImGui::PushID( m_subIdx++ ); + char buf[1024]; + snprintf( buf, sizeof( buf ), ICON_FA_REPLY " Tool response (%s/%s)...", name.c_str(), id.substr( 0, 8 ).c_str() ); + if( ImGui::TreeNode( buf ) ) + { + std::string parsed; + try + { + parsed = nlohmann::json::parse( content.c_str() ).dump( 2 ); + } + catch( nlohmann::json::exception& ) + { + parsed = content; + } + ImGui::PushFont( g_fonts.mono, FontNormal ); + ImGui::TextWrapped( "%s", parsed.c_str() ); + ImGui::PopFont(); + ImGui::TreePop(); + } + ImGui::PopID(); + } + ImGui::PopStyleColor(); } - if( endThink == std::string::npos ) break; - pos = endThink; - do + else { - pos += sizeof( "" ) - 1; - while( content[pos] == '\n' || content[pos] == ' ' ) pos++; + m_subIdx++; } - while( strncmp( content.c_str() + pos, "", sizeof( "" ) - 1 ) == 0 ); } else { - assert( minPos == posTool ); - pos += sizeof( "" ) - 1; - while( content[pos] == '\n' || content[pos] == ' ' ) pos++; - auto endTool = content.find( "", pos ); - ThinkScope(); - if( m_thinkOpen ) PrintToolCall( content.c_str() + pos, std::min( end, endTool ) - pos ); - if( endTool == std::string::npos ) break; - pos = endTool + sizeof( "" ) - 1; - while( content[pos] == '\n' || content[pos] == ' ' ) pos++; + if( !content.empty() ) + { + NormalScope(); + m_markdown.Print( content.c_str(), content.size() ); + } } } } ImGui::PopStyleColor(); - if( ImGui::IsMouseClicked( ImGuiMouseButton_Right ) && - ImGui::IsWindowHovered() && - ImGui::IsMouseHoveringRect( posStart, ImGui::GetCursorScreenPos() + ImVec2( ImGui::GetContentRegionAvail().x, 0 ) ) ) + if( json.contains( "content" ) ) { - ImGui::OpenPopup( "ContextMenu" ); - } - if( ImGui::BeginPopup( "ContextMenu" ) ) - { - if( ImGui::Selectable( ICON_FA_CLIPBOARD " Copy" ) ) + if( ImGui::IsMouseClicked( ImGuiMouseButton_Right ) && + ImGui::IsWindowHovered() && + ImGui::IsMouseHoveringRect( posStart, ImGui::GetCursorScreenPos() + ImVec2( ImGui::GetContentRegionAvail().x, 0 ) ) ) { - ImGui::SetClipboardText( content.c_str() ); - ImGui::CloseCurrentPopup(); + ImGui::OpenPopup( "ContextMenu" ); + } + if( ImGui::BeginPopup( "ContextMenu" ) ) + { + if( ImGui::Selectable( ICON_FA_CLIPBOARD " Copy" ) ) + { + ImGui::SetClipboardText( json["content"].get_ref().c_str() ); + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); } - ImGui::EndPopup(); } return keep; diff --git a/profiler/src/profiler/TracyLlmChat.hpp b/profiler/src/profiler/TracyLlmChat.hpp index e73768fb..91168a33 100644 --- a/profiler/src/profiler/TracyLlmChat.hpp +++ b/profiler/src/profiler/TracyLlmChat.hpp @@ -1,7 +1,7 @@ #ifndef __TRACYLLMCHAT_HPP__ #define __TRACYLLMCHAT_HPP__ -#include +#include #include "TracyMarkdown.hpp" @@ -31,7 +31,7 @@ public: void Begin(); void End(); - bool Turn( TurnRole role, const std::string& content ); + bool Turn( TurnRole role, const nlohmann::json& json ); private: void NormalScope();