diff --git a/FrontEnd/StyleSheets/Chatbook.nb b/FrontEnd/StyleSheets/Chatbook.nb index 6647e054..08341435 100644 --- a/FrontEnd/StyleSheets/Chatbook.nb +++ b/FrontEnd/StyleSheets/Chatbook.nb @@ -1161,7 +1161,7 @@ Notebook[ ], Cell[ StyleData["ChatStyleSheetInformation"], - TaggingRules -> <|"StyleSheetVersion" -> "1.4.0.3913547000"|> + TaggingRules -> <|"StyleSheetVersion" -> "1.4.1.3913792210"|> ], Cell[ StyleData["Text"], @@ -17593,4 +17593,4 @@ Notebook[ ] }, StyleDefinitions -> "PrivateStylesheetFormatting.nb" -] +] \ No newline at end of file diff --git a/PacletInfo.wl b/PacletInfo.wl index 15559d3e..53345f9f 100644 --- a/PacletInfo.wl +++ b/PacletInfo.wl @@ -1,7 +1,7 @@ PacletObject[ <| "Name" -> "Wolfram/Chatbook", "PublisherID" -> "Wolfram", - "Version" -> "1.4.0", + "Version" -> "1.4.1", "WolframVersion" -> "13.3+", "Description" -> "Wolfram Notebooks + LLMs", "License" -> "MIT", diff --git a/Scripts/Common.wl b/Scripts/Common.wl index 60216f26..38d3a304 100644 --- a/Scripts/Common.wl +++ b/Scripts/Common.wl @@ -368,6 +368,7 @@ Print[ "ResourceSystemBase: ", $ResourceSystemBase ]; $defNB = File @ FileNameJoin @ { $pacletDir, "ResourceDefinition.nb" }; Print[ "Definition Notebook: ", $defNB ]; +PacletDirectoryLoad @ $pacletDir; $loadedDefinitions = True; diff --git a/Source/Chatbook/Actions.wl b/Source/Chatbook/Actions.wl index 6faa9007..cd255e8d 100644 --- a/Source/Chatbook/Actions.wl +++ b/Source/Chatbook/Actions.wl @@ -20,6 +20,7 @@ BeginPackage[ "Wolfram`Chatbook`Actions`" ]; `$autoAssistMode; `$autoOpen; `$finalCell; +`$lastCellObject; `$lastChatString; `$lastMessages; `$lastSettings; @@ -406,14 +407,32 @@ EvaluateChatInput[ evalCell_CellObject, nbo_NotebookObject ] := EvaluateChatInput[ evalCell, nbo, currentChatSettings @ nbo ]; EvaluateChatInput[ evalCell_CellObject, nbo_NotebookObject, settings_Association? AssociationQ ] := - withChatState @ Block[ { $autoAssistMode = False }, - $lastMessages = None; + withChatState @ Block[ { $autoAssistMode = False, $aborted = False }, + $lastCellObject = None; $lastChatString = None; + $lastMessages = None; $nextTaskEvaluation = None; - clearMinimizedChats @ nbo; $enableLLMServices = settings[ "EnableLLMServices" ]; - sendChat[ evalCell, nbo, settings ]; - waitForLastTask[ ]; + clearMinimizedChats @ nbo; + + (* Send chat while listening for an abort: *) + CheckAbort[ + sendChat[ evalCell, nbo, settings ]; + waitForLastTask[ ] + , + (* The user has issued an abort: *) + $aborted = True; + (* Clean up the current chat evaluation: *) + With[ { cell = $lastCellObject }, + If[ MatchQ[ cell, _CellObject ], + StopChat @ cell, + removeTask @ $lastTask + ] + ] + , + PropagateAborts -> False + ]; + blockChatObject[ If[ ListQ @ $lastMessages && StringQ @ $lastChatString, With[ @@ -423,11 +442,9 @@ EvaluateChatInput[ evalCell_CellObject, nbo_NotebookObject, settings_Association <| "Role" -> "Assistant", "Content" -> $lastChatString |> ] }, - applyHandlerFunction[ settings, "ChatPost", <| "ChatObject" -> chat, "NotebookObject" -> nbo |> ]; - Sow[ chat, $chatObjectTag ] + applyChatPost[ chat, settings, nbo, $aborted ] ], - applyHandlerFunction[ settings, "ChatPost", <| "ChatObject" -> None, "NotebookObject" -> nbo |> ]; - Sow[ None, $chatObjectTag ]; + applyChatPost[ None, settings, nbo, $aborted ]; Null ]; ] @@ -435,6 +452,21 @@ EvaluateChatInput[ evalCell_CellObject, nbo_NotebookObject, settings_Association EvaluateChatInput // endDefinition; +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*applyChatPost*) +applyChatPost // beginDefinition; + +applyChatPost[ chat_, settings_, nbo_, aborted: True|False ] := ( + If[ aborted, + applyHandlerFunction[ settings, "ChatAbort", <| "ChatObject" -> chat, "NotebookObject" -> nbo |> ], + applyHandlerFunction[ settings, "ChatPost" , <| "ChatObject" -> chat, "NotebookObject" -> nbo |> ] + ]; + Sow[ chat, $chatObjectTag ] +); + +applyChatPost // endDefinition; + (* ::**************************************************************************************************************:: *) (* ::Subsection::Closed:: *) (*blockChatObject*) @@ -481,10 +513,23 @@ ensureChatInputCell // endDefinition; (* ::**************************************************************************************************************:: *) (* ::Subsection::Closed:: *) (*chatOutputCellQ*) -chatOutputCellQ[ cell_CellObject ] := chatOutputCellQ[ cell ] = chatOutputCellQ[ cell, Developer`CellInformation @ cell ]; -chatOutputCellQ[ cell_, KeyValuePattern[ "Style" -> $$chatOutputStyle ] ] := True; -chatOutputCellQ[ cell_CellObject, ___ ] := ($badCellObject = cell; $badCell = NotebookRead @ cell; False); -chatOutputCellQ[ ___ ] := False; +chatOutputCellQ[ cell_CellObject ] := chatOutputCellQ[ cell ] = + chatOutputCellQ[ cell, Developer`CellInformation @ cell ]; + +chatOutputCellQ[ cell_, KeyValuePattern[ "Style" -> $$chatOutputStyle ] ] := + True; + +chatOutputCellQ[ cell_CellObject, KeyValuePattern[ "Style" -> "Output" ] ] /; $cloudNotebooks := + MatchQ[ CurrentValue[ cell, { TaggingRules, "ChatNotebookSettings", "CellObject" } ], _CellObject ]; + +chatOutputCellQ[ cell_CellObject, ___ ] := ( + $badCellObject = cell; + $badCell = NotebookRead @ cell; + False +); + +chatOutputCellQ[ ___ ] := + False; (* ::**************************************************************************************************************:: *) (* ::Subsection::Closed:: *) diff --git a/Source/Chatbook/ChatHistory.wl b/Source/Chatbook/ChatHistory.wl index 5f8d41c8..e02af223 100644 --- a/Source/Chatbook/ChatHistory.wl +++ b/Source/Chatbook/ChatHistory.wl @@ -186,6 +186,7 @@ chatExcludedQ[ KeyValuePattern[ "Style" -> $$chatIgnoredStyle ] ] := True; chatExcludedQ[ KeyValuePattern[ "ChatNotebookSettings" -> settings_ ] ] := chatExcludedQ @ settings; chatExcludedQ[ KeyValuePattern[ "ExcludeFromChat" -> exclude_ ] ] := TrueQ @ exclude; chatExcludedQ[ KeyValuePattern[ { } ] ] := False; +chatExcludedQ[ Inherited ] := False; chatExcludedQ // endDefinition; diff --git a/Source/Chatbook/ChatMessages.wl b/Source/Chatbook/ChatMessages.wl index 8d0ef48a..0e600ebd 100644 --- a/Source/Chatbook/ChatMessages.wl +++ b/Source/Chatbook/ChatMessages.wl @@ -714,6 +714,7 @@ inferMultimodalTypes[ content_List ] := Enclose[ inferMultimodalTypes // endDefinition; +(* TODO: add a way to control image detail and insert "Detail" -> "..." here when appropriate: *) inferMultimodalTypes0 // beginDefinition; inferMultimodalTypes0[ content_List ] := inferMultimodalTypes0 /@ content; inferMultimodalTypes0[ content_String ] := <| "Type" -> "Text" , "Data" -> content |>; diff --git a/Source/Chatbook/Chatbook.wl b/Source/Chatbook/Chatbook.wl index c8b08eff..cc2e449b 100644 --- a/Source/Chatbook/Chatbook.wl +++ b/Source/Chatbook/Chatbook.wl @@ -12,7 +12,15 @@ Quiet[ Unprotect[ "Wolfram`Chatbook`*" ]; ClearAll[ "Wolfram`Chatbook`*" ]; ClearAll[ "Wolfram`Chatbook`*`*" ]; - Get @ Wolfram`ChatbookLoader`$MXFile + Get @ Wolfram`ChatbookLoader`$MXFile; + (* Ensure all subcontexts are in $Packages to avoid reloading subcontexts out of order: *) + If[ MatchQ[ Wolfram`Chatbook`$ChatbookContexts, { __String } ], + WithCleanup[ + Unprotect @ $Packages, + $Packages = DeleteDuplicates @ Join[ $Packages, Wolfram`Chatbook`$ChatbookContexts ], + Protect @ $Packages + ] + ] , WithCleanup[ PreemptProtect[ diff --git a/Source/Chatbook/CloudToolbar.wl b/Source/Chatbook/CloudToolbar.wl index b33a91ab..c007d130 100644 --- a/Source/Chatbook/CloudToolbar.wl +++ b/Source/Chatbook/CloudToolbar.wl @@ -4,6 +4,7 @@ BeginPackage[ "Wolfram`Chatbook`CloudToolbar`" ]; HoldComplete[ `makeChatCloudDockedCellContents; + `forceRefreshCloudPreferences; ]; Begin[ "`Private`" ]; @@ -14,6 +15,7 @@ Needs[ "Wolfram`Chatbook`Dialogs`" ]; Needs[ "Wolfram`Chatbook`Dynamics`" ]; Needs[ "Wolfram`Chatbook`PreferencesContent`" ]; Needs[ "Wolfram`Chatbook`Services`" ]; +Needs[ "Wolfram`Chatbook`UI`" ]; (* ::**************************************************************************************************************:: *) (* ::Section::Closed:: *) @@ -25,6 +27,9 @@ $notebookTypeLabelOptions = Sequence[ FontWeight -> "DemiBold" ]; +$buttonHeight = 20; +$menuItemIconSize = { 20, 20 }; + (* ::**************************************************************************************************************:: *) (* ::Section::Closed:: *) (*Docked Cell Contents*) @@ -34,142 +39,175 @@ $notebookTypeLabelOptions = Sequence[ (*makeChatCloudDockedCellContents*) makeChatCloudDockedCellContents // beginDefinition; -makeChatCloudDockedCellContents[ ] := - Block[ { $preferencesScope := EvaluationNotebook[ ] }, - DynamicWrapper[ - Grid[ - { - { - Item[ $cloudChatBanner, Alignment -> Left ], - Item[ "", ItemSize -> Fit ], - makePersonaSelector[ ], - cloudModelSelector[ ] - } - }, - Alignment -> { Left, Baseline }, - Dividers -> { { False, False, False, True }, False }, - Spacings -> { 2, 0 }, - BaseStyle -> { "Text", FontSize -> 14, FontColor -> GrayLevel[ 0.4 ] }, - FrameStyle -> Directive[ Thickness[ 2 ], GrayLevel[ 0.9 ] ] - ], - Needs[ "GeneralUtilities`" -> None ]; - CurrentValue[ EvaluationNotebook[ ], TaggingRules ] = - GeneralUtilities`ToAssociations @ Replace[ - CurrentValue[ EvaluationNotebook[ ], TaggingRules ], - Except[ KeyValuePattern @ { } ] :> <| |> - ] - ] - ]; +makeChatCloudDockedCellContents[ ] := Grid[ + { + { + Item[ $cloudChatBanner, Alignment -> Left ], + Item[ "", ItemSize -> Fit ], + cloudCellInsertMenu[ ], + cloudPreferencesButton[ ] + } + }, + Alignment -> { Left, Center }, + Spacings -> { 0.7, 0 }, + BaseStyle -> { "Text", FontSize -> 14 }, + FrameStyle -> Directive[ Thickness[ 2 ], GrayLevel[ 0.9 ] ] +]; makeChatCloudDockedCellContents // endDefinition; (* ::**************************************************************************************************************:: *) (* ::Subsubsection::Closed:: *) -(*cloudModelSelector*) -cloudModelSelector // beginDefinition; +(*cloudCellInsertMenu*) +cloudCellInsertMenu // beginDefinition; -cloudModelSelector[ ] := - DynamicModule[ { serviceSelector, modelSelector }, - - serviceSelector = PopupMenu[ - Dynamic[ - Replace[ - CurrentValue[ EvaluationNotebook[ ], { TaggingRules, "ChatNotebookSettings", "Model" } ], - { - _String|Inherited :> "OpenAI", - KeyValuePattern[ "Service" -> service_String ] :> service, - _ :> Set[ - CurrentValue[ - EvaluationNotebook[ ], - { TaggingRules, "ChatNotebookSettings", "Model" } - ], - $DefaultModel - ][ "Service" ] - } - ], - Function[ - CurrentValue[ - EvaluationNotebook[ ], - { TaggingRules, "ChatNotebookSettings", "Model", "Service" } - ] = #1; - - CurrentValue[ - EvaluationNotebook[ ], - { TaggingRules, "ChatNotebookSettings", "Model", "Name" } - ] = Automatic; - - cloudModelNameSelector[ Dynamic @ modelSelector, #1 ] - ] - ], - KeyValueMap[ - #1 -> Row @ { inlineTemplateBoxes[ #2[ "Icon" ] ], Spacer[ 1 ], #2[ "Service" ] } &, - $availableServices - ] - ]; - - cloudModelNameSelector[ - Dynamic @ modelSelector, +cloudCellInsertMenu[ ] := ActionMenu[ + toolbarButtonLabel @ Row @ { "Insert Chat Cell", Spacer[ 5 ], RawBoxes @ TemplateBox[ { }, "ChatInputIcon" ] }, + { + insertStyleMenuItem[ "ChatInputIcon", "ChatInput", "'" ], + insertStyleMenuItem[ "SideChatIcon", "SideChat", "' '" ], + insertStyleMenuItem[ "ChatSystemIcon", "ChatSystemInput", "' ' '" ], + Delimiter, + insertStyleMenuItem[ None, "ChatDelimiter", "~" ], + insertStyleMenuItem[ None, "ChatBlockDivider", "~ ~" ] + }, + FrameMargins -> { { 0, 0 }, { 0, 0 } } +]; + +cloudCellInsertMenu // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*toolbarButtonLabel*) +toolbarButtonLabel // beginDefinition; + +toolbarButtonLabel[ label_ ] := Pane[ + label, + ImageSize -> { Automatic, $buttonHeight }, + Alignment -> { Center, Baseline }, + FrameMargins -> { { 8, 0 }, { 2, 2 } } +]; + +toolbarButtonLabel // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*insertStyleMenuItem*) +insertStyleMenuItem // beginDefinition; + +insertStyleMenuItem[ icon_String, style_, shortcut_ ] := + insertStyleMenuItem[ chatbookIcon[ icon, False ], style, shortcut ]; + +insertStyleMenuItem[ None, style_, shortcut_ ] := + insertStyleMenuItem[ Spacer[ 0 ], style, shortcut ]; + +insertStyleMenuItem[ icon_, style_, shortcut_ ] := + Grid[ + { { + Pane[ icon, ImageSize -> $menuItemIconSize ], + Item[ style, ItemSize -> 12 ], + Style[ shortcut, FontColor -> GrayLevel[ 0.75 ] ] + } }, + Alignment -> { { Center, Left, Right }, Center } + ] :> insertCellStyle @ style; + +insertStyleMenuItem // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*insertCellStyle*) +insertCellStyle // beginDefinition; + +insertCellStyle[ style_String ] := + insertCellStyle[ style, EvaluationNotebook[ ] ]; + +insertCellStyle[ style_String, nbo_NotebookObject ] := Enclose[ + Module[ { tag, cell, cellObject }, + tag = ConfirmBy[ CreateUUID[ ], StringQ, "UUID" ]; + cell = Cell[ "", style, CellTags -> tag ]; + SelectionMove[ nbo, After, Notebook ]; + NotebookWrite[ nbo, cell ]; + cellObject = ConfirmMatch[ First[ Cells[ nbo, CellTags -> tag ], $Failed ], _CellObject, "CellObject" ]; + If[ style =!= "ChatDelimiter", SelectionMove[ cellObject, Before, CellContents ] ]; + SetOptions[ cellObject, CellTags -> Inherited ]; + cellObject + ], + throwInternalFailure +]; + +insertCellStyle // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*cloudPreferencesButton*) +cloudPreferencesButton // beginDefinition; + +cloudPreferencesButton[ ] := Button[ + toolbarButtonLabel @ Row @ { "Chat Settings", Spacer[ 5 ], RawBoxes @ TemplateBox[ { }, "AdvancedSettings" ] }, + toggleCloudPreferences @ EvaluationNotebook[ ], + FrameMargins -> { { 0, 4 }, { 0, 0 } } +]; + +cloudPreferencesButton // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*toggleCloudPreferences*) +toggleCloudPreferences // beginDefinition; + +toggleCloudPreferences[ nbo_NotebookObject ] := + toggleCloudPreferences[ nbo, Flatten @ List @ CurrentValue[ nbo, DockedCells ] ]; + +toggleCloudPreferences[ nbo_NotebookObject, { cell_Cell } ] := + SetOptions[ nbo, DockedCells -> { cell, $cloudPreferencesCell } ]; + +toggleCloudPreferences[ nbo_NotebookObject, { Inherited|$Failed } ] := SetOptions[ + nbo, + DockedCells -> { + Cell[ BoxData @ DynamicBox @ ToBoxes @ MakeChatCloudDockedCellContents[ ], Background -> None ], + $cloudPreferencesCell + } +]; + +toggleCloudPreferences[ nbo_NotebookObject, { cell_Cell, _Cell } ] := + SetOptions[ nbo, DockedCells -> { cell } ]; + +toggleCloudPreferences[ nbo_NotebookObject, _ ] := + SetOptions[ nbo, DockedCells -> Inherited ]; + +toggleCloudPreferences // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsubsection::Closed:: *) +(*$cloudPreferencesCell*) +$cloudPreferencesCell := $cloudPreferencesCell = Cell[ + BoxData @ ToBoxes @ Pane[ + Dynamic[ Replace[ - CurrentValue[ EvaluationNotebook[ ], { TaggingRules, "ChatNotebookSettings", "Model" } ], - { - _String|Inherited :> "OpenAI", - KeyValuePattern[ "Service" -> service_String ] :> service, - _ :> Set[ - CurrentValue[ EvaluationNotebook[ ], { TaggingRules, "ChatNotebookSettings", "Model" } ], - $DefaultModel - ][ "Service" ] - } - ] - ]; - - Row @ { - "LLM Service: ", serviceSelector, - Spacer[ 5 ], - "Model: ", Dynamic @ modelSelector - } - ]; + createCloudPreferencesContent[ ], + _createCloudPreferencesContent -> ProgressIndicator[ Appearance -> "Percolate" ] + ], + BaseStyle -> { "Text" } + ], + ImageSize -> { Scaled[ 1 ], Automatic }, + Alignment -> { Center, Top } + ], + Background -> GrayLevel[ 0.95 ] +]; -cloudModelSelector // endDefinition; +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*createCloudPreferencesContent*) +createCloudPreferencesContent // beginDefinition; +createCloudPreferencesContent[ ] := createCloudPreferencesContent[ ] = createPreferencesContent[ ]; +createCloudPreferencesContent // endDefinition; (* ::**************************************************************************************************************:: *) (* ::Subsubsection::Closed:: *) -(*cloudModelNameSelector*) -cloudModelNameSelector // beginDefinition; - -cloudModelNameSelector[ Dynamic[ modelSelector_ ], service_String ] := - modelSelector = DynamicModule[ { display, models }, - display = ProgressIndicator[ Appearance -> "Percolate" ]; - Dynamic[ display ], - Initialization :> ( - models = getServiceModelList @ service; - If[ SameQ[ - CurrentValue[ EvaluationNotebook[ ], { TaggingRules, "ChatNotebookSettings", "Model", "Name" } ], - Automatic - ], - CurrentValue[ EvaluationNotebook[ ], { TaggingRules, "ChatNotebookSettings", "Model", "Name" } ] = - First[ models, <| "Name" -> Automatic |> ][ "Name" ] - ]; - - display = PopupMenu[ - Dynamic[ - Replace[ - CurrentChatSettings[ EvaluationNotebook[ ], "Model" ], - { KeyValuePattern[ "Name" -> model_String ] :> model, _ :> Automatic } - ], - Function[ - CurrentValue[ - EvaluationNotebook[ ], - { TaggingRules, "ChatNotebookSettings", "Model", "Name" } - ] = #1 - ] - ], - (#Name -> #DisplayName &) /@ models - ] - ), - SynchronousInitialization -> False - ]; - -cloudModelNameSelector // endDefinition; +(*cloudModelSelector*) +cloudModelSelector // beginDefinition; +cloudModelSelector[ ] := makeModelSelector[ ]; +cloudModelSelector // endDefinition; (* ::**************************************************************************************************************:: *) (* ::Section::Closed:: *) @@ -214,6 +252,27 @@ $chatEnabledNotebookLabel := Grid[ Spacings -> 0.5 ]; +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Utilities*) + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*forceRefreshCloudPreferences*) +forceRefreshCloudPreferences // beginDefinition; + +forceRefreshCloudPreferences[ ] /; ! TrueQ @ $cloudNotebooks := Null; + +forceRefreshCloudPreferences[ ] := forceRefreshCloudPreferences @ EvaluationNotebook[ ]; + +forceRefreshCloudPreferences[ nbo_NotebookObject ] := ( + SetOptions[ nbo, DockedCells -> Inherited ]; + Pause[ 0.5 ]; + toggleCloudPreferences @ nbo +); + +forceRefreshCloudPreferences // endDefinition; + (* ::**************************************************************************************************************:: *) (* ::Section::Closed:: *) (*Package Footer*) diff --git a/Source/Chatbook/Common.wl b/Source/Chatbook/Common.wl index 1429776c..2565579f 100644 --- a/Source/Chatbook/Common.wl +++ b/Source/Chatbook/Common.wl @@ -466,7 +466,7 @@ messageFailure[ args___ ] := quiet = If[ TrueQ @ $failed, Quiet, Identity ]; message = messageFailure0; WithCleanup[ - StackInhibit @ quiet @ message @ args, + StackInhibit @ convertCloudFailure @ quiet @ message @ args, If[ TrueQ @ $catching, $failed = True ] ] ]; @@ -474,6 +474,29 @@ messageFailure[ args___ ] := (* https://resources.wolframcloud.com/FunctionRepository/resources/MessageFailure *) importResourceFunction[ messageFailure0, "MessageFailure" ]; +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*convertCloudFailure*) +convertCloudFailure // beginDefinition; + +convertCloudFailure[ Failure[ + "Chatbook::Internal", + as: KeyValuePattern @ { "MessageParameters" :> { Hyperlink[ _, url_ ], params___ } } +] ] /; $CloudEvaluation := + Failure[ + "Chatbook::Internal", + Association[ + as, + "MessageParameters" -> { "", params }, + "Link" -> Hyperlink[ "Report this issue \[RightGuillemet]", url ] + ] + ]; + +convertCloudFailure[ failure_ ] := + failure; + +convertCloudFailure // endDefinition; + (* ::**************************************************************************************************************:: *) (* ::Subsection::Closed:: *) (*messagePrint*) diff --git a/Source/Chatbook/Dialogs.wl b/Source/Chatbook/Dialogs.wl index 3e8048e4..d5536c9e 100644 --- a/Source/Chatbook/Dialogs.wl +++ b/Source/Chatbook/Dialogs.wl @@ -213,6 +213,15 @@ autoMargins0 // endDefinition; (*grayDialogButtonLabel*) grayDialogButtonLabel // beginDefinition; +grayDialogButtonLabel[ { normal_, hover_, pressed_ } ] /; $cloudNotebooks := + Mouseover[ + Framed[ normal , BaseStyle -> "ButtonGray1Normal" , BaselinePosition -> Baseline ], + Framed[ hover , BaseStyle -> "ButtonGray1Hover" , BaselinePosition -> Baseline ], + BaseStyle -> "DialogTextBasic", + ContentPadding -> False, + ImageSize -> All + ]; + grayDialogButtonLabel[ { normal_, hover_, pressed_ } ] := NotebookTools`Mousedown[ Framed[ normal , BaseStyle -> "ButtonGray1Normal" , BaselinePosition -> Baseline ], @@ -232,6 +241,15 @@ grayDialogButtonLabel // endDefinition; (*redDialogButtonLabel*) redDialogButtonLabel // beginDefinition; +redDialogButtonLabel[ { normal_, hover_, pressed_ } ] /; $cloudNotebooks := + Mouseover[ + Framed[ normal , BaseStyle -> "ButtonRed1Normal" , BaselinePosition -> Baseline ], + Framed[ hover , BaseStyle -> "ButtonRed1Hover" , BaselinePosition -> Baseline ], + BaseStyle -> "DialogTextBasic", + ContentPadding -> False, + ImageSize -> All + ]; + redDialogButtonLabel[ { normal_, hover_, pressed_ } ] := NotebookTools`Mousedown[ Framed[ normal , BaseStyle -> "ButtonRed1Normal" , BaselinePosition -> Baseline ], diff --git a/Source/Chatbook/Formatting.wl b/Source/Chatbook/Formatting.wl index 22eb9a25..5e3164e2 100644 --- a/Source/Chatbook/Formatting.wl +++ b/Source/Chatbook/Formatting.wl @@ -73,6 +73,8 @@ $externalLanguageRules = Replace[ $$mdRow = Except[ "\n" ].. ~~ Repeated[ ("|" ~~ Except[ "\n" ]..), { 2, Infinity } ] ~~ ("\n"|EndOfString); $$mdTable = $$mdRow ~~ $$mdRow ..; +$chatGeneratedCellTag = "ChatGeneratedCell"; + (* ::**************************************************************************************************************:: *) (* ::Section::Closed:: *) (*Chat Output Formatting*) @@ -444,8 +446,7 @@ insertCodeBelow[ cell_Cell, evaluate_ ] := Module[ { cellObj, nbo }, cellObj = topParentCell @ EvaluationCell[ ]; nbo = parentNotebook @ cellObj; - SelectionMove[ cellObj, After, Cell ]; - NotebookWrite[ nbo, stripMarkdownBoxes @ cell, All ]; + insertAfterChatGeneratedCells[ cellObj, cell ]; If[ TrueQ @ evaluate, selectionEvaluateCreateCell @ nbo, SelectionMove[ nbo, After, CellContents ] @@ -457,6 +458,35 @@ insertCodeBelow[ string_String, evaluate_ ] := insertCodeBelow // endDefinition; +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*insertAfterChatGeneratedCells*) +insertAfterChatGeneratedCells // beginDefinition; + +insertAfterChatGeneratedCells[ cellObj_CellObject, cell_Cell ] := Enclose[ + Module[ { nbo, allCells, cellsAfter, tagged, inserted, insertionPoint }, + + nbo = ConfirmMatch[ parentNotebook @ cellObj, _NotebookObject, "ParentNotebook" ]; + allCells = ConfirmMatch[ Cells @ nbo, { __CellObject }, "AllCells" ]; + cellsAfter = Replace[ allCells, { { ___, cellObj, after___ } :> { after }, _ :> { } } ]; + + tagged = ConfirmBy[ + AssociationThread[ cellsAfter -> Flatten @* List /@ CurrentValue[ cellsAfter, CellTags ] ], + AssociationQ, + "Tagged" + ]; + + inserted = ConfirmBy[ TakeWhile[ tagged, MemberQ[ $chatGeneratedCellTag ] ], AssociationQ, "Inserted" ]; + insertionPoint = ConfirmMatch[ Last[ Keys @ inserted, cellObj ], _CellObject, "InsertionPoint" ]; + + SelectionMove[ insertionPoint, After, Cell ]; + NotebookWrite[ nbo, preprocessInsertedCell @ cell, All ]; + ], + throwInternalFailure +]; + +insertAfterChatGeneratedCells // endDefinition; + (* ::**************************************************************************************************************:: *) (* ::Subsubsection::Closed:: *) (*copyCode*) @@ -465,6 +495,29 @@ copyCode[ cell_CellObject ] := copyCode @ getCodeBlockContent @ cell; copyCode[ code: _Cell|_String ] := CopyToClipboard @ stripMarkdownBoxes @ code; copyCode // endDefinition; +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*preprocessInsertedCell*) +preprocessInsertedCell // beginDefinition; +preprocessInsertedCell[ cell_ ] := addInsertedCellTags @ stripMarkdownBoxes @ cell; +preprocessInsertedCell // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*addInsertedCellTags*) +addInsertedCellTags // beginDefinition; + +addInsertedCellTags[ Cell[ a__, CellTags -> tag_String, b___ ] ] := + addInsertedCellTags @ Cell[ a, CellTags -> { tag }, b ]; + +addInsertedCellTags[ Cell[ a__, CellTags -> { tags___String }, b___ ] ] := + Cell[ a, CellTags -> DeleteDuplicates @ { $chatGeneratedCellTag, tags }, b ]; + +addInsertedCellTags[ Cell[ a: Except[ CellTags -> _ ].. ] ] := + Cell[ a, CellTags -> { $chatGeneratedCellTag } ]; + +addInsertedCellTags // endDefinition; + (* ::**************************************************************************************************************:: *) (* ::Subsubsection::Closed:: *) (*stripMarkdownBoxes*) diff --git a/Source/Chatbook/FrontEnd.wl b/Source/Chatbook/FrontEnd.wl index cf91e05c..4a4032ac 100644 --- a/Source/Chatbook/FrontEnd.wl +++ b/Source/Chatbook/FrontEnd.wl @@ -463,9 +463,8 @@ fixCloudCell // endDefinition; (* ::Subsubsection::Closed:: *) (*applyCloudCellFixes*) applyCloudCellFixes // beginDefinition; -applyCloudCellFixes[ text_String ] := text; -applyCloudCellFixes[ boxes_BoxData ] := boxes; applyCloudCellFixes[ text_TextData ] := ReplaceRepeated[ text, $cloudCellFixes ]; +applyCloudCellFixes[ boxes_ ] := boxes; applyCloudCellFixes // endDefinition; $cloudCellFixes := $cloudCellFixes = Dispatch @ { diff --git a/Source/Chatbook/InlineReferences.wl b/Source/Chatbook/InlineReferences.wl index 02880782..d6bd76f2 100644 --- a/Source/Chatbook/InlineReferences.wl +++ b/Source/Chatbook/InlineReferences.wl @@ -3,6 +3,7 @@ (* ::Section::Closed:: *) (*Package Header*) +(* TODO: Figure out how to show autocomplete menu when field is empty *) BeginPackage[ "Wolfram`Chatbook`InlineReferences`" ]; diff --git a/Source/Chatbook/Main.wl b/Source/Chatbook/Main.wl index 5f1baeca..9a0144c1 100644 --- a/Source/Chatbook/Main.wl +++ b/Source/Chatbook/Main.wl @@ -7,6 +7,8 @@ BeginPackage[ "Wolfram`Chatbook`" ]; (* ::Subsection::Closed:: *) (*Declare Symbols*) `$AvailableTools; +`$ChatAbort; +`$ChatbookContexts; `$ChatHandlerData; `$ChatPost; `$ChatPre; @@ -130,6 +132,11 @@ Protect[ WriteChatOutputCell ]; +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Subcontexts*) +$ChatbookContexts = Select[ Contexts[ "Wolfram`Chatbook`*" ], StringFreeQ[ "`Private`" ] ]; + (* ::**************************************************************************************************************:: *) (* ::Section::Closed:: *) (*Package Footer*) diff --git a/Source/Chatbook/Models.wl b/Source/Chatbook/Models.wl index 0b8ba160..35cd313d 100644 --- a/Source/Chatbook/Models.wl +++ b/Source/Chatbook/Models.wl @@ -6,13 +6,17 @@ BeginPackage[ "Wolfram`Chatbook`Models`" ]; (* :!CodeAnalysis::BeginBlock:: *) -`chatModelQ; -`getModelList; -`modelDisplayName; -`multimodalModelQ; -`snapshotModelQ; -`standardizeModelData; -`toModelName; +HoldComplete[ + `chatModelQ; + `chooseDefaultModelName; + `getModelList; + `modelDisplayName; + `multimodalModelQ; + `snapshotModelQ; + `standardizeModelData; + `resolveFullModelSpec; + `toModelName; +]; Begin[ "`Private`" ]; @@ -20,6 +24,7 @@ Needs[ "Wolfram`Chatbook`" ]; Needs[ "Wolfram`Chatbook`Actions`" ]; Needs[ "Wolfram`Chatbook`Common`" ]; Needs[ "Wolfram`Chatbook`Dynamics`" ]; +Needs[ "Wolfram`Chatbook`Services`" ]; Needs[ "Wolfram`Chatbook`UI`" ]; (* ::**************************************************************************************************************:: *) @@ -110,6 +115,10 @@ getModelList // endDefinition; $fallbackModelList = { "gpt-3.5-turbo", "gpt-3.5-turbo-16k", "gpt-4" }; +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Model Utility Functions*) + (* ::**************************************************************************************************************:: *) (* ::Subsection::Closed:: *) (*chatModelQ*) @@ -331,8 +340,62 @@ standardizeModelData[ service_String, model_ ] := standardizeModelData[ KeyValuePattern[ "Service" -> service_String ], model_ ] := standardizeModelData[ service, model ]; +standardizeModelData[ $$unspecified ] := + With[ { model = $DefaultModel }, + standardizeModelData @ model /; MatchQ[ model, Except[ $$unspecified ] ] + ]; + standardizeModelData // endDefinition; +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*chooseDefaultModelName*) +(* + Choose a default initial model according to the following rules: + 1. If the service name is the same as the one in $DefaultModel, use the model name in $DefaultModel. + 2. If the registered service specifies a "DefaultModel" property, we'll use that. + 3. If the model list is already cached for the service, we'll use the first model in that list. + 4. Otherwise, give Automatic to indicate a model name that must be resolved later. +*) +chooseDefaultModelName // beginDefinition; +chooseDefaultModelName[ service_String ] /; service === $DefaultModel[ "Service" ] := $DefaultModel[ "Name" ]; +chooseDefaultModelName[ service_String ] := chooseDefaultModelName @ $availableServices @ service; +chooseDefaultModelName[ KeyValuePattern[ "DefaultModel" -> model_ ] ] := toModelName @ model; +chooseDefaultModelName[ KeyValuePattern[ "CachedModels" -> models_List ] ] := chooseDefaultModelName @ models; +chooseDefaultModelName[ { model_, ___ } ] := toModelName @ model; +chooseDefaultModelName[ service_ ] := Automatic; +chooseDefaultModelName // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*resolveFullModelSpec*) +resolveFullModelSpec // beginDefinition; + +resolveFullModelSpec[ settings: KeyValuePattern[ "Model" -> model_ ] ] := + resolveFullModelSpec @ model; + +resolveFullModelSpec[ { service_String, Automatic } ] := + resolveFullModelSpec @ <| "Service" -> service, "Name" -> Automatic |>; + +resolveFullModelSpec[ model: KeyValuePattern @ { "Service" -> service_String, "Name" -> Automatic } ] := Enclose[ + Catch @ Module[ { default, models, name }, + default = ConfirmMatch[ chooseDefaultModelName @ service, Automatic | _String, "Default" ]; + If[ StringQ @ default, Throw @ standardizeModelData @ <| model, "Name" -> default |> ]; + models = ConfirmMatch[ getServiceModelList @ service, _List | Missing[ "NotConnected" ], "Models" ]; + If[ MissingQ @ models, throwTop @ $Canceled ]; + name = ConfirmBy[ chooseDefaultModelName @ models, StringQ, "ResolvedName" ]; + standardizeModelData @ <| model, "Name" -> name |> + ], + throwInternalFailure +]; + +resolveFullModelSpec[ model_ ] := + With[ { spec = standardizeModelData @ model }, + spec /; AssociationQ @ spec + ]; + +resolveFullModelSpec // endDefinition; + (* ::**************************************************************************************************************:: *) (* ::Section::Closed:: *) (*SetModel*) diff --git a/Source/Chatbook/PersonaManager.wl b/Source/Chatbook/PersonaManager.wl index 943a819a..36b3b5ce 100644 --- a/Source/Chatbook/PersonaManager.wl +++ b/Source/Chatbook/PersonaManager.wl @@ -10,6 +10,7 @@ BeginPackage[ "Wolfram`Chatbook`PersonaManager`" ]; Begin[ "`Private`" ]; Needs[ "Wolfram`Chatbook`" ]; +Needs[ "Wolfram`Chatbook`CloudToolbar`" ]; Needs[ "Wolfram`Chatbook`Common`" ]; Needs[ "Wolfram`Chatbook`Dialogs`" ]; Needs[ "Wolfram`Chatbook`Personas`" ]; @@ -39,10 +40,10 @@ CreatePersonaManagerDialog // endDefinition; CreatePersonaManagerPanel // beginDefinition; CreatePersonaManagerPanel[ ] := DynamicModule[{favorites, delimColor}, - favorites = - Replace[ - CurrentValue[$FrontEnd, {PrivateFrontEndOptions, "InterfaceSettings", "Chatbook", "PersonaFavorites"}], - Except[{___String}] :> $corePersonaNames]; + favorites = Replace[ + CurrentChatSettings[ $FrontEnd, "PersonaFavorites" ], + Except[ { ___String } ] :> $corePersonaNames + ]; Framed[ Grid[ @@ -57,6 +58,7 @@ CreatePersonaManagerPanel[ ] := DynamicModule[{favorites, delimColor}, "Install from", Button[ grayDialogButtonLabel[ "Prompt Repository \[UpperRightArrow]" ], + If[ $CloudEvaluation, SetOptions[ EvaluationNotebook[ ], DockedCells -> Inherited ] ]; ResourceInstallFromRepository[ "Prompt" ], Appearance -> "Suppressed", BaselinePosition -> Baseline, @@ -64,6 +66,7 @@ CreatePersonaManagerPanel[ ] := DynamicModule[{favorites, delimColor}, ], Button[ grayDialogButtonLabel[ "URL" ], + If[ $CloudEvaluation, SetOptions[ EvaluationNotebook[ ], DockedCells -> Inherited ] ]; Block[ { PrintTemporary }, ResourceInstallFromURL[ "Prompt" ] ], Appearance -> "Suppressed", BaselinePosition -> Baseline, @@ -154,18 +157,14 @@ CreatePersonaManagerPanel[ ] := DynamicModule[{favorites, delimColor}, GetPersonaData[]; (* sets $CachedPersonaData *) (* make sure there are no unexpected extra personas *) Enclose[ - CurrentValue[$FrontEnd, {PrivateFrontEndOptions, "InterfaceSettings", "Chatbook", "VisiblePersonas"}] = - ConfirmBy[ - Intersection[ - CurrentValue[$FrontEnd, {PrivateFrontEndOptions, "InterfaceSettings", "Chatbook", "VisiblePersonas"}], - Keys[$CachedPersonaData] - ], + CurrentChatSettings[ $FrontEnd, "VisiblePersonas" ] = ConfirmBy[ + Quiet @ Intersection[ CurrentChatSettings[ $FrontEnd, "VisiblePersonas" ], Keys @ $CachedPersonaData ], ListQ ] ] ), Deinitialization :> If[ MatchQ[ favorites, { ___String } ], - CurrentValue[$FrontEnd, {PrivateFrontEndOptions, "InterfaceSettings", "Chatbook","PersonaFavorites"}] = favorites + CurrentChatSettings[ $FrontEnd, "PersonaFavorites" ] = favorites ] ]; @@ -275,18 +274,14 @@ formatPacletLink // endDefinition; addRemovePersonaListingCheckbox // beginDefinition; addRemovePersonaListingCheckbox[ name_String ] := - With[ - { - path = { PrivateFrontEndOptions, "InterfaceSettings", "Chatbook", "VisiblePersonas" }, - core = $corePersonaNames - }, + With[ { core = $corePersonaNames }, Checkbox @ Dynamic[ - MemberQ[ CurrentValue[ $FrontEnd, path, core ], name ], + MemberQ[ CurrentChatSettings[ $FrontEnd, "VisiblePersonas" ], name ], Function[ - CurrentValue[ $FrontEnd, path ] = - With[ { current = Replace[ CurrentValue[ $FrontEnd, path ], Except[ { ___String } ] :> core ] }, - If[ #, Union[ current, { name } ], Complement[ current, { name } ] ] - ] + CurrentChatSettings[ $FrontEnd, "VisiblePersonas" ] = With[ + { current = Replace[ CurrentChatSettings[ $FrontEnd, "VisiblePersonas" ], Except[ { ___String } ] :> core ] }, + If[ #1, Union[ current, { name } ], Complement[ current, { name } ] ] + ] ] ] ]; @@ -306,7 +301,11 @@ uninstallButton[ name_String, installedQ_, pacletName_String ] := StringTemplate["This persona cannot be uninstalled because it is provided by the `1` paclet."][pacletName]]}, Dynamic[Which[!installedQ, "Disabled", CurrentValue["MouseOver"], "Hover", True, "Default"]], ImageSize -> Automatic], - Block[ { PrintTemporary }, ResourceUninstall[ "Prompt", name ]; GetPersonaData[] ], + Block[ { PrintTemporary }, + ResourceUninstall[ "Prompt", name ]; + GetPersonaData[ ]; + forceRefreshCloudPreferences[ ] + ], Appearance -> "Suppressed", Enabled -> installedQ, ImageMargins -> {{0, 13}, {0, 0}}, diff --git a/Source/Chatbook/PreferencesContent.wl b/Source/Chatbook/PreferencesContent.wl index 45771a8b..8eed5971 100644 --- a/Source/Chatbook/PreferencesContent.wl +++ b/Source/Chatbook/PreferencesContent.wl @@ -3,10 +3,12 @@ BeginPackage[ "Wolfram`Chatbook`PreferencesContent`" ]; HoldComplete[ + `$cloudEvaluationNotebook; `$preferencesScope; `createPreferencesContent; `makeModelSelector; `makePersonaSelector; + `notebookSettingsPanel; `openPreferencesPage; ]; @@ -28,12 +30,20 @@ Needs[ "Wolfram`Chatbook`UI`" ]; (* ::**************************************************************************************************************:: *) (* ::Section::Closed:: *) (*Configuration*) +$preferencesWidth = 640; +$cloudEvaluationNotebook = None; + $preferencesPages = { "Notebooks", "Services", "Personas", "Tools" }; $$preferencesPage = Alternatives @@ $preferencesPages; $preferencesScope := $FrontEnd; $inFrontEndScope := MatchQ[ OwnValues @ $preferencesScope, { _ :> $FrontEnd|_FrontEndObject } ]; +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*Cloud Overrides*) +$displayedPreferencesPages := $preferencesPages; + (* ::**************************************************************************************************************:: *) (* ::Section::Closed:: *) (*Scope Utilities*) @@ -97,16 +107,9 @@ scopedTrackedDynamic // endDefinition; (*currentTabPageDynamic*) currentTabPageDynamic // beginDefinition; -currentTabPageDynamic[ scope_FrontEndObject ] := Dynamic @ CurrentValue[ - $FrontEnd, - { PrivateFrontEndOptions, "DialogSettings", "Preferences", "TabSettings", "AI", "Top" }, - "Notebooks" -]; - -currentTabPageDynamic[ scope_ ] := Dynamic @ CurrentValue[ - scope, - { TaggingRules, "ChatNotebookSettings", "CurrentPreferencesTab" }, - "Notebooks" +currentTabPageDynamic[ scope_ ] := Dynamic[ + Replace[ CurrentChatSettings[ scope, "CurrentPreferencesTab" ], $$unspecified -> "Notebooks" ], + (CurrentChatSettings[ scope, "CurrentPreferencesTab" ] = #1) & ]; currentTabPageDynamic // endDefinition; @@ -116,16 +119,9 @@ currentTabPageDynamic // endDefinition; (*currentTabPage*) currentTabPage // beginDefinition; -currentTabPage[ scope_FrontEndObject ] := CurrentValue[ - $FrontEnd, - { PrivateFrontEndOptions, "DialogSettings", "Preferences", "TabSettings", "AI", "Top" }, - "Notebooks" -]; - -currentTabPage[ scope_ ] := CurrentValue[ - scope, - { TaggingRules, "ChatNotebookSettings", "CurrentPreferencesTab" }, - "Notebooks" +currentTabPage[ scope_ ] := Replace[ + CurrentChatSettings[ scope, "CurrentPreferencesTab" ], + $$unspecified -> "Notebooks" ]; currentTabPage // endDefinition; @@ -141,32 +137,23 @@ currentTabPage // endDefinition; createPreferencesContent // beginDefinition; createPreferencesContent[ ] := Enclose[ - Module[ { notebookSettings, serviceSettings, personaSettings, toolSettings, tabView, reset }, - + Module[ { tabs, tabView, reset }, (* Retrieve the dynamic content for each preferences tab, confirming that it matches the expected types: *) - notebookSettings = ConfirmMatch[ preferencesContent[ "Notebooks" ], _Dynamic | _DynamicModule, "Notebooks" ]; - serviceSettings = ConfirmMatch[ preferencesContent[ "Services" ], _Dynamic | _DynamicModule, "Services" ]; - personaSettings = ConfirmMatch[ preferencesContent[ "Personas" ], _Dynamic | _DynamicModule, "Personas" ]; - toolSettings = ConfirmMatch[ preferencesContent[ "Tools" ], _Dynamic | _DynamicModule, "Tools" ]; + tabs = ConfirmMatch[ createTabViewTabs[ ], { { _String, _String -> _Dynamic|_DynamicModule }.. }, "Tabs" ]; (* Create a TabView for the preferences content, with the tab state stored in the FE's private options: *) tabView = TabView[ - { - { "Notebooks", "Notebooks" -> notebookSettings }, - { "Services" , "Services" -> serviceSettings }, - { "Personas" , "Personas" -> personaSettings }, - { "Tools" , "Tools" -> toolSettings } - }, + tabs, currentTabPageDynamic @ $preferencesScope, Background -> None, FrameMargins -> { { 2, 2 }, { 2, 3 } }, ImageMargins -> { { 10, 10 }, { 2, 2 } }, - ImageSize -> { 640, Automatic }, + ImageSize -> { $preferencesWidth, Automatic }, LabelStyle -> "feTabView" (* Defined in the SystemDialog stylesheet: *) ]; (* Create a reset button that will reset preferences to default settings: *) - reset = Pane[ $resetButton, ImageMargins -> { { 20, 0 }, { 0, 10 } }, ImageSize -> 640 ]; + reset = Pane[ $resetButton, ImageMargins -> { { 20, 0 }, { 0, 10 } }, ImageSize -> $preferencesWidth ]; (* Arrange the TabView and reset button in a Grid layout with vertical spacers: *) Grid[ @@ -190,6 +177,21 @@ createPreferencesContent[ ] := Enclose[ createPreferencesContent // endDefinition; +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*createTabViewTabs*) +createTabViewTabs // beginDefinition; +createTabViewTabs[ ] := createTabViewTabs @ $displayedPreferencesPages; +createTabViewTabs[ pages: { __String } ] := createTabViewTab /@ pages; +createTabViewTabs // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*createTabViewTab*) +createTabViewTab // beginDefinition; +createTabViewTab[ name_String ] := { name, name -> preferencesContent @ name }; +createTabViewTab // endDefinition; + (* ::**************************************************************************************************************:: *) (* ::Subsection::Closed:: *) (*preferencesContent*) @@ -256,7 +258,7 @@ createNotebookSettingsPanel[ ] := Enclose[ ]; (* Label for the interface section using a style from SystemDialog.nb: *) - interfaceLabel = Style[ "Chat Notebook Cells", "subsectionText" ]; + interfaceLabel = subsectionText[ "Chat Notebook Cells" ]; (* Retrieve and confirm the content for the chat notebook interface, ensuring it is not an error from makeInterfaceContent: *) @@ -267,7 +269,7 @@ createNotebookSettingsPanel[ ] := Enclose[ ]; (* Label for the features section using a style from SystemDialog.nb: *) - featuresLabel = Style[ "Features", "subsectionText" ]; + featuresLabel = subsectionText[ "Features" ]; (* Retrieve and confirm the content for the chat notebook features, ensuring it is not an error from makeFeaturesContent: *) @@ -312,7 +314,7 @@ makeDefaultSettingsContent // beginDefinition; makeDefaultSettingsContent[ ] := Enclose[ Module[ { assistanceCheckbox, personaSelector, modelSelector, temperatureInput, openAICompletionURLInput }, (* Checkbox to enable automatic assistance for normal shift-enter evaluations: *) - assistanceCheckbox = ConfirmMatch[ makeAssistanceCheckbox[ ], _Style, "AssistanceCheckbox" ]; + assistanceCheckbox = ConfirmMatch[ makeAssistanceCheckbox[ ], _Style|Nothing, "AssistanceCheckbox" ]; (* The personaSelector is a pop-up menu for selecting the default persona: *) personaSelector = ConfirmMatch[ makePersonaSelector[ ], _Dynamic, "PersonaSelector" ]; (* The modelSelector is a dynamic module containing menus to select the service and model separately: *) @@ -369,18 +371,7 @@ makePersonaSelector0[ personas: { (_String -> _).. } ] := Row @ { "Persona:", Spacer[ 3 ], - PopupMenu[ - scopedDynamic[ - CurrentChatSettings[ $preferencesScope, "LLMEvaluator" ], - Function[ - CurrentValue[ - $preferencesScope, - { TaggingRules, "ChatNotebookSettings", "LLMEvaluator" } - ] = #1 - ] - ], - personas - ] + PopupMenu[ scopedDynamic @ CurrentChatSettings[ $preferencesScope, "LLMEvaluator" ], personas ] }, "Notebooks", "LLMEvaluator" @@ -417,7 +408,7 @@ makeModelSelector0[ ] := makeModelSelector0[ services_Association? AssociationQ ] := Enclose[ DynamicModule[ { default, service, model, state, serviceSelector, modelSelector, highlight }, - default = currentChatSettings[ $preferencesScope, "Model" ]; + default = CurrentChatSettings[ $preferencesScope, "Model" ]; service = ConfirmBy[ extractServiceName @ default, StringQ, "ServiceName" ]; model = ConfirmBy[ extractModelName @ default , StringQ, "ModelName" ]; state = If[ modelListCachedQ @ service, "Loaded", "Loading" ]; @@ -449,7 +440,7 @@ makeModelSelector0[ services_Association? AssociationQ ] := Enclose[ highlight[ "Model:", Dynamic[ - If[ state === "Loading", $loadingPopupMenu, modelSelector ], + If[ state === "Loading" || MatchQ[ modelSelector, _Symbol ], $loadingPopupMenu, modelSelector ], TrackedSymbols :> { state, modelSelector } ], "ModelName" @@ -528,7 +519,7 @@ serviceSelectCallback[ (* Finish loading the model name selector: *) If[ state === "Loading", - expandScope @ SessionSubmit[ + expandScope @ If[ $CloudEvaluation, Identity, SessionSubmit ][ Block[ { $scopePlaceholder := $preferencesScope }, modelSelector = makeModelNameSelector[ Dynamic @ service, @@ -595,7 +586,7 @@ makeModelNameSelector[ fallback = <| "Service" -> service, "Name" -> default |>; If[ ! MemberQ[ models, KeyValuePattern[ "Name" -> current ] ], - CurrentValue[ $preferencesScope, { TaggingRules, "ChatNotebookSettings", "Model" } ] = fallback + CurrentChatSettings[ $preferencesScope, "Model" ] = fallback ]; With[ { m = fallback }, @@ -603,11 +594,7 @@ makeModelNameSelector[ scopedDynamic[ Replace[ extractModelName @ CurrentChatSettings[ $preferencesScope, "Model" ], - { - Except[ _String ] :> ( - CurrentValue[ $preferencesScope, { TaggingRules, "ChatNotebookSettings", "Model" } ] = m - ) - } + Except[ _String ] :> (CurrentChatSettings[ $preferencesScope, "Model" ] = m) ], modelSelectCallback[ Dynamic @ service, Dynamic @ model ] ], @@ -630,7 +617,7 @@ modelNameInputField // beginDefinition; modelNameInputField[ Dynamic[ service_ ], Dynamic[ model_ ], Dynamic[ modelSelector_ ], Dynamic[ state_ ] ] := prefsInputField[ - "Model:", + None, scopedDynamic[ Replace[ extractModelName @ CurrentChatSettings[ $preferencesScope, "Model" ], @@ -695,15 +682,15 @@ modelSelectCallback[ ensureServiceName @ service; ConfirmAssert[ StringQ @ service, "ServiceName" ]; + (* Store the service/model in FE settings: *) + CurrentChatSettings[ $preferencesScope, "Model" ] = <| "Service" -> service, "Name" -> model |>; + (* Remember the selected model for the given service, so it will be automatically chosen when choosing this service again: *) - CurrentValue[ $preferencesScope, { TaggingRules, "ChatNotebookSettings", "ServiceDefaultModel", service } ] = model; - - (* Store the service/model in FE settings: *) - CurrentValue[ $preferencesScope, { TaggingRules, "ChatNotebookSettings", "Model" } ] = <| - "Service" -> service, - "Name" -> model - |> + CurrentChatSettings[ $preferencesScope, "ServiceDefaultModel" ] = Append[ + CurrentChatSettings[ $preferencesScope, "ServiceDefaultModel" ], + service -> model + ] , throwInternalFailure ]; @@ -715,20 +702,24 @@ modelSelectCallback // endDefinition; (*makeAssistanceCheckbox*) makeAssistanceCheckbox // beginDefinition; -makeAssistanceCheckbox[ ] := highlightControl[ - prefsCheckbox[ - scopedDynamic[ - TrueQ @ CurrentChatSettings[ $preferencesScope, "Assistance" ], - (CurrentChatSettings[ $preferencesScope, "Assistance" ] = #1) & - ], - infoTooltip[ - "Enable automatic assistance", - "If enabled, automatic AI provided suggestions will be added following evaluation results." +makeAssistanceCheckbox[ ] := + If[ TrueQ @ $cloudNotebooks, + Nothing, + highlightControl[ + prefsCheckbox[ + scopedDynamic[ + TrueQ @ CurrentChatSettings[ $preferencesScope, "Assistance" ], + (CurrentChatSettings[ $preferencesScope, "Assistance" ] = #1) & + ], + infoTooltip[ + "Enable automatic assistance", + "If enabled, automatic AI provided suggestions will be added following evaluation results." + ] + ], + "Notebooks", + "Assistance" ] - ], - "Notebooks", - "Assistance" -]; + ]; makeAssistanceCheckbox // endDefinition; @@ -1087,7 +1078,7 @@ servicesSettingsPanel // beginDefinition; servicesSettingsPanel[ ] := Enclose[ Module[ { settingsLabel, settings, serviceGrid }, - settingsLabel = Style[ "Registered Services", "subsectionText" ]; + settingsLabel = subsectionText[ "Registered Services" ]; settings = ConfirmMatch[ makeModelSelector[ ], _Dynamic, "ServicesSettings" ]; serviceGrid = ConfirmMatch[ makeServiceGrid[ ], _Grid, "ServiceGrid" ]; @@ -1305,12 +1296,23 @@ toolSettingsPanel // endDefinition; (* ::Section::Closed:: *) (*Common*) +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*subsectionText*) +subsectionText // beginDefinition; +subsectionText[ text_ ] /; $CloudEvaluation := Style[ text, "subsectionText", FontSize -> 16, FontWeight -> "DemiBold" ]; +subsectionText[ text_ ] := Style[ text, "subsectionText" ]; +subsectionText // endDefinition; + (* ::**************************************************************************************************************:: *) (* ::Subsection::Closed:: *) (*prefsInputField*) prefsInputField // beginDefinition; (* cSpell: ignore leadin *) +prefsInputField[ None, value_Dynamic, type_, opts: OptionsPattern[ ] ] := + prefsInputField0[ value, type, opts ]; + prefsInputField[ label_, value_Dynamic, type_, opts: OptionsPattern[ ] ] := Grid[ { { label, prefsInputField0[ value, type, opts ] } }, Alignment -> { Automatic, Baseline }, @@ -1342,7 +1344,11 @@ prefsInputField0[ value_Dynamic, type_, opts: OptionsPattern[ ] ] := RawBoxes @ }, "InputFieldAppearance:RoundedFrame" ], - FrameBoxOptions -> { BaselinePosition -> Top -> Scaled[ 1.3 ] } + (* The following FrameBoxOptions do not work well in cloud, so disable if needed: *) + If[ TrueQ @ $CloudEvaluation, + Sequence @@ { }, + FrameBoxOptions -> { BaselinePosition -> Top -> Scaled[ 1.3 ] } + ] ]; prefsInputField0 // endDefinition; @@ -1444,43 +1450,36 @@ extractModelName // endDefinition; (*getServiceDefaultModel*) getServiceDefaultModel // beginDefinition; -getServiceDefaultModel[ selected_String ] := Replace[ - (* Use the last model name that was selected for this service if it exists: *) - CurrentValue[ - $preferencesScope, - { TaggingRules, "ChatNotebookSettings", "ServiceDefaultModel", selected } - ], +getServiceDefaultModel[ service_String ] := Enclose[ + Module[ { lastSelected, name }, + (* Get last selected models by service: *) + lastSelected = Replace[ + Association @ CurrentChatSettings[ $preferencesScope, "ServiceDefaultModel" ], + Except[ _? AssociationQ ] :> <| |> + ]; - (* Otherwise determine a starting model from the registered service: *) - $$unspecified :> ( - CurrentValue[ - $preferencesScope, - { TaggingRules, "ChatNotebookSettings", "ServiceDefaultModel", selected } - ] = chooseDefaultModelName @ selected - ) + (* The last model name that was selected for this service (if it exists): *) + name = ConfirmMatch[ + Lookup[ lastSelected, service, Inherited ], + _String|Automatic|Inherited, + "Name" + ]; + + (* If the service has not been selected before, choose a default model by service name and save it: *) + If[ ! StringQ @ name, + name = ConfirmMatch[ chooseDefaultModelName @ service, _String|Automatic, "DefaultName" ]; + lastSelected[ service ] = name; + CurrentChatSettings[ $preferencesScope, "ServiceDefaultModel" ] = lastSelected; + ]; + + (* Return the name: *) + ConfirmMatch[ name, _String|Automatic, "Result" ] + ], + throwInternalFailure ]; getServiceDefaultModel // endDefinition; -(* ::**************************************************************************************************************:: *) -(* ::Subsubsubsubsection::Closed:: *) -(*chooseDefaultModelName*) -(* - Choose a default initial model according to the following rules: - 1. If the service name is the same as the one in $DefaultModel, use the model name in $DefaultModel. - 2. If the registered service specifies a "DefaultModel" property, we'll use that. - 3. If the model list is already cached for the service, we'll use the first model in that list. - 4. Otherwise, give Automatic to indicate a model name that must be resolved later. -*) -chooseDefaultModelName // beginDefinition; -chooseDefaultModelName[ service_String ] /; service === $DefaultModel[ "Service" ] := $DefaultModel[ "Name" ]; -chooseDefaultModelName[ service_String ] := chooseDefaultModelName @ $availableServices @ service; -chooseDefaultModelName[ KeyValuePattern[ "DefaultModel" -> model_ ] ] := toModelName @ model; -chooseDefaultModelName[ KeyValuePattern[ "CachedModels" -> models_List ] ] := chooseDefaultModelName @ models; -chooseDefaultModelName[ { model_, ___ } ] := toModelName @ model; -chooseDefaultModelName[ service_ ] := Automatic; -chooseDefaultModelName // endDefinition; - (* ::**************************************************************************************************************:: *) (* ::Section::Closed:: *) (*ServiceConnection Utilities*) @@ -1643,33 +1642,29 @@ $resetButton := resetChatPreferences // beginDefinition; resetChatPreferences[ "Notebooks" ] := expandScope[ - FrontEndExecute @ FrontEnd`RemoveOptions[ - $preferencesScope, - { LLMEvaluator, { TaggingRules, "ChatNotebookSettings" } } - ]; + CurrentChatSettings[ $preferencesScope ] = Inherited; updateDynamics[ "Preferences" ]; ]; -resetChatPreferences[ "Personas" ] := - (* TODO: this won't work when $preferencesScope is something other than $FrontEnd *) - With[ { path = Sequence[ PrivateFrontEndOptions, "InterfaceSettings", "Chatbook" ] }, - (* TODO: choice dialog to uninstall personas *) - CurrentValue[ $preferencesScope, { path, "VisiblePersonas" } ] = $corePersonaNames; - CurrentValue[ $preferencesScope, { path, "PersonaFavorites" } ] = $corePersonaNames; - resetChatPreferences[ "Notebooks" ]; - ]; +resetChatPreferences[ "Personas" ] := ( + (* TODO: choice dialog to uninstall personas *) + CurrentChatSettings[ $preferencesScope, "VisiblePersonas" ] = $corePersonaNames; + CurrentChatSettings[ $preferencesScope, "PersonaFavorites" ] = $corePersonaNames; + updateDynamics[ "Personas" ]; +); -resetChatPreferences[ "Tools" ] := ( +resetChatPreferences[ "Tools" ] := expandScope[ + CurrentChatSettings[ $preferencesScope, "ToolSelectionType" ] = Inherited; + CurrentChatSettings[ $preferencesScope, "ToolSelections" ] = Inherited; (* TODO: choice dialog to uninstall tools *) - resetChatPreferences[ "Notebooks" ]; -); + updateDynamics[ "Tools" ]; +]; resetChatPreferences[ "Services" ] := ( (* TODO: choice dialog to clear service connections *) Needs[ "LLMServices`" -> None ]; LLMServices`ResetServices[ ]; InvalidateServiceCache[ ]; - resetChatPreferences[ "Notebooks" ]; ); resetChatPreferences // endDefinition; @@ -1686,11 +1681,15 @@ openPreferencesPage // beginDefinition; openPreferencesPage[ ] := openPreferencesPage[ "Notebooks" ]; -openPreferencesPage[ page: $$preferencesPage ] := - NotebookTools`OpenPreferencesDialog @ { "AI", page }; +openPreferencesPage[ page: $$preferencesPage ] := ( + CurrentChatSettings[ $FrontEnd, "CurrentPreferencesTab" ] = page; + NotebookTools`OpenPreferencesDialog @ { "AI", page } +); -openPreferencesPage[ page: $$preferencesPage, id_ ] := - NotebookTools`OpenPreferencesDialog[ { "AI", page }, { "AI", page, id } ]; +openPreferencesPage[ page: $$preferencesPage, id_ ] := ( + CurrentChatSettings[ $FrontEnd, "CurrentPreferencesTab" ] = page; + NotebookTools`OpenPreferencesDialog[ { "AI", page }, { "AI", page, id } ] +); openPreferencesPage // endDefinition; diff --git a/Source/Chatbook/SendChat.wl b/Source/Chatbook/SendChat.wl index 9bb62717..f8429a65 100644 --- a/Source/Chatbook/SendChat.wl +++ b/Source/Chatbook/SendChat.wl @@ -39,7 +39,7 @@ Needs[ "Wolfram`Chatbook`Utils`" ]; (*Configuration*) $resizeDingbats = True; splitDynamicTaskFunction = createFETask; -$defaultHandlerKeys = { "Body", "BodyChunk", "StatusCode", "Task", "TaskStatus", "EventName" }; +$defaultHandlerKeys = { "Body", "BodyChunk", "BodyChunkProcessed", "StatusCode", "TaskStatus", "EventName" }; $chatSubmitDroppedHandlers = { "ChatPost", "ChatPre", "Resolved" }; (* ::**************************************************************************************************************:: *) @@ -67,7 +67,7 @@ $buffer = ""; sendChat // beginDefinition; sendChat[ evalCell_, nbo_, settings0_ ] /; $useLLMServices := catchTopAs[ ChatbookAction ] @ Enclose[ - Module[ { cells0, cells, target, settings, messages, data, persona, cell, cellObject, container, task }, + Module[ { cells0, cells, target, settings, messages, data, persona, cellTags, cell, cellObject, container, task }, initFETaskWidget @ nbo; @@ -122,9 +122,11 @@ sendChat[ evalCell_, nbo_, settings0_ ] /; $useLLMServices := catchTopAs[ Chatbo |>; $reformattedCell = None; + cellTags = CurrentValue[ evalCell, CellTags ]; cell = activeAIAssistantCell[ container, - Association[ settings, "Container" :> container, "CellObject" :> cellObject, "Task" :> task ] + Association[ settings, "Container" :> container, "CellObject" :> cellObject, "Task" :> task ], + cellTags ]; Quiet[ @@ -178,7 +180,11 @@ sendChat[ evalCell_, nbo_, settings0_ ] /; $useLLMServices := catchTopAs[ Chatbo (* TODO: this definition is obsolete once LLMServices is widely available: *) sendChat[ evalCell_, nbo_, settings0_ ] := catchTopAs[ ChatbookAction ] @ Enclose[ - Module[ { cells0, cells, target, settings, id, key, messages, req, data, persona, cell, cellObject, container, task }, + Module[ + { + cells0, cells, target, settings, id, key, messages, req, data, persona, + cellTags, cell, cellObject, container, task + }, initFETaskWidget @ nbo; @@ -240,9 +246,11 @@ sendChat[ evalCell_, nbo_, settings0_ ] := catchTopAs[ ChatbookAction ] @ Enclos |>; $reformattedCell = None; + cellTags = CurrentValue[ evalCell, CellTags ]; cell = activeAIAssistantCell[ container, - Association[ settings, "Container" :> container, "CellObject" :> cellObject, "Task" :> task ] + Association[ settings, "Container" :> container, "CellObject" :> cellObject, "Task" :> task ], + cellTags ]; Quiet[ @@ -492,10 +500,11 @@ chatSubmit // Attributes = { HoldFirst }; chatSubmit[ args__ ] := Quiet[ chatSubmit0 @ args, { - (* cSpell: ignore wname, invm *) + (* cSpell: ignore wname, invm, invk *) ServiceConnections`SavedConnections::wname, ServiceConnections`ServiceConnections::wname, - URLSubmit::invm + URLSubmit::invm, + URLSubmit::invk } ]; @@ -1124,7 +1133,7 @@ selectChatCells0[ cell_, cells: { __CellObject }, final_ ] := Enclose[ If[ filtered === { }, throwTop @ Null ]; (* Delete output cells that come after the evaluation cell *) - rest = deleteExistingChatOutputs @ Drop[ cellData, cellPosition ]; + rest = keepValidGeneratedCells @ Drop[ cellData, cellPosition ]; (* Get the selected cell objects from the filtered cell info *) selectedCells = ConfirmMatch[ @@ -1147,8 +1156,8 @@ selectChatCells0 // endDefinition; (* ::**************************************************************************************************************:: *) (* ::Subsubsection::Closed:: *) -(*deleteExistingChatOutputs*) -deleteExistingChatOutputs // beginDefinition; +(*keepValidGeneratedCells*) +keepValidGeneratedCells // beginDefinition; (* When chat is triggered by an evaluation instead of a chat input, there can be generated cells between the evaluation cell and the previous chat output. For example: @@ -1160,10 +1169,10 @@ deleteExistingChatOutputs // beginDefinition; At this point, the front end has already cleared previous output cells (messages, output, etc.), but the previous chat output cell is still there. We need to delete it so that the new chat output cell can be inserted in its place. - `deleteExistingChatOutputs` gathers up all the generated cells that come after the evaluation cell and if it finds + `keepValidGeneratedCells` gathers up all the generated cells that come after the evaluation cell and if it finds a chat output cell, it deletes it. *) -deleteExistingChatOutputs[ cellData: { KeyValuePattern[ "CellObject" -> _CellObject ] ... } ] /; $autoAssistMode := +keepValidGeneratedCells[ cellData: { KeyValuePattern[ "CellObject" -> _CellObject ] ... } ] /; $autoAssistMode := Module[ { delete, chatOutputs, cells }, delete = TakeWhile[ cellData, MatchQ @ KeyValuePattern[ "CellAutoOverwrite" -> True ] ]; chatOutputs = Cases[ delete, KeyValuePattern[ "Style" -> $$chatOutputStyle ] ]; @@ -1172,10 +1181,11 @@ deleteExistingChatOutputs[ cellData: { KeyValuePattern[ "CellObject" -> _CellObj DeleteCases[ delete, KeyValuePattern[ "CellObject" -> Alternatives @@ cells ] ] ]; -deleteExistingChatOutputs[ cellData_ ] := - TakeWhile[ cellData, MatchQ @ KeyValuePattern[ "CellAutoOverwrite" -> True ] ]; +(* When chat is triggered by a normal chat input evaluation, we only want to keep the next chat output if it exists: *) +keepValidGeneratedCells[ cellData_ ] := + TakeWhile[ cellData, MatchQ @ KeyValuePattern @ { "CellAutoOverwrite" -> True, "Style" -> $$chatOutputStyle } ]; -deleteExistingChatOutputs // endDefinition; +keepValidGeneratedCells // endDefinition; (* ::**************************************************************************************************************:: *) (* ::Subsubsection::Closed:: *) @@ -1210,6 +1220,7 @@ resolveAutoSettings[ settings_Association ] := resolveAutoSettings0 @ <| "HandlerFunctions" -> getHandlerFunctions @ settings, "LLMEvaluator" -> getLLMEvaluator @ settings, "ProcessingFunctions" -> getProcessingFunctions @ settings, + "Model" -> resolveFullModelSpec @ settings, If[ StringQ @ settings[ "Tokenizer" ], <| "TokenizerName" -> getTokenizerName @ settings, @@ -1692,12 +1703,13 @@ prepareChatOutputPage // endDefinition; activeAIAssistantCell // beginDefinition; activeAIAssistantCell // Attributes = { HoldFirst }; -activeAIAssistantCell[ container_, settings_Association? AssociationQ ] := - activeAIAssistantCell[ container, settings, Lookup[ settings, "ShowMinimized", Automatic ] ]; +activeAIAssistantCell[ container_, settings_Association? AssociationQ, cellTags_ ] := + activeAIAssistantCell[ container, settings, cellTags, Lookup[ settings, "ShowMinimized", Automatic ] ]; activeAIAssistantCell[ container_, settings: KeyValuePattern[ "CellObject" :> cellObject_ ], + cellTags0_, minimized_ ] /; $cloudNotebooks := With[ @@ -1706,7 +1718,8 @@ activeAIAssistantCell[ id = $SessionID, reformat = dynamicAutoFormatQ @ settings, task = Lookup[ settings, "Task" ], - formatter = getFormattingFunction @ settings + formatter = getFormattingFunction @ settings, + cellTags = Replace[ cellTags0, Except[ _String | { ___String } ] :> Inherited ] }, Module[ { x = 0 }, ClearAttributes[ { x, cellObject }, Temporary ]; @@ -1748,8 +1761,9 @@ activeAIAssistantCell[ Selectable -> False, Editable -> False, CellDingbat -> Cell[ BoxData @ makeActiveOutputDingbat @ settings, Background -> None ], + CellTags -> cellTags, CellTrayWidgets -> <| "ChatFeedback" -> <| "Visible" -> False |> |>, - TaggingRules -> <| "ChatNotebookSettings" -> settings |> + TaggingRules -> <| "ChatNotebookSettings" -> smallSettings @ settings |> ] ] ]; @@ -1757,6 +1771,7 @@ activeAIAssistantCell[ activeAIAssistantCell[ container_, settings: KeyValuePattern[ "CellObject" :> cellObject_ ], + cellTags0_, minimized_ ] := With[ @@ -1766,7 +1781,8 @@ activeAIAssistantCell[ reformat = dynamicAutoFormatQ @ settings, task = Lookup[ settings, "Task" ], uuid = container[ "UUID" ], - formatter = getFormattingFunction @ settings + formatter = getFormattingFunction @ settings, + cellTags = Replace[ cellTags0, Except[ _String | { ___String } ] :> Inherited ] }, Cell[ BoxData @ TagBox[ @@ -1794,6 +1810,7 @@ activeAIAssistantCell[ ], CellDingbat -> Cell[ BoxData @ makeActiveOutputDingbat @ settings, Background -> None ], CellEditDuplicate -> False, + CellTags -> cellTags, CellTrayWidgets -> <| "ChatFeedback" -> <| "Visible" -> False |> |>, CodeAssistOptions -> { "AutoDetectHyperlinks" -> False }, Editable -> True, @@ -1802,7 +1819,7 @@ activeAIAssistantCell[ Selectable -> True, ShowAutoSpellCheck -> False, ShowCursorTracker -> False, - TaggingRules -> <| "ChatNotebookSettings" -> settings |>, + TaggingRules -> <| "ChatNotebookSettings" -> smallSettings @ settings |>, If[ scrollOutputQ @ settings, PrivateCellOptions -> { "TrackScrollingWhenPlaced" -> True }, Sequence @@ { } @@ -1896,8 +1913,10 @@ makeActiveOutputDingbat // endDefinition; (* ::Subsubsection::Closed:: *) (*makeOutputDingbat*) makeOutputDingbat // beginDefinition; +makeOutputDingbat[ as: KeyValuePattern[ "LLMEvaluator" -> name_String ] ] := makeOutputDingbat[ as, name ]; makeOutputDingbat[ as: KeyValuePattern[ "LLMEvaluator" -> config_Association ] ] := makeOutputDingbat[ as, config ]; makeOutputDingbat[ as_Association ] := makeOutputDingbat[ as, as ]; +makeOutputDingbat[ as_, name_String ] := makeOutputDingbat[ as, GetCachedPersonaData @ name ]; makeOutputDingbat[ as_, KeyValuePattern[ "PersonaIcon" -> icon_ ] ] := makeOutputDingbat[ as, icon ]; makeOutputDingbat[ as_, KeyValuePattern[ "Icon" -> icon_ ] ] := makeOutputDingbat[ as, icon ]; makeOutputDingbat[ as_, KeyValuePattern[ "Default" -> icon_ ] ] := makeOutputDingbat[ as, icon ]; @@ -1952,7 +1971,7 @@ writeReformattedCell[ settings_, None, cell_CellObject ] := writeReformattedCell[ settings_, string_String, cell_CellObject ] := Enclose[ Block[ { $dynamicText = False }, - Module[ { tag, scroll, open, label, pageData, uuid, new, output, createTask, info }, + Module[ { tag, scroll, open, label, pageData, cellTags, uuid, new, output, createTask, info }, tag = ConfirmMatch[ Replace[ CurrentValue[ cell, { TaggingRules, "MessageTag" } ], $Failed -> Inherited ], @@ -1964,8 +1983,9 @@ writeReformattedCell[ settings_, string_String, cell_CellObject ] := Enclose[ open = $lastOpen = cellOpenQ @ cell; label = RawBoxes @ TemplateBox[ { }, "MinimizedChat" ]; pageData = CurrentValue[ cell, { TaggingRules, "PageData" } ]; + cellTags = CurrentValue[ cell, CellTags ]; uuid = CreateUUID[ ]; - new = reformatCell[ settings, string, tag, open, label, pageData, uuid ]; + new = reformatCell[ settings, string, tag, open, label, pageData, cellTags, uuid ]; output = CellObject @ uuid; $lastChatString = string; @@ -2063,7 +2083,7 @@ scrollOutput // endDefinition; (*reformatCell*) reformatCell // beginDefinition; -reformatCell[ settings_, string_, tag_, open_, label_, pageData_, uuid_ ] := UsingFrontEnd @ Enclose[ +reformatCell[ settings_, string_, tag_, open_, label_, pageData_, cellTags_, uuid_ ] := UsingFrontEnd @ Enclose[ Module[ { formatter, content, rules, dingbat }, formatter = Confirm[ getFormattingFunction @ settings, "GetFormattingFunction" ]; @@ -2098,6 +2118,7 @@ reformatCell[ settings_, string_, tag_, open_, label_, pageData_, uuid_ ] := Usi ], GeneratedCell -> True, CellAutoOverwrite -> True, + CellTags -> cellTags, TaggingRules -> rules, If[ TrueQ[ rules[ "PageData", "PageCount" ] > 1 ], CellDingbat -> Cell[ BoxData @ TemplateBox[ { dingbat }, "AssistantIconTabbed" ], Background -> None ], @@ -2113,7 +2134,7 @@ reformatCell[ settings_, string_, tag_, open_, label_, pageData_, uuid_ ] := Usi ExpressionUUID -> uuid ] ], - throwInternalFailure[ reformatCell[ settings, string, tag, open, label, pageData, uuid ], ## ] & + throwInternalFailure ]; reformatCell // endDefinition; @@ -2232,6 +2253,9 @@ smallSettings // endDefinition; smallSettings0 // beginDefinition; +smallSettings0[ as: KeyValuePattern[ "Model" -> model: KeyValuePattern[ "Icon" -> _ ] ] ] := + smallSettings0 @ <| as, "Model" -> KeyTake[ model, { "Service", "Name" } ] |>; + smallSettings0[ as_Association ] := smallSettings0[ as, as[ "LLMEvaluator" ] ]; diff --git a/Source/Chatbook/Services.wl b/Source/Chatbook/Services.wl index b0d4a7e2..4f2890e4 100644 --- a/Source/Chatbook/Services.wl +++ b/Source/Chatbook/Services.wl @@ -19,10 +19,11 @@ HoldComplete[ Begin[ "`Private`" ]; -Needs[ "Wolfram`Chatbook`" ]; -Needs[ "Wolfram`Chatbook`Common`" ]; -Needs[ "Wolfram`Chatbook`Models`" ]; -Needs[ "Wolfram`Chatbook`UI`" ]; +Needs[ "Wolfram`Chatbook`" ]; +Needs[ "Wolfram`Chatbook`Common`" ]; +Needs[ "Wolfram`Chatbook`Dynamics`" ]; +Needs[ "Wolfram`Chatbook`Models`" ]; +Needs[ "Wolfram`Chatbook`UI`" ]; $ContextAliases[ "llm`" ] = "LLMServices`"; @@ -46,7 +47,7 @@ $llmServicesAvailable := $llmServicesAvailable = ( (* ::Section::Closed:: *) (*InvalidateServiceCache*) InvalidateServiceCache // beginDefinition; -InvalidateServiceCache[ ] := ($serviceCache = None; Null); +InvalidateServiceCache[ ] := catchAlways[ $serviceCache = None; updateDynamics[ { "Models", "Services" } ]; ]; InvalidateServiceCache // endDefinition; (* ::**************************************************************************************************************:: *) @@ -91,9 +92,13 @@ getServiceModelList[ KeyValuePattern[ "Service" -> service_String ] ] := getServiceModelList[ service_String ] := With[ { models = $availableServices[ service, "CachedModels" ] }, - If[ ListQ @ models, + Replace[ models, - getServiceModelList[ service, $availableServices[ service ] ] + { + { } | None :> Missing[ "NoModelList" ], + list_List :> list, + _ :> getServiceModelList[ service, $availableServices[ service ] ] + } ] ]; @@ -185,7 +190,7 @@ checkModelList // endDefinition; (* ::**************************************************************************************************************:: *) (* ::Subsection::Closed:: *) (*$availableServices*) -$availableServices := getAvailableServices[ ]; +$availableServices := Block[ { $availableServices = <| |> }, getAvailableServices[ ] ]; (* ::**************************************************************************************************************:: *) (* ::Subsection::Closed:: *) diff --git a/Source/Chatbook/Settings.wl b/Source/Chatbook/Settings.wl index 3e36c89f..1d64ee48 100644 --- a/Source/Chatbook/Settings.wl +++ b/Source/Chatbook/Settings.wl @@ -12,9 +12,11 @@ HoldComplete[ Begin[ "`Private`" ]; -Needs[ "Wolfram`Chatbook`" ]; -Needs[ "Wolfram`Chatbook`Common`" ]; -Needs[ "Wolfram`Chatbook`FrontEnd`" ]; +Needs[ "Wolfram`Chatbook`" ]; +Needs[ "Wolfram`Chatbook`Common`" ]; +Needs[ "Wolfram`Chatbook`FrontEnd`" ]; +Needs[ "Wolfram`Chatbook`Personas`" ]; +Needs[ "Wolfram`Chatbook`ResourceInstaller`" ]; (* ::**************************************************************************************************************:: *) (* ::Section::Closed:: *) @@ -46,8 +48,8 @@ $defaultChatSettings = <| "Model" :> $DefaultModel, "Multimodal" -> Automatic, "NotebookWriteMethod" -> Automatic, - "OpenAIKey" -> Automatic, (* TODO: remove this once LLMServices is widely available *) "OpenAIAPICompletionURL" -> "https://api.openai.com/v1/chat/completions", + "OpenAIKey" -> Automatic, (* TODO: remove this once LLMServices is widely available *) "PresencePenalty" -> 0.1, "ProcessingFunctions" :> $DefaultChatProcessingFunctions, "Prompts" -> { }, @@ -58,19 +60,25 @@ $defaultChatSettings = <| "ToolCallFrequency" -> Automatic, "ToolOptions" :> $DefaultToolOptions, "Tools" -> Automatic, + "ToolSelectionType" -> <| |>, "ToolsEnabled" -> Automatic, "TopP" -> 1, - "TrackScrollingWhenPlaced" -> Automatic + "TrackScrollingWhenPlaced" -> Automatic, + "VisiblePersonas" -> $corePersonaNames |>; +$cachedGlobalSettings := $cachedGlobalSettings = getGlobalSettingsFile[ ]; + (* ::**************************************************************************************************************:: *) (* ::Subsection::Closed:: *) (*Argument Patterns*) $$validRootSettingValue = Inherited | _? (AssociationQ@*Association); +$$frontEndObject = HoldPattern[ $FrontEnd | _FrontEndObject ]; (* ::**************************************************************************************************************:: *) (* ::Section::Closed:: *) (*Defaults*) +$ChatAbort = None; $ChatPost = None; $ChatPre = None; $DefaultModel = <| "Service" -> "OpenAI", "Name" -> "gpt-4" |>; @@ -79,8 +87,9 @@ $DefaultModel = <| "Service" -> "OpenAI", "Name" -> "gpt-4" |>; (* ::Subsection::Closed:: *) (*Handler Functions*) $DefaultChatHandlerFunctions = <| - "ChatPost" :> $ChatPost, - "ChatPre" :> $ChatPre + "ChatAbort" :> $ChatAbort, + "ChatPost" :> $ChatPost, + "ChatPre" :> $ChatPre |>; (* ::**************************************************************************************************************:: *) @@ -97,6 +106,9 @@ $DefaultChatProcessingFunctions = <| (* ::**************************************************************************************************************:: *) (* ::Section::Closed:: *) (*CurrentChatSettings*) + +(* TODO: need to support something like CurrentChatSettings[scope, {"key1", "key2", ...}] for nested values *) + GeneralUtilities`SetUsage[ CurrentChatSettings, "\ CurrentChatSettings[obj$, \"key$\"] gives the current chat settings for the CellObject or NotebookObject obj$ for the specified key. CurrentChatSettings[obj$] gives all current chat settings for obj$. @@ -151,78 +163,238 @@ CurrentChatSettings[ args___ ] := (* ::Subsection::Closed:: *) (*UpValues*) CurrentChatSettings /: HoldPattern @ Set[ CurrentChatSettings[ args___ ], value_ ] := - catchTop[ UsingFrontEnd @ setCurrentChatSettings[ args, value ], CurrentChatSettings ]; + catchTop[ setCurrentChatSettings[ args, value ], CurrentChatSettings ]; CurrentChatSettings /: HoldPattern @ Unset[ CurrentChatSettings[ args___ ] ] := - catchTop[ UsingFrontEnd @ unsetCurrentChatSettings @ args, CurrentChatSettings ]; + catchTop[ unsetCurrentChatSettings @ args, CurrentChatSettings ]; (* ::**************************************************************************************************************:: *) (* ::Subsubsection::Closed:: *) (*setCurrentChatSettings*) setCurrentChatSettings // beginDefinition; +setCurrentChatSettings[ args___ ] /; $CloudEvaluation := setCurrentChatSettings0 @ args; +setCurrentChatSettings[ args___ ] := UsingFrontEnd @ setCurrentChatSettings0 @ args; +setCurrentChatSettings // endDefinition; + + +setCurrentChatSettings0 // beginDefinition; (* Root settings: *) -setCurrentChatSettings[ value: $$validRootSettingValue ] := - setCurrentChatSettings0[ $FrontEnd, value ]; +setCurrentChatSettings0[ value: $$validRootSettingValue ] := + setCurrentChatSettings1[ $FrontEnd, value ]; -setCurrentChatSettings[ obj: $$feObj, value: $$validRootSettingValue ] := - setCurrentChatSettings0[ obj, value ]; +setCurrentChatSettings0[ obj: $$feObj, value: $$validRootSettingValue ] := + setCurrentChatSettings1[ obj, value ]; (* Key settings: *) -setCurrentChatSettings[ key_String? StringQ, value_ ] := - setCurrentChatSettings0[ $FrontEnd, key, value ]; +setCurrentChatSettings0[ key_String? StringQ, value_ ] := + setCurrentChatSettings1[ $FrontEnd, key, value ]; -setCurrentChatSettings[ obj: $$feObj, key_String? StringQ, value_ ] := - setCurrentChatSettings0[ obj, key, value ]; +setCurrentChatSettings0[ obj: $$feObj, key_String? StringQ, value_ ] := + setCurrentChatSettings1[ obj, key, value ]; (* Invalid scope: *) -setCurrentChatSettings[ obj: Except[ $$feObj ], a__ ] := throwFailure[ +setCurrentChatSettings0[ obj: Except[ $$feObj ], a__ ] := throwFailure[ "InvalidFrontEndScope", obj, CurrentChatSettings, - HoldForm @ setCurrentChatSettings[ obj, a ] + HoldForm @ setCurrentChatSettings0[ obj, a ] ]; (* Invalid key: *) -setCurrentChatSettings[ obj: $$feObj, key_, value_ ] := throwFailure[ +setCurrentChatSettings0[ obj: $$feObj, key_, value_ ] := throwFailure[ "InvalidSettingsKey", key, CurrentChatSettings, - HoldForm @ setCurrentChatSettings[ obj, key, value ] + HoldForm @ setCurrentChatSettings0[ obj, key, value ] ]; (* Invalid root settings: *) -setCurrentChatSettings[ value: Except[ $$validRootSettingValue ] ] := throwFailure[ +setCurrentChatSettings0[ value: Except[ $$validRootSettingValue ] ] := throwFailure[ "InvalidRootSettings", value, CurrentChatSettings, - HoldForm @ setCurrentChatSettings @ value + HoldForm @ setCurrentChatSettings0 @ value ]; -setCurrentChatSettings[ obj: $$feObj, value: Except[ $$validRootSettingValue ] ] := throwFailure[ +setCurrentChatSettings0[ obj: $$feObj, value: Except[ $$validRootSettingValue ] ] := throwFailure[ "InvalidRootSettings", value, CurrentChatSettings, - HoldForm @ setCurrentChatSettings @ value + HoldForm @ setCurrentChatSettings0 @ value ]; -setCurrentChatSettings // endDefinition; +setCurrentChatSettings0 // endDefinition; -setCurrentChatSettings0 // beginDefinition; +setCurrentChatSettings1 // beginDefinition; + +setCurrentChatSettings1[ scope: $$feObj, Inherited ] := + If[ TrueQ @ $CloudEvaluation, + setCurrentChatSettingsCloud[ scope, Inherited ], + CurrentValue[ scope, { TaggingRules, "ChatNotebookSettings" } ] = Inherited + ]; + +setCurrentChatSettings1[ scope: $$feObj, value_ ] := + With[ { as = Association @ value }, + If[ TrueQ @ $CloudEvaluation, + setCurrentChatSettingsCloud[ scope, as ], + CurrentValue[ scope, { TaggingRules, "ChatNotebookSettings" } ] = as + ] /; AssociationQ @ as + ]; + +setCurrentChatSettings1[ scope: $$feObj, key_String? StringQ, value_ ] := + If[ TrueQ @ $CloudEvaluation, + setCurrentChatSettingsCloud[ scope, key, value ], + CurrentValue[ scope, { TaggingRules, "ChatNotebookSettings", key } ] = value + ]; + +setCurrentChatSettings1 // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*setCurrentChatSettingsCloud*) +setCurrentChatSettingsCloud // beginDefinition; + +setCurrentChatSettingsCloud[ scope: $$frontEndObject, value_ ] := + With[ { as = Association @ value }, + ( + setGlobalChatSettings @ as; + CurrentValue[ scope, { TaggingRules, "ChatNotebookSettings" } ] = as + ) /; AssociationQ @ as + ]; + +setCurrentChatSettingsCloud[ scope: $$frontEndObject, Inherited ] := ( + setGlobalChatSettings @ Inherited; + CurrentValue[ scope, { TaggingRules, "ChatNotebookSettings" } ] = Inherited +); -setCurrentChatSettings0[ scope: $$feObj, Inherited ] := - CurrentValue[ scope, { TaggingRules, "ChatNotebookSettings" } ] = Inherited; +setCurrentChatSettingsCloud[ scope: $$frontEndObject, key_String? StringQ, value_ ] := ( + setGlobalChatSettings[ key, value ]; + Needs[ "GeneralUtilities`" -> None ]; + CurrentValue[ scope, { TaggingRules, "ChatNotebookSettings", key } ] = value; + CurrentValue[ scope, TaggingRules ] = GeneralUtilities`ToAssociations @ CurrentValue[ scope, TaggingRules ]; + value +); -setCurrentChatSettings0[ scope: $$feObj, value_ ] := +setCurrentChatSettingsCloud[ scope: $$feObj, value_ ] := With[ { as = Association @ value }, (CurrentValue[ scope, { TaggingRules, "ChatNotebookSettings" } ] = as) /; AssociationQ @ as ]; -setCurrentChatSettings0[ scope: $$feObj, key_String? StringQ, value_ ] := +setCurrentChatSettingsCloud[ scope: $$feObj, Inherited ] := ( + CurrentValue[ scope, { TaggingRules, "ChatNotebookSettings" } ] = Inherited +); + +setCurrentChatSettingsCloud[ scope: $$feObj, key_String? StringQ, value_ ] := ( + Needs[ "GeneralUtilities`" -> None ]; CurrentValue[ scope, { TaggingRules, "ChatNotebookSettings", key } ] = value; + CurrentValue[ scope, TaggingRules ] = GeneralUtilities`ToAssociations @ CurrentValue[ scope, TaggingRules ]; + value +); -setCurrentChatSettings0 // endDefinition; +setCurrentChatSettingsCloud // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*setGlobalChatSettings*) +setGlobalChatSettings // beginDefinition; + +setGlobalChatSettings[ Inherited ] := + storeGlobalSettings @ <| |>; + +setGlobalChatSettings[ settings_Association? AssociationQ ] := + storeGlobalSettings @ settings; + +setGlobalChatSettings[ key_String? StringQ, Inherited ] := Enclose[ + Module[ { settings }, + settings = ConfirmBy[ getGlobalSettingsFile[ ], AssociationQ, "ReadSettings" ]; + KeyDropFrom[ settings, key ]; + ConfirmBy[ storeGlobalSettings @ settings, StringQ, "StoreSettings" ]; + $cachedGlobalSettings = settings; + Inherited + ], + throwInternalFailure +]; + +setGlobalChatSettings[ key_String? StringQ, value_ ] := Enclose[ + Module[ { settings }, + settings = ConfirmBy[ getGlobalSettingsFile[ ], AssociationQ, "ReadSettings" ]; + settings[ key ] = value; + ConfirmBy[ storeGlobalSettings @ settings, StringQ, "StoreSettings" ]; + $cachedGlobalSettings = settings; + value + ], + throwInternalFailure +]; + +setGlobalChatSettings // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*getGlobalChatSettings*) +getGlobalChatSettings // beginDefinition; + +getGlobalChatSettings[ ] := + mergeChatSettings @ Flatten @ { $defaultChatSettings, getGlobalSettingsFile[ ] }; + +getGlobalChatSettings[ key_String? StringQ ] := + getGlobalChatSettings[ getGlobalChatSettings[ ], key ]; + +getGlobalChatSettings[ settings_Association? AssociationQ, key_String? StringQ ] := + Lookup[ settings, key, Lookup[ $defaultChatSettings, key, Inherited ] ]; + +getGlobalChatSettings // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*getGlobalSettingsFile*) +getGlobalSettingsFile // beginDefinition; + +getGlobalSettingsFile[ ] := Enclose[ + Module[ { file }, + file = ConfirmBy[ $globalSettingsFile, StringQ, "GlobalSettingsFile" ]; + $cachedGlobalSettings = + If[ FileExistsQ @ file, + ConfirmBy[ Developer`ReadWXFFile @ file, AssociationQ, "ReadSettings" ], + <| |> + ] + ], + Function[ + Quiet @ DeleteFile @ $globalSettingsFile; + throwInternalFailure[ getGlobalSettingsFile[ ], ## ] + ] +]; + +getGlobalSettingsFile // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*storeGlobalSettings*) +storeGlobalSettings // beginDefinition; + +storeGlobalSettings[ settings_Association? AssociationQ ] := Enclose[ + Module[ { file }, + file = ConfirmBy[ $globalSettingsFile, StringQ, "GlobalSettingsFile" ]; + ConfirmBy[ Developer`WriteWXFFile[ file, settings ], StringQ, "StoreSettings" ]; + $cachedGlobalSettings = settings; + file + ], + throwInternalFailure +]; + +storeGlobalSettings // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*$globalSettingsFile*) +$globalSettingsFile := Enclose[ + Module[ { dir }, + dir = ConfirmBy[ $ResourceInstallationDirectory, DirectoryQ, "ResourceInstallationDirectory" ]; + $globalSettingsFile = ConfirmBy[ FileNameJoin @ { dir, "GlobalChatSettings.wxf" }, StringQ, "File" ] + ], + throwInternalFailure +]; (* ::**************************************************************************************************************:: *) (* ::Subsubsection::Closed:: *) @@ -257,6 +429,7 @@ unsetCurrentChatSettings // endDefinition; unsetCurrentChatSettings0 // beginDefinition; +(* FIXME: make this work in cloud *) unsetCurrentChatSettings0[ obj: $$feObj ] := (CurrentValue[ obj, { TaggingRules, "ChatNotebookSettings" } ] = Inherited); @@ -270,6 +443,12 @@ unsetCurrentChatSettings0 // endDefinition; (*currentChatSettings*) currentChatSettings // beginDefinition; +currentChatSettings[ fe: $$frontEndObject ] /; $cloudNotebooks := + getGlobalChatSettings[ ]; + +currentChatSettings[ fe: $$frontEndObject, key_String ] /; $cloudNotebooks := + getGlobalChatSettings[ key ]; + currentChatSettings[ obj: _NotebookObject|_FrontEndObject|$FrontEndSession ] := ( verifyInheritance @ obj; currentChatSettings0 @ obj @@ -310,7 +489,11 @@ currentChatSettings[ cell0_CellObject ] := Catch @ Enclose[ AssociationQ ]; - ConfirmBy[ mergeChatSettings @ Flatten @ { $defaultChatSettings, settings }, AssociationQ, "CombinedSettings" ] + ConfirmBy[ + mergeChatSettings @ Flatten @ { $defaultChatSettings, $cachedGlobalSettings, settings }, + AssociationQ, + "CombinedSettings" + ] ], throwInternalFailure[ currentChatSettings @ cell0, ## ] & ]; @@ -345,7 +528,7 @@ currentChatSettings[ cell0_CellObject, key_String ] := Catch @ Enclose[ ! MemberQ[ cells, cell ], (*It's not in the list of cells*) MatchQ[ CurrentValue[ nbo, DefaultNewCellStyle ], $$chatInputStyle ] (*Due to DefaultNewCellStyle*) ], - Throw @ Lookup[ $defaultChatSettings, key, Inherited ] + Throw @ Lookup[ $cachedGlobalSettings, key, Lookup[ $defaultChatSettings, key, Inherited ] ] ]; delimiter = ConfirmMatch[ getPrecedingDelimiter[ cell, nbo, cells ], _CellObject|_Missing, "Delimiter" ]; @@ -358,7 +541,7 @@ currentChatSettings[ cell0_CellObject, key_String ] := Catch @ Enclose[ Except[ Inherited ], Replace[ absoluteCurrentValue[ cell, { TaggingRules, "ChatNotebookSettings", key } ], - Inherited :> Lookup[ $defaultChatSettings, key, Inherited ] + Inherited :> Lookup[ $cachedGlobalSettings, key, Lookup[ $defaultChatSettings, key, Inherited ] ] ] ] ], @@ -373,6 +556,7 @@ currentChatSettings0 // beginDefinition; currentChatSettings0[ obj: _CellObject|_NotebookObject|_FrontEndObject|$FrontEndSession ] := Association[ $defaultChatSettings, + $cachedGlobalSettings, Replace[ Association @ absoluteCurrentValue[ obj, { TaggingRules, "ChatNotebookSettings" } ], Except[ _? AssociationQ ] :> <| |> @@ -381,7 +565,7 @@ currentChatSettings0[ obj: _CellObject|_NotebookObject|_FrontEndObject|$FrontEnd currentChatSettings0[ obj: _CellObject|_NotebookObject|_FrontEndObject|$FrontEndSession, key_String ] := Replace[ absoluteCurrentValue[ obj, { TaggingRules, "ChatNotebookSettings", key } ], - Inherited :> Lookup[ $defaultChatSettings, key, Inherited ] + Inherited :> Lookup[ $cachedGlobalSettings, key, Lookup[ $defaultChatSettings, key, Inherited ] ] ]; currentChatSettings0 // endDefinition; diff --git a/Source/Chatbook/ToolManager.wl b/Source/Chatbook/ToolManager.wl index e8de9d2d..2295b64c 100644 --- a/Source/Chatbook/ToolManager.wl +++ b/Source/Chatbook/ToolManager.wl @@ -61,7 +61,7 @@ CreateLLMToolManagerPanel[ ] := catchMine @ ]; CreateLLMToolManagerPanel[ tools0_List, personas_List ] := - catchMine @ cvExpand @ Module[ + catchMine @ cvExpand @ Catch @ Module[ { globalTools, personaTools, personaToolNames, personaToolLookup, tools, preppedPersonas, preppedTools, personaNames, personaDisplayNames, @@ -90,6 +90,8 @@ CreateLLMToolManagerPanel[ tools0_List, personas_List ] := toolData @ Flatten @ Values @ personaTools ]; + If[ TrueQ @ $cloudNotebooks, Throw @ cloudToolManager @ tools ]; + gridOpts = Sequence[ Spacings -> { 0, 0 }, ItemSize -> { 0, 0 }, @@ -136,44 +138,13 @@ CreateLLMToolManagerPanel[ tools0_List, personas_List ] := DynamicWrapper[ Grid[ { - If[ TrueQ @ $inDialog, dialogHeader[ "Add & Manage LLM Tools" ], Nothing ], + titleSection[ ], (* ----- Install Tools ----- *) - dialogSubHeader[ "Install Tools" ], - dialogBody[ - Grid @ { - { - "Install from", - Button[ - grayDialogButtonLabel[ "LLM Tool Repository \[UpperRightArrow]" ], - ResourceInstallFromRepository[ "LLMTool" ], - Appearance -> "Suppressed", - BaselinePosition -> Baseline, - Method -> "Queued" - ], - Button[ - grayDialogButtonLabel[ "URL" ], - Block[ { PrintTemporary }, ResourceInstallFromURL[ "LLMTool" ] ], - Appearance -> "Suppressed", - BaselinePosition -> Baseline, - Method -> "Queued" - ] - } - } - ], + installToolsSection[ ], (* ----- Configure and Enable Tools ----- *) - dialogSubHeader[ "Manage and Enable Tools" ], - dialogBody[ - Grid @ { - { - "Show enabled tools for:", - scopeSelector @ Dynamic @ scopeMode, - Dynamic @ catchAlways @ toolModelWarning @ scopeMode[ ] - } - }, - { Automatic, { 5, Automatic } } - ], + manageAndEnableToolsSection @ Dynamic[ scopeMode ], dialogBody[ EventHandler[ Grid[ @@ -340,27 +311,7 @@ CreateLLMToolManagerPanel[ tools0_List, personas_List ] := ], { { Automatic, 0 }, Automatic } ], (* ----- Dialog Buttons ----- *) - If[ TrueQ @ $inDialog, - { - Item[ - Framed[ - Button[ - redDialogButtonLabel[ "OK" ], - NotebookClose @ EvaluationNotebook[ ], - Appearance -> "Suppressed", - BaselinePosition -> Baseline, - Method -> "Queued" - ], - FrameStyle -> None, - ImageSize -> { 100, 50 }, - Alignment -> { Center, Center } - ], - Alignment -> { Right, Automatic } - ], - SpanFromLeft - }, - Nothing - ] + dialogButtonSection[ ] }, Alignment -> { Left, Top }, BaseStyle -> $baseStyle, @@ -607,6 +558,7 @@ toolModelWarning[ scope_ ] := toolModelWarning[ scope, currentChatSettings[ scop toolModelWarning[ scope_, True ] := ""; toolModelWarning[ scope_, False ] := $toolsDisabledWarning; toolModelWarning[ scope_, enabled_ ] := toolModelWarning[ scope, enabled, currentChatSettings[ scope, "Model" ] ]; +toolModelWarning[ scope_, enabled_, KeyValuePattern[ "Name" -> Automatic ] ] := ""; toolModelWarning[ scope_, enabled_, model_? toolsEnabledQ ] := ""; toolModelWarning[ scope_, enabled_, model_ ] := toolModelWarning0[ scope, model ]; toolModelWarning // endDefinition; @@ -895,7 +847,10 @@ nonConfigurableTooltip // endDefinition; (*deleteTool*) deleteTool // beginDefinition; -deleteTool[ KeyValuePattern @ { "CanonicalName" -> cName_, "ResourceName" -> rName_String } ] := ( +deleteTool[ KeyValuePattern @ { "CanonicalName" -> cName_, "ResourceName" -> rName_String } ] := + deleteTool[ cName, rName ]; + +deleteTool[ cName_, rName_String ] := ( unsetCV[ $FrontEnd, "ToolSelections" , cName ]; unsetCV[ $FrontEnd, "ToolSelectionType", cName ]; ResourceUninstall[ "LLMTool", rName ] @@ -1234,6 +1189,275 @@ iconData // beginDefinition; iconData[ name_String, color_ ] := Insert[ chatbookIcon[ "ToolManager"<>name, False ], color, { 1, 1, 1 } ]; iconData // endDefinition; +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Dialog Elements*) + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*titleSection*) +titleSection // beginDefinition; +titleSection[ ] := If[ TrueQ @ $inDialog, dialogHeader[ "Add & Manage LLM Tools" ], Nothing ]; +titleSection // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*installToolsSection*) +installToolsSection // beginDefinition; + +installToolsSection[ ] := Sequence[ + dialogSubHeader[ "Install Tools" ], + dialogBody[ + Grid @ { + { + "Install from", + Button[ + grayDialogButtonLabel[ "LLM Tool Repository \[UpperRightArrow]" ], + If[ $CloudEvaluation, SetOptions[ EvaluationNotebook[ ], DockedCells -> Inherited ] ]; + ResourceInstallFromRepository[ "LLMTool" ], + Appearance -> "Suppressed", + BaselinePosition -> Baseline, + Method -> "Queued" + ], + Button[ + grayDialogButtonLabel[ "URL" ], + If[ $CloudEvaluation, SetOptions[ EvaluationNotebook[ ], DockedCells -> Inherited ] ]; + Block[ { PrintTemporary }, ResourceInstallFromURL[ "LLMTool" ] ], + Appearance -> "Suppressed", + BaselinePosition -> Baseline, + Method -> "Queued" + ] + } + } + ] +]; + +installToolsSection // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*manageAndEnableToolsSection*) +manageAndEnableToolsSection // beginDefinition; + +manageAndEnableToolsSection[ Dynamic[ scopeMode_ ] ] := Sequence[ + manageAndEnableToolsSection[ ], + dialogBody[ + Grid @ { + { + "Show enabled tools for:", + scopeSelector @ Dynamic @ scopeMode, + Dynamic @ catchAlways @ toolModelWarning @ scopeMode[ ] + } + }, + { Automatic, { 5, Automatic } } + ] +]; + +manageAndEnableToolsSection[ ] := dialogSubHeader[ "Manage and Enable Tools" ]; + +manageAndEnableToolsSection // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*dialogButtonSection*) +dialogButtonSection // beginDefinition; + +dialogButtonSection[ ] := + If[ TrueQ @ $inDialog, + { + Item[ + Framed[ + Button[ + redDialogButtonLabel[ "OK" ], + NotebookClose @ EvaluationNotebook[ ], + Appearance -> "Suppressed", + BaselinePosition -> Baseline, + Method -> "Queued" + ], + FrameStyle -> None, + ImageSize -> { 100, 50 }, + Alignment -> { Center, Center } + ], + Alignment -> { Right, Automatic } + ], + SpanFromLeft + }, + Nothing + ]; + +dialogButtonSection // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Cloud Definitions*) + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*cloudToolManager*) +cloudToolManager // beginDefinition; + +cloudToolManager[ tools: { __Association } ] := Grid[ + { + titleSection[ ], + + (* ----- Install Tools ----- *) + installToolsSection[ ], + + (* ----- Configure and Enable Tools ----- *) + manageAndEnableToolsSection[ ], + + (* ----- Tool Grid ----- *) + dialogBody[ cloudToolGrid @ tools, { { Automatic, 0 }, Automatic } ], + + (* ----- Dialog Buttons ----- *) + dialogButtonSection[ ] + }, + Alignment -> { Left, Top }, + BaseStyle -> $baseStyle, + Dividers -> { + False, + { + If[ TrueQ @ $inDialog, Sequence @@ { False, $dividerCol }, False ], + False, + $dividerCol, + False, + { False } + } + }, + ItemSize -> { Automatic, 0 }, + Spacings -> { 0, 0 } +]; + +cloudToolManager // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*cloudToolGrid*) +cloudToolGrid // beginDefinition; + +cloudToolGrid[ tools: { __Association } ] := Grid[ + Join[ + { { + "Tool", + "", + "", + "", + "Enabled" + } }, + cloudToolGridRow /@ tools + ], + Alignment -> { Left, Center }, + Background -> { White, { GrayLevel[ 0.898 ], White } }, + BaseStyle -> "Text", + Dividers -> { True, All }, + FrameStyle -> GrayLevel[ 0.898 ] +]; + +cloudToolGrid // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*cloudToolGridRow*) +cloudToolGridRow // beginDefinition; + +cloudToolGridRow[ tool_Association ] := { + Grid[ + { { + Pane[ + tool[ "Icon" ], + ImageSize -> { 22, 20 }, + ImageSizeAction -> "ShrinkToFit" + ], + tool[ "CanonicalName" ] + } }, + Alignment -> { { Center, Left }, Center }, + Spacings -> 0.5 + ], + Item[ "", ItemSize -> Fit ], + cloudDeleteToolButton @ tool, + cloudConfigureToolButton @ tool, + Item[ cloudToolEnablePopup @ tool, Alignment -> Right ] +}; + +cloudToolGridRow // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*cloudToolEnablePopup*) +cloudToolEnablePopup // beginDefinition; + +cloudToolEnablePopup[ KeyValuePattern[ "CanonicalName" -> name_String ] ] := + cloudToolEnablePopup @ name; + +cloudToolEnablePopup[ name_String ] := + PopupMenu[ + Dynamic[ + Replace[ CurrentChatSettings[ $FrontEnd, "ToolSelectionType" ][ name ], Except[ None|All ] -> Inherited ], + Function[ + CurrentChatSettings[ $FrontEnd, "ToolSelectionType" ] = + DeleteCases[ + Append[ + Replace[ + CurrentChatSettings[ $FrontEnd, "ToolSelectionType" ], + Except[ _? AssociationQ ] -> <| |> + ], + name -> #1 + ], + Inherited + ] + ] + ], + { + All -> "Always enabled", + Inherited -> "Automatic by persona", + None -> "Never enabled" + } + ]; + +cloudToolEnablePopup // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*cloudConfigureToolButton*) +cloudConfigureToolButton // beginDefinition; + +(* TODO: needs a definition for configurable tools *) + +cloudConfigureToolButton[ tool_Association ] := + Tooltip[ + Dynamic @ iconData[ "Cog", GrayLevel[ 0.8 ] ], + nonConfigurableTooltip @ tool + ]; + +cloudConfigureToolButton // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*cloudDeleteToolButton*) +cloudDeleteToolButton // beginDefinition; + +cloudDeleteToolButton[ tool_Association? deletableToolQ ] := + With[ { cName = tool[ "CanonicalName" ], rName = tool[ "ResourceName" ] }, + Button[ + $cloudDeleteToolButtonLabel, + deleteTool[ cName, rName ], + Appearance -> "Suppressed" + ] + ]; + +cloudDeleteToolButton[ tool_Association ] := + Tooltip[ + Dynamic @ iconData[ "Bin", GrayLevel[ 0.8 ] ], + nonDeletableTooltip @ tool + ]; + +cloudDeleteToolButton // endDefinition; + +$cloudDeleteToolButtonLabel = Mouseover[ + Dynamic @ iconData[ "Bin", GrayLevel[ 0.65 ] ], + Dynamic @ iconData[ "Bin", $activeBlue ] +]; + (* ::**************************************************************************************************************:: *) (* ::Section::Closed:: *) (*Package Footer*) diff --git a/Source/Chatbook/Tools/ChatPreferences.wl b/Source/Chatbook/Tools/ChatPreferences.wl new file mode 100644 index 00000000..a837b038 --- /dev/null +++ b/Source/Chatbook/Tools/ChatPreferences.wl @@ -0,0 +1,248 @@ +(* ::Section::Closed:: *) +(*Package Header*) +BeginPackage[ "Wolfram`Chatbook`Tools`" ]; + +(* :!CodeAnalysis::BeginBlock:: *) + +Begin[ "`Private`" ]; + +Needs[ "Wolfram`Chatbook`" ]; +Needs[ "Wolfram`Chatbook`ChatMessages`" ]; +Needs[ "Wolfram`Chatbook`Common`" ]; +Needs[ "Wolfram`Chatbook`Formatting`" ]; +Needs[ "Wolfram`Chatbook`Models`" ]; +Needs[ "Wolfram`Chatbook`Personas`" ]; +Needs[ "Wolfram`Chatbook`Prompting`" ]; +Needs[ "Wolfram`Chatbook`ResourceInstaller`" ]; +Needs[ "Wolfram`Chatbook`Sandbox`" ]; +Needs[ "Wolfram`Chatbook`Serialization`" ]; +Needs[ "Wolfram`Chatbook`Utils`" ]; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Config*) +$usableChatSettingsKeys = { + "Assistance", + "ChatHistoryLength", + "LLMEvaluator", + "MaxCellStringLength", + "MergeMessages", + "Model", + "Prompts", + "Temperature", + "ToolCallFrequency", + "Tools" +}; + +$$scope = _NotebookObject | $FrontEnd; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Chat Preferences*) + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*Tool Description*) +$chatPreferencesDescription = "\ +Get and set current chat preferences. + +Getting chat preferences +* When getting, the `key` parameter is optional and the `value` parameter is unused. +* If `key` is omitted, all available chat preferences will be returned. +* When getting chat preferences, you'll receive the current values along with additional information about possible values. + +Setting chat preferences +* When setting, both the `key` parameter and the `value` parameter are required. + +Key descriptions +| key | type | default | description | +| ------------------- | ---------- | --------------- | ----------- | +| Assistance | boolean | false | Whether to enable automatic AI assistance for normal input cell evaluations. | +| ChatHistoryLength | integer | 100 | The maximum number of cells to include in chat history. | +| LLMEvaluator | string | 'CodeAssistant' | The name of the LLM evaluator (aka persona) to use for chat. Use the 'get' action for available names. | +| MaxCellStringLength | integer | 0 | The maximum number of characters to include in a cell's string representation. A 0 means determine automatically. | +| MergeMessages | boolean | true | Whether to merge consecutive messages from the same user into a single message. | +| Model | string | 'gpt-4' | The name of the model to use for AI assistance. | +| Prompts | [ string ] | [ ] | A list of instructions to append to the system prompt. | +| Temperature | real | 0.7 | The temperature to use for the LLM. Values should be a real between 0 and 2. | +| ToolCallFrequency | real | 0.5 | The frequency with which to use tools. Values should be a number between 0 and 1. | +| Tools | [ string ] | | The list of currently enabled tools. Use the 'get' action for available names. | +"; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*Definitions*) + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*chatPreferences*) +chatPreferences // beginDefinition; +chatPreferences[ args_Association ] := chatPreferences[ args[ "action" ], args ]; +chatPreferences[ "get", args_Association ] := getChatPreferences @ args; +chatPreferences[ "set", args_Association ] := setChatPreferences @ args; +chatPreferences // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*getChatPreferences*) +getChatPreferences // beginDefinition; + +getChatPreferences[ as_Association ] := + getChatPreferences[ toScope @ as[ "scope" ], as[ "key" ] ]; + +getChatPreferences[ scope: $$scope, key_String ] := + CurrentChatSettings[ scope, key ]; + +(* TODO: format as JSON *) +getChatPreferences[ scope: $$scope, _Missing ] := + KeyTake[ CurrentChatSettings @ scope, $usableChatSettingsKeys ]; + +getChatPreferences[ args___ ] := + Failure[ + "NotImplemented", + <| "MessageTemplate" -> "Method is not implemented yet.", "MessageParameters" -> { args } |> + ]; + +getChatPreferences // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*setChatPreferences*) +setChatPreferences // beginDefinition; +setChatPreferences[ as_Association ] := setChatPreferences[ as[ "scope" ], as[ "key" ], as[ "value" ] ]; +setChatPreferences[ scope_, key_, value_ ] := setChatPreferences0[ toScope @ scope, key, interpretValue[ key, value ] ]; +setChatPreferences // endDefinition; + +setChatPreferences0 // beginDefinition; +setChatPreferences0[ failure_Failure, key_, value_ ] := failure; +setChatPreferences0[ scope_, key_, failure_Failure ] := failure; +setChatPreferences0[ scope_, key_, value_ ] := setChatPreferences1[ scope, key, value ]; +setChatPreferences0 // endDefinition; + +setChatPreferences1 // beginDefinition; +setChatPreferences1[ scope: $FrontEnd|_NotebookObject, key_String, value: Except[ _Failure ] ] := ( + CurrentChatSettings[ scope, key ] = value +); +setChatPreferences1 // endDefinition; + +(* TODO: return something indicating success instead of the value *) + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*interpretValue*) +interpretValue // beginDefinition; +interpretValue[ key_String, value_String ] := $interpreters[ key ][ value ]; +interpretValue // endDefinition; + +$interpreters = Interpreter /@ <| + "Assistance" -> "Boolean", + "ChatHistoryLength" -> "Integer", + "LLMEvaluator" -> interpretLLMEvaluator, + "MaxCellStringLength" -> "Integer", + "MergeMessages" -> "Boolean", + "Model" -> interpretModel, + "Prompts" -> RepeatingElement[ "String" ], + "Temperature" -> Restricted[ "Number", { 0, 2 } ], + "ToolCallFrequency" -> Restricted[ "Number", { 0, 1 } ], + "Tools" -> interpretTools +|>; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsubsection::Closed:: *) +(*interpretLLMEvaluator*) +interpretLLMEvaluator // beginDefinition; + +interpretLLMEvaluator[ name_String ] := + With[ { personas = Keys @ GetCachedPersonaData[ ] }, + If[ MemberQ[ personas, name ], + name, + Failure[ + "InvalidLLMEvaluator", + <| + "MessageTemplate" -> "`1` is not among the list of available personas: `2`", + "MessageParameters" -> personas + |> + ] + ] + ]; + +interpretLLMEvaluator // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsubsection::Closed:: *) +(*interpretModel*) +interpretModel // beginDefinition; + +interpretModel[ name_String ] := + With[ { models = Select[ getModelList[ ], chatModelQ ] }, + If[ MemberQ[ models, ToLowerCase @ name ], + ToLowerCase @ name, + Failure[ + "InvalidModel", + <| + "MessageTemplate" -> "`1` is not among the list of available models: `2`", + "MessageParameters" -> models + |> + ] + ] + ]; + +interpretModel // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsubsection::Closed:: *) +(*interpretTools*) +interpretTools // beginDefinition; + +interpretTools[ name_String ] := + With[ { tools = Keys @ $AvailableTools }, + If[ MemberQ[ tools, name ], + name, + Failure[ + "InvalidTool", + <| + "MessageTemplate" -> "`1` is not among the list of available tools: `2`", + "MessageParameters" -> tools + |> + ] + ] + ]; + +interpretTools // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*toScope*) +toScope // beginDefinition; +toScope[ "global" ] := $FrontEnd; +toScope[ "notebook" ] := EvaluationNotebook[ ]; +toScope[ _Missing ] := toScope[ "notebook" ]; +toScope[ failure_Failure ] := failure; +toScope // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*setPrefChatAssistance*) +setPrefChatAssistance // beginDefinition; +setPrefChatAssistance[ scope: $$scope, value: True|False ] := CurrentChatSettings[ scope, "Assistance" ] = value; +setPrefChatAssistance[ scope_, failure_Failure ] := failure; +setPrefChatAssistance // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*setPrefModel*) +setPrefModel // beginDefinition; +setPrefModel[ scope: $$scope, value_String ] := CurrentChatSettings[ scope, "Model" ] = value; +setPrefModel // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Package Footer*) +If[ Wolfram`ChatbookInternal`$BuildingMX, + Null; +]; + +(* :!CodeAnalysis::EndBlock:: *) + +End[ ]; +EndPackage[ ]; diff --git a/Source/Chatbook/Tools/Common.wl b/Source/Chatbook/Tools/Common.wl new file mode 100644 index 00000000..a20e4c62 --- /dev/null +++ b/Source/Chatbook/Tools/Common.wl @@ -0,0 +1,734 @@ +(* ::Section::Closed:: *) +(*Package Header*) +BeginPackage[ "Wolfram`Chatbook`Tools`" ]; + +(* :!CodeAnalysis::BeginBlock:: *) + +Begin[ "`Private`" ]; + +Needs[ "Wolfram`Chatbook`" ]; +Needs[ "Wolfram`Chatbook`ChatMessages`" ]; +Needs[ "Wolfram`Chatbook`Common`" ]; +Needs[ "Wolfram`Chatbook`Formatting`" ]; +Needs[ "Wolfram`Chatbook`Models`" ]; +Needs[ "Wolfram`Chatbook`Personas`" ]; +Needs[ "Wolfram`Chatbook`Prompting`" ]; +Needs[ "Wolfram`Chatbook`ResourceInstaller`" ]; +Needs[ "Wolfram`Chatbook`Sandbox`" ]; +Needs[ "Wolfram`Chatbook`Serialization`" ]; +Needs[ "Wolfram`Chatbook`Utils`" ]; + +HoldComplete[ + System`LLMTool; + System`LLMConfiguration; +]; + +(* TODO: + ImageSynthesize + LongTermMemory + Definitions + TestWriter +*) +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Tool Lists*) +$DefaultTools := $defaultChatTools; +$InstalledTools := $installedTools; +$AvailableTools := Association[ $DefaultTools, $InstalledTools ]; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Exported Functions for Tool Repository*) +$ToolFunctions = <| + "ChatPreferences" -> chatPreferences, + "DocumentationLookup" -> documentationLookup, + "DocumentationSearcher" -> documentationSearch, + "WebFetcher" -> webFetch, + "WebImageSearcher" -> webImageSearch, + "WebSearcher" -> webSearch, + "WolframAlpha" -> getWolframAlphaText, + "WolframLanguageEvaluator" -> wolframLanguageEvaluator +|>; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Tool Configuration*) +$defaultWebTextLength = 12000; +$toolResultStringLength := Ceiling[ $initialCellStringBudget/2 ]; +$webSessionVisible = False; + +$DefaultToolOptions = <| + "WolframLanguageEvaluator" -> <| + "AllowedExecutePaths" -> Automatic, + "AllowedReadPaths" -> All, + "AllowedWritePaths" -> Automatic, + "EvaluationTimeConstraint" -> 60, + "PingTimeConstraint" -> 30 + |>, + "WebFetcher" -> <| + "MaxContentLength" -> $defaultWebTextLength + |>, + "WebSearcher" -> <| + "AllowAdultContent" -> Inherited, + "Language" -> Inherited, + "MaxItems" -> 5, + "Method" -> "Google" + |>, + "WebImageSearcher" -> <| + "AllowAdultContent" -> Inherited, + "Language" -> Inherited, + "MaxItems" -> 5, + "Method" -> "Google" + |> +|>; + +$defaultToolIcon = RawBoxes @ TemplateBox[ { }, "WrenchIcon" ]; + +$attachments = <| |>; +$selectedTools = <| |>; +$toolBox = <| |>; +$toolEvaluationResults = <| |>; +$toolOptions = <| |>; + +$cloudUnsupportedTools = { "DocumentationSearcher" }; + +$defaultToolOrder = { + "DocumentationLookup", + "DocumentationSearcher", + "WolframAlpha", + "WolframLanguageEvaluator" +}; + +$toolNameAliases = <| + "DocumentationSearch" -> "DocumentationSearcher", + "WebFetch" -> "WebFetcher", + "WebImageSearch" -> "WebImageSearcher", + "WebSearch" -> "WebSearcher" +|>; + +$installedToolExtraKeys = { + "Description", + "DocumentationLink", + "Origin", + "ResourceName", + "Templated", + "Version" +}; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Default Tools*) +$defaultChatTools := If[ TrueQ @ $CloudEvaluation, + KeyDrop[ $defaultChatTools0, $cloudUnsupportedTools ], + $defaultChatTools0 + ]; + +$defaultChatTools0 = <| |>; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Installed Tools*) +$installedTools := Association @ Cases[ + GetInstalledResourceData[ "LLMTool" ], + as: KeyValuePattern[ "Tool" -> tool_ ] :> (toolName @ tool -> addExtraToolData[ tool, as ]) +]; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*addExtraToolData*) +addExtraToolData // beginDefinition; + +addExtraToolData[ tool: HoldPattern @ LLMTool[ as_Association, a___ ], extra_Association ] := + With[ { new = Join[ KeyTake[ extra, $installedToolExtraKeys ], as ] }, LLMTool[ new, a ] ]; + +addExtraToolData // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*getToolByName*) +getToolByName // beginDefinition; +getToolByName[ name_String ] := Lookup[ $toolBox, toCanonicalToolName @ name ]; +getToolByName // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*toolName*) +toolName // beginDefinition; +toolName[ tool_ ] := toolName[ tool, Automatic ]; +toolName[ HoldPattern @ LLMTool[ as_Association, ___ ], type_ ] := toolName[ as, type ]; +toolName[ KeyValuePattern[ "CanonicalName" -> name_String ], "Canonical" ] := name; +toolName[ KeyValuePattern[ "DisplayName" -> name_String ], "Display" ] := name; +toolName[ KeyValuePattern[ "Name" -> name_String ], type_ ] := toolName[ name, type ]; +toolName[ tool_, Automatic ] := toolName[ tool, "Canonical" ]; +toolName[ name_String, "Machine" ] := toMachineToolName @ name; +toolName[ name_String, "Canonical" ] := toCanonicalToolName @ name; +toolName[ name_String, "Display" ] := toDisplayToolName @ name; +toolName[ tools_List, type_ ] := toolName[ #, type ] & /@ tools; +toolName // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*toolData*) +toolData // beginDefinition; + +toolData[ HoldPattern @ LLMTool[ as_Association, ___ ] ] := + toolData @ as; + +toolData[ name_String ] /; KeyExistsQ[ $toolBox, name ] := + toolData @ $toolBox[ name ]; + +toolData[ name_String ] /; KeyExistsQ[ $defaultChatTools, name ] := + toolData @ $defaultChatTools[ name ]; + +toolData[ as: KeyValuePattern @ { "Function"|"ToolCall" -> _ } ] := <| + toolDefaultData @ toolName @ as, + "Icon" -> toolDefaultIcon @ as, + as +|>; + +toolData[ tools_List ] := + toolData /@ tools; + +toolData // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*toolDefaultIcon*) +toolDefaultIcon // beginDefinition; + +toolDefaultIcon[ KeyValuePattern[ "Origin" -> "LLMToolRepository" ] ] := + RawBoxes @ TemplateBox[ { }, "ToolManagerRepository" ]; + +toolDefaultIcon[ _Association ] := + $defaultToolIcon; + +toolDefaultIcon // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*toolDefaultData*) +toolDefaultData // beginDefinition; + +toolDefaultData[ name_String ] := <| + "CanonicalName" -> toCanonicalToolName @ name, + "DisplayName" -> toDisplayToolName @ name, + "Name" -> toMachineToolName @ name, + "Icon" -> $defaultToolIcon +|>; + +toolDefaultData // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*toMachineToolName*) +toMachineToolName // beginDefinition; + +toMachineToolName[ s_String ] := + ToLowerCase @ StringReplace[ + StringTrim @ s, + { " " -> "_", a_?LowerCaseQ ~~ b_?UpperCaseQ ~~ c_?LowerCaseQ :> a<>"_"<>b<>c } + ]; + +toMachineToolName // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*toCanonicalToolName*) +toCanonicalToolName // beginDefinition; + +toCanonicalToolName[ s_String ] := + Capitalize @ StringReplace[ StringTrim @ s, a_~~("_"|" ")~~b_ :> a <> ToUpperCase @ b ]; + +toCanonicalToolName // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*toDisplayToolName*) +toDisplayToolName // beginDefinition; + +toDisplayToolName[ s_String ] := + Capitalize[ + StringReplace[ + StringTrim @ s, + { "_" :> " ", a_?LowerCaseQ ~~ b_?UpperCaseQ ~~ c_?LowerCaseQ :> a<>" "<>b<>c } + ], + "TitleCase" + ]; + +toDisplayToolName // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*formatToolCallExample*) +formatToolCallExample // beginDefinition; + +formatToolCallExample[ name_String, params_Association ] := + TemplateApply[ + (* cSpell: ignore TOOLCALL, ENDARGUMENTS, ENDTOOLCALL *) + "TOOLCALL: `1`\n`2`\nENDARGUMENTS\nENDTOOLCALL", + { toMachineToolName @ name, Developer`WriteRawJSONString @ params } + ]; + +formatToolCallExample // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Toolbox*) + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*withToolBox*) +withToolBox // beginDefinition; +withToolBox // Attributes = { HoldFirst }; +withToolBox[ eval_ ] := Block[ { $selectedTools = <| |>, $toolOptions = $DefaultToolOptions }, eval ]; +withToolBox // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*selectTools*) +selectTools // beginDefinition; + +selectTools[ as_Association ] := Enclose[ + Module[ { llmEvaluatorName, toolNames, selections, selectionTypes, add, remove, selectedNames }, + + llmEvaluatorName = ConfirmBy[ getLLMEvaluatorName @ as, StringQ, "LLMEvaluatorName" ]; + toolNames = ConfirmMatch[ getToolNames @ as, { ___String }, "Names" ]; + selections = ConfirmBy[ getToolSelections @ as, AssociationQ, "Selections" ]; + selectionTypes = ConfirmBy[ getToolSelectionTypes @ as, AssociationQ, "SelectionTypes" ]; + + add = ConfirmMatch[ + Union[ + Keys @ Select[ selections, Lookup @ llmEvaluatorName ], + Keys @ Select[ selectionTypes, SameAs @ All ] + ], + { ___String }, + "ToolAdditions" + ]; + + remove = ConfirmMatch[ + Union[ + Keys @ Select[ selections, Not @* Lookup[ llmEvaluatorName ] ], + Keys @ Select[ selectionTypes, SameAs @ None ] + ], + { ___String }, + "ToolRemovals" + ]; + + selectedNames = ConfirmMatch[ + Complement[ Union[ toolNames, add ], remove ], + { ___String }, + "SelectedNames" + ]; + + selectTools0 /@ selectedNames + ], + throwInternalFailure[ selectTools @ as, ## ] & +]; + +selectTools // endDefinition; + + +(* TODO: Most of this functionality is moved to `getToolNames`. This only needs to operate on strings. *) +selectTools0 // beginDefinition; + +selectTools0[ Automatic|Inherited ] := selectTools0 @ $defaultChatTools; +selectTools0[ None ] := $selectedTools = <| |>; +selectTools0[ name_String ] /; KeyExistsQ[ $toolBox, name ] := $selectedTools[ name ] = $toolBox[ name ]; +selectTools0[ name_String ] /; KeyExistsQ[ $toolNameAliases, name ] := selectTools0 @ $toolNameAliases @ name; +selectTools0[ name_String ] := selectTools0[ name, Lookup[ $AvailableTools, name ] ]; +selectTools0[ tools_List ] := selectTools0 /@ tools; +selectTools0[ tools_Association ] := KeyValueMap[ selectTools0, tools ]; + +(* Literal LLMTool specification: *) +selectTools0[ tool: HoldPattern @ LLMTool[ KeyValuePattern[ "Name" -> name_ ], ___ ] ] := selectTools0[ name, tool ]; + +(* Rules can be used to enable/disable by name: *) +selectTools0[ (Rule|RuleDelayed)[ name_String, tool_ ] ] := selectTools0[ name, tool ]; + +(* Inherit from core tools: *) +selectTools0[ name_String, Automatic|Inherited ] := selectTools0[ name, Lookup[ $defaultChatTools, name ] ]; + +(* Disable tool: *) +selectTools0[ name_String, None ] := KeyDropFrom[ $selectedTools, name ]; + +(* Select a literal LLMTool: *) +selectTools0[ name_String, tool: HoldPattern[ _LLMTool ] ] := $selectedTools[ name ] = $toolBox[ name ] = tool; + +(* Tool not found: *) +selectTools0[ name_String, Missing[ "KeyAbsent", name_ ] ] := + If[ TrueQ @ KeyExistsQ[ $defaultChatTools0, name ], + (* A default tool that was filtered for compatibility *) + Null, + (* An unknown tool name *) + messagePrint[ "ToolNotFound", name ] + ]; + +selectTools0 // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*getLLMEvaluatorName*) +getLLMEvaluatorName // beginDefinition; +getLLMEvaluatorName[ KeyValuePattern[ "LLMEvaluatorName" -> name_String ] ] := name; +getLLMEvaluatorName[ KeyValuePattern[ "LLMEvaluator" -> name_String ] ] := name; + +getLLMEvaluatorName[ KeyValuePattern[ "LLMEvaluator" -> evaluator_Association ] ] := + Lookup[ evaluator, "LLMEvaluatorName", Lookup[ evaluator, "Name" ] ]; + +getLLMEvaluatorName // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*getToolNames*) +getToolNames // beginDefinition; + +(* Persona declares tools, so combine with defaults as appropriate *) +getToolNames[ as: KeyValuePattern[ "LLMEvaluator" -> KeyValuePattern[ "Tools" -> tools_ ] ] ] := + getToolNames[ Lookup[ as, "Tools", None ], tools ]; + +(* No tool specification by persona, so get defaults *) +getToolNames[ as_Association ] := + getToolNames @ Lookup[ as, "Tools", Automatic ]; + +(* Persona does not want any tools *) +getToolNames[ tools_, None ] := { }; + +(* Persona wants default tools *) +getToolNames[ tools_, Automatic|Inherited ] := getToolNames @ tools; + +(* Persona declares an explicit list of tools *) +getToolNames[ Automatic|None|Inherited, personaTools_List ] := getToolNames @ personaTools; + +(* The user has specified an explicit list of tools as well, so include them *) +getToolNames[ tools_List, personaTools_List ] := Union[ getToolNames @ tools, getToolNames @ personaTools ]; + +(* Get name of each tool *) +getToolNames[ tools_List ] := DeleteDuplicates @ Flatten[ getCachedToolName /@ tools ]; + +(* Default tools *) +getToolNames[ Automatic|Inherited ] := Keys @ $DefaultTools; + +(* All tools *) +getToolNames[ All ] := Keys @ $AvailableTools; + +(* No tools *) +getToolNames[ None ] := { }; + +(* A single tool specification without an enclosing list *) +getToolNames[ tool: Except[ _List ] ] := getToolNames @ { tool }; + +getToolNames // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*getCachedToolName*) +getCachedToolName // beginDefinition; + +getCachedToolName[ tool: HoldPattern[ _LLMTool ] ] := Enclose[ + Module[ { name }, + name = ConfirmBy[ toolName @ tool, StringQ, "Name" ]; + ConfirmAssert[ AssociationQ @ $toolBox, "ToolBox" ]; + $toolBox[ name ] = tool; + name + ], + throwInternalFailure[ getCachedToolName @ tool, ## ] & +]; + +getCachedToolName[ name_String ] := + With[ { canonical = toCanonicalToolName @ name }, + Which[ + KeyExistsQ[ $toolBox , canonical ], canonical, + KeyExistsQ[ $toolNameAliases , canonical ], getCachedToolName @ $toolNameAliases @ canonical, + KeyExistsQ[ $defaultChatTools, canonical ], getCachedToolName @ $defaultChatTools @ canonical, + True , name + ] + ]; + +getCachedToolName // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*getToolSelections*) +getToolSelections // beginDefinition; +getToolSelections[ as_Association ] := getToolSelections[ as, Lookup[ as, "ToolSelections", <| |> ] ]; +getToolSelections[ as_, selections_Association ] := KeyTake[ selections, Keys @ $AvailableTools ]; +getToolSelections[ as_, Except[ _Association ] ] := <| |>; +getToolSelections // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*getToolSelectionTypes*) +getToolSelectionTypes // beginDefinition; +getToolSelectionTypes[ as_Association ] := getToolSelectionTypes[ as, Lookup[ as, "ToolSelectionType", <| |> ] ]; +getToolSelectionTypes[ as_, selections_Association ] := KeyTake[ selections, Keys @ $AvailableTools ]; +getToolSelectionTypes[ as_, Except[ _Association ] ] := <| |>; +getToolSelectionTypes // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*initTools*) +initTools // beginDefinition; + +initTools[ ] := initTools[ ] = ( + + If[ $CloudEvaluation && $VersionNumber <= 13.2, + + If[ PacletFind[ "ServiceConnection_OpenAI" ] === { }, + PacletInstall[ "ServiceConnection_OpenAI", PacletSite -> "https://pacletserver.wolfram.com" ] + ]; + + WithCleanup[ + Unprotect @ TemplateObject, + TemplateObject // Options = DeleteDuplicatesBy[ + Append[ Options @ TemplateObject, MetaInformation -> <| |> ], + ToString @* First + ], + Protect @ TemplateObject + ] + ]; + + + installLLMFunctions[ ]; +); + +initTools // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*installLLMFunctions*) +installLLMFunctions // beginDefinition; + +installLLMFunctions[ ] := Enclose[ + Module[ { before, paclet, opts, reload }, + before = Quiet @ PacletObject[ "Wolfram/LLMFunctions" ]; + paclet = ConfirmBy[ PacletInstall[ "Wolfram/LLMFunctions" ], PacletObjectQ, "PacletInstall" ]; + + If[ ! TrueQ @ Quiet @ PacletNewerQ[ paclet, "1.2.1" ], + opts = If[ $CloudEvaluation, PacletSite -> "https://pacletserver.wolfram.com", UpdatePacletSites -> True ]; + paclet = ConfirmBy[ PacletInstall[ "Wolfram/LLMFunctions", opts ], PacletObjectQ, "PacletUpdate" ]; + ConfirmAssert[ PacletNewerQ[ paclet, "1.2.1" ], "PacletVersion" ]; + reload = True, + reload = PacletObjectQ @ before && PacletNewerQ[ paclet, before ] + ]; + + If[ TrueQ @ reload, reloadLLMFunctions[ ] ]; + Needs[ "Wolfram`LLMFunctions`" -> None ]; + installLLMFunctions[ ] = paclet + ], + throwInternalFailure[ installLLMFunctions[ ], ## ] & +]; + +installLLMFunctions // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*reloadLLMFunctions*) +reloadLLMFunctions // beginDefinition; + +reloadLLMFunctions[ ] := Enclose[ + Module[ { paclet, files }, + paclet = ConfirmBy[ PacletObject[ "Wolfram/LLMFunctions" ], PacletObjectQ, "PacletObject" ]; + files = Select[ $LoadedFiles, StringContainsQ[ "LLMFunctions" ] ]; + If[ ! AnyTrue[ files, StringStartsQ @ paclet[ "Location" ] ], + (* Force paclet to reload if the new one has not been loaded *) + WithCleanup[ + Unprotect @ $Packages, + $Packages = Select[ $Packages, Not @* StringStartsQ[ "Wolfram`LLMFunctions`" ] ]; + ClearAll[ "Wolfram`LLMFunctions`*" ]; + ClearAll[ "Wolfram`LLMFunctions`*`*" ]; + Block[ { $ContextPath }, Get[ "Wolfram`LLMFunctions`" ] ], + Protect @ $Packages + ] + ] + ], + throwInternalFailure[ reloadLLMFunctions[ ], ## ] & +]; + +reloadLLMFunctions // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*resolveTools*) +resolveTools // beginDefinition; + +resolveTools[ settings: KeyValuePattern[ "ToolsEnabled" -> True ] ] := ( + initTools[ ]; + selectTools @ settings; + $toolOptions = Lookup[ settings, "ToolOptions", $DefaultToolOptions ]; + $lastSelectedTools = $selectedTools; + $lastToolOptions = $toolOptions; + If[ KeyExistsQ[ $selectedTools, "WolframLanguageEvaluator" ], needsBasePrompt[ "WolframLanguageEvaluatorTool" ] ]; + Append[ settings, "Tools" -> Values @ $selectedTools ] +); + +resolveTools[ settings_Association ] := settings; + +resolveTools // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*makeToolConfiguration*) +makeToolConfiguration // beginDefinition; + +makeToolConfiguration[ settings_Association ] := Enclose[ + Module[ { tools }, + tools = ConfirmMatch[ DeleteDuplicates @ Flatten @ Values @ $selectedTools, { ___LLMTool }, "SelectedTools" ]; + $toolConfiguration = LLMConfiguration @ <| "Tools" -> tools, "ToolPrompt" -> makeToolPrompt @ settings |> + ], + throwInternalFailure[ makeToolConfiguration @ settings, ## ] & +]; + +makeToolConfiguration // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*$toolConfiguration*) +$toolConfiguration := $toolConfiguration = LLMConfiguration @ <| "Tools" -> Values @ $defaultChatTools |>; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*toolRequestParser*) +toolRequestParser := toolRequestParser = + Quiet[ Check[ $toolConfiguration[ "ToolRequestParser" ], + Wolfram`LLMFunctions`LLMConfiguration`$DefaultTextualToolMethod[ "ToolRequestParser" ], + LLMConfiguration::invprop + ], + LLMConfiguration::invprop + ]; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*makeToolPrompt*) +makeToolPrompt // beginDefinition; + +makeToolPrompt[ settings_Association ] := $lastToolPrompt = TemplateObject[ + Riffle[ + DeleteMissing @ { + $toolPre, + TemplateSequence[ + TemplateExpression @ TemplateObject[ + { + "Tool Name: ", + TemplateSlot[ "Name" ], + "\nDisplay Name: ", + TemplateSlot[ + "DisplayName", + DefaultValue :> TemplateExpression @ toDisplayToolName @ TemplateSlot[ "Name" ] + ], + "\nDescription: ", + TemplateSlot[ "Description" ], + "\nSchema:\n", + TemplateSlot[ "Schema" ], + "\n\n" + }, + CombinerFunction -> StringJoin, + InsertionFunction -> TextString + ], + TemplateExpression @ Map[ + Append[ #[ "Data" ], "Schema" -> ExportString[ #[ "JSONSchema" ], "JSON" ] ] &, + TemplateSlot[ "Tools" ] + ] + ], + $toolPost, + $fullExamples, + makeToolPreferencePrompt @ settings + }, + "\n\n" + ], + CombinerFunction -> StringJoin, + InsertionFunction -> TextString +]; + +makeToolPrompt // endDefinition; + + +$toolPre = "\ +# Tool Instructions + +You have access to tools which can be used to do things, fetch data, compute, etc. while you create your response. \ +Each tool takes input as JSON following a JSON schema. Here are the available tools and their associated schemas:"; + + +$toolPost := "\ +To call a tool, write the following at any time during your response: + +TOOLCALL: +{ + \"\": + \"\": +} +ENDARGUMENTS +ENDTOOLCALL + +Always use valid JSON to specify the parameters in the tool call. Always follow the tool's JSON schema to specify the \ +parameters in the tool call. Fill in the values in <> brackets with the values for the particular tool. Provide as \ +many parameters as the tool requires. Always make one tool call at a time. Always write two line breaks before each \ +tool call. + +The system will execute the requested tool call and you will receive a system message containing the result. \ +You can then use this result to finish writing your response for the user. + +You must write the TOOLCALL in your CURRENT response. \ +Do not state that you will use a tool and end your message before making the tool call. + +If a user asks you to use a specific tool, you MUST attempt to use that tool as requested, \ +even if you think it will not work. \ +If the tool fails, use any error message to correct the issue or explain why it failed. \ +NEVER state that a tool cannot be used for a particular task without trying it first. \ +You did not create these tools, so you do not know what they can and cannot do. + +You should try to avoid mentioning tools by name in your response and instead speak generally about their function. \ +For example, if there were a number_adder tool, you would instead talk about \"adding numbers\". If you must mention \ +a tool by name, you should use the DisplayName property instead of the tool name."; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsubsection::Closed:: *) +(*makeToolPreferencePrompt*) +makeToolPreferencePrompt // beginDefinition; + +makeToolPreferencePrompt[ settings_ ] := + makeToolPreferencePrompt[ settings, settings[ "ToolCallFrequency" ] ]; + +makeToolPreferencePrompt[ settings_, Automatic ] := + Missing[ "NotAvailable" ]; + +makeToolPreferencePrompt[ settings_, freq_? NumberQ ] := + With[ { key = Round @ Clip[ 5 * freq, { 0, 5 } ] }, + TemplateApply[ + $toolPreferencePrompt, + <| "Number" -> Round[ freq * 100 ], "Explanation" -> Lookup[ $toolFrequencyExplanations, key, "" ] |> + ] + ]; + +makeToolPreferencePrompt // endDefinition; + + +$toolPreferencePrompt = "\ +## User Tool Call Preferences + +The user has specified their desired tool calling frequency to be `Number`% with the following instructions: + +IMPORTANT +`Explanation`"; + + +$toolFrequencyExplanations = <| + 0 -> "Only use a tool if explicitly instructed to use tools. Never use tools unless specifically asked to.", + 1 -> "Avoid using tools unless you think it is necessary.", + 2 -> "Only use tools if you think it will significantly improve the quality of your response.", + 3 -> "Use tools whenever it is appropriate to do so.", + 4 -> "Use tools whenever there's even a slight chance that it could improve the quality of your response (e.g. fact checking).", + 5 -> "ALWAYS make a tool call in EVERY response, no matter what." +|>; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Package Footer*) +If[ Wolfram`ChatbookInternal`$BuildingMX, + Null; +]; + +(* :!CodeAnalysis::EndBlock:: *) + +End[ ]; +EndPackage[ ]; diff --git a/Source/Chatbook/Tools.wl b/Source/Chatbook/Tools/DefaultTools.wl similarity index 60% rename from Source/Chatbook/Tools.wl rename to Source/Chatbook/Tools/DefaultTools.wl index 699a2df2..69e96897 100644 --- a/Source/Chatbook/Tools.wl +++ b/Source/Chatbook/Tools/DefaultTools.wl @@ -7,810 +7,55 @@ BeginPackage[ "Wolfram`Chatbook`Tools`" ]; (* :!CodeAnalysis::BeginBlock:: *) (* :!CodeAnalysis::Disable::SuspiciousSessionSymbol:: *) -HoldComplete[ -`$attachments; -`$defaultChatTools; -`$toolConfiguration; -`$toolEvaluationResults; -`$toolOptions; -`$toolResultStringLength; -`getToolByName; -`getToolDisplayName; -`getToolFormattingFunction; -`getToolIcon; -`initTools; -`makeExpressionURI; -`makeToolConfiguration; -`makeToolResponseString; -`resolveTools; -`toolData; -`toolName; -`toolOptionValue; -`toolRequestParser; -`withToolBox; -]; - Begin[ "`Private`" ]; Needs[ "Wolfram`Chatbook`" ]; Needs[ "Wolfram`Chatbook`ChatMessages`" ]; Needs[ "Wolfram`Chatbook`Common`" ]; Needs[ "Wolfram`Chatbook`Formatting`" ]; +Needs[ "Wolfram`Chatbook`Models`" ]; +Needs[ "Wolfram`Chatbook`Personas`" ]; Needs[ "Wolfram`Chatbook`Prompting`" ]; Needs[ "Wolfram`Chatbook`ResourceInstaller`" ]; Needs[ "Wolfram`Chatbook`Sandbox`" ]; Needs[ "Wolfram`Chatbook`Serialization`" ]; Needs[ "Wolfram`Chatbook`Utils`" ]; -HoldComplete[ - System`LLMTool; - System`LLMConfiguration; -]; - -(* TODO: - ImageSynthesize - LongTermMemory - Definitions - TestWriter -*) -(* ::**************************************************************************************************************:: *) -(* ::Section::Closed:: *) -(*Tool Lists*) -$DefaultTools := $defaultChatTools; -$InstalledTools := $installedTools; -$AvailableTools := Association[ $DefaultTools, $InstalledTools ]; - -(* ::**************************************************************************************************************:: *) -(* ::Section::Closed:: *) -(*Exported Functions for Tool Repository*) -$ToolFunctions = <| - "DocumentationLookup" -> documentationLookup, - "DocumentationSearcher" -> documentationSearch, - "WebFetcher" -> webFetch, - "WebImageSearcher" -> webImageSearch, - "WebSearcher" -> webSearch, - "WolframAlpha" -> getWolframAlphaText, - "WolframLanguageEvaluator" -> wolframLanguageEvaluator -|>; - -(* ::**************************************************************************************************************:: *) -(* ::Section::Closed:: *) -(*Tool Configuration*) -$defaultWebTextLength = 12000; -$toolResultStringLength := Ceiling[ $initialCellStringBudget/2 ]; -$webSessionVisible = False; - -$DefaultToolOptions = <| - "WolframLanguageEvaluator" -> <| - "AllowedExecutePaths" -> Automatic, - "AllowedReadPaths" -> All, - "AllowedWritePaths" -> Automatic, - "EvaluationTimeConstraint" -> 60, - "PingTimeConstraint" -> 30 - |>, - "WebFetcher" -> <| - "MaxContentLength" -> $defaultWebTextLength - |>, - "WebSearcher" -> <| - "AllowAdultContent" -> Inherited, - "Language" -> Inherited, - "MaxItems" -> 5, - "Method" -> "Google" - |>, - "WebImageSearcher" -> <| - "AllowAdultContent" -> Inherited, - "Language" -> Inherited, - "MaxItems" -> 5, - "Method" -> "Google" - |> -|>; - -$defaultToolIcon = RawBoxes @ TemplateBox[ { }, "WrenchIcon" ]; - -$attachments = <| |>; -$selectedTools = <| |>; -$toolBox = <| |>; -$toolEvaluationResults = <| |>; -$toolOptions = <| |>; - -$cloudUnsupportedTools = { "DocumentationSearcher" }; - -$defaultToolOrder = { - "DocumentationLookup", - "DocumentationSearcher", - "WolframAlpha", - "WolframLanguageEvaluator" -}; - -$toolNameAliases = <| - "DocumentationSearch" -> "DocumentationSearcher", - "WebFetch" -> "WebFetcher", - "WebImageSearch" -> "WebImageSearcher", - "WebSearch" -> "WebSearcher" -|>; - -$installedToolExtraKeys = { - "Description", - "DocumentationLink", - "Origin", - "ResourceName", - "Templated", - "Version" -}; - -(* ::**************************************************************************************************************:: *) -(* ::Section::Closed:: *) -(*Tool Options*) - -(* ::**************************************************************************************************************:: *) -(* ::Subsection::Closed:: *) -(*SetToolOptions*) -SetToolOptions // ClearAll; - -SetToolOptions[ name_String, opts: OptionsPattern[ ] ] := - SetToolOptions[ $FrontEnd, name, opts ]; - -SetToolOptions[ scope_, name_String, opts: OptionsPattern[ ] ] := UsingFrontEnd[ - KeyValueMap[ - (CurrentValue[ scope, { TaggingRules, "ChatNotebookSettings", "ToolOptions", name, ToString[ #1 ] } ] = #2) &, - Association @ Reverse @ { opts } - ]; - CurrentValue[ scope, { TaggingRules, "ChatNotebookSettings", "ToolOptions" } ] -]; - -SetToolOptions[ name_String, Inherited ] := - SetToolOptions[ $FrontEnd, name, Inherited ]; - -SetToolOptions[ scope_, name_String, Inherited ] := UsingFrontEnd[ - CurrentValue[ scope, { TaggingRules, "ChatNotebookSettings", "ToolOptions", name } ] = Inherited; - CurrentValue[ scope, { TaggingRules, "ChatNotebookSettings", "ToolOptions" } ] -]; - -(* ::**************************************************************************************************************:: *) -(* ::Subsection::Closed:: *) -(*toolOptionValue*) -toolOptionValue // beginDefinition; -toolOptionValue[ name_String, key_String ] := toolOptionValue[ name, $toolOptions[ name ], key ]; -toolOptionValue[ name_String, _Missing, key_String ] := toolOptionValue0[ $DefaultToolOptions[ name ], key ]; -toolOptionValue[ name_String, opts_Association, key_String ] := toolOptionValue0[ opts, key ]; -toolOptionValue // endDefinition; - -toolOptionValue0 // beginDefinition; -toolOptionValue0[ opts_Association, key_String ] := Lookup[ opts, key, Lookup[ $DefaultToolOptions, key ] ]; -toolOptionValue0 // endDefinition; - -(* ::**************************************************************************************************************:: *) -(* ::Subsection::Closed:: *) -(*toolOptions*) -toolOptions // beginDefinition; - -toolOptions[ name_String ] := - toolOptions[ name, $toolOptions[ name ], $DefaultToolOptions[ name ] ]; - -toolOptions[ name_, opts_Association, defaults_Association ] := - toolOptions[ name, <| DeleteCases[ defaults, Inherited ], DeleteCases[ opts, Inherited ] |> ]; - -toolOptions[ name_, _Missing, defaults_Association ] := - toolOptions[ name, DeleteCases[ defaults, Inherited ] ]; - -toolOptions[ name_, opts_Association ] := - Normal[ KeyMap[ toOptionKey, opts ], Association ]; - -toolOptions // endDefinition; - -(* ::**************************************************************************************************************:: *) -(* ::Subsubsection::Closed:: *) -(*toOptionKey*) -toOptionKey // beginDefinition; -toOptionKey[ name_String ] /; StringContainsQ[ name, "`" ] := toOptionKey @ Last @ StringSplit[ name, "`" ]; -toOptionKey[ name_String ] /; NameQ[ "System`" <> name ] := Symbol @ name; -toOptionKey[ symbol_Symbol ] := symbol; -toOptionKey // endDefinition; - -(* ::**************************************************************************************************************:: *) -(* ::Section::Closed:: *) -(*Toolbox*) - -(* ::**************************************************************************************************************:: *) -(* ::Subsection::Closed:: *) -(*withToolBox*) -withToolBox // beginDefinition; -withToolBox // Attributes = { HoldFirst }; -withToolBox[ eval_ ] := Block[ { $selectedTools = <| |>, $toolOptions = $DefaultToolOptions }, eval ]; -withToolBox // endDefinition; - -(* ::**************************************************************************************************************:: *) -(* ::Subsection::Closed:: *) -(*selectTools*) -selectTools // beginDefinition; - -selectTools[ as_Association ] := Enclose[ - Module[ { llmEvaluatorName, toolNames, selections, selectionTypes, add, remove, selectedNames }, - - llmEvaluatorName = ConfirmBy[ getLLMEvaluatorName @ as, StringQ, "LLMEvaluatorName" ]; - toolNames = ConfirmMatch[ getToolNames @ as, { ___String }, "Names" ]; - selections = ConfirmBy[ getToolSelections @ as, AssociationQ, "Selections" ]; - selectionTypes = ConfirmBy[ getToolSelectionTypes @ as, AssociationQ, "SelectionTypes" ]; - - add = ConfirmMatch[ - Union[ - Keys @ Select[ selections, Lookup @ llmEvaluatorName ], - Keys @ Select[ selectionTypes, SameAs @ All ] - ], - { ___String }, - "ToolAdditions" - ]; - - remove = ConfirmMatch[ - Union[ - Keys @ Select[ selections, Not @* Lookup[ llmEvaluatorName ] ], - Keys @ Select[ selectionTypes, SameAs @ None ] - ], - { ___String }, - "ToolRemovals" - ]; - - selectedNames = ConfirmMatch[ - Complement[ Union[ toolNames, add ], remove ], - { ___String }, - "SelectedNames" - ]; - - selectTools0 /@ selectedNames - ], - throwInternalFailure[ selectTools @ as, ## ] & -]; - -selectTools // endDefinition; - - -(* TODO: Most of this functionality is moved to `getToolNames`. This only needs to operate on strings. *) -selectTools0 // beginDefinition; - -selectTools0[ Automatic|Inherited ] := selectTools0 @ $defaultChatTools; -selectTools0[ None ] := $selectedTools = <| |>; -selectTools0[ name_String ] /; KeyExistsQ[ $toolBox, name ] := $selectedTools[ name ] = $toolBox[ name ]; -selectTools0[ name_String ] /; KeyExistsQ[ $toolNameAliases, name ] := selectTools0 @ $toolNameAliases @ name; -selectTools0[ name_String ] := selectTools0[ name, Lookup[ $AvailableTools, name ] ]; -selectTools0[ tools_List ] := selectTools0 /@ tools; -selectTools0[ tools_Association ] := KeyValueMap[ selectTools0, tools ]; - -(* Literal LLMTool specification: *) -selectTools0[ tool: HoldPattern @ LLMTool[ KeyValuePattern[ "Name" -> name_ ], ___ ] ] := selectTools0[ name, tool ]; - -(* Rules can be used to enable/disable by name: *) -selectTools0[ (Rule|RuleDelayed)[ name_String, tool_ ] ] := selectTools0[ name, tool ]; - -(* Inherit from core tools: *) -selectTools0[ name_String, Automatic|Inherited ] := selectTools0[ name, Lookup[ $defaultChatTools, name ] ]; - -(* Disable tool: *) -selectTools0[ name_String, None ] := KeyDropFrom[ $selectedTools, name ]; - -(* Select a literal LLMTool: *) -selectTools0[ name_String, tool: HoldPattern[ _LLMTool ] ] := $selectedTools[ name ] = $toolBox[ name ] = tool; - -(* Tool not found: *) -selectTools0[ name_String, Missing[ "KeyAbsent", name_ ] ] := - If[ TrueQ @ KeyExistsQ[ $defaultChatTools0, name ], - (* A default tool that was filtered for compatibility *) - Null, - (* An unknown tool name *) - messagePrint[ "ToolNotFound", name ] - ]; - -selectTools0 // endDefinition; - -(* ::**************************************************************************************************************:: *) -(* ::Subsubsection::Closed:: *) -(*getLLMEvaluatorName*) -getLLMEvaluatorName // beginDefinition; -getLLMEvaluatorName[ KeyValuePattern[ "LLMEvaluatorName" -> name_String ] ] := name; -getLLMEvaluatorName[ KeyValuePattern[ "LLMEvaluator" -> name_String ] ] := name; - -getLLMEvaluatorName[ KeyValuePattern[ "LLMEvaluator" -> evaluator_Association ] ] := - Lookup[ evaluator, "LLMEvaluatorName", Lookup[ evaluator, "Name" ] ]; - -getLLMEvaluatorName // endDefinition; - -(* ::**************************************************************************************************************:: *) -(* ::Subsubsection::Closed:: *) -(*getToolNames*) -getToolNames // beginDefinition; - -(* Persona declares tools, so combine with defaults as appropriate *) -getToolNames[ as: KeyValuePattern[ "LLMEvaluator" -> KeyValuePattern[ "Tools" -> tools_ ] ] ] := - getToolNames[ Lookup[ as, "Tools", None ], tools ]; - -(* No tool specification by persona, so get defaults *) -getToolNames[ as_Association ] := - getToolNames @ Lookup[ as, "Tools", Automatic ]; - -(* Persona does not want any tools *) -getToolNames[ tools_, None ] := { }; - -(* Persona wants default tools *) -getToolNames[ tools_, Automatic|Inherited ] := getToolNames @ tools; - -(* Persona declares an explicit list of tools *) -getToolNames[ Automatic|None|Inherited, personaTools_List ] := getToolNames @ personaTools; - -(* The user has specified an explicit list of tools as well, so include them *) -getToolNames[ tools_List, personaTools_List ] := Union[ getToolNames @ tools, getToolNames @ personaTools ]; - -(* Get name of each tool *) -getToolNames[ tools_List ] := DeleteDuplicates @ Flatten[ getCachedToolName /@ tools ]; - -(* Default tools *) -getToolNames[ Automatic|Inherited ] := Keys @ $DefaultTools; - -(* All tools *) -getToolNames[ All ] := Keys @ $AvailableTools; - -(* No tools *) -getToolNames[ None ] := { }; - -(* A single tool specification without an enclosing list *) -getToolNames[ tool: Except[ _List ] ] := getToolNames @ { tool }; - -getToolNames // endDefinition; - -(* ::**************************************************************************************************************:: *) -(* ::Subsubsection::Closed:: *) -(*getCachedToolName*) -getCachedToolName // beginDefinition; - -getCachedToolName[ tool: HoldPattern[ _LLMTool ] ] := Enclose[ - Module[ { name }, - name = ConfirmBy[ toolName @ tool, StringQ, "Name" ]; - ConfirmAssert[ AssociationQ @ $toolBox, "ToolBox" ]; - $toolBox[ name ] = tool; - name - ], - throwInternalFailure[ getCachedToolName @ tool, ## ] & -]; - -getCachedToolName[ name_String ] := - With[ { canonical = toCanonicalToolName @ name }, - Which[ - KeyExistsQ[ $toolBox , canonical ], canonical, - KeyExistsQ[ $toolNameAliases , canonical ], getCachedToolName @ $toolNameAliases @ canonical, - KeyExistsQ[ $defaultChatTools, canonical ], getCachedToolName @ $defaultChatTools @ canonical, - True , name - ] - ]; - -getCachedToolName // endDefinition; - -(* ::**************************************************************************************************************:: *) -(* ::Subsubsection::Closed:: *) -(*getToolSelections*) -getToolSelections // beginDefinition; -getToolSelections[ as_Association ] := getToolSelections[ as, Lookup[ as, "ToolSelections", <| |> ] ]; -getToolSelections[ as_, selections_Association ] := KeyTake[ selections, Keys @ $AvailableTools ]; -getToolSelections[ as_, Except[ _Association ] ] := <| |>; -getToolSelections // endDefinition; - -(* ::**************************************************************************************************************:: *) -(* ::Subsubsection::Closed:: *) -(*getToolSelectionTypes*) -getToolSelectionTypes // beginDefinition; -getToolSelectionTypes[ as_Association ] := getToolSelectionTypes[ as, Lookup[ as, "ToolSelectionType", <| |> ] ]; -getToolSelectionTypes[ as_, selections_Association ] := KeyTake[ selections, Keys @ $AvailableTools ]; -getToolSelectionTypes[ as_, Except[ _Association ] ] := <| |>; -getToolSelectionTypes // endDefinition; - -(* ::**************************************************************************************************************:: *) -(* ::Subsection::Closed:: *) -(*initTools*) -initTools // beginDefinition; - -initTools[ ] := initTools[ ] = ( - - If[ $CloudEvaluation && $VersionNumber <= 13.2, - - If[ PacletFind[ "ServiceConnection_OpenAI" ] === { }, - PacletInstall[ "ServiceConnection_OpenAI", PacletSite -> "https://pacletserver.wolfram.com" ] - ]; - - WithCleanup[ - Unprotect @ TemplateObject, - TemplateObject // Options = DeleteDuplicatesBy[ - Append[ Options @ TemplateObject, MetaInformation -> <| |> ], - ToString @* First - ], - Protect @ TemplateObject - ] - ]; - - - installLLMFunctions[ ]; -); - -initTools // endDefinition; - -(* ::**************************************************************************************************************:: *) -(* ::Subsubsection::Closed:: *) -(*installLLMFunctions*) -installLLMFunctions // beginDefinition; - -installLLMFunctions[ ] := Enclose[ - Module[ { before, paclet, opts, reload }, - before = Quiet @ PacletObject[ "Wolfram/LLMFunctions" ]; - paclet = ConfirmBy[ PacletInstall[ "Wolfram/LLMFunctions" ], PacletObjectQ, "PacletInstall" ]; - - If[ ! TrueQ @ Quiet @ PacletNewerQ[ paclet, "1.2.1" ], - opts = If[ $CloudEvaluation, PacletSite -> "https://pacletserver.wolfram.com", UpdatePacletSites -> True ]; - paclet = ConfirmBy[ PacletInstall[ "Wolfram/LLMFunctions", opts ], PacletObjectQ, "PacletUpdate" ]; - ConfirmAssert[ PacletNewerQ[ paclet, "1.2.1" ], "PacletVersion" ]; - reload = True, - reload = PacletObjectQ @ before && PacletNewerQ[ paclet, before ] - ]; - - If[ TrueQ @ reload, reloadLLMFunctions[ ] ]; - Needs[ "Wolfram`LLMFunctions`" -> None ]; - installLLMFunctions[ ] = paclet - ], - throwInternalFailure[ installLLMFunctions[ ], ## ] & -]; - -installLLMFunctions // endDefinition; - -(* ::**************************************************************************************************************:: *) -(* ::Subsubsection::Closed:: *) -(*reloadLLMFunctions*) -reloadLLMFunctions // beginDefinition; - -reloadLLMFunctions[ ] := Enclose[ - Module[ { paclet, files }, - paclet = ConfirmBy[ PacletObject[ "Wolfram/LLMFunctions" ], PacletObjectQ, "PacletObject" ]; - files = Select[ $LoadedFiles, StringContainsQ[ "LLMFunctions" ] ]; - If[ ! AnyTrue[ files, StringStartsQ @ paclet[ "Location" ] ], - (* Force paclet to reload if the new one has not been loaded *) - WithCleanup[ - Unprotect @ $Packages, - $Packages = Select[ $Packages, Not @* StringStartsQ[ "Wolfram`LLMFunctions`" ] ]; - ClearAll[ "Wolfram`LLMFunctions`*" ]; - ClearAll[ "Wolfram`LLMFunctions`*`*" ]; - Block[ { $ContextPath }, Get[ "Wolfram`LLMFunctions`" ] ], - Protect @ $Packages - ] - ] - ], - throwInternalFailure[ reloadLLMFunctions[ ], ## ] & -]; - -reloadLLMFunctions // endDefinition; - -(* ::**************************************************************************************************************:: *) -(* ::Subsection::Closed:: *) -(*resolveTools*) -resolveTools // beginDefinition; - -resolveTools[ settings: KeyValuePattern[ "ToolsEnabled" -> True ] ] := ( - initTools[ ]; - selectTools @ settings; - $toolOptions = Lookup[ settings, "ToolOptions", $DefaultToolOptions ]; - $lastSelectedTools = $selectedTools; - $lastToolOptions = $toolOptions; - If[ KeyExistsQ[ $selectedTools, "WolframLanguageEvaluator" ], needsBasePrompt[ "WolframLanguageEvaluatorTool" ] ]; - Append[ settings, "Tools" -> Values @ $selectedTools ] -); - -resolveTools[ settings_Association ] := settings; - -resolveTools // endDefinition; - -(* ::**************************************************************************************************************:: *) -(* ::Subsection::Closed:: *) -(*makeToolConfiguration*) -makeToolConfiguration // beginDefinition; - -makeToolConfiguration[ settings_Association ] := Enclose[ - Module[ { tools }, - tools = ConfirmMatch[ DeleteDuplicates @ Flatten @ Values @ $selectedTools, { ___LLMTool }, "SelectedTools" ]; - $toolConfiguration = LLMConfiguration @ <| "Tools" -> tools, "ToolPrompt" -> makeToolPrompt @ settings |> - ], - throwInternalFailure[ makeToolConfiguration @ settings, ## ] & -]; - -makeToolConfiguration // endDefinition; - -(* ::**************************************************************************************************************:: *) -(* ::Subsubsection::Closed:: *) -(*$toolConfiguration*) -$toolConfiguration := $toolConfiguration = LLMConfiguration @ <| "Tools" -> Values @ $defaultChatTools |>; - -(* ::**************************************************************************************************************:: *) -(* ::Subsubsection::Closed:: *) -(*toolRequestParser*) -toolRequestParser := toolRequestParser = - Quiet[ Check[ $toolConfiguration[ "ToolRequestParser" ], - Wolfram`LLMFunctions`LLMConfiguration`$DefaultTextualToolMethod[ "ToolRequestParser" ], - LLMConfiguration::invprop - ], - LLMConfiguration::invprop - ]; - -(* ::**************************************************************************************************************:: *) -(* ::Subsubsection::Closed:: *) -(*makeToolPrompt*) -makeToolPrompt // beginDefinition; - -makeToolPrompt[ settings_Association ] := $lastToolPrompt = TemplateObject[ - Riffle[ - DeleteMissing @ { - $toolPre, - TemplateSequence[ - TemplateExpression @ TemplateObject[ - { - "Tool Name: ", - TemplateSlot[ "Name" ], - "\nDisplay Name: ", - TemplateSlot[ - "DisplayName", - DefaultValue :> TemplateExpression @ toDisplayToolName @ TemplateSlot[ "Name" ] - ], - "\nDescription: ", - TemplateSlot[ "Description" ], - "\nSchema:\n", - TemplateSlot[ "Schema" ], - "\n\n" - }, - CombinerFunction -> StringJoin, - InsertionFunction -> TextString - ], - TemplateExpression @ Map[ - Append[ #[ "Data" ], "Schema" -> ExportString[ #[ "JSONSchema" ], "JSON" ] ] &, - TemplateSlot[ "Tools" ] - ] - ], - $toolPost, - $fullExamples, - makeToolPreferencePrompt @ settings - }, - "\n\n" - ], - CombinerFunction -> StringJoin, - InsertionFunction -> TextString -]; - -makeToolPrompt // endDefinition; - - -$toolPre = "\ -# Tool Instructions - -You have access to tools which can be used to do things, fetch data, compute, etc. while you create your response. \ -Each tool takes input as JSON following a JSON schema. Here are the available tools and their associated schemas:"; - - -$toolPost := "\ -To call a tool, write the following at any time during your response: - -TOOLCALL: -{ - \"\": - \"\": -} -ENDARGUMENTS -ENDTOOLCALL - -Always use valid JSON to specify the parameters in the tool call. Always follow the tool's JSON schema to specify the \ -parameters in the tool call. Fill in the values in <> brackets with the values for the particular tool. Provide as \ -many parameters as the tool requires. Always make one tool call at a time. Always write two line breaks before each \ -tool call. - -The system will execute the requested tool call and you will receive a system message containing the result. \ -You can then use this result to finish writing your response for the user. - -You must write the TOOLCALL in your CURRENT response. \ -Do not state that you will use a tool and end your message before making the tool call. - -If a user asks you to use a specific tool, you MUST attempt to use that tool as requested, \ -even if you think it will not work. \ -If the tool fails, use any error message to correct the issue or explain why it failed. \ -NEVER state that a tool cannot be used for a particular task without trying it first. \ -You did not create these tools, so you do not know what they can and cannot do. - -You should try to avoid mentioning tools by name in your response and instead speak generally about their function. \ -For example, if there were a number_adder tool, you would instead talk about \"adding numbers\". If you must mention \ -a tool by name, you should use the DisplayName property instead of the tool name."; - -(* ::**************************************************************************************************************:: *) -(* ::Subsubsubsection::Closed:: *) -(*makeToolPreferencePrompt*) -makeToolPreferencePrompt // beginDefinition; - -makeToolPreferencePrompt[ settings_ ] := - makeToolPreferencePrompt[ settings, settings[ "ToolCallFrequency" ] ]; - -makeToolPreferencePrompt[ settings_, Automatic ] := - Missing[ "NotAvailable" ]; - -makeToolPreferencePrompt[ settings_, freq_? NumberQ ] := - With[ { key = Round @ Clip[ 5 * freq, { 0, 5 } ] }, - TemplateApply[ - $toolPreferencePrompt, - <| "Number" -> Round[ freq * 100 ], "Explanation" -> Lookup[ $toolFrequencyExplanations, key, "" ] |> - ] - ]; - -makeToolPreferencePrompt // endDefinition; - - -$toolPreferencePrompt = "\ -## User Tool Call Preferences - -The user has specified their desired tool calling frequency to be `Number`% with the following instructions: - -IMPORTANT -`Explanation`"; - - -$toolFrequencyExplanations = <| - 0 -> "Only use a tool if explicitly instructed to use tools. Never use tools unless specifically asked to.", - 1 -> "Avoid using tools unless you think it is necessary.", - 2 -> "Only use tools if you think it will significantly improve the quality of your response.", - 3 -> "Use tools whenever it is appropriate to do so.", - 4 -> "Use tools whenever there's even a slight chance that it could improve the quality of your response (e.g. fact checking).", - 5 -> "ALWAYS make a tool call in EVERY response, no matter what." -|>; - -(* ::**************************************************************************************************************:: *) -(* ::Section::Closed:: *) -(*Default Tools*) -$defaultChatTools := If[ TrueQ @ $CloudEvaluation, - KeyDrop[ $defaultChatTools0, $cloudUnsupportedTools ], - $defaultChatTools0 - ]; - -$defaultChatTools0 = <| |>; - -(* ::**************************************************************************************************************:: *) -(* ::Section::Closed:: *) -(*Installed Tools*) -$installedTools := Association @ Cases[ - GetInstalledResourceData[ "LLMTool" ], - as: KeyValuePattern[ "Tool" -> tool_ ] :> (toolName @ tool -> addExtraToolData[ tool, as ]) -]; - -(* ::**************************************************************************************************************:: *) -(* ::Subsection::Closed:: *) -(*addExtraToolData*) -addExtraToolData // beginDefinition; - -addExtraToolData[ tool: HoldPattern @ LLMTool[ as_Association, a___ ], extra_Association ] := - With[ { new = Join[ KeyTake[ extra, $installedToolExtraKeys ], as ] }, LLMTool[ new, a ] ]; - -addExtraToolData // endDefinition; - -(* ::**************************************************************************************************************:: *) -(* ::Subsection::Closed:: *) -(*getToolByName*) -getToolByName // beginDefinition; -getToolByName[ name_String ] := Lookup[ $toolBox, toCanonicalToolName @ name ]; -getToolByName // endDefinition; - -(* ::**************************************************************************************************************:: *) -(* ::Subsection::Closed:: *) -(*toolName*) -toolName // beginDefinition; -toolName[ tool_ ] := toolName[ tool, Automatic ]; -toolName[ HoldPattern @ LLMTool[ as_Association, ___ ], type_ ] := toolName[ as, type ]; -toolName[ KeyValuePattern[ "CanonicalName" -> name_String ], "Canonical" ] := name; -toolName[ KeyValuePattern[ "DisplayName" -> name_String ], "Display" ] := name; -toolName[ KeyValuePattern[ "Name" -> name_String ], type_ ] := toolName[ name, type ]; -toolName[ tool_, Automatic ] := toolName[ tool, "Canonical" ]; -toolName[ name_String, "Machine" ] := toMachineToolName @ name; -toolName[ name_String, "Canonical" ] := toCanonicalToolName @ name; -toolName[ name_String, "Display" ] := toDisplayToolName @ name; -toolName[ tools_List, type_ ] := toolName[ #, type ] & /@ tools; -toolName // endDefinition; - -(* ::**************************************************************************************************************:: *) -(* ::Subsection::Closed:: *) -(*toolData*) -toolData // beginDefinition; - -toolData[ HoldPattern @ LLMTool[ as_Association, ___ ] ] := - toolData @ as; - -toolData[ name_String ] /; KeyExistsQ[ $toolBox, name ] := - toolData @ $toolBox[ name ]; - -toolData[ name_String ] /; KeyExistsQ[ $defaultChatTools, name ] := - toolData @ $defaultChatTools[ name ]; - -toolData[ as: KeyValuePattern @ { "Function"|"ToolCall" -> _ } ] := <| - toolDefaultData @ toolName @ as, - "Icon" -> toolDefaultIcon @ as, - as -|>; - -toolData[ tools_List ] := - toolData /@ tools; - -toolData // endDefinition; - -(* ::**************************************************************************************************************:: *) -(* ::Subsubsection::Closed:: *) -(*toolDefaultIcon*) -toolDefaultIcon // beginDefinition; - -toolDefaultIcon[ KeyValuePattern[ "Origin" -> "LLMToolRepository" ] ] := - RawBoxes @ TemplateBox[ { }, "ToolManagerRepository" ]; - -toolDefaultIcon[ _Association ] := - $defaultToolIcon; - -toolDefaultIcon // endDefinition; - (* ::**************************************************************************************************************:: *) (* ::Subsection::Closed:: *) -(*toolDefaultData*) -toolDefaultData // beginDefinition; - -toolDefaultData[ name_String ] := <| - "CanonicalName" -> toCanonicalToolName @ name, - "DisplayName" -> toDisplayToolName @ name, - "Name" -> toMachineToolName @ name, - "Icon" -> $defaultToolIcon -|>; - -toolDefaultData // endDefinition; - -(* ::**************************************************************************************************************:: *) -(* ::Subsubsection::Closed:: *) -(*toMachineToolName*) -toMachineToolName // beginDefinition; - -toMachineToolName[ s_String ] := - ToLowerCase @ StringReplace[ - StringTrim @ s, - { " " -> "_", a_?LowerCaseQ ~~ b_?UpperCaseQ ~~ c_?LowerCaseQ :> a<>"_"<>b<>c } - ]; - -toMachineToolName // endDefinition; - -(* ::**************************************************************************************************************:: *) -(* ::Subsubsection::Closed:: *) -(*toCanonicalToolName*) -toCanonicalToolName // beginDefinition; - -toCanonicalToolName[ s_String ] := - Capitalize @ StringReplace[ StringTrim @ s, a_~~("_"|" ")~~b_ :> a <> ToUpperCase @ b ]; - -toCanonicalToolName // endDefinition; - -(* ::**************************************************************************************************************:: *) -(* ::Subsubsection::Closed:: *) -(*toDisplayToolName*) -toDisplayToolName // beginDefinition; - -toDisplayToolName[ s_String ] := - Capitalize[ - StringReplace[ - StringTrim @ s, - { "_" :> " ", a_?LowerCaseQ ~~ b_?UpperCaseQ ~~ c_?LowerCaseQ :> a<>" "<>b<>c } - ], - "TitleCase" - ]; - -toDisplayToolName // endDefinition; - -(* ::**************************************************************************************************************:: *) -(* ::Subsection::Closed:: *) -(*formatToolCallExample*) -formatToolCallExample // beginDefinition; - -formatToolCallExample[ name_String, params_Association ] := - TemplateApply[ - "TOOLCALL: `1`\n`2`\nENDARGUMENTS\nENDTOOLCALL", - { toMachineToolName @ name, Developer`WriteRawJSONString @ params } - ]; - -formatToolCallExample // endDefinition; +(*ChatPreferences*) + +(* Uncomment the following when the ChatPreferences tool is ready: *) +(* $defaultChatTools0[ "ChatPreferences" ] = <| + toolDefaultData[ "ChatPreferences" ], + "Icon" -> RawBoxes @ TemplateBox[ { }, "ChatBlockSettingsMenuIcon" ], + "Description" -> $chatPreferencesDescription, + "Function" -> chatPreferences, + "FormattingFunction" -> toolAutoFormatter, + "Source" -> "BuiltIn", + "Parameters" -> { + "action" -> <| + "Interpreter" -> { "get", "set" }, + "Help" -> "Whether to get or set chat settings", + "Required" -> True + |>, + "key" -> <| + "Interpreter" -> "String", + "Help" -> "Which chat setting to get or set", + "Required" -> False + |>, + "value" -> <| + "Interpreter" -> "String", + "Help" -> "The value to set the chat setting to", + "Required" -> False + |>, + "scope" -> <| + "Interpreter" -> { "global", "notebook" }, + "Help" -> "The scope of the chat setting (default is 'notebook')", + "Required" -> False + |> + } +|>; *) (* ::**************************************************************************************************************:: *) (* ::Subsection::Closed:: *) @@ -1339,6 +584,9 @@ fetchWebText[ url_String, session_WebSessionObject ] := Enclose[ shortenWebText @ niceWebText @ Import[ url, { "HTML", "Plaintext" } ] & ]; +fetchWebText[ url_String, _Missing | _? FailureQ ] := + shortenWebText @ niceWebText @ Import[ url, { "HTML", "Plaintext" } ]; + fetchWebText // endDefinition; (* ::**************************************************************************************************************:: *) @@ -1390,7 +638,13 @@ validWebSessionQ[ ___ ] := False; (* ::Subsubsection::Closed:: *) (*startWebSession*) startWebSession // beginDefinition; -startWebSession[ ] := $currentWebSession = StartWebSession[ Visible -> $webSessionVisible ]; + +startWebSession[ ] := $currentWebSession = + If[ TrueQ @ $CloudEvaluation, + Missing[ "NotAvailable" ], + StartWebSession[ Visible -> $webSessionVisible ] + ]; + startWebSession // endDefinition; (* ::**************************************************************************************************************:: *) diff --git a/Source/Chatbook/Tools/ToolOptions.wl b/Source/Chatbook/Tools/ToolOptions.wl new file mode 100644 index 00000000..cbce76de --- /dev/null +++ b/Source/Chatbook/Tools/ToolOptions.wl @@ -0,0 +1,100 @@ +(* ::Section::Closed:: *) +(*Package Header*) +BeginPackage[ "Wolfram`Chatbook`Tools`" ]; + +(* :!CodeAnalysis::BeginBlock:: *) + +Begin[ "`Private`" ]; + +Needs[ "Wolfram`Chatbook`" ]; +Needs[ "Wolfram`Chatbook`ChatMessages`" ]; +Needs[ "Wolfram`Chatbook`Common`" ]; +Needs[ "Wolfram`Chatbook`Formatting`" ]; +Needs[ "Wolfram`Chatbook`Models`" ]; +Needs[ "Wolfram`Chatbook`Personas`" ]; +Needs[ "Wolfram`Chatbook`Prompting`" ]; +Needs[ "Wolfram`Chatbook`ResourceInstaller`" ]; +Needs[ "Wolfram`Chatbook`Sandbox`" ]; +Needs[ "Wolfram`Chatbook`Serialization`" ]; +Needs[ "Wolfram`Chatbook`Utils`" ]; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Tool Options*) + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*SetToolOptions*) +SetToolOptions // ClearAll; + +SetToolOptions[ name_String, opts: OptionsPattern[ ] ] := + SetToolOptions[ $FrontEnd, name, opts ]; + +SetToolOptions[ scope_, name_String, opts: OptionsPattern[ ] ] := UsingFrontEnd[ + KeyValueMap[ + (CurrentValue[ scope, { TaggingRules, "ChatNotebookSettings", "ToolOptions", name, ToString[ #1 ] } ] = #2) &, + Association @ Reverse @ { opts } + ]; + CurrentValue[ scope, { TaggingRules, "ChatNotebookSettings", "ToolOptions" } ] +]; + +SetToolOptions[ name_String, Inherited ] := + SetToolOptions[ $FrontEnd, name, Inherited ]; + +SetToolOptions[ scope_, name_String, Inherited ] := UsingFrontEnd[ + CurrentValue[ scope, { TaggingRules, "ChatNotebookSettings", "ToolOptions", name } ] = Inherited; + CurrentValue[ scope, { TaggingRules, "ChatNotebookSettings", "ToolOptions" } ] +]; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*toolOptionValue*) +toolOptionValue // beginDefinition; +toolOptionValue[ name_String, key_String ] := toolOptionValue[ name, $toolOptions[ name ], key ]; +toolOptionValue[ name_String, _Missing, key_String ] := toolOptionValue0[ $DefaultToolOptions[ name ], key ]; +toolOptionValue[ name_String, opts_Association, key_String ] := toolOptionValue0[ opts, key ]; +toolOptionValue // endDefinition; + +toolOptionValue0 // beginDefinition; +toolOptionValue0[ opts_Association, key_String ] := Lookup[ opts, key, Lookup[ $DefaultToolOptions, key ] ]; +toolOptionValue0 // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsection::Closed:: *) +(*toolOptions*) +toolOptions // beginDefinition; + +toolOptions[ name_String ] := + toolOptions[ name, $toolOptions[ name ], $DefaultToolOptions[ name ] ]; + +toolOptions[ name_, opts_Association, defaults_Association ] := + toolOptions[ name, <| DeleteCases[ defaults, Inherited ], DeleteCases[ opts, Inherited ] |> ]; + +toolOptions[ name_, _Missing, defaults_Association ] := + toolOptions[ name, DeleteCases[ defaults, Inherited ] ]; + +toolOptions[ name_, opts_Association ] := + Normal[ KeyMap[ toOptionKey, opts ], Association ]; + +toolOptions // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Subsubsection::Closed:: *) +(*toOptionKey*) +toOptionKey // beginDefinition; +toOptionKey[ name_String ] /; StringContainsQ[ name, "`" ] := toOptionKey @ Last @ StringSplit[ name, "`" ]; +toOptionKey[ name_String ] /; NameQ[ "System`" <> name ] := Symbol @ name; +toOptionKey[ symbol_Symbol ] := symbol; +toOptionKey // endDefinition; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Package Footer*) +If[ Wolfram`ChatbookInternal`$BuildingMX, + Null; +]; + +(* :!CodeAnalysis::EndBlock:: *) + +End[ ]; +EndPackage[ ]; diff --git a/Source/Chatbook/Tools/Tools.wl b/Source/Chatbook/Tools/Tools.wl new file mode 100644 index 00000000..83fc66d4 --- /dev/null +++ b/Source/Chatbook/Tools/Tools.wl @@ -0,0 +1,48 @@ +(* ::Section::Closed:: *) +(*Package Header*) +BeginPackage[ "Wolfram`Chatbook`Tools`" ]; + +HoldComplete[ + `$attachments; + `$defaultChatTools; + `$toolConfiguration; + `$toolEvaluationResults; + `$toolOptions; + `$toolResultStringLength; + `getToolByName; + `getToolDisplayName; + `getToolFormattingFunction; + `getToolIcon; + `initTools; + `makeExpressionURI; + `makeToolConfiguration; + `makeToolResponseString; + `resolveTools; + `toolData; + `toolName; + `toolOptionValue; + `toolRequestParser; + `withToolBox; +]; + +Begin[ "`Private`" ]; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Load Subcontexts*) +Get[ "Wolfram`Chatbook`Tools`Common`" ]; +Get[ "Wolfram`Chatbook`Tools`ToolOptions`" ]; +Get[ "Wolfram`Chatbook`Tools`DefaultTools`" ]; +Get[ "Wolfram`Chatbook`Tools`ChatPreferences`" ]; + +(* ::**************************************************************************************************************:: *) +(* ::Section::Closed:: *) +(*Package Footer*) +If[ Wolfram`ChatbookInternal`$BuildingMX, + Null; +]; + +(* :!CodeAnalysis::EndBlock:: *) + +End[ ]; +EndPackage[ ];