mirror of
https://github.com/nomic-ai/gpt4all.git
synced 2026-07-17 10:58:08 +00:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be6347389e | ||
|
|
8c10eccd24 | ||
|
|
6ef0bd518e | ||
|
|
04dc157b98 | ||
|
|
014bf67c63 | ||
|
|
8c9f26e249 | ||
|
|
d4e6a6e485 | ||
|
|
a081255951 | ||
|
|
36c852b8be | ||
|
|
c38c7455d8 | ||
|
|
9131f4c432 | ||
|
|
6bfa014594 | ||
|
|
5af31278b7 | ||
|
|
a80f023ed2 | ||
|
|
126042fdc9 | ||
|
|
1f2712d57c | ||
|
|
f8f78c6677 | ||
|
|
643c733be3 | ||
|
|
0734694fb8 | ||
|
|
e267512db9 | ||
|
|
34037f3101 | ||
|
|
007a7af1c8 | ||
|
|
f914ee56c9 | ||
|
|
8a0ec5c303 | ||
|
|
c2ee252ef2 | ||
|
|
64dcf7682e | ||
|
|
22b8278ef1 | ||
|
|
adafa17c37 | ||
|
|
343a4b6b6a | ||
|
|
6a8a840681 | ||
|
|
88f5dac133 | ||
|
|
0d974297a5 | ||
|
|
4fbc20ced9 | ||
|
|
f4f7de51e7 | ||
|
|
c01ac7fa93 | ||
|
|
173fdb18c2 | ||
|
|
8790586e57 | ||
|
|
b98501c786 | ||
|
|
49df6464a7 | ||
|
|
6b719e99b5 | ||
|
|
d85fe40de8 | ||
|
|
15f66570fe | ||
|
|
a97a28fe4f | ||
|
|
df2d124c19 |
@@ -8,6 +8,10 @@ workflows:
|
||||
generate-config:
|
||||
jobs:
|
||||
- path-filtering/filter:
|
||||
filters:
|
||||
tags:
|
||||
only:
|
||||
- /.*/
|
||||
base-revision: main
|
||||
config-path: .circleci/continue_config.yml
|
||||
mapping: |
|
||||
|
||||
@@ -22,10 +22,17 @@ jobs:
|
||||
# work around CircleCI-Public/path-filtering-orb#20
|
||||
noop:
|
||||
docker:
|
||||
- image: cimg/base:current
|
||||
- image: cimg/base:stable
|
||||
steps:
|
||||
- run: "true"
|
||||
|
||||
validate-commit-on-main:
|
||||
docker:
|
||||
- image: cimg/base:stable
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Verify that commit is on the main branch
|
||||
command: git merge-base --is-ancestor HEAD main
|
||||
build-offline-chat-installer-macos:
|
||||
macos:
|
||||
xcode: 15.4.0
|
||||
@@ -48,10 +55,15 @@ jobs:
|
||||
- run:
|
||||
name: Installing Qt
|
||||
command: |
|
||||
wget "https://gpt4all.io/ci/qt-unified-macOS-x64-4.6.0-online.dmg"
|
||||
hdiutil attach qt-unified-macOS-x64-4.6.0-online.dmg
|
||||
/Volumes/qt-unified-macOS-x64-4.6.0-online/qt-unified-macOS-x64-4.6.0-online.app/Contents/MacOS/qt-unified-macOS-x64-4.6.0-online --no-force-installations --no-default-installations --no-size-checking --default-answer --accept-licenses --confirm-command --accept-obligations --email $QT_EMAIL --password $QT_PASSWORD install qt.tools.cmake qt.tools.ifw.48 qt.tools.ninja qt.qt6.651.clang_64 qt.qt6.651.qt5compat qt.qt6.651.debug_info qt.qt6.651.addons.qtpdf qt.qt6.651.addons.qthttpserver
|
||||
hdiutil detach /Volumes/qt-unified-macOS-x64-4.6.0-online
|
||||
wget "https://qt.mirror.constant.com/archive/online_installers/4.8/qt-online-installer-macOS-x64-4.8.1.dmg"
|
||||
hdiutil attach qt-online-installer-macOS-x64-4.8.1.dmg
|
||||
/Volumes/qt-online-installer-macOS-x64-4.8.1/qt-online-installer-macOS-x64-4.8.1.app/Contents/MacOS/qt-online-installer-macOS-x64-4.8.1 \
|
||||
--no-force-installations --no-default-installations --no-size-checking --default-answer \
|
||||
--accept-licenses --confirm-command --accept-obligations --email "$QT_EMAIL" --password "$QT_PASSWORD" \
|
||||
install \
|
||||
qt.tools.cmake qt.tools.ifw.48 qt.tools.ninja qt.qt6.682.clang_64 qt.qt6.682.addons.qt5compat \
|
||||
extensions.qtpdf.682 qt.qt6.682.addons.qthttpserver
|
||||
hdiutil detach /Volumes/qt-online-installer-macOS-x64-4.8.1
|
||||
- run:
|
||||
name: Setup Keychain
|
||||
command: |
|
||||
@@ -72,19 +84,29 @@ jobs:
|
||||
~/Qt/Tools/CMake/CMake.app/Contents/bin/cmake \
|
||||
-S ../gpt4all-chat -B . -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_PREFIX_PATH:PATH=~/Qt/6.5.1/macos/lib/cmake \
|
||||
-DCMAKE_PREFIX_PATH:PATH=~/Qt/6.8.2/macos/lib/cmake \
|
||||
-DCMAKE_MAKE_PROGRAM:FILEPATH=~/Qt/Tools/Ninja/ninja \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DBUILD_UNIVERSAL=ON \
|
||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=12.6 \
|
||||
-DGGML_METAL_MACOSX_VERSION_MIN=12.6 \
|
||||
-DMACDEPLOYQT=~/Qt/6.5.1/macos/bin/macdeployqt \
|
||||
-DMACDEPLOYQT=~/Qt/6.8.2/macos/bin/macdeployqt \
|
||||
-DGPT4ALL_OFFLINE_INSTALLER=ON \
|
||||
-DGPT4ALL_SIGN_INSTALL=ON \
|
||||
-DGPT4ALL_GEN_CPACK_CONFIG=ON
|
||||
~/Qt/Tools/CMake/CMake.app/Contents/bin/cmake --build . --target package
|
||||
~/Qt/Tools/CMake/CMake.app/Contents/bin/cmake . -DGPT4ALL_GEN_CPACK_CONFIG=OFF
|
||||
# The 'install' step here *should* be completely unnecessary. There is absolutely no reason we should have
|
||||
# to copy all of the build artifacts to an output directory that we do not use (because we package GPT4All
|
||||
# as an installer instead).
|
||||
# However, because of the way signing is implemented in the cmake script, the *source* files are signed at
|
||||
# install time instead of the *installed* files. This side effect is the *only* way libraries that are not
|
||||
# processed by macdeployqt, such as libllmodel.so, get signed (at least, with -DBUILD_UNIVERSAL=ON).
|
||||
# Also, we have to run this as a *separate* step. Telling cmake to run both targets in one command causes it
|
||||
# to execute them in parallel, since it is not aware of the dependency of the package target on the install
|
||||
# target.
|
||||
~/Qt/Tools/CMake/CMake.app/Contents/bin/cmake --build . --target install
|
||||
~/Qt/Tools/CMake/CMake.app/Contents/bin/cmake --build . --target package
|
||||
ccache -s
|
||||
mkdir upload
|
||||
@@ -164,6 +186,21 @@ jobs:
|
||||
xcrun stapler staple build/upload/gpt4all-installer-darwin-signed.dmg
|
||||
- store_artifacts:
|
||||
path: build/upload
|
||||
- run:
|
||||
name: Install Rosetta
|
||||
command: softwareupdate --install-rosetta --agree-to-license # needed for QtIFW
|
||||
- run:
|
||||
name: Test installation and verify that it is signed
|
||||
command: |
|
||||
set -e
|
||||
hdiutil attach build/upload/gpt4all-installer-darwin-signed.dmg
|
||||
codesign --verify --deep --verbose /Volumes/gpt4all-installer-darwin/gpt4all-installer-darwin.app
|
||||
/Volumes/gpt4all-installer-darwin/gpt4all-installer-darwin.app/Contents/MacOS/gpt4all-installer-darwin \
|
||||
--no-size-checking --default-answer --accept-licenses --confirm-command \
|
||||
install gpt4all
|
||||
codesign --verify --deep --verbose /Applications/gpt4all/bin/gpt4all.app
|
||||
codesign --verify --deep --verbose /Applications/gpt4all/maintenancetool.app
|
||||
hdiutil detach /Volumes/gpt4all-installer-darwin
|
||||
|
||||
build-online-chat-installer-macos:
|
||||
macos:
|
||||
@@ -187,10 +224,15 @@ jobs:
|
||||
- run:
|
||||
name: Installing Qt
|
||||
command: |
|
||||
wget "https://gpt4all.io/ci/qt-unified-macOS-x64-4.6.0-online.dmg"
|
||||
hdiutil attach qt-unified-macOS-x64-4.6.0-online.dmg
|
||||
/Volumes/qt-unified-macOS-x64-4.6.0-online/qt-unified-macOS-x64-4.6.0-online.app/Contents/MacOS/qt-unified-macOS-x64-4.6.0-online --no-force-installations --no-default-installations --no-size-checking --default-answer --accept-licenses --confirm-command --accept-obligations --email $QT_EMAIL --password $QT_PASSWORD install qt.tools.cmake qt.tools.ifw.48 qt.tools.ninja qt.qt6.651.clang_64 qt.qt6.651.qt5compat qt.qt6.651.debug_info qt.qt6.651.addons.qtpdf qt.qt6.651.addons.qthttpserver
|
||||
hdiutil detach /Volumes/qt-unified-macOS-x64-4.6.0-online
|
||||
wget "https://qt.mirror.constant.com/archive/online_installers/4.8/qt-online-installer-macOS-x64-4.8.1.dmg"
|
||||
hdiutil attach qt-online-installer-macOS-x64-4.8.1.dmg
|
||||
/Volumes/qt-online-installer-macOS-x64-4.8.1/qt-online-installer-macOS-x64-4.8.1.app/Contents/MacOS/qt-online-installer-macOS-x64-4.8.1 \
|
||||
--no-force-installations --no-default-installations --no-size-checking --default-answer \
|
||||
--accept-licenses --confirm-command --accept-obligations --email "$QT_EMAIL" --password "$QT_PASSWORD" \
|
||||
install \
|
||||
qt.tools.cmake qt.tools.ifw.48 qt.tools.ninja qt.qt6.682.clang_64 qt.qt6.682.addons.qt5compat \
|
||||
extensions.qtpdf.682 qt.qt6.682.addons.qthttpserver
|
||||
hdiutil detach /Volumes/qt-online-installer-macOS-x64-4.8.1
|
||||
- run:
|
||||
name: Setup Keychain
|
||||
command: |
|
||||
@@ -211,19 +253,21 @@ jobs:
|
||||
~/Qt/Tools/CMake/CMake.app/Contents/bin/cmake \
|
||||
-S ../gpt4all-chat -B . -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_PREFIX_PATH:PATH=~/Qt/6.5.1/macos/lib/cmake \
|
||||
-DCMAKE_PREFIX_PATH:PATH=~/Qt/6.8.2/macos/lib/cmake \
|
||||
-DCMAKE_MAKE_PROGRAM:FILEPATH=~/Qt/Tools/Ninja/ninja \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DBUILD_UNIVERSAL=ON \
|
||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=12.6 \
|
||||
-DGGML_METAL_MACOSX_VERSION_MIN=12.6 \
|
||||
-DMACDEPLOYQT=~/Qt/6.5.1/macos/bin/macdeployqt \
|
||||
-DMACDEPLOYQT=~/Qt/6.8.2/macos/bin/macdeployqt \
|
||||
-DGPT4ALL_OFFLINE_INSTALLER=OFF \
|
||||
-DGPT4ALL_SIGN_INSTALL=ON \
|
||||
-DGPT4ALL_GEN_CPACK_CONFIG=ON
|
||||
~/Qt/Tools/CMake/CMake.app/Contents/bin/cmake --build . --target package
|
||||
~/Qt/Tools/CMake/CMake.app/Contents/bin/cmake . -DGPT4ALL_GEN_CPACK_CONFIG=OFF
|
||||
# See comment above related to the 'install' target.
|
||||
~/Qt/Tools/CMake/CMake.app/Contents/bin/cmake --build . --target install
|
||||
~/Qt/Tools/CMake/CMake.app/Contents/bin/cmake --build . --target package
|
||||
ccache -s
|
||||
mkdir upload
|
||||
@@ -304,6 +348,22 @@ jobs:
|
||||
xcrun stapler staple build/upload/gpt4all-installer-darwin-signed.dmg
|
||||
- store_artifacts:
|
||||
path: build/upload
|
||||
- run:
|
||||
name: Install Rosetta
|
||||
command: softwareupdate --install-rosetta --agree-to-license # needed for QtIFW
|
||||
- run:
|
||||
name: Test installation and verify that it is signed
|
||||
command: |
|
||||
set -e
|
||||
hdiutil attach build/upload/gpt4all-installer-darwin-signed.dmg
|
||||
codesign --verify --deep --verbose /Volumes/gpt4all-installer-darwin/gpt4all-installer-darwin.app
|
||||
tar -xf build/upload/repository.tar.gz
|
||||
/Volumes/gpt4all-installer-darwin/gpt4all-installer-darwin.app/Contents/MacOS/gpt4all-installer-darwin \
|
||||
--no-size-checking --default-answer --accept-licenses --confirm-command --set-temp-repository repository \
|
||||
install gpt4all
|
||||
codesign --verify --deep --verbose /Applications/gpt4all/bin/gpt4all.app
|
||||
codesign --verify --deep --verbose /Applications/gpt4all/maintenancetool.app
|
||||
hdiutil detach /Volumes/gpt4all-installer-darwin
|
||||
|
||||
build-offline-chat-installer-linux:
|
||||
machine:
|
||||
@@ -340,9 +400,13 @@ jobs:
|
||||
- run:
|
||||
name: Installing Qt
|
||||
command: |
|
||||
wget "https://gpt4all.io/ci/qt-unified-linux-x64-4.6.0-online.run"
|
||||
chmod +x qt-unified-linux-x64-4.6.0-online.run
|
||||
./qt-unified-linux-x64-4.6.0-online.run --no-force-installations --no-default-installations --no-size-checking --default-answer --accept-licenses --confirm-command --accept-obligations --email $QT_EMAIL --password $QT_PASSWORD install qt.tools.cmake qt.tools.ifw.48 qt.tools.ninja qt.qt6.651.gcc_64 qt.qt6.651.qt5compat qt.qt6.651.debug_info qt.qt6.651.addons.qtpdf qt.qt6.651.addons.qthttpserver
|
||||
wget "https://qt.mirror.constant.com/archive/online_installers/4.8/qt-online-installer-linux-x64-4.8.1.run"
|
||||
chmod +x qt-online-installer-linux-x64-4.8.1.run
|
||||
./qt-online-installer-linux-x64-4.8.1.run --no-force-installations --no-default-installations \
|
||||
--no-size-checking --default-answer --accept-licenses --confirm-command --accept-obligations \
|
||||
--email "$QT_EMAIL" --password "$QT_PASSWORD" install \
|
||||
qt.tools.cmake qt.tools.ifw.48 qt.tools.ninja qt.qt6.682.linux_gcc_64 qt.qt6.682.addons.qt5compat \
|
||||
qt.qt6.682.debug_info extensions.qtpdf.682 qt.qt6.682.addons.qthttpserver
|
||||
- run:
|
||||
name: Build linuxdeployqt
|
||||
command: |
|
||||
@@ -353,7 +417,7 @@ jobs:
|
||||
no_output_timeout: 30m
|
||||
command: |
|
||||
set -eo pipefail
|
||||
export CMAKE_PREFIX_PATH=~/Qt/6.5.1/gcc_64/lib/cmake
|
||||
export CMAKE_PREFIX_PATH=~/Qt/6.8.2/gcc_64/lib/cmake
|
||||
export PATH=$PATH:$HOME/Qt/Tools/QtInstallerFramework/4.8/bin
|
||||
export PATH=$PATH:/usr/local/cuda/bin
|
||||
ccache -o "cache_dir=${PWD}/../.ccache" -o max_size=500M -p -z
|
||||
@@ -382,6 +446,13 @@ jobs:
|
||||
when: always
|
||||
paths:
|
||||
- ../.ccache
|
||||
- run:
|
||||
name: Test installation
|
||||
command: |
|
||||
mkdir ~/Desktop
|
||||
build/upload/gpt4all-installer-linux.run --no-size-checking --default-answer --accept-licenses \
|
||||
--confirm-command \
|
||||
install gpt4all
|
||||
|
||||
build-online-chat-installer-linux:
|
||||
machine:
|
||||
@@ -418,9 +489,13 @@ jobs:
|
||||
- run:
|
||||
name: Installing Qt
|
||||
command: |
|
||||
wget "https://gpt4all.io/ci/qt-unified-linux-x64-4.6.0-online.run"
|
||||
chmod +x qt-unified-linux-x64-4.6.0-online.run
|
||||
./qt-unified-linux-x64-4.6.0-online.run --no-force-installations --no-default-installations --no-size-checking --default-answer --accept-licenses --confirm-command --accept-obligations --email $QT_EMAIL --password $QT_PASSWORD install qt.tools.cmake qt.tools.ifw.48 qt.tools.ninja qt.qt6.651.gcc_64 qt.qt6.651.qt5compat qt.qt6.651.debug_info qt.qt6.651.addons.qtpdf qt.qt6.651.addons.qthttpserver
|
||||
wget "https://qt.mirror.constant.com/archive/online_installers/4.8/qt-online-installer-linux-x64-4.8.1.run"
|
||||
chmod +x qt-online-installer-linux-x64-4.8.1.run
|
||||
./qt-online-installer-linux-x64-4.8.1.run --no-force-installations --no-default-installations \
|
||||
--no-size-checking --default-answer --accept-licenses --confirm-command --accept-obligations \
|
||||
--email "$QT_EMAIL" --password "$QT_PASSWORD" install \
|
||||
qt.tools.cmake qt.tools.ifw.48 qt.tools.ninja qt.qt6.682.linux_gcc_64 qt.qt6.682.addons.qt5compat \
|
||||
qt.qt6.682.debug_info extensions.qtpdf.682 qt.qt6.682.addons.qthttpserver
|
||||
- run:
|
||||
name: Build linuxdeployqt
|
||||
command: |
|
||||
@@ -431,7 +506,7 @@ jobs:
|
||||
no_output_timeout: 30m
|
||||
command: |
|
||||
set -eo pipefail
|
||||
export CMAKE_PREFIX_PATH=~/Qt/6.5.1/gcc_64/lib/cmake
|
||||
export CMAKE_PREFIX_PATH=~/Qt/6.8.2/gcc_64/lib/cmake
|
||||
export PATH=$PATH:$HOME/Qt/Tools/QtInstallerFramework/4.8/bin
|
||||
export PATH=$PATH:/usr/local/cuda/bin
|
||||
ccache -o "cache_dir=${PWD}/../.ccache" -o max_size=500M -p -z
|
||||
@@ -461,6 +536,14 @@ jobs:
|
||||
when: always
|
||||
paths:
|
||||
- ../.ccache
|
||||
- run:
|
||||
name: Test installation
|
||||
command: |
|
||||
mkdir ~/Desktop
|
||||
build/upload/gpt4all-installer-linux.run --no-size-checking --default-answer --accept-licenses \
|
||||
--confirm-command \
|
||||
--set-temp-repository build/_CPack_Packages/Linux/IFW/gpt4all-installer-linux/repository \
|
||||
install gpt4all
|
||||
|
||||
build-offline-chat-installer-windows:
|
||||
machine:
|
||||
@@ -484,8 +567,12 @@ jobs:
|
||||
- run:
|
||||
name: Installing Qt
|
||||
command: |
|
||||
wget.exe "https://gpt4all.io/ci/qt-unified-windows-x64-4.6.0-online.exe"
|
||||
& .\qt-unified-windows-x64-4.6.0-online.exe --no-force-installations --no-default-installations --no-size-checking --default-answer --accept-licenses --confirm-command --accept-obligations --email ${Env:QT_EMAIL} --password ${Env:QT_PASSWORD} install qt.tools.cmake qt.tools.ifw.48 qt.tools.ninja qt.qt6.651.win64_msvc2019_64 qt.qt6.651.qt5compat qt.qt6.651.debug_info qt.qt6.651.addons.qtpdf qt.qt6.651.addons.qthttpserver
|
||||
wget.exe "https://qt.mirror.constant.com/archive/online_installers/4.8/qt-online-installer-windows-x64-4.8.1.exe"
|
||||
& .\qt-online-installer-windows-x64-4.8.1.exe --no-force-installations --no-default-installations `
|
||||
--no-size-checking --default-answer --accept-licenses --confirm-command --accept-obligations `
|
||||
--email "${Env:QT_EMAIL}" --password "${Env:QT_PASSWORD}" install `
|
||||
qt.tools.cmake qt.tools.ifw.48 qt.tools.ninja qt.qt6.682.win64_msvc2022_64 qt.qt6.682.addons.qt5compat `
|
||||
qt.qt6.682.debug_info extensions.qtpdf.682 qt.qt6.682.addons.qthttpserver
|
||||
- run:
|
||||
name: Install VulkanSDK
|
||||
command: |
|
||||
@@ -526,7 +613,7 @@ jobs:
|
||||
& "C:\Qt\Tools\CMake_64\bin\cmake.exe" `
|
||||
-S ..\gpt4all-chat -B . -G Ninja `
|
||||
-DCMAKE_BUILD_TYPE=Release `
|
||||
"-DCMAKE_PREFIX_PATH:PATH=C:\Qt\6.5.1\msvc2019_64" `
|
||||
"-DCMAKE_PREFIX_PATH:PATH=C:\Qt\6.8.2\msvc2022_64" `
|
||||
"-DCMAKE_MAKE_PROGRAM:FILEPATH=C:\Qt\Tools\Ninja\ninja.exe" `
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache `
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache `
|
||||
@@ -586,6 +673,12 @@ jobs:
|
||||
AzureSignTool.exe sign -du "https://gpt4all.io/index.html" -kvu https://gpt4all.vault.azure.net -kvi "$Env:AZSignGUID" -kvs "$Env:AZSignPWD" -kvc "$Env:AZSignCertName" -kvt "$Env:AZSignTID" -tr http://timestamp.digicert.com -v "$($(Get-Location).Path)\build\upload\gpt4all-installer-win64.exe"
|
||||
- store_artifacts:
|
||||
path: build/upload
|
||||
- run:
|
||||
name: Test installation
|
||||
command: |
|
||||
build\upload\gpt4all-installer-win64.exe --no-size-checking --default-answer --accept-licenses `
|
||||
--confirm-command `
|
||||
install gpt4all
|
||||
|
||||
build-online-chat-installer-windows:
|
||||
machine:
|
||||
@@ -608,8 +701,12 @@ jobs:
|
||||
- run:
|
||||
name: Installing Qt
|
||||
command: |
|
||||
wget.exe "https://gpt4all.io/ci/qt-unified-windows-x64-4.6.0-online.exe"
|
||||
& .\qt-unified-windows-x64-4.6.0-online.exe --no-force-installations --no-default-installations --no-size-checking --default-answer --accept-licenses --confirm-command --accept-obligations --email ${Env:QT_EMAIL} --password ${Env:QT_PASSWORD} install qt.tools.cmake qt.tools.ifw.48 qt.tools.ninja qt.qt6.651.win64_msvc2019_64 qt.qt6.651.qt5compat qt.qt6.651.debug_info qt.qt6.651.addons.qtpdf qt.qt6.651.addons.qthttpserver
|
||||
wget.exe "https://qt.mirror.constant.com/archive/online_installers/4.8/qt-online-installer-windows-x64-4.8.1.exe"
|
||||
& .\qt-online-installer-windows-x64-4.8.1.exe --no-force-installations --no-default-installations `
|
||||
--no-size-checking --default-answer --accept-licenses --confirm-command --accept-obligations `
|
||||
--email "${Env:QT_EMAIL}" --password "${Env:QT_PASSWORD}" install `
|
||||
qt.tools.cmake qt.tools.ifw.48 qt.tools.ninja qt.qt6.682.win64_msvc2022_64 qt.qt6.682.addons.qt5compat `
|
||||
qt.qt6.682.debug_info extensions.qtpdf.682 qt.qt6.682.addons.qthttpserver
|
||||
- run:
|
||||
name: Install VulkanSDK
|
||||
command: |
|
||||
@@ -655,7 +752,7 @@ jobs:
|
||||
& "C:\Qt\Tools\CMake_64\bin\cmake.exe" `
|
||||
-S ..\gpt4all-chat -B . -G Ninja `
|
||||
-DCMAKE_BUILD_TYPE=Release `
|
||||
"-DCMAKE_PREFIX_PATH:PATH=C:\Qt\6.5.1\msvc2019_64" `
|
||||
"-DCMAKE_PREFIX_PATH:PATH=C:\Qt\6.8.2\msvc2022_64" `
|
||||
"-DCMAKE_MAKE_PROGRAM:FILEPATH=C:\Qt\Tools\Ninja\ninja.exe" `
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache `
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache `
|
||||
@@ -722,6 +819,13 @@ jobs:
|
||||
AzureSignTool.exe sign -du "https://gpt4all.io/index.html" -kvu https://gpt4all.vault.azure.net -kvi "$Env:AZSignGUID" -kvs "$Env:AZSignPWD" -kvc "$Env:AZSignCertName" -kvt "$Env:AZSignTID" -tr http://timestamp.digicert.com -v "$($(Get-Location).Path)/build/upload/gpt4all-installer-win64.exe"
|
||||
- store_artifacts:
|
||||
path: build/upload
|
||||
- run:
|
||||
name: Test installation
|
||||
command: |
|
||||
Expand-Archive -LiteralPath build\upload\repository.zip -DestinationPath .
|
||||
build\upload\gpt4all-installer-win64.exe --no-size-checking --default-answer --accept-licenses `
|
||||
--confirm-command --set-temp-repository repository `
|
||||
install gpt4all
|
||||
|
||||
build-offline-chat-installer-windows-arm:
|
||||
machine:
|
||||
@@ -745,13 +849,13 @@ jobs:
|
||||
- run:
|
||||
name: Installing Qt
|
||||
command: |
|
||||
wget.exe "https://gpt4all.io/ci/qt-unified-windows-x64-4.6.0-online.exe"
|
||||
# note: need x86_64 toolset for windeployqt.exe, which isn't provided with the ARM64 cross toolset
|
||||
& .\qt-unified-windows-x64-4.6.0-online.exe --no-force-installations --no-default-installations `
|
||||
wget.exe "https://qt.mirror.constant.com/archive/online_installers/4.8/qt-online-installer-windows-x64-4.8.1.exe"
|
||||
& .\qt-online-installer-windows-x64-4.8.1.exe --no-force-installations --no-default-installations `
|
||||
--no-size-checking --default-answer --accept-licenses --confirm-command --accept-obligations `
|
||||
--email ${Env:QT_EMAIL} --password ${Env:QT_PASSWORD} install `
|
||||
qt.tools.cmake qt.tools.ifw.48 qt.tools.ninja qt.qt6.651.win64_msvc2019_64 `
|
||||
qt.qt6.651.win64_msvc2019_arm64 qt.qt6.651.qt5compat qt.qt6.651.debug_info qt.qt6.651.addons.qthttpserver
|
||||
--email "${Env:QT_EMAIL}" --password "${Env:QT_PASSWORD}" install `
|
||||
qt.tools.cmake qt.tools.ifw.48 qt.tools.ninja qt.qt6.682.win64_msvc2022_64 `
|
||||
qt.qt6.682.win64_msvc2022_arm64_cross_compiled qt.qt6.682.addons.qt5compat qt.qt6.682.debug_info `
|
||||
qt.qt6.682.addons.qthttpserver
|
||||
- run:
|
||||
name: "Install Dotnet 8"
|
||||
command: |
|
||||
@@ -781,14 +885,14 @@ jobs:
|
||||
& "C:\Qt\Tools\CMake_64\bin\cmake.exe" `
|
||||
-S ..\gpt4all-chat -B . -G Ninja `
|
||||
-DCMAKE_BUILD_TYPE=Release `
|
||||
"-DCMAKE_PREFIX_PATH:PATH=C:\Qt\6.5.1\msvc2019_arm64" `
|
||||
"-DCMAKE_PREFIX_PATH:PATH=C:\Qt\6.8.2\msvc2022_arm64" `
|
||||
"-DCMAKE_MAKE_PROGRAM:FILEPATH=C:\Qt\Tools\Ninja\ninja.exe" `
|
||||
"-DCMAKE_TOOLCHAIN_FILE=C:\Qt\6.5.1\msvc2019_arm64\lib\cmake\Qt6\qt.toolchain.cmake" `
|
||||
"-DCMAKE_TOOLCHAIN_FILE=C:\Qt\6.8.2\msvc2022_arm64\lib\cmake\Qt6\qt.toolchain.cmake" `
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache `
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache `
|
||||
-DLLMODEL_CUDA=OFF `
|
||||
-DLLMODEL_KOMPUTE=OFF `
|
||||
"-DWINDEPLOYQT=C:\Qt\6.5.1\msvc2019_64\bin\windeployqt.exe;--qtpaths;C:\Qt\6.5.1\msvc2019_arm64\bin\qtpaths.bat" `
|
||||
"-DWINDEPLOYQT=C:\Qt\6.8.2\msvc2022_64\bin\windeployqt.exe;--qtpaths;C:\Qt\6.8.2\msvc2022_arm64\bin\qtpaths.bat" `
|
||||
-DGPT4ALL_TEST=OFF `
|
||||
-DGPT4ALL_OFFLINE_INSTALLER=ON
|
||||
& "C:\Qt\Tools\Ninja\ninja.exe"
|
||||
@@ -844,6 +948,12 @@ jobs:
|
||||
AzureSignTool.exe sign -du "https://gpt4all.io/index.html" -kvu https://gpt4all.vault.azure.net -kvi "$Env:AZSignGUID" -kvs "$Env:AZSignPWD" -kvc "$Env:AZSignCertName" -kvt "$Env:AZSignTID" -tr http://timestamp.digicert.com -v "$($(Get-Location).Path)\build\upload\gpt4all-installer-win64-arm.exe"
|
||||
- store_artifacts:
|
||||
path: build/upload
|
||||
- run:
|
||||
name: Test installation
|
||||
command: |
|
||||
build\upload\gpt4all-installer-win64-arm.exe --no-size-checking --default-answer --accept-licenses `
|
||||
--confirm-command `
|
||||
install gpt4all
|
||||
|
||||
build-online-chat-installer-windows-arm:
|
||||
machine:
|
||||
@@ -866,13 +976,13 @@ jobs:
|
||||
- run:
|
||||
name: Installing Qt
|
||||
command: |
|
||||
wget.exe "https://gpt4all.io/ci/qt-unified-windows-x64-4.6.0-online.exe"
|
||||
# note: need x86_64 toolset for windeployqt.exe, which isn't provided with the ARM64 cross toolset
|
||||
& .\qt-unified-windows-x64-4.6.0-online.exe --no-force-installations --no-default-installations `
|
||||
wget.exe "https://qt.mirror.constant.com/archive/online_installers/4.8/qt-online-installer-windows-x64-4.8.1.exe"
|
||||
& .\qt-online-installer-windows-x64-4.8.1.exe --no-force-installations --no-default-installations `
|
||||
--no-size-checking --default-answer --accept-licenses --confirm-command --accept-obligations `
|
||||
--email ${Env:QT_EMAIL} --password ${Env:QT_PASSWORD} install `
|
||||
qt.tools.cmake qt.tools.ifw.48 qt.tools.ninja qt.qt6.651.win64_msvc2019_64 `
|
||||
qt.qt6.651.win64_msvc2019_arm64 qt.qt6.651.qt5compat qt.qt6.651.debug_info qt.qt6.651.addons.qthttpserver
|
||||
--email "${Env:QT_EMAIL}" --password "${Env:QT_PASSWORD}" install `
|
||||
qt.tools.cmake qt.tools.ifw.48 qt.tools.ninja qt.qt6.682.win64_msvc2022_64 `
|
||||
qt.qt6.682.win64_msvc2022_arm64_cross_compiled qt.qt6.682.addons.qt5compat qt.qt6.682.debug_info `
|
||||
qt.qt6.682.addons.qthttpserver
|
||||
- run:
|
||||
name: "Install Dotnet 8"
|
||||
command: |
|
||||
@@ -907,14 +1017,14 @@ jobs:
|
||||
& "C:\Qt\Tools\CMake_64\bin\cmake.exe" `
|
||||
-S ..\gpt4all-chat -B . -G Ninja `
|
||||
-DCMAKE_BUILD_TYPE=Release `
|
||||
"-DCMAKE_PREFIX_PATH:PATH=C:\Qt\6.5.1\msvc2019_arm64" `
|
||||
"-DCMAKE_PREFIX_PATH:PATH=C:\Qt\6.8.2\msvc2022_arm64" `
|
||||
"-DCMAKE_MAKE_PROGRAM:FILEPATH=C:\Qt\Tools\Ninja\ninja.exe" `
|
||||
"-DCMAKE_TOOLCHAIN_FILE=C:\Qt\6.5.1\msvc2019_arm64\lib\cmake\Qt6\qt.toolchain.cmake" `
|
||||
"-DCMAKE_TOOLCHAIN_FILE=C:\Qt\6.8.2\msvc2022_arm64\lib\cmake\Qt6\qt.toolchain.cmake" `
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache `
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache `
|
||||
-DLLMODEL_CUDA=OFF `
|
||||
-DLLMODEL_KOMPUTE=OFF `
|
||||
"-DWINDEPLOYQT=C:\Qt\6.5.1\msvc2019_64\bin\windeployqt.exe;--qtpaths;C:\Qt\6.5.1\msvc2019_arm64\bin\qtpaths.bat" `
|
||||
"-DWINDEPLOYQT=C:\Qt\6.8.2\msvc2022_64\bin\windeployqt.exe;--qtpaths;C:\Qt\6.8.2\msvc2022_arm64\bin\qtpaths.bat" `
|
||||
-DGPT4ALL_TEST=OFF `
|
||||
-DGPT4ALL_OFFLINE_INSTALLER=OFF
|
||||
& "C:\Qt\Tools\Ninja\ninja.exe"
|
||||
@@ -977,6 +1087,13 @@ jobs:
|
||||
AzureSignTool.exe sign -du "https://gpt4all.io/index.html" -kvu https://gpt4all.vault.azure.net -kvi "$Env:AZSignGUID" -kvs "$Env:AZSignPWD" -kvc "$Env:AZSignCertName" -kvt "$Env:AZSignTID" -tr http://timestamp.digicert.com -v "$($(Get-Location).Path)/build/upload/gpt4all-installer-win64-arm.exe"
|
||||
- store_artifacts:
|
||||
path: build/upload
|
||||
- run:
|
||||
name: Test installation
|
||||
command: |
|
||||
Expand-Archive -LiteralPath build\upload\repository.zip -DestinationPath .
|
||||
build\upload\gpt4all-installer-win64-arm.exe --no-size-checking --default-answer --accept-licenses `
|
||||
--confirm-command --set-temp-repository repository `
|
||||
install gpt4all
|
||||
|
||||
build-gpt4all-chat-linux:
|
||||
machine:
|
||||
@@ -1013,14 +1130,18 @@ jobs:
|
||||
- run:
|
||||
name: Installing Qt
|
||||
command: |
|
||||
wget "https://gpt4all.io/ci/qt-unified-linux-x64-4.6.0-online.run"
|
||||
chmod +x qt-unified-linux-x64-4.6.0-online.run
|
||||
./qt-unified-linux-x64-4.6.0-online.run --no-force-installations --no-default-installations --no-size-checking --default-answer --accept-licenses --confirm-command --accept-obligations --email $QT_EMAIL --password $QT_PASSWORD install qt.tools.cmake qt.tools.ifw.48 qt.tools.ninja qt.qt6.651.gcc_64 qt.qt6.651.qt5compat qt.qt6.651.debug_info qt.qt6.651.addons.qtpdf qt.qt6.651.addons.qthttpserver
|
||||
wget "https://qt.mirror.constant.com/archive/online_installers/4.8/qt-online-installer-linux-x64-4.8.1.run"
|
||||
chmod +x qt-online-installer-linux-x64-4.8.1.run
|
||||
./qt-online-installer-linux-x64-4.8.1.run --no-force-installations --no-default-installations \
|
||||
--no-size-checking --default-answer --accept-licenses --confirm-command --accept-obligations \
|
||||
--email "$QT_EMAIL" --password "$QT_PASSWORD" install \
|
||||
qt.tools.cmake qt.tools.ifw.48 qt.tools.ninja qt.qt6.682.linux_gcc_64 qt.qt6.682.addons.qt5compat \
|
||||
qt.qt6.682.debug_info extensions.qtpdf.682 qt.qt6.682.addons.qthttpserver
|
||||
- run:
|
||||
name: Build
|
||||
no_output_timeout: 30m
|
||||
command: |
|
||||
export CMAKE_PREFIX_PATH=~/Qt/6.5.1/gcc_64/lib/cmake
|
||||
export CMAKE_PREFIX_PATH=~/Qt/6.8.2/gcc_64/lib/cmake
|
||||
export PATH=$PATH:/usr/local/cuda/bin
|
||||
ccache -o "cache_dir=${PWD}/../.ccache" -o max_size=500M -p -z
|
||||
~/Qt/Tools/CMake/bin/cmake \
|
||||
@@ -1061,8 +1182,12 @@ jobs:
|
||||
- run:
|
||||
name: Installing Qt
|
||||
command: |
|
||||
wget.exe "https://gpt4all.io/ci/qt-unified-windows-x64-4.6.0-online.exe"
|
||||
& .\qt-unified-windows-x64-4.6.0-online.exe --no-force-installations --no-default-installations --no-size-checking --default-answer --accept-licenses --confirm-command --accept-obligations --email ${Env:QT_EMAIL} --password ${Env:QT_PASSWORD} install qt.tools.cmake qt.tools.ifw.48 qt.tools.ninja qt.qt6.651.win64_msvc2019_64 qt.qt6.651.qt5compat qt.qt6.651.debug_info qt.qt6.651.addons.qtpdf qt.qt6.651.addons.qthttpserver
|
||||
wget.exe "https://qt.mirror.constant.com/archive/online_installers/4.8/qt-online-installer-windows-x64-4.8.1.exe"
|
||||
& .\qt-online-installer-windows-x64-4.8.1.exe --no-force-installations --no-default-installations `
|
||||
--no-size-checking --default-answer --accept-licenses --confirm-command --accept-obligations `
|
||||
--email "${Env:QT_EMAIL}" --password "${Env:QT_PASSWORD}" install `
|
||||
qt.tools.cmake qt.tools.ifw.48 qt.tools.ninja qt.qt6.682.win64_msvc2022_64 qt.qt6.682.addons.qt5compat `
|
||||
qt.qt6.682.debug_info extensions.qtpdf.682 qt.qt6.682.addons.qthttpserver
|
||||
- run:
|
||||
name: Install VulkanSDK
|
||||
command: |
|
||||
@@ -1087,7 +1212,7 @@ jobs:
|
||||
& "C:\Qt\Tools\CMake_64\bin\cmake.exe" `
|
||||
-S gpt4all-chat -B build -G Ninja `
|
||||
-DCMAKE_BUILD_TYPE=Release `
|
||||
"-DCMAKE_PREFIX_PATH:PATH=C:\Qt\6.5.1\msvc2019_64" `
|
||||
"-DCMAKE_PREFIX_PATH:PATH=C:\Qt\6.8.2\msvc2022_64" `
|
||||
"-DCMAKE_MAKE_PROGRAM:FILEPATH=C:\Qt\Tools\Ninja\ninja.exe" `
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache `
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache `
|
||||
@@ -1123,10 +1248,15 @@ jobs:
|
||||
- run:
|
||||
name: Installing Qt
|
||||
command: |
|
||||
wget "https://gpt4all.io/ci/qt-unified-macOS-x64-4.6.0-online.dmg"
|
||||
hdiutil attach qt-unified-macOS-x64-4.6.0-online.dmg
|
||||
/Volumes/qt-unified-macOS-x64-4.6.0-online/qt-unified-macOS-x64-4.6.0-online.app/Contents/MacOS/qt-unified-macOS-x64-4.6.0-online --no-force-installations --no-default-installations --no-size-checking --default-answer --accept-licenses --confirm-command --accept-obligations --email $QT_EMAIL --password $QT_PASSWORD install qt.tools.cmake qt.tools.ifw.48 qt.tools.ninja qt.qt6.651.clang_64 qt.qt6.651.qt5compat qt.qt6.651.debug_info qt.qt6.651.addons.qtpdf qt.qt6.651.addons.qthttpserver
|
||||
hdiutil detach /Volumes/qt-unified-macOS-x64-4.6.0-online
|
||||
wget "https://qt.mirror.constant.com/archive/online_installers/4.8/qt-online-installer-macOS-x64-4.8.1.dmg"
|
||||
hdiutil attach qt-online-installer-macOS-x64-4.8.1.dmg
|
||||
/Volumes/qt-online-installer-macOS-x64-4.8.1/qt-online-installer-macOS-x64-4.8.1.app/Contents/MacOS/qt-online-installer-macOS-x64-4.8.1 \
|
||||
--no-force-installations --no-default-installations --no-size-checking --default-answer \
|
||||
--accept-licenses --confirm-command --accept-obligations --email "$QT_EMAIL" --password "$QT_PASSWORD" \
|
||||
install \
|
||||
qt.tools.cmake qt.tools.ifw.48 qt.tools.ninja qt.qt6.682.clang_64 qt.qt6.682.addons.qt5compat \
|
||||
extensions.qtpdf.682 qt.qt6.682.addons.qthttpserver
|
||||
hdiutil detach /Volumes/qt-online-installer-macOS-x64-4.8.1
|
||||
- run:
|
||||
name: Build
|
||||
no_output_timeout: 30m
|
||||
@@ -1135,7 +1265,7 @@ jobs:
|
||||
~/Qt/Tools/CMake/CMake.app/Contents/bin/cmake \
|
||||
-S gpt4all-chat -B build -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_PREFIX_PATH:PATH=~/Qt/6.5.1/macos/lib/cmake \
|
||||
-DCMAKE_PREFIX_PATH:PATH=~/Qt/6.8.2/macos/lib/cmake \
|
||||
-DCMAKE_MAKE_PROGRAM:FILEPATH=~/Qt/Tools/Ninja/ninja \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
@@ -1169,7 +1299,7 @@ jobs:
|
||||
cd gpt4all-bindings/typescript
|
||||
npm run docs:build
|
||||
|
||||
build-py-docs:
|
||||
deploy-docs:
|
||||
docker:
|
||||
- image: circleci/python:3.8
|
||||
steps:
|
||||
@@ -1373,7 +1503,7 @@ jobs:
|
||||
paths:
|
||||
- "*.whl"
|
||||
|
||||
publish-wheels:
|
||||
deploy-wheels:
|
||||
docker:
|
||||
- image: circleci/python:3.8
|
||||
steps:
|
||||
@@ -1670,7 +1800,7 @@ jobs:
|
||||
- prebuilds/win32-x64/*.node
|
||||
- runtimes/win32-x64/*-*.dll
|
||||
|
||||
prepare-npm-pkg:
|
||||
deploy-npm-pkg:
|
||||
docker:
|
||||
- image: cimg/base:stable
|
||||
steps:
|
||||
@@ -1726,11 +1856,27 @@ jobs:
|
||||
npm set //registry.npmjs.org/:_authToken=$NPM_TOKEN
|
||||
npm publish
|
||||
|
||||
main_only: &main_only
|
||||
# only run a job on the main branch
|
||||
job_only_main: &job_only_main
|
||||
filters:
|
||||
branches:
|
||||
only: main
|
||||
|
||||
# allow a job to run on tags as well as commits
|
||||
job_allow_tags: &job_allow_tags
|
||||
filters:
|
||||
tags:
|
||||
only:
|
||||
- /.*/
|
||||
|
||||
# standard chat workflow filter
|
||||
workflow-when-chat-requested: &workflow-when-chat-requested
|
||||
when:
|
||||
and:
|
||||
- or: [ << pipeline.parameters.run-all-workflows >>, << pipeline.parameters.run-chat-workflow >> ]
|
||||
- not:
|
||||
equal: [ << pipeline.trigger_source >>, scheduled_pipeline ]
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
noop:
|
||||
@@ -1741,23 +1887,143 @@ workflows:
|
||||
- << pipeline.parameters.run-python-workflow >>
|
||||
- << pipeline.parameters.run-ts-workflow >>
|
||||
- << pipeline.parameters.run-chat-workflow >>
|
||||
- equal: [ << pipeline.trigger_source >>, scheduled_pipeline ]
|
||||
jobs:
|
||||
- noop
|
||||
build-chat-offline-installers:
|
||||
schedule:
|
||||
# only run when scheduled by CircleCI
|
||||
when:
|
||||
or:
|
||||
- << pipeline.parameters.run-all-workflows >>
|
||||
- << pipeline.parameters.run-chat-workflow >>
|
||||
equal: [ << pipeline.trigger_source >>, scheduled_pipeline ]
|
||||
jobs:
|
||||
- hold:
|
||||
- build-offline-chat-installer-macos:
|
||||
context: gpt4all
|
||||
- build-offline-chat-installer-windows:
|
||||
context: gpt4all
|
||||
- build-offline-chat-installer-windows-arm:
|
||||
context: gpt4all
|
||||
- build-offline-chat-installer-linux:
|
||||
context: gpt4all
|
||||
- sign-online-chat-installer-macos:
|
||||
context: gpt4all
|
||||
requires:
|
||||
- build-online-chat-installer-macos
|
||||
- notarize-online-chat-installer-macos:
|
||||
context: gpt4all
|
||||
requires:
|
||||
- sign-online-chat-installer-macos
|
||||
- sign-online-chat-installer-windows:
|
||||
context: gpt4all
|
||||
requires:
|
||||
- build-online-chat-installer-windows
|
||||
- sign-online-chat-installer-windows-arm:
|
||||
context: gpt4all
|
||||
requires:
|
||||
- build-online-chat-installer-windows-arm
|
||||
build-chat-installers-release:
|
||||
# only run on main branch tags that start with 'v' and a digit
|
||||
when:
|
||||
and:
|
||||
- matches: { pattern: '^v\d.*', value: << pipeline.git.tag >> }
|
||||
- not:
|
||||
equal: [ << pipeline.trigger_source >>, scheduled_pipeline ]
|
||||
jobs:
|
||||
- validate-commit-on-main:
|
||||
<<: *job_allow_tags
|
||||
- build-offline-chat-installer-macos:
|
||||
<<: *job_allow_tags
|
||||
context: gpt4all
|
||||
requires:
|
||||
- validate-commit-on-main
|
||||
- build-offline-chat-installer-windows:
|
||||
<<: *job_allow_tags
|
||||
context: gpt4all
|
||||
requires:
|
||||
- validate-commit-on-main
|
||||
- build-offline-chat-installer-windows-arm:
|
||||
<<: *job_allow_tags
|
||||
context: gpt4all
|
||||
requires:
|
||||
- validate-commit-on-main
|
||||
- build-offline-chat-installer-linux:
|
||||
<<: *job_allow_tags
|
||||
context: gpt4all
|
||||
requires:
|
||||
- validate-commit-on-main
|
||||
- sign-offline-chat-installer-macos:
|
||||
<<: *job_allow_tags
|
||||
context: gpt4all
|
||||
requires:
|
||||
- build-offline-chat-installer-macos
|
||||
- notarize-offline-chat-installer-macos:
|
||||
<<: *job_allow_tags
|
||||
context: gpt4all
|
||||
requires:
|
||||
- sign-offline-chat-installer-macos
|
||||
- sign-offline-chat-installer-windows:
|
||||
<<: *job_allow_tags
|
||||
context: gpt4all
|
||||
requires:
|
||||
- build-offline-chat-installer-windows
|
||||
- sign-offline-chat-installer-windows-arm:
|
||||
<<: *job_allow_tags
|
||||
context: gpt4all
|
||||
requires:
|
||||
- build-offline-chat-installer-windows-arm
|
||||
- build-online-chat-installer-macos:
|
||||
<<: *job_allow_tags
|
||||
context: gpt4all
|
||||
requires:
|
||||
- validate-commit-on-main
|
||||
- build-online-chat-installer-windows:
|
||||
<<: *job_allow_tags
|
||||
context: gpt4all
|
||||
requires:
|
||||
- validate-commit-on-main
|
||||
- build-online-chat-installer-windows-arm:
|
||||
<<: *job_allow_tags
|
||||
context: gpt4all
|
||||
requires:
|
||||
- validate-commit-on-main
|
||||
- build-online-chat-installer-linux:
|
||||
<<: *job_allow_tags
|
||||
context: gpt4all
|
||||
requires:
|
||||
- validate-commit-on-main
|
||||
- sign-online-chat-installer-macos:
|
||||
<<: *job_allow_tags
|
||||
context: gpt4all
|
||||
requires:
|
||||
- build-online-chat-installer-macos
|
||||
- notarize-online-chat-installer-macos:
|
||||
<<: *job_allow_tags
|
||||
context: gpt4all
|
||||
requires:
|
||||
- sign-online-chat-installer-macos
|
||||
- sign-online-chat-installer-windows:
|
||||
<<: *job_allow_tags
|
||||
context: gpt4all
|
||||
requires:
|
||||
- build-online-chat-installer-windows
|
||||
- sign-online-chat-installer-windows-arm:
|
||||
<<: *job_allow_tags
|
||||
context: gpt4all
|
||||
requires:
|
||||
- build-online-chat-installer-windows-arm
|
||||
build-chat-offline-installers:
|
||||
<<: *workflow-when-chat-requested
|
||||
jobs:
|
||||
- build-hold:
|
||||
type: approval
|
||||
- sign-hold:
|
||||
type: approval
|
||||
- build-offline-chat-installer-macos:
|
||||
context: gpt4all
|
||||
requires:
|
||||
- hold
|
||||
- build-hold
|
||||
- sign-offline-chat-installer-macos:
|
||||
context: gpt4all
|
||||
requires:
|
||||
- sign-hold
|
||||
- build-offline-chat-installer-macos
|
||||
- notarize-offline-chat-installer-macos:
|
||||
context: gpt4all
|
||||
@@ -1766,77 +2032,69 @@ workflows:
|
||||
- build-offline-chat-installer-windows:
|
||||
context: gpt4all
|
||||
requires:
|
||||
- hold
|
||||
- build-hold
|
||||
- sign-offline-chat-installer-windows:
|
||||
context: gpt4all
|
||||
requires:
|
||||
- sign-hold
|
||||
- build-offline-chat-installer-windows
|
||||
- build-offline-chat-installer-windows-arm:
|
||||
context: gpt4all
|
||||
requires:
|
||||
- hold
|
||||
- build-hold
|
||||
- sign-offline-chat-installer-windows-arm:
|
||||
context: gpt4all
|
||||
requires:
|
||||
- sign-hold
|
||||
- build-offline-chat-installer-windows-arm
|
||||
- build-offline-chat-installer-linux:
|
||||
context: gpt4all
|
||||
requires:
|
||||
- hold
|
||||
- build-hold
|
||||
build-chat-online-installers:
|
||||
when:
|
||||
or:
|
||||
- << pipeline.parameters.run-all-workflows >>
|
||||
- << pipeline.parameters.run-chat-workflow >>
|
||||
<<: *workflow-when-chat-requested
|
||||
jobs:
|
||||
- hold:
|
||||
<<: *main_only
|
||||
- build-hold:
|
||||
type: approval
|
||||
- sign-hold:
|
||||
type: approval
|
||||
- build-online-chat-installer-macos:
|
||||
<<: *main_only
|
||||
context: gpt4all
|
||||
requires:
|
||||
- hold
|
||||
- build-hold
|
||||
- sign-online-chat-installer-macos:
|
||||
<<: *main_only
|
||||
context: gpt4all
|
||||
requires:
|
||||
- sign-hold
|
||||
- build-online-chat-installer-macos
|
||||
- notarize-online-chat-installer-macos:
|
||||
<<: *main_only
|
||||
context: gpt4all
|
||||
requires:
|
||||
- sign-online-chat-installer-macos
|
||||
- build-online-chat-installer-windows:
|
||||
<<: *main_only
|
||||
context: gpt4all
|
||||
requires:
|
||||
- hold
|
||||
- build-hold
|
||||
- sign-online-chat-installer-windows:
|
||||
<<: *main_only
|
||||
context: gpt4all
|
||||
requires:
|
||||
- sign-hold
|
||||
- build-online-chat-installer-windows
|
||||
- build-online-chat-installer-windows-arm:
|
||||
<<: *main_only
|
||||
context: gpt4all
|
||||
requires:
|
||||
- hold
|
||||
- build-hold
|
||||
- sign-online-chat-installer-windows-arm:
|
||||
<<: *main_only
|
||||
context: gpt4all
|
||||
requires:
|
||||
- sign-hold
|
||||
- build-online-chat-installer-windows-arm
|
||||
- build-online-chat-installer-linux:
|
||||
<<: *main_only
|
||||
context: gpt4all
|
||||
requires:
|
||||
- hold
|
||||
- build-hold
|
||||
build-and-test-gpt4all-chat:
|
||||
when:
|
||||
or:
|
||||
- << pipeline.parameters.run-all-workflows >>
|
||||
- << pipeline.parameters.run-chat-workflow >>
|
||||
<<: *workflow-when-chat-requested
|
||||
jobs:
|
||||
- hold:
|
||||
type: approval
|
||||
@@ -1854,23 +2112,25 @@ workflows:
|
||||
- hold
|
||||
deploy-docs:
|
||||
when:
|
||||
or:
|
||||
- << pipeline.parameters.run-all-workflows >>
|
||||
- << pipeline.parameters.run-python-workflow >>
|
||||
and:
|
||||
- equal: [ << pipeline.git.branch >>, main ]
|
||||
- or:
|
||||
- << pipeline.parameters.run-all-workflows >>
|
||||
- << pipeline.parameters.run-python-workflow >>
|
||||
- not:
|
||||
equal: [ << pipeline.trigger_source >>, scheduled_pipeline ]
|
||||
jobs:
|
||||
- build-ts-docs:
|
||||
<<: *main_only
|
||||
- build-py-docs:
|
||||
<<: *main_only
|
||||
- deploy-docs:
|
||||
context: gpt4all
|
||||
build-python:
|
||||
when:
|
||||
or:
|
||||
- << pipeline.parameters.run-all-workflows >>
|
||||
- << pipeline.parameters.run-python-workflow >>
|
||||
and:
|
||||
- or: [ << pipeline.parameters.run-all-workflows >>, << pipeline.parameters.run-python-workflow >> ]
|
||||
- not:
|
||||
equal: [ << pipeline.trigger_source >>, scheduled_pipeline ]
|
||||
jobs:
|
||||
- pypi-hold:
|
||||
<<: *main_only
|
||||
<<: *job_only_main
|
||||
type: approval
|
||||
- hold:
|
||||
type: approval
|
||||
@@ -1883,8 +2143,8 @@ workflows:
|
||||
- build-py-windows:
|
||||
requires:
|
||||
- hold
|
||||
- publish-wheels:
|
||||
<<: *main_only
|
||||
- deploy-wheels:
|
||||
<<: *job_only_main
|
||||
context: gpt4all
|
||||
requires:
|
||||
- pypi-hold
|
||||
@@ -1893,36 +2153,29 @@ workflows:
|
||||
- build-py-macos
|
||||
build-bindings:
|
||||
when:
|
||||
or:
|
||||
- << pipeline.parameters.run-all-workflows >>
|
||||
- << pipeline.parameters.run-python-workflow >>
|
||||
- << pipeline.parameters.run-ts-workflow >>
|
||||
and:
|
||||
- or: [ << pipeline.parameters.run-all-workflows >>, << pipeline.parameters.run-ts-workflow >> ]
|
||||
- not:
|
||||
equal: [ << pipeline.trigger_source >>, scheduled_pipeline ]
|
||||
jobs:
|
||||
- hold:
|
||||
- backend-hold:
|
||||
type: approval
|
||||
- nodejs-hold:
|
||||
type: approval
|
||||
- npm-hold:
|
||||
<<: *main_only
|
||||
<<: *job_only_main
|
||||
type: approval
|
||||
- docs-hold:
|
||||
type: approval
|
||||
- build-bindings-backend-linux:
|
||||
requires:
|
||||
- hold
|
||||
- backend-hold
|
||||
- build-bindings-backend-macos:
|
||||
requires:
|
||||
- hold
|
||||
- backend-hold
|
||||
- build-bindings-backend-windows:
|
||||
requires:
|
||||
- hold
|
||||
|
||||
# NodeJs Jobs
|
||||
- prepare-npm-pkg:
|
||||
<<: *main_only
|
||||
requires:
|
||||
- npm-hold
|
||||
- build-nodejs-linux
|
||||
- build-nodejs-windows
|
||||
- build-nodejs-macos
|
||||
- backend-hold
|
||||
- build-nodejs-linux:
|
||||
requires:
|
||||
- nodejs-hold
|
||||
@@ -1935,3 +2188,13 @@ workflows:
|
||||
requires:
|
||||
- nodejs-hold
|
||||
- build-bindings-backend-macos
|
||||
- build-ts-docs:
|
||||
requires:
|
||||
- docs-hold
|
||||
- deploy-npm-pkg:
|
||||
<<: *job_only_main
|
||||
requires:
|
||||
- npm-hold
|
||||
- build-nodejs-linux
|
||||
- build-nodejs-windows
|
||||
- build-nodejs-macos
|
||||
|
||||
12
.gitmodules
vendored
12
.gitmodules
vendored
@@ -17,9 +17,9 @@
|
||||
[submodule "gpt4all-chat/deps/QXlsx"]
|
||||
path = gpt4all-chat/deps/QXlsx
|
||||
url = https://github.com/nomic-ai/QXlsx.git
|
||||
[submodule "gpt4all-chat/deps/Jinja2Cpp"]
|
||||
path = gpt4all-chat/deps/Jinja2Cpp
|
||||
url = https://github.com/nomic-ai/jinja2cpp.git
|
||||
[submodule "gpt4all-chat/deps/rapidjson"]
|
||||
path = gpt4all-chat/deps/rapidjson
|
||||
url = https://github.com/nomic-ai/rapidjson.git
|
||||
[submodule "gpt4all-chat/deps/minja"]
|
||||
path = gpt4all-chat/deps/minja
|
||||
url = https://github.com/nomic-ai/minja.git
|
||||
[submodule "gpt4all-chat/deps/json"]
|
||||
path = gpt4all-chat/deps/json
|
||||
url = https://github.com/nlohmann/json.git
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<h1 align="center">GPT4All</h1>
|
||||
|
||||
<p align="center">
|
||||
Now with support for DeepSeek R1 Distillations
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.nomic.ai/gpt4all">Website</a> • <a href="https://docs.gpt4all.io">Documentation</a> • <a href="https://discord.gg/mGZE39AS3e">Discord</a> • <a href="https://www.youtube.com/watch?v=gQcZDXRVJok">YouTube Tutorial</a>
|
||||
</p>
|
||||
|
||||
Submodule gpt4all-backend/deps/llama.cpp-mainline updated: 58a55efc4a...3ebb3603e8
@@ -53,6 +53,7 @@ static const std::vector<const char *> KNOWN_ARCHES {
|
||||
"gpt2",
|
||||
// "gptj", -- no inference code
|
||||
"gptneox",
|
||||
"granitemoe",
|
||||
"mpt",
|
||||
"baichuan",
|
||||
"starcoder",
|
||||
@@ -80,6 +81,7 @@ static const std::vector<const char *> KNOWN_ARCHES {
|
||||
"command-r",
|
||||
// "dbrx", -- 16x12B parameters
|
||||
"olmo",
|
||||
"olmoe",
|
||||
"openelm",
|
||||
// "arctic", -- 10B+128x3.66B parameters
|
||||
"deepseek2",
|
||||
|
||||
@@ -4,11 +4,45 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
|
||||
## [3.9.0] - 2025-02-04
|
||||
|
||||
### Added
|
||||
- Whitelist OLMoE and Granite MoE model architectures (no Vulkan) (by [@ThiloteE](https://github.com/ThiloteE) in [#3449](https://github.com/nomic-ai/gpt4all/pull/3449))
|
||||
|
||||
### Fixed
|
||||
- Fix "index N is not a prompt" when using LocalDocs with reasoning ([#3451](https://github.com/nomic-ai/gpt4all/pull/3451)
|
||||
- Work around rendering artifacts on Snapdragon SoCs with Windows ([#3450](https://github.com/nomic-ai/gpt4all/pull/3450))
|
||||
- Prevent DeepSeek-R1 reasoning from appearing in chat names and follow-up questions ([#3458](https://github.com/nomic-ai/gpt4all/pull/3458))
|
||||
- Fix LocalDocs crash on Windows ARM when reading PDFs ([#3460](https://github.com/nomic-ai/gpt4all/pull/3460))
|
||||
- Fix UI freeze when chat template is `{#` ([#3446](https://github.com/nomic-ai/gpt4all/pull/3446))
|
||||
|
||||
## [3.8.0] - 2025-01-30
|
||||
|
||||
### Added
|
||||
- Support DeepSeek-R1 Qwen models ([#3431](https://github.com/nomic-ai/gpt4all/pull/3431))
|
||||
- Support for think tags in the GUI ([#3440](https://github.com/nomic-ai/gpt4all/pull/3440))
|
||||
- Support specifying SHA256 hash in models3.json instead of MD5 ([#3437](https://github.com/nomic-ai/gpt4all/pull/3437))
|
||||
|
||||
### Changed
|
||||
- Use minja instead of Jinja2Cpp for significantly improved template compatibility ([#3433](https://github.com/nomic-ai/gpt4all/pull/3433))
|
||||
|
||||
### Fixed
|
||||
- Fix regression while using localdocs with server API ([#3410](https://github.com/nomic-ai/gpt4all/pull/3410))
|
||||
- Don't show system messages in server chat view ([#3411](https://github.com/nomic-ai/gpt4all/pull/3411))
|
||||
- Fix `codesign --verify` failure on macOS ([#3413](https://github.com/nomic-ai/gpt4all/pull/3413))
|
||||
- Code Interpreter: Fix console.log not accepting a single string after v3.7.0 ([#3426](https://github.com/nomic-ai/gpt4all/pull/3426))
|
||||
- Fix Phi 3.1 Mini 128K Instruct template (by [@ThiloteE](https://github.com/ThiloteE) in [#3412](https://github.com/nomic-ai/gpt4all/pull/3412))
|
||||
- Don't block the gui thread for reasoning ([#3435](https://github.com/nomic-ai/gpt4all/pull/3435))
|
||||
- Fix corruption of unicode in output of reasoning models ([#3443](https://github.com/nomic-ai/gpt4all/pull/3443))
|
||||
|
||||
## [3.7.0] - 2025-01-21
|
||||
|
||||
### Added
|
||||
- Add support for the Windows ARM64 target platform (CPU-only) ([#3385](https://github.com/nomic-ai/gpt4all/pull/3385))
|
||||
|
||||
### Changed
|
||||
- Update from Qt 6.5.1 to 6.8.1 ([#3386](https://github.com/nomic-ai/gpt4all/pull/3386))
|
||||
|
||||
### Fixed
|
||||
- Fix the timeout error in code interpreter ([#3369](https://github.com/nomic-ai/gpt4all/pull/3369))
|
||||
- Fix code interpreter console.log not accepting multiple arguments ([#3371](https://github.com/nomic-ai/gpt4all/pull/3371))
|
||||
@@ -17,7 +51,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
- Save chats on quit, even if the window isn't closed first ([#3387](https://github.com/nomic-ai/gpt4all/pull/3387))
|
||||
- Add chat template replacements for five new models and fix EM German Mistral ([#3393](https://github.com/nomic-ai/gpt4all/pull/3393))
|
||||
- Fix crash when entering `{{ a["foo"(` as chat template ([#3394](https://github.com/nomic-ai/gpt4all/pull/3394))
|
||||
- Sign the maintenance tool on macOS to prevent crash on Sonoma ([#3391](https://github.com/nomic-ai/gpt4all/pull/3391))
|
||||
- Sign the maintenance tool on macOS to prevent crash on Sequoia ([#3391](https://github.com/nomic-ai/gpt4all/pull/3391))
|
||||
- Jinja2Cpp: Fix operator precedence in 'not X is defined' ([#3402](https://github.com/nomic-ai/gpt4all/pull/3402))
|
||||
|
||||
## [3.6.1] - 2024-12-20
|
||||
@@ -261,6 +295,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
- Fix several Vulkan resource management issues ([#2694](https://github.com/nomic-ai/gpt4all/pull/2694))
|
||||
- Fix crash/hang when some models stop generating, by showing special tokens ([#2701](https://github.com/nomic-ai/gpt4all/pull/2701))
|
||||
|
||||
[3.9.0]: https://github.com/nomic-ai/gpt4all/compare/v3.8.0...v3.9.0
|
||||
[3.8.0]: https://github.com/nomic-ai/gpt4all/compare/v3.7.0...v3.8.0
|
||||
[3.7.0]: https://github.com/nomic-ai/gpt4all/compare/v3.6.1...v3.7.0
|
||||
[3.6.1]: https://github.com/nomic-ai/gpt4all/compare/v3.6.0...v3.6.1
|
||||
[3.6.0]: https://github.com/nomic-ai/gpt4all/compare/v3.5.3...v3.6.0
|
||||
[3.5.3]: https://github.com/nomic-ai/gpt4all/compare/v3.5.2...v3.5.3
|
||||
|
||||
@@ -3,13 +3,17 @@ cmake_minimum_required(VERSION 3.25) # for try_compile SOURCE_FROM_VAR
|
||||
include(../common/common.cmake)
|
||||
|
||||
set(APP_VERSION_MAJOR 3)
|
||||
set(APP_VERSION_MINOR 7)
|
||||
set(APP_VERSION_MINOR 9)
|
||||
set(APP_VERSION_PATCH 0)
|
||||
set(APP_VERSION_BASE "${APP_VERSION_MAJOR}.${APP_VERSION_MINOR}.${APP_VERSION_PATCH}")
|
||||
set(APP_VERSION "${APP_VERSION_BASE}")
|
||||
|
||||
project(gpt4all VERSION ${APP_VERSION_BASE} LANGUAGES CXX C)
|
||||
|
||||
if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||
set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/install CACHE PATH "..." FORCE)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
option(BUILD_UNIVERSAL "Build a Universal binary on macOS" OFF)
|
||||
if(BUILD_UNIVERSAL)
|
||||
@@ -31,6 +35,8 @@ option(GPT4ALL_SIGN_INSTALL "Sign installed binaries and installers (requires si
|
||||
option(GPT4ALL_GEN_CPACK_CONFIG "Generate the CPack config.xml in the package step and nothing else." OFF)
|
||||
set(GPT4ALL_USE_QTPDF "AUTO" CACHE STRING "Whether to Use QtPDF for LocalDocs. If OFF or not available on this platform, PDFium is used.")
|
||||
set_property(CACHE GPT4ALL_USE_QTPDF PROPERTY STRINGS AUTO ON OFF)
|
||||
set(GPT4ALL_FORCE_D3D12 "AUTO" CACHE STRING "Whether to use Direct3D 12 as the Qt scene graph backend. Defaults to ON on Windows ARM.")
|
||||
set_property(CACHE GPT4ALL_FORCE_D3D12 PROPERTY STRINGS AUTO ON OFF)
|
||||
|
||||
include(cmake/cpack_config.cmake)
|
||||
|
||||
@@ -47,7 +53,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
if (MSVC)
|
||||
# Enable accurate __cplusplus macro to fix errors in Jinja2Cpp
|
||||
# Enable accurate __cplusplus macro
|
||||
add_compile_options($<$<COMPILE_LANGUAGE:CXX>:/Zc:__cplusplus>)
|
||||
endif()
|
||||
|
||||
@@ -86,12 +92,6 @@ include_directories("${CMAKE_CURRENT_BINARY_DIR}")
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
# Generate a header file with the version number
|
||||
configure_file(
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/config.h.in"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/config.h"
|
||||
)
|
||||
|
||||
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL ON)
|
||||
set(GPT4ALL_QT_COMPONENTS Core HttpServer LinguistTools Quick QuickDialogs2 Sql Svg)
|
||||
set(GPT4ALL_USING_QTPDF OFF)
|
||||
@@ -126,10 +126,24 @@ message(STATUS "Qt 6 root directory: ${Qt6_ROOT_DIR}")
|
||||
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||
|
||||
if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||
set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/install CACHE PATH "..." FORCE)
|
||||
set(GPT4ALL_CONFIG_FORCE_D3D12 -1)
|
||||
if (NOT CMAKE_SYSTEM_NAME MATCHES Windows OR Qt6_VERSION VERSION_LESS "6.6")
|
||||
# Direct3D 12 is not available.
|
||||
if (GPT4ALL_FORCE_D3D12 STREQUAL "ON")
|
||||
message(FATAL_ERROR "Cannot use Direct3D 12 on this platform.")
|
||||
endif()
|
||||
elseif (GPT4ALL_FORCE_D3D12 MATCHES "^(ON|AUTO)$")
|
||||
if (GPT4ALL_FORCE_D3D12 STREQUAL "ON" OR CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|AARCH64|arm64|ARM64)$")
|
||||
set(GPT4ALL_CONFIG_FORCE_D3D12 1)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Generate a header file for configuration
|
||||
configure_file(
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/config.h.in"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/config.h"
|
||||
)
|
||||
|
||||
add_subdirectory(deps)
|
||||
add_subdirectory(../gpt4all-backend llmodel)
|
||||
|
||||
@@ -437,7 +451,10 @@ else()
|
||||
target_link_libraries(chat PRIVATE pdfium)
|
||||
endif()
|
||||
target_link_libraries(chat
|
||||
PRIVATE llmodel SingleApplication fmt::fmt duckx::duckx QXlsx jinja2cpp)
|
||||
PRIVATE llmodel SingleApplication fmt::fmt duckx::duckx QXlsx)
|
||||
target_include_directories(chat PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/deps/json/include)
|
||||
target_include_directories(chat PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/deps/json/include/nlohmann)
|
||||
target_include_directories(chat PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/deps/minja/include)
|
||||
|
||||
if (APPLE)
|
||||
target_link_libraries(chat PRIVATE ${COCOA_LIBRARY})
|
||||
@@ -445,12 +462,18 @@ endif()
|
||||
|
||||
# -- install --
|
||||
|
||||
if (APPLE)
|
||||
set(GPT4ALL_LIB_DEST bin/gpt4all.app/Contents/Frameworks)
|
||||
else()
|
||||
set(GPT4ALL_LIB_DEST lib)
|
||||
endif()
|
||||
|
||||
install(TARGETS chat DESTINATION bin COMPONENT ${COMPONENT_NAME_MAIN})
|
||||
|
||||
install(
|
||||
TARGETS llmodel
|
||||
LIBRARY DESTINATION lib COMPONENT ${COMPONENT_NAME_MAIN} # .so/.dylib
|
||||
RUNTIME DESTINATION bin COMPONENT ${COMPONENT_NAME_MAIN} # .dll
|
||||
LIBRARY DESTINATION ${GPT4ALL_LIB_DEST} COMPONENT ${COMPONENT_NAME_MAIN} # .so/.dylib
|
||||
RUNTIME DESTINATION bin COMPONENT ${COMPONENT_NAME_MAIN} # .dll
|
||||
)
|
||||
|
||||
# We should probably iterate through the list of the cmake for backend, but these need to be installed
|
||||
@@ -473,8 +496,8 @@ endif()
|
||||
|
||||
install(
|
||||
TARGETS ${MODEL_IMPL_TARGETS}
|
||||
LIBRARY DESTINATION lib COMPONENT ${COMPONENT_NAME_MAIN} # .so/.dylib
|
||||
RUNTIME DESTINATION lib COMPONENT ${COMPONENT_NAME_MAIN} # .dll
|
||||
LIBRARY DESTINATION ${GPT4ALL_LIB_DEST} COMPONENT ${COMPONENT_NAME_MAIN} # .so/.dylib
|
||||
RUNTIME DESTINATION lib COMPONENT ${COMPONENT_NAME_MAIN} # .dll
|
||||
)
|
||||
|
||||
if(APPLE AND GPT4ALL_SIGN_INSTALL)
|
||||
@@ -503,7 +526,7 @@ if (LLMODEL_CUDA)
|
||||
TARGETS llamamodel-mainline-cuda
|
||||
llamamodel-mainline-cuda-avxonly
|
||||
RUNTIME_DEPENDENCY_SET llama-cuda-deps
|
||||
LIBRARY DESTINATION lib COMPONENT ${COMPONENT_NAME_MAIN} # .so/.dylib
|
||||
LIBRARY DESTINATION lib COMPONENT ${COMPONENT_NAME_MAIN} # .so
|
||||
RUNTIME DESTINATION lib COMPONENT ${COMPONENT_NAME_MAIN} # .dll
|
||||
)
|
||||
if (WIN32)
|
||||
@@ -520,9 +543,9 @@ endif()
|
||||
if (NOT GPT4ALL_USING_QTPDF)
|
||||
# Install PDFium
|
||||
if (WIN32)
|
||||
install(FILES "${PDFium_LIBRARY}" DESTINATION bin COMPONENT ${COMPONENT_NAME_MAIN})
|
||||
install(FILES ${PDFium_LIBRARY} DESTINATION bin COMPONENT ${COMPONENT_NAME_MAIN}) # .dll
|
||||
else()
|
||||
install(FILES "${PDFium_LIBRARY}" DESTINATION lib COMPONENT ${COMPONENT_NAME_MAIN})
|
||||
install(FILES ${PDFium_LIBRARY} DESTINATION ${GPT4ALL_LIB_DEST} COMPONENT ${COMPONENT_NAME_MAIN}) # .so/.dylib
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
# gpt4all-chat
|
||||
|
||||
Cross platform Qt based GUI for GPT4All versions with GPT-J as the base
|
||||
model. NOTE: The model seen in the screenshot is actually a preview of a
|
||||
new training run for GPT4All based on GPT-J. The GPT4All project is busy
|
||||
at work getting ready to release this model including installers for all
|
||||
three major OS's. In the meantime, you can try this UI out with the original
|
||||
GPT-J model by following build instructions below.
|
||||
|
||||

|
||||
|
||||
## Install
|
||||
|
||||
One click installers for macOS, Linux, and Windows at https://www.nomic.ai/gpt4all
|
||||
|
||||
## Features
|
||||
|
||||
* Cross-platform (Linux, Windows, MacOSX)
|
||||
* The UI is made to look and feel like you've come to expect from a chatty gpt
|
||||
* Check for updates so you can always stay fresh with latest models
|
||||
* Easy to install with precompiled binaries available for all three major desktop platforms
|
||||
* Multi-modal - Ability to load more than one model and switch between them
|
||||
* Multi-chat - a list of current and past chats and the ability to save/delete/export and switch between
|
||||
* Supports models that are supported by llama.cpp
|
||||
* Model downloader in GUI featuring many popular open source models
|
||||
* Settings dialog to change temp, top_p, min_p, top_k, threads, etc
|
||||
* Copy your conversation to clipboard
|
||||
* RAG via LocalDocs feature
|
||||
* Check for updates to get the very latest GUI
|
||||
|
||||
## Building and running
|
||||
|
||||
* Follow the visual instructions on the [build_and_run](build_and_run.md) page
|
||||
|
||||
## Getting the latest
|
||||
|
||||
If you've already checked out the source code and/or built the program make sure when you do a git fetch to get the latest changes and that you also do `git submodule update --init --recursive` to update the submodules. (If you ever run into trouble, deinitializing via `git submodule deinit -f .` and then initializing again via `git submodule update --init --recursive` fixes most issues)
|
||||
|
||||
## Contributing
|
||||
|
||||
* Pull requests welcome. See the feature wish list for ideas :)
|
||||
|
||||
|
||||
## License
|
||||
The source code of this chat interface is currently under a MIT license.
|
||||
@@ -1,6 +0,0 @@
|
||||
#ifndef CONFIG_H
|
||||
#define CONFIG_H
|
||||
|
||||
#define APP_VERSION "@APP_VERSION@"
|
||||
|
||||
#endif // CONFIG_H
|
||||
@@ -37,7 +37,6 @@ set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH})
|
||||
set(CPACK_PACKAGE_HOMEPAGE_URL "https://www.nomic.ai/gpt4all")
|
||||
set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}/icons/gpt4all-48.png")
|
||||
set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_CURRENT_SOURCE_DIR}/LICENSE)
|
||||
set(CPACK_RESOURCE_FILE_README ${CMAKE_CURRENT_SOURCE_DIR}/README.md)
|
||||
set(CPACK_PACKAGE_EXECUTABLES "GPT4All")
|
||||
set(CPACK_CREATE_DESKTOP_LINKS "GPT4All")
|
||||
set(CPACK_IFW_PACKAGE_NAME "GPT4All")
|
||||
|
||||
@@ -8,12 +8,6 @@ if (GPT4ALL_SIGN_INSTALL)
|
||||
set(MAC_NOTARIZE -sign-for-notarization=${GPT4ALL_SIGNING_ID})
|
||||
endif()
|
||||
execute_process(COMMAND ${MACDEPLOYQT} ${CPACK_TEMPORARY_INSTALL_DIRECTORY}/packages/${COMPONENT_NAME_MAIN}/data/bin/gpt4all.app -qmldir=${CMAKE_CURRENT_SOURCE_DIR} -verbose=2 ${MAC_NOTARIZE})
|
||||
file(GLOB MYLLAMALIBS ${CPACK_TEMPORARY_INSTALL_DIRECTORY}/packages/${COMPONENT_NAME_MAIN}/data/lib/libllama*)
|
||||
file(GLOB MYLLMODELLIBS ${CPACK_TEMPORARY_INSTALL_DIRECTORY}/packages/${COMPONENT_NAME_MAIN}/data/lib/libllmodel.*)
|
||||
file(COPY ${MYLLAMALIBS}
|
||||
DESTINATION ${CPACK_TEMPORARY_INSTALL_DIRECTORY}/packages/${COMPONENT_NAME_MAIN}/data/bin/gpt4all.app/Contents/Frameworks)
|
||||
file(COPY ${MYLLMODELLIBS}
|
||||
DESTINATION ${CPACK_TEMPORARY_INSTALL_DIRECTORY}/packages/${COMPONENT_NAME_MAIN}/data/bin/gpt4all.app/Contents/Frameworks)
|
||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/icons/gpt4all-32.png"
|
||||
DESTINATION ${CPACK_TEMPORARY_INSTALL_DIRECTORY}/packages/${COMPONENT_NAME_MAIN}/data)
|
||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/icons/gpt4all-48.png"
|
||||
|
||||
@@ -15,43 +15,34 @@ add_subdirectory(DuckX)
|
||||
set(QT_VERSION_MAJOR 6)
|
||||
add_subdirectory(QXlsx/QXlsx)
|
||||
|
||||
# forked dependency of Jinja2Cpp
|
||||
set(RAPIDJSON_BUILD_DOC OFF)
|
||||
set(RAPIDJSON_BUILD_EXAMPLES OFF)
|
||||
set(RAPIDJSON_BUILD_TESTS OFF)
|
||||
set(RAPIDJSON_ENABLE_INSTRUMENTATION_OPT OFF)
|
||||
add_subdirectory(rapidjson)
|
||||
|
||||
add_subdirectory(Jinja2Cpp)
|
||||
|
||||
if (NOT GPT4ALL_USING_QTPDF)
|
||||
# If we do not use QtPDF, we need to get PDFium.
|
||||
set(GPT4ALL_PDFIUM_TAG "chromium/6954")
|
||||
set(GPT4ALL_PDFIUM_TAG "chromium/6996")
|
||||
if (CMAKE_SYSTEM_NAME MATCHES Linux)
|
||||
FetchContent_Declare(
|
||||
pdfium
|
||||
URL "https://github.com/bblanchon/pdfium-binaries/releases/download/${GPT4ALL_PDFIUM_TAG}/pdfium-linux-x64.tgz"
|
||||
URL_HASH "SHA256=69917fd9543befc6c806254aff6c8a604d9e7cd3999a3e70fc32b8690d372da2"
|
||||
URL_HASH "SHA256=68b381b87efed539f2e33ae1e280304c9a42643a878cc296c1d66a93b0cb4335"
|
||||
)
|
||||
elseif (CMAKE_SYSTEM_NAME MATCHES Windows)
|
||||
if (CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$")
|
||||
FetchContent_Declare(
|
||||
pdfium
|
||||
URL "https://github.com/bblanchon/pdfium-binaries/releases/download/${GPT4ALL_PDFIUM_TAG}/pdfium-win-x64.tgz"
|
||||
URL_HASH "SHA256=62ecac78fbaf658457beaffcc05eb147f493d435a2e1309e6a731808b4e80d38"
|
||||
URL_HASH "SHA256=83e714c302ceacccf403826d5cb57ea39b77f393d83b8d5781283012774a9378"
|
||||
)
|
||||
elseif (CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|AARCH64|arm64|ARM64)$")
|
||||
FetchContent_Declare(
|
||||
pdfium
|
||||
URL "https://github.com/bblanchon/pdfium-binaries/releases/download/${GPT4ALL_PDFIUM_TAG}/pdfium-win-arm64.tgz"
|
||||
URL_HASH "SHA256=a0b69014467f2b9824776c064920bc95359c9ba0d88793bdda1894a0f22206f8"
|
||||
URL_HASH "SHA256=78e77e871453a4915cbf66fb381b951c9932f88a747c6b2b33c9f27ec2371445"
|
||||
)
|
||||
endif()
|
||||
elseif (CMAKE_SYSTEM_NAME MATCHES Darwin)
|
||||
FetchContent_Declare(
|
||||
pdfium
|
||||
URL "https://github.com/bblanchon/pdfium-binaries/releases/download/${GPT4ALL_PDFIUM_TAG}/pdfium-mac-univ.tgz"
|
||||
URL_HASH "SHA256=7442f1dc6bef90898b2b7bd38dbec369ddd81bbf66c1c5aac3a1b60e107098f9"
|
||||
URL_HASH "SHA256=e7577f3242ff9c1df50025f9615673a43601a201bc51ee4792975f98920793a2"
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
Submodule gpt4all-chat/deps/Jinja2Cpp deleted from ce10f783ba
1
gpt4all-chat/deps/json
Submodule
1
gpt4all-chat/deps/json
Submodule
Submodule gpt4all-chat/deps/json added at 606b6347ed
1
gpt4all-chat/deps/minja
Submodule
1
gpt4all-chat/deps/minja
Submodule
Submodule gpt4all-chat/deps/minja added at e97bb2442c
Submodule gpt4all-chat/deps/rapidjson deleted from 9b547ef4bd
@@ -1,16 +1,16 @@
|
||||
## Latest News
|
||||
|
||||
GPT4All v3.6.1 was released on December 20th and fixes issues with the stop generation and copy conversation buttons which were broken in v3.6.0.
|
||||
GPT4All v3.8.0 was released on January 30th. Changes include:
|
||||
|
||||
---
|
||||
|
||||
GPT4All v3.6.0 was released on December 19th. Changes include:
|
||||
|
||||
* **Reasoner v1:**
|
||||
* Built-in javascript code interpreter tool.
|
||||
* Custom curated model that utilizes the code interpreter to break down, analyze, perform, and verify complex reasoning tasks.
|
||||
* **Templates:** Automatically substitute chat templates that are not compatible with Jinja2Cpp in GGUFs.
|
||||
* **Fixes:**
|
||||
* Remote model template to allow for XML in messages.
|
||||
* Jinja2Cpp bug that broke system message detection in chat templates.
|
||||
* LocalDocs sources displaying in unconsolidated form after v3.5.0.
|
||||
* **Native DeepSeek-R1-Distill Support:** GPT4All now has robust support for the DeepSeek-R1 family of distillations.
|
||||
* Several model variants are now available on the downloads page.
|
||||
* Reasoning (wrapped in "think" tags) is displayed similarly to the Reasoner model.
|
||||
* The DeepSeek-R1 Qwen pretokenizer is now supported, resolving the loading failure in previous versions.
|
||||
* The model is now configured with a GPT4All-compatible prompt template by default.
|
||||
* **Chat Templating Overhaul:** The template parser has been *completely* replaced with one that has much better compatibility with common models.
|
||||
* **Code Interpreter Fixes:**
|
||||
* An issue preventing the code interpreter from logging a single string in v3.7.0 has been fixed.
|
||||
* The UI no longer freezes while the code interpreter is running a computation.
|
||||
* **Local Server Fixes:**
|
||||
* An issue preventing the server from using LocalDocs after the first request since v3.5.0 has been fixed.
|
||||
* System messages are now correctly hidden from the message history.
|
||||
|
||||
@@ -32,6 +32,66 @@
|
||||
"systemPrompt": "",
|
||||
"chatTemplate": "{%- set loop_messages = messages %}\n{%- for message in loop_messages %}\n {%- set content = '<|start_header_id|>' + message['role'] + '<|end_header_id|>\\n\\n'+ message['content'] | trim + '<|eot_id|>' %}\n {{- content }}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|start_header_id|>assistant<|end_header_id|>\\n\\n' }}\n{%- endif %}"
|
||||
},
|
||||
{
|
||||
"order": "aa1",
|
||||
"sha256sum": "5cd4ee65211770f1d99b4f6f4951780b9ef40e29314bd6542bb5bd0ad0bc29d1",
|
||||
"name": "DeepSeek-R1-Distill-Qwen-7B",
|
||||
"filename": "DeepSeek-R1-Distill-Qwen-7B-Q4_0.gguf",
|
||||
"filesize": "4444121056",
|
||||
"requires": "3.8.0",
|
||||
"ramrequired": "8",
|
||||
"parameters": "7 billion",
|
||||
"quant": "q4_0",
|
||||
"type": "deepseek",
|
||||
"description": "<p>The official Qwen2.5-Math-7B distillation of DeepSeek-R1.</p><ul><li>License: <a href=\"https://opensource.org/license/mit\">MIT</a></li><li>No restrictions on commercial use</li><li>#reasoning</li></ul>",
|
||||
"url": "https://huggingface.co/bartowski/DeepSeek-R1-Distill-Qwen-7B-GGUF/resolve/main/DeepSeek-R1-Distill-Qwen-7B-Q4_0.gguf",
|
||||
"chatTemplate": "{%- if not add_generation_prompt is defined %}\n {%- set add_generation_prompt = false %}\n{%- endif %}\n{%- if messages[0]['role'] == 'system' %}\n {{- messages[0]['content'] }}\n{%- endif %}\n{%- for message in messages %}\n {%- if message['role'] == 'user' %}\n {{- '<|User|>' + message['content'] }}\n {%- endif %}\n {%- if message['role'] == 'assistant' %}\n {%- set content = message['content'] | regex_replace('^[\\\\s\\\\S]*</think>', '') %}\n {{- '<|Assistant|>' + content + '<|end▁of▁sentence|>' }}\n {%- endif %}\n{%- endfor -%}\n{%- if add_generation_prompt %}\n {{- '<|Assistant|>' }}\n{%- endif %}"
|
||||
},
|
||||
{
|
||||
"order": "aa2",
|
||||
"sha256sum": "906b3382f2680f4ce845459b4a122e904002b075238080307586bcffcde49eef",
|
||||
"name": "DeepSeek-R1-Distill-Qwen-14B",
|
||||
"filename": "DeepSeek-R1-Distill-Qwen-14B-Q4_0.gguf",
|
||||
"filesize": "8544267680",
|
||||
"requires": "3.8.0",
|
||||
"ramrequired": "16",
|
||||
"parameters": "14 billion",
|
||||
"quant": "q4_0",
|
||||
"type": "deepseek",
|
||||
"description": "<p>The official Qwen2.5-14B distillation of DeepSeek-R1.</p><ul><li>License: <a href=\"https://opensource.org/license/mit\">MIT</a></li><li>No restrictions on commercial use</li><li>#reasoning</li></ul>",
|
||||
"url": "https://huggingface.co/bartowski/DeepSeek-R1-Distill-Qwen-14B-GGUF/resolve/main/DeepSeek-R1-Distill-Qwen-14B-Q4_0.gguf",
|
||||
"chatTemplate": "{%- if not add_generation_prompt is defined %}\n {%- set add_generation_prompt = false %}\n{%- endif %}\n{%- if messages[0]['role'] == 'system' %}\n {{- messages[0]['content'] }}\n{%- endif %}\n{%- for message in messages %}\n {%- if message['role'] == 'user' %}\n {{- '<|User|>' + message['content'] }}\n {%- endif %}\n {%- if message['role'] == 'assistant' %}\n {%- set content = message['content'] | regex_replace('^[\\\\s\\\\S]*</think>', '') %}\n {{- '<|Assistant|>' + content + '<|end▁of▁sentence|>' }}\n {%- endif %}\n{%- endfor -%}\n{%- if add_generation_prompt %}\n {{- '<|Assistant|>' }}\n{%- endif %}"
|
||||
},
|
||||
{
|
||||
"order": "aa3",
|
||||
"sha256sum": "0eb93e436ac8beec18aceb958c120d282cb2cf5451b23185e7be268fe9d375cc",
|
||||
"name": "DeepSeek-R1-Distill-Llama-8B",
|
||||
"filename": "DeepSeek-R1-Distill-Llama-8B-Q4_0.gguf",
|
||||
"filesize": "4675894112",
|
||||
"requires": "3.8.0",
|
||||
"ramrequired": "8",
|
||||
"parameters": "8 billion",
|
||||
"quant": "q4_0",
|
||||
"type": "deepseek",
|
||||
"description": "<p>The official Llama-3.1-8B distillation of DeepSeek-R1.</p><ul><li>License: <a href=\"https://opensource.org/license/mit\">MIT</a></li><li>No restrictions on commercial use</li><li>#reasoning</li></ul>",
|
||||
"url": "https://huggingface.co/bartowski/DeepSeek-R1-Distill-Llama-8B-GGUF/resolve/main/DeepSeek-R1-Distill-Llama-8B-Q4_0.gguf",
|
||||
"chatTemplate": "{%- if not add_generation_prompt is defined %}\n {%- set add_generation_prompt = false %}\n{%- endif %}\n{%- if messages[0]['role'] == 'system' %}\n {{- messages[0]['content'] }}\n{%- endif %}\n{%- for message in messages %}\n {%- if message['role'] == 'user' %}\n {{- '<|User|>' + message['content'] }}\n {%- endif %}\n {%- if message['role'] == 'assistant' %}\n {%- set content = message['content'] | regex_replace('^[\\\\s\\\\S]*</think>', '') %}\n {{- '<|Assistant|>' + content + '<|end▁of▁sentence|>' }}\n {%- endif %}\n{%- endfor -%}\n{%- if add_generation_prompt %}\n {{- '<|Assistant|>' }}\n{%- endif %}"
|
||||
},
|
||||
{
|
||||
"order": "aa4",
|
||||
"sha256sum": "b3af887d0a015b39fab2395e4faf682c1a81a6a3fd09a43f0d4292f7d94bf4d0",
|
||||
"name": "DeepSeek-R1-Distill-Qwen-1.5B",
|
||||
"filename": "DeepSeek-R1-Distill-Qwen-1.5B-Q4_0.gguf",
|
||||
"filesize": "1068807776",
|
||||
"requires": "3.8.0",
|
||||
"ramrequired": "3",
|
||||
"parameters": "1.5 billion",
|
||||
"quant": "q4_0",
|
||||
"type": "deepseek",
|
||||
"description": "<p>The official Qwen2.5-Math-1.5B distillation of DeepSeek-R1.</p><ul><li>License: <a href=\"https://opensource.org/license/mit\">MIT</a></li><li>No restrictions on commercial use</li><li>#reasoning</li></ul>",
|
||||
"url": "https://huggingface.co/bartowski/DeepSeek-R1-Distill-Qwen-1.5B-GGUF/resolve/main/DeepSeek-R1-Distill-Qwen-1.5B-Q4_0.gguf",
|
||||
"chatTemplate": "{%- if not add_generation_prompt is defined %}\n {%- set add_generation_prompt = false %}\n{%- endif %}\n{%- if messages[0]['role'] == 'system' %}\n {{- messages[0]['content'] }}\n{%- endif %}\n{%- for message in messages %}\n {%- if message['role'] == 'user' %}\n {{- '<|User|>' + message['content'] }}\n {%- endif %}\n {%- if message['role'] == 'assistant' %}\n {%- set content = message['content'] | regex_replace('^[\\\\s\\\\S]*</think>', '') %}\n {{- '<|Assistant|>' + content + '<|end▁of▁sentence|>' }}\n {%- endif %}\n{%- endfor -%}\n{%- if add_generation_prompt %}\n {{- '<|Assistant|>' }}\n{%- endif %}"
|
||||
},
|
||||
{
|
||||
"order": "b",
|
||||
"md5sum": "27b44e8ae1817525164ddf4f8dae8af4",
|
||||
@@ -472,7 +532,7 @@
|
||||
"filename": "qwen2-1_5b-instruct-q4_0.gguf",
|
||||
"filesize": "937532800",
|
||||
"requires": "3.0",
|
||||
"ramrequired": "4",
|
||||
"ramrequired": "3",
|
||||
"parameters": "1.5 billion",
|
||||
"quant": "q4_0",
|
||||
"type": "qwen2",
|
||||
|
||||
@@ -251,12 +251,22 @@
|
||||
},
|
||||
{
|
||||
"version": "3.6.0",
|
||||
"notes": "* **Reasoner v1:**\n * Built-in javascript code interpreter tool.\n * Custom curated model that utilizes the code interpreter to break down, analyze, perform, and verify complex reasoning tasks.\n* **Templates:** Automatically substitute chat templates that are not compatible with Jinja2Cpp in GGUFs.\n* **Fixes:**\n * Remote model template to allow for XML in messages.\n * Jinja2Cpp bug that broke system message detection in chat templates.\n * LocalDocs sources displaying in unconsolidated form after v3.5.0.",
|
||||
"notes": "* **Reasoner v1:**\n * Built-in javascript code interpreter tool.\n * Custom curated model that utilizes the code interpreter to break down, analyze, perform, and verify complex reasoning tasks.\n* **Templates:** Automatically substitute chat templates that are not compatible with Jinja2Cpp in GGUFs.\n* **Fixes:**\n * Remote model template to allow for XML in messages.\n * Jinja2Cpp bug that broke system message detection in chat templates.\n * LocalDocs sources displaying in unconsolidated form after v3.5.0.\n",
|
||||
"contributors": "* Adam Treat (Nomic AI)\n* Jared Van Bortel (Nomic AI)"
|
||||
},
|
||||
{
|
||||
"version": "3.6.1",
|
||||
"notes": "* **Fixes:**\n * The stop generation button no longer working in v3.6.0.\n * The copy entire conversation button no longer working in v3.6.0.",
|
||||
"notes": "* **Fixes:**\n * The stop generation button no longer working in v3.6.0.\n * The copy entire conversation button no longer working in v3.6.0.\n",
|
||||
"contributors": "* Adam Treat (Nomic AI)"
|
||||
},
|
||||
{
|
||||
"version": "3.7.0",
|
||||
"notes": "* **Windows ARM Support:** GPT4All now supports the Windows ARM platform, ensuring compatibility with devices powered by Qualcomm Snapdragon and Microsoft SQ-series processors.\n * **NOTE:** Support for GPU and/or NPU acceleration is not available at this time. Only the CPU will be used to run LLMs.\n * **NOTE:** You must install the new *Windows ARM* version of GPT4All from the website. The standard *Windows* version will not work due to emulation limitations.\n* **Fixed Updating on macOS:** The maintenance tool no longer crashes when attempting to update or uninstall GPT4All on Sequoia.\n * **NOTE:** If you have installed the version from the GitHub releases as a workaround for this issue, you can safely uninstall it and switch back to the version from the website.\n* **Fixed Chat Saving on macOS:** Chats now save as expected when the application is quit with Command-Q.\n* **Code Interpreter Improvements:**\n * The behavior when the code takes too long to execute and times out has been improved.\n * console.log now accepts multiple arguments for better compatibility with native JavaScript.\n* **Chat Templating Improvements:**\n * Two crashes and one compatibility issue have been fixed in the chat template parser.\n * The default chat template for EM German Mistral has been fixed.\n * Automatic replacements have been added for five new models as we continue to improve compatibility with common chat templates.\n",
|
||||
"contributors": "* Jared Van Bortel (Nomic AI)\n* Adam Treat (Nomic AI)\n* Riccardo Giovanetti (`@Harvester62`)"
|
||||
},
|
||||
{
|
||||
"version": "3.8.0",
|
||||
"notes": "* **Native DeepSeek-R1-Distill Support:** GPT4All now has robust support for the DeepSeek-R1 family of distillations.\n * Several model variants are now available on the downloads page.\n * Reasoning (wrapped in \"think\" tags) is displayed similarly to the Reasoner model.\n * The DeepSeek-R1 Qwen pretokenizer is now supported, resolving the loading failure in previous versions.\n * The model is now configured with a GPT4All-compatible prompt template by default.\n* **Chat Templating Overhaul:** The template parser has been *completely* replaced with one that has much better compatibility with common models.\n* **Code Interpreter Fixes:**\n * An issue preventing the code interpreter from logging a single string in v3.7.0 has been fixed.\n * The UI no longer freezes while the code interpreter is running a computation.\n* **Local Server Fixes:**\n * An issue preventing the server from using LocalDocs after the first request since v3.5.0 has been fixed.\n * System messages are now correctly hidden from the message history.\n",
|
||||
"contributors": "* Jared Van Bortel (Nomic AI)\n* Adam Treat (Nomic AI)\n* ThiloteE (`@ThiloteE`)"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -13,6 +13,8 @@ ColumnLayout {
|
||||
property alias textContent: innerTextItem.textContent
|
||||
property bool isCurrent: false
|
||||
property bool isError: false
|
||||
property bool isThinking: false
|
||||
property int thinkingTime: 0
|
||||
|
||||
Layout.topMargin: 10
|
||||
Layout.bottomMargin: 10
|
||||
@@ -26,16 +28,20 @@ ColumnLayout {
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
Item {
|
||||
width: myTextArea.width
|
||||
height: myTextArea.height
|
||||
Layout.preferredWidth: myTextArea.implicitWidth
|
||||
Layout.preferredHeight: myTextArea.implicitHeight
|
||||
TextArea {
|
||||
id: myTextArea
|
||||
text: {
|
||||
if (isError)
|
||||
return qsTr("Analysis encountered error");
|
||||
if (isCurrent)
|
||||
return qsTr("Analyzing");
|
||||
return qsTr("Analyzed");
|
||||
return isThinking ? qsTr("Thinking") : qsTr("Analyzing");
|
||||
return isThinking
|
||||
? qsTr("Thought for %1 %2")
|
||||
.arg(Math.ceil(thinkingTime / 1000.0))
|
||||
.arg(Math.ceil(thinkingTime / 1000.0) === 1 ? qsTr("second") : qsTr("seconds"))
|
||||
: qsTr("Analyzed");
|
||||
}
|
||||
padding: 0
|
||||
font.pixelSize: theme.fontSizeLarger
|
||||
|
||||
@@ -110,6 +110,7 @@ GridLayout {
|
||||
case Chat.PromptProcessing: return qsTr("processing ...")
|
||||
case Chat.ResponseGeneration: return qsTr("generating response ...");
|
||||
case Chat.GeneratingQuestions: return qsTr("generating questions ...");
|
||||
case Chat.ToolCallGeneration: return qsTr("generating toolcall ...");
|
||||
default: return ""; // handle unexpected values
|
||||
}
|
||||
}
|
||||
@@ -188,6 +189,17 @@ GridLayout {
|
||||
isError: modelData.isToolCallError
|
||||
}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: "Think: ";
|
||||
ChatCollapsibleItem {
|
||||
Layout.fillWidth: true
|
||||
textContent: modelData.content
|
||||
isCurrent: modelData.isCurrentResponse
|
||||
isError: false
|
||||
isThinking: true
|
||||
thinkingTime: modelData.thinkingTime
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delegate: chooser
|
||||
|
||||
@@ -828,7 +828,7 @@ Rectangle {
|
||||
textInput.cursorPosition = text.length;
|
||||
}
|
||||
height: visible ? implicitHeight : 0
|
||||
visible: name !== "ToolResponse: "
|
||||
visible: name !== "ToolResponse: " && name !== "System: "
|
||||
}
|
||||
|
||||
remove: Transition {
|
||||
|
||||
@@ -181,6 +181,11 @@ QVariant Chat::popPrompt(int index)
|
||||
|
||||
void Chat::stopGenerating()
|
||||
{
|
||||
// In future if we have more than one tool we'll have to keep track of which tools are possibly
|
||||
// running, but for now we only have one
|
||||
Tool *toolInstance = ToolModel::globalInstance()->get(ToolCallConstants::CodeInterpreterFunction);
|
||||
Q_ASSERT(toolInstance);
|
||||
toolInstance->interrupt();
|
||||
m_llmodel->stopGenerating();
|
||||
}
|
||||
|
||||
@@ -242,56 +247,71 @@ void Chat::responseStopped(qint64 promptResponseMs)
|
||||
|
||||
const QString possibleToolcall = m_chatModel->possibleToolcall();
|
||||
|
||||
ToolCallParser parser;
|
||||
parser.update(possibleToolcall);
|
||||
|
||||
if (parser.state() == ToolEnums::ParseState::Complete) {
|
||||
const QString toolCall = parser.toolCall();
|
||||
|
||||
// Regex to remove the formatting around the code
|
||||
static const QRegularExpression regex("^\\s*```javascript\\s*|\\s*```\\s*$");
|
||||
QString code = toolCall;
|
||||
code.remove(regex);
|
||||
code = code.trimmed();
|
||||
|
||||
// Right now the code interpreter is the only available tool
|
||||
Tool *toolInstance = ToolModel::globalInstance()->get(ToolCallConstants::CodeInterpreterFunction);
|
||||
Q_ASSERT(toolInstance);
|
||||
|
||||
// The param is the code
|
||||
const ToolParam param = { "code", ToolEnums::ParamType::String, code };
|
||||
const QString result = toolInstance->run({param}, 10000 /*msecs to timeout*/);
|
||||
const ToolEnums::Error error = toolInstance->error();
|
||||
const QString errorString = toolInstance->errorString();
|
||||
|
||||
// Update the current response with meta information about toolcall and re-parent
|
||||
m_chatModel->updateToolCall({
|
||||
ToolCallConstants::CodeInterpreterFunction,
|
||||
{ param },
|
||||
result,
|
||||
error,
|
||||
errorString
|
||||
});
|
||||
|
||||
++m_consecutiveToolCalls;
|
||||
|
||||
// We limit the number of consecutive toolcalls otherwise we get into a potentially endless loop
|
||||
if (m_consecutiveToolCalls < 3 || error == ToolEnums::Error::NoError) {
|
||||
resetResponseState();
|
||||
emit promptRequested(m_collections); // triggers a new response
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_generatedName.isEmpty())
|
||||
emit generateNameRequested();
|
||||
|
||||
m_consecutiveToolCalls = 0;
|
||||
Network::globalInstance()->trackChatEvent("response_complete", {
|
||||
Network::globalInstance()->trackChatEvent("response_stopped", {
|
||||
{"first", m_firstResponse},
|
||||
{"message_count", chatModel()->count()},
|
||||
{"$duration", promptResponseMs / 1000.},
|
||||
});
|
||||
|
||||
ToolCallParser parser;
|
||||
parser.update(possibleToolcall.toUtf8());
|
||||
if (parser.state() == ToolEnums::ParseState::Complete && parser.startTag() != ToolCallConstants::ThinkStartTag)
|
||||
processToolCall(parser.toolCall());
|
||||
else
|
||||
responseComplete();
|
||||
}
|
||||
|
||||
void Chat::processToolCall(const QString &toolCall)
|
||||
{
|
||||
m_responseState = Chat::ToolCallGeneration;
|
||||
emit responseStateChanged();
|
||||
// Regex to remove the formatting around the code
|
||||
static const QRegularExpression regex("^\\s*```javascript\\s*|\\s*```\\s*$");
|
||||
QString code = toolCall;
|
||||
code.remove(regex);
|
||||
code = code.trimmed();
|
||||
|
||||
// Right now the code interpreter is the only available tool
|
||||
Tool *toolInstance = ToolModel::globalInstance()->get(ToolCallConstants::CodeInterpreterFunction);
|
||||
Q_ASSERT(toolInstance);
|
||||
connect(toolInstance, &Tool::runComplete, this, &Chat::toolCallComplete, Qt::SingleShotConnection);
|
||||
|
||||
// The param is the code
|
||||
const ToolParam param = { "code", ToolEnums::ParamType::String, code };
|
||||
m_responseInProgress = true;
|
||||
emit responseInProgressChanged();
|
||||
toolInstance->run({param});
|
||||
}
|
||||
|
||||
void Chat::toolCallComplete(const ToolCallInfo &info)
|
||||
{
|
||||
// Update the current response with meta information about toolcall and re-parent
|
||||
m_chatModel->updateToolCall(info);
|
||||
|
||||
++m_consecutiveToolCalls;
|
||||
|
||||
m_responseInProgress = false;
|
||||
emit responseInProgressChanged();
|
||||
|
||||
// We limit the number of consecutive toolcalls otherwise we get into a potentially endless loop
|
||||
if (m_consecutiveToolCalls < 3 || info.error == ToolEnums::Error::NoError) {
|
||||
resetResponseState();
|
||||
emit promptRequested(m_collections); // triggers a new response
|
||||
return;
|
||||
}
|
||||
|
||||
responseComplete();
|
||||
}
|
||||
|
||||
void Chat::responseComplete()
|
||||
{
|
||||
if (m_generatedName.isEmpty())
|
||||
emit generateNameRequested();
|
||||
|
||||
m_responseState = Chat::ResponseStopped;
|
||||
emit responseStateChanged();
|
||||
|
||||
m_consecutiveToolCalls = 0;
|
||||
m_firstResponse = false;
|
||||
}
|
||||
|
||||
@@ -361,11 +381,8 @@ void Chat::trySwitchContextOfLoadedModel()
|
||||
|
||||
void Chat::generatedNameChanged(const QString &name)
|
||||
{
|
||||
// Only use the first three words maximum and remove newlines and extra spaces
|
||||
m_generatedName = name.simplified();
|
||||
QStringList words = m_generatedName.split(' ', Qt::SkipEmptyParts);
|
||||
int wordCount = qMin(7, words.size());
|
||||
m_name = words.mid(0, wordCount).join(' ');
|
||||
m_generatedName = name;
|
||||
m_name = name;
|
||||
emit nameChanged();
|
||||
m_needsSave = true;
|
||||
}
|
||||
|
||||
@@ -55,7 +55,8 @@ public:
|
||||
LocalDocsProcessing,
|
||||
PromptProcessing,
|
||||
GeneratingQuestions,
|
||||
ResponseGeneration
|
||||
ResponseGeneration,
|
||||
ToolCallGeneration
|
||||
};
|
||||
Q_ENUM(ResponseState)
|
||||
|
||||
@@ -166,6 +167,9 @@ private Q_SLOTS:
|
||||
void promptProcessing();
|
||||
void generatingQuestions();
|
||||
void responseStopped(qint64 promptResponseMs);
|
||||
void processToolCall(const QString &toolCall);
|
||||
void toolCallComplete(const ToolCallInfo &info);
|
||||
void responseComplete();
|
||||
void generatedNameChanged(const QString &name);
|
||||
void generatedQuestionFinished(const QString &question);
|
||||
void handleModelLoadingError(const QString &error);
|
||||
|
||||
@@ -12,12 +12,8 @@
|
||||
#include "toolcallparser.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <jinja2cpp/error_info.h>
|
||||
#include <jinja2cpp/template.h>
|
||||
#include <jinja2cpp/template_env.h>
|
||||
#include <jinja2cpp/user_callable.h>
|
||||
#include <jinja2cpp/value.h>
|
||||
#include <minja/minja.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <QDataStream>
|
||||
#include <QDebug>
|
||||
@@ -33,6 +29,7 @@
|
||||
#include <QRegularExpression>
|
||||
#include <QRegularExpressionMatch>
|
||||
#include <QSet>
|
||||
#include <QTextStream>
|
||||
#include <QUrl>
|
||||
#include <QWaitCondition>
|
||||
#include <Qt>
|
||||
@@ -60,44 +57,104 @@
|
||||
using namespace Qt::Literals::StringLiterals;
|
||||
using namespace ToolEnums;
|
||||
namespace ranges = std::ranges;
|
||||
using json = nlohmann::ordered_json;
|
||||
|
||||
//#define DEBUG
|
||||
//#define DEBUG_MODEL_LOADING
|
||||
|
||||
// NOTE: not threadsafe
|
||||
static jinja2::TemplateEnv *jinjaEnv()
|
||||
static const std::shared_ptr<minja::Context> &jinjaEnv()
|
||||
{
|
||||
static std::optional<jinja2::TemplateEnv> environment;
|
||||
static std::shared_ptr<minja::Context> environment;
|
||||
if (!environment) {
|
||||
auto &env = environment.emplace();
|
||||
auto &settings = env.GetSettings();
|
||||
settings.trimBlocks = true;
|
||||
settings.lstripBlocks = true;
|
||||
env.AddGlobal("raise_exception", jinja2::UserCallable(
|
||||
/*callable*/ [](auto ¶ms) -> jinja2::Value {
|
||||
auto messageArg = params.args.find("message");
|
||||
if (messageArg == params.args.end() || !messageArg->second.isString())
|
||||
throw std::runtime_error("'message' argument to raise_exception() must be a string");
|
||||
throw std::runtime_error(fmt::format("Jinja template error: {}", messageArg->second.asString()));
|
||||
},
|
||||
/*argsInfo*/ { jinja2::ArgInfo("message", /*isMandatory*/ true) }
|
||||
));
|
||||
env.AddGlobal("strftime_now", jinja2::UserCallable(
|
||||
/*callable*/ [](auto ¶ms) -> jinja2::Value {
|
||||
environment = minja::Context::builtins();
|
||||
environment->set("strftime_now", minja::simple_function(
|
||||
"strftime_now", { "format" },
|
||||
[](const std::shared_ptr<minja::Context> &, minja::Value &args) -> minja::Value {
|
||||
auto format = args.at("format").get<std::string>();
|
||||
using Clock = std::chrono::system_clock;
|
||||
auto formatArg = params.args.find("format");
|
||||
if (formatArg == params.args.end() || !formatArg->second.isString())
|
||||
throw std::runtime_error("'format' argument to strftime_now() must be a string");
|
||||
time_t nowUnix = Clock::to_time_t(Clock::now());
|
||||
auto localDate = *std::localtime(&nowUnix);
|
||||
std::ostringstream ss;
|
||||
ss << std::put_time(&localDate, formatArg->second.asString().c_str());
|
||||
ss << std::put_time(&localDate, format.c_str());
|
||||
return ss.str();
|
||||
},
|
||||
/*argsInfo*/ { jinja2::ArgInfo("format", /*isMandatory*/ true) }
|
||||
}
|
||||
));
|
||||
environment->set("regex_replace", minja::simple_function(
|
||||
"regex_replace", { "str", "pattern", "repl" },
|
||||
[](const std::shared_ptr<minja::Context> &, minja::Value &args) -> minja::Value {
|
||||
auto str = args.at("str" ).get<std::string>();
|
||||
auto pattern = args.at("pattern").get<std::string>();
|
||||
auto repl = args.at("repl" ).get<std::string>();
|
||||
return std::regex_replace(str, std::regex(pattern), repl);
|
||||
}
|
||||
));
|
||||
}
|
||||
return &*environment;
|
||||
return environment;
|
||||
}
|
||||
|
||||
class BaseResponseHandler {
|
||||
public:
|
||||
virtual void onSplitIntoTwo (const QString &startTag, const QString &firstBuffer, const QString &secondBuffer) = 0;
|
||||
virtual void onSplitIntoThree (const QString &secondBuffer, const QString &thirdBuffer) = 0;
|
||||
// "old-style" responses, with all of the implementation details left in
|
||||
virtual void onOldResponseChunk(const QByteArray &chunk) = 0;
|
||||
// notify of a "new-style" response that has been cleaned of tool calling
|
||||
virtual bool onBufferResponse (const QString &response, int bufferIdx) = 0;
|
||||
// notify of a "new-style" response, no tool calling applicable
|
||||
virtual bool onRegularResponse () = 0;
|
||||
virtual bool getStopGenerating () const = 0;
|
||||
};
|
||||
|
||||
static auto promptModelWithTools(
|
||||
LLModel *model, const LLModel::PromptCallback &promptCallback, BaseResponseHandler &respHandler,
|
||||
const LLModel::PromptContext &ctx, const QByteArray &prompt, const QStringList &toolNames
|
||||
) -> std::pair<QStringList, bool>
|
||||
{
|
||||
ToolCallParser toolCallParser(toolNames);
|
||||
auto handleResponse = [&toolCallParser, &respHandler](LLModel::Token token, std::string_view piece) -> bool {
|
||||
Q_UNUSED(token)
|
||||
|
||||
toolCallParser.update(piece.data());
|
||||
|
||||
// Split the response into two if needed
|
||||
if (toolCallParser.numberOfBuffers() < 2 && toolCallParser.splitIfPossible()) {
|
||||
const auto parseBuffers = toolCallParser.buffers();
|
||||
Q_ASSERT(parseBuffers.size() == 2);
|
||||
respHandler.onSplitIntoTwo(toolCallParser.startTag(), parseBuffers.at(0), parseBuffers.at(1));
|
||||
}
|
||||
|
||||
// Split the response into three if needed
|
||||
if (toolCallParser.numberOfBuffers() < 3 && toolCallParser.startTag() == ToolCallConstants::ThinkStartTag
|
||||
&& toolCallParser.splitIfPossible()) {
|
||||
const auto parseBuffers = toolCallParser.buffers();
|
||||
Q_ASSERT(parseBuffers.size() == 3);
|
||||
respHandler.onSplitIntoThree(parseBuffers.at(1), parseBuffers.at(2));
|
||||
}
|
||||
|
||||
respHandler.onOldResponseChunk(QByteArray::fromRawData(piece.data(), piece.size()));
|
||||
|
||||
bool ok;
|
||||
const auto parseBuffers = toolCallParser.buffers();
|
||||
if (parseBuffers.size() > 1) {
|
||||
ok = respHandler.onBufferResponse(parseBuffers.last(), parseBuffers.size() - 1);
|
||||
} else {
|
||||
ok = respHandler.onRegularResponse();
|
||||
}
|
||||
if (!ok)
|
||||
return false;
|
||||
|
||||
const bool shouldExecuteToolCall = toolCallParser.state() == ToolEnums::ParseState::Complete
|
||||
&& toolCallParser.startTag() != ToolCallConstants::ThinkStartTag;
|
||||
|
||||
return !shouldExecuteToolCall && !respHandler.getStopGenerating();
|
||||
};
|
||||
model->prompt(std::string_view(prompt), promptCallback, handleResponse, ctx);
|
||||
|
||||
const bool shouldExecuteToolCall = toolCallParser.state() == ToolEnums::ParseState::Complete
|
||||
&& toolCallParser.startTag() != ToolCallConstants::ThinkStartTag;
|
||||
|
||||
return { toolCallParser.buffers(), shouldExecuteToolCall };
|
||||
}
|
||||
|
||||
class LLModelStore {
|
||||
@@ -738,7 +795,8 @@ std::vector<MessageItem> ChatLLM::forkConversation(const QString &prompt) const
|
||||
conversation.reserve(items.size() + 1);
|
||||
conversation.assign(items.begin(), items.end());
|
||||
}
|
||||
conversation.emplace_back(MessageItem::Type::Prompt, prompt.toUtf8());
|
||||
qsizetype nextIndex = conversation.empty() ? 0 : conversation.back().index().value() + 1;
|
||||
conversation.emplace_back(nextIndex, MessageItem::Type::Prompt, prompt.toUtf8());
|
||||
return conversation;
|
||||
}
|
||||
|
||||
@@ -757,19 +815,18 @@ static uint parseJinjaTemplateVersion(QStringView tmpl)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static auto loadJinjaTemplate(
|
||||
std::optional<jinja2::Template> &tmpl /*out*/, const std::string &source
|
||||
) -> jinja2::Result<void>
|
||||
static std::shared_ptr<minja::TemplateNode> loadJinjaTemplate(const std::string &source)
|
||||
{
|
||||
tmpl.emplace(jinjaEnv());
|
||||
return tmpl->Load(source);
|
||||
return minja::Parser::parse(source, { .trim_blocks = true, .lstrip_blocks = true, .keep_trailing_newline = false });
|
||||
}
|
||||
|
||||
std::optional<std::string> ChatLLM::checkJinjaTemplateError(const std::string &source)
|
||||
{
|
||||
std::optional<jinja2::Template> tmpl;
|
||||
if (auto res = loadJinjaTemplate(tmpl, source); !res)
|
||||
return res.error().ToString();
|
||||
try {
|
||||
loadJinjaTemplate(source);
|
||||
} catch (const std::runtime_error &e) {
|
||||
return e.what();
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -801,29 +858,29 @@ std::string ChatLLM::applyJinjaTemplate(std::span<const MessageItem> items) cons
|
||||
uint version = parseJinjaTemplateVersion(chatTemplate);
|
||||
|
||||
auto makeMap = [version](const MessageItem &item) {
|
||||
return jinja2::GenericMap([msg = std::make_shared<JinjaMessage>(version, item)] { return msg.get(); });
|
||||
return JinjaMessage(version, item).AsJson();
|
||||
};
|
||||
|
||||
std::unique_ptr<MessageItem> systemItem;
|
||||
bool useSystem = !isAllSpace(systemMessage);
|
||||
|
||||
jinja2::ValuesList messages;
|
||||
json::array_t messages;
|
||||
messages.reserve(useSystem + items.size());
|
||||
if (useSystem) {
|
||||
systemItem = std::make_unique<MessageItem>(MessageItem::Type::System, systemMessage.toUtf8());
|
||||
systemItem = std::make_unique<MessageItem>(MessageItem::system_tag, systemMessage.toUtf8());
|
||||
messages.emplace_back(makeMap(*systemItem));
|
||||
}
|
||||
for (auto &item : items)
|
||||
messages.emplace_back(makeMap(item));
|
||||
|
||||
jinja2::ValuesList toolList;
|
||||
json::array_t toolList;
|
||||
const int toolCount = ToolModel::globalInstance()->count();
|
||||
for (int i = 0; i < toolCount; ++i) {
|
||||
Tool *t = ToolModel::globalInstance()->get(i);
|
||||
toolList.push_back(t->jinjaValue());
|
||||
}
|
||||
|
||||
jinja2::ValuesMap params {
|
||||
json::object_t params {
|
||||
{ "messages", std::move(messages) },
|
||||
{ "add_generation_prompt", true },
|
||||
{ "toolList", toolList },
|
||||
@@ -831,12 +888,14 @@ std::string ChatLLM::applyJinjaTemplate(std::span<const MessageItem> items) cons
|
||||
for (auto &[name, token] : model->specialTokens())
|
||||
params.emplace(std::move(name), std::move(token));
|
||||
|
||||
std::optional<jinja2::Template> tmpl;
|
||||
auto maybeRendered = loadJinjaTemplate(tmpl, chatTemplate.toStdString())
|
||||
.and_then([&] { return tmpl->RenderAsString(params); });
|
||||
if (!maybeRendered)
|
||||
throw std::runtime_error(fmt::format("Failed to parse chat template: {}", maybeRendered.error().ToString()));
|
||||
return *maybeRendered;
|
||||
try {
|
||||
auto tmpl = loadJinjaTemplate(chatTemplate.toStdString());
|
||||
auto context = minja::Context::make(minja::Value(std::move(params)), jinjaEnv());
|
||||
return tmpl->render(context);
|
||||
} catch (const std::runtime_error &e) {
|
||||
throw std::runtime_error(fmt::format("Failed to parse chat template: {}", e.what()));
|
||||
}
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
|
||||
auto ChatLLM::promptInternalChat(const QStringList &enabledCollections, const LLModel::PromptContext &ctx,
|
||||
@@ -862,7 +921,7 @@ auto ChatLLM::promptInternalChat(const QStringList &enabledCollections, const LL
|
||||
// Find the prompt that represents the query. Server chats are flexible and may not have one.
|
||||
auto items = getChat();
|
||||
if (auto peer = m_chatModel->getPeer(items, items.end() - 1)) // peer of response
|
||||
query = { *peer - items.begin(), (*peer)->content() };
|
||||
query = { (*peer)->index().value(), (*peer)->content() };
|
||||
}
|
||||
|
||||
if (query) {
|
||||
@@ -888,6 +947,62 @@ auto ChatLLM::promptInternalChat(const QStringList &enabledCollections, const LL
|
||||
};
|
||||
}
|
||||
|
||||
class ChatViewResponseHandler : public BaseResponseHandler {
|
||||
public:
|
||||
ChatViewResponseHandler(ChatLLM *cllm, QElapsedTimer *totalTime, ChatLLM::PromptResult *result)
|
||||
: m_cllm(cllm), m_totalTime(totalTime), m_result(result) {}
|
||||
|
||||
void onSplitIntoTwo(const QString &startTag, const QString &firstBuffer, const QString &secondBuffer) override
|
||||
{
|
||||
if (startTag == ToolCallConstants::ThinkStartTag)
|
||||
m_cllm->m_chatModel->splitThinking({ firstBuffer, secondBuffer });
|
||||
else
|
||||
m_cllm->m_chatModel->splitToolCall({ firstBuffer, secondBuffer });
|
||||
}
|
||||
|
||||
void onSplitIntoThree(const QString &secondBuffer, const QString &thirdBuffer) override
|
||||
{
|
||||
m_cllm->m_chatModel->endThinking({ secondBuffer, thirdBuffer }, m_totalTime->elapsed());
|
||||
}
|
||||
|
||||
void onOldResponseChunk(const QByteArray &chunk) override
|
||||
{
|
||||
m_result->responseTokens++;
|
||||
m_cllm->m_timer->inc();
|
||||
m_result->response.append(chunk);
|
||||
}
|
||||
|
||||
bool onBufferResponse(const QString &response, int bufferIdx) override
|
||||
{
|
||||
Q_UNUSED(bufferIdx)
|
||||
try {
|
||||
m_cllm->m_chatModel->setResponseValue(response);
|
||||
} catch (const std::exception &e) {
|
||||
// We have a try/catch here because the main thread might have removed the response from
|
||||
// the chatmodel by erasing the conversation during the response... the main thread sets
|
||||
// m_stopGenerating before doing so, but it doesn't wait after that to reset the chatmodel
|
||||
Q_ASSERT(m_cllm->m_stopGenerating);
|
||||
return false;
|
||||
}
|
||||
emit m_cllm->responseChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool onRegularResponse() override
|
||||
{
|
||||
auto respStr = QString::fromUtf8(m_result->response);
|
||||
return onBufferResponse(removeLeadingWhitespace(respStr), 0);
|
||||
}
|
||||
|
||||
bool getStopGenerating() const override
|
||||
{ return m_cllm->m_stopGenerating; }
|
||||
|
||||
private:
|
||||
ChatLLM *m_cllm;
|
||||
QElapsedTimer *m_totalTime;
|
||||
ChatLLM::PromptResult *m_result;
|
||||
};
|
||||
|
||||
auto ChatLLM::promptInternal(
|
||||
const std::variant<std::span<const MessageItem>, std::string_view> &prompt,
|
||||
const LLModel::PromptContext &ctx,
|
||||
@@ -935,53 +1050,22 @@ auto ChatLLM::promptInternal(
|
||||
return !m_stopGenerating;
|
||||
};
|
||||
|
||||
ToolCallParser toolCallParser;
|
||||
auto handleResponse = [this, &result, &toolCallParser](LLModel::Token token, std::string_view piece) -> bool {
|
||||
Q_UNUSED(token)
|
||||
result.responseTokens++;
|
||||
m_timer->inc();
|
||||
|
||||
// FIXME: This is *not* necessarily fully formed utf data because it can be partial at this point
|
||||
// handle this like below where we have a QByteArray
|
||||
toolCallParser.update(QString::fromStdString(piece.data()));
|
||||
|
||||
// Create a toolcall and split the response if needed
|
||||
if (!toolCallParser.hasSplit() && toolCallParser.state() == ToolEnums::ParseState::Partial) {
|
||||
const QPair<QString, QString> pair = toolCallParser.split();
|
||||
m_chatModel->splitToolCall(pair);
|
||||
}
|
||||
|
||||
result.response.append(piece.data(), piece.size());
|
||||
auto respStr = QString::fromUtf8(result.response);
|
||||
|
||||
try {
|
||||
if (toolCallParser.hasSplit())
|
||||
m_chatModel->setResponseValue(toolCallParser.buffer());
|
||||
else
|
||||
m_chatModel->setResponseValue(removeLeadingWhitespace(respStr));
|
||||
} catch (const std::exception &e) {
|
||||
// We have a try/catch here because the main thread might have removed the response from
|
||||
// the chatmodel by erasing the conversation during the response... the main thread sets
|
||||
// m_stopGenerating before doing so, but it doesn't wait after that to reset the chatmodel
|
||||
Q_ASSERT(m_stopGenerating);
|
||||
return false;
|
||||
}
|
||||
|
||||
emit responseChanged();
|
||||
|
||||
const bool foundToolCall = toolCallParser.state() == ToolEnums::ParseState::Complete;
|
||||
return !foundToolCall && !m_stopGenerating;
|
||||
};
|
||||
|
||||
QElapsedTimer totalTime;
|
||||
totalTime.start();
|
||||
m_timer->start();
|
||||
ChatViewResponseHandler respHandler(this, &totalTime, &result);
|
||||
|
||||
m_timer->start();
|
||||
QStringList finalBuffers;
|
||||
bool shouldExecuteTool;
|
||||
try {
|
||||
emit promptProcessing();
|
||||
m_llModelInfo.model->setThreadCount(mySettings->threadCount());
|
||||
m_stopGenerating = false;
|
||||
m_llModelInfo.model->prompt(conversation, handlePrompt, handleResponse, ctx);
|
||||
std::tie(finalBuffers, shouldExecuteTool) = promptModelWithTools(
|
||||
m_llModelInfo.model.get(), handlePrompt, respHandler, ctx,
|
||||
QByteArray::fromRawData(conversation.data(), conversation.size()),
|
||||
ToolCallConstants::AllTagNames
|
||||
);
|
||||
} catch (...) {
|
||||
m_timer->stop();
|
||||
throw;
|
||||
@@ -990,20 +1074,18 @@ auto ChatLLM::promptInternal(
|
||||
m_timer->stop();
|
||||
qint64 elapsed = totalTime.elapsed();
|
||||
|
||||
const bool foundToolCall = toolCallParser.state() == ToolEnums::ParseState::Complete;
|
||||
|
||||
// trim trailing whitespace
|
||||
auto respStr = QString::fromUtf8(result.response);
|
||||
if (!respStr.isEmpty() && (std::as_const(respStr).back().isSpace() || foundToolCall)) {
|
||||
if (toolCallParser.hasSplit())
|
||||
m_chatModel->setResponseValue(toolCallParser.buffer());
|
||||
if (!respStr.isEmpty() && (std::as_const(respStr).back().isSpace() || finalBuffers.size() > 1)) {
|
||||
if (finalBuffers.size() > 1)
|
||||
m_chatModel->setResponseValue(finalBuffers.last());
|
||||
else
|
||||
m_chatModel->setResponseValue(respStr.trimmed());
|
||||
emit responseChanged();
|
||||
}
|
||||
|
||||
bool doQuestions = false;
|
||||
if (!m_isServer && messageItems && !foundToolCall) {
|
||||
if (!m_isServer && messageItems && !shouldExecuteTool) {
|
||||
switch (mySettings->suggestionMode()) {
|
||||
case SuggestionMode::On: doQuestions = true; break;
|
||||
case SuggestionMode::LocalDocsOnly: doQuestions = usedLocalDocs; break;
|
||||
@@ -1081,6 +1163,66 @@ void ChatLLM::reloadModel()
|
||||
loadModel(m);
|
||||
}
|
||||
|
||||
// This class throws discards the text within thinking tags, for use with chat names and follow-up questions.
|
||||
class SimpleResponseHandler : public BaseResponseHandler {
|
||||
public:
|
||||
SimpleResponseHandler(ChatLLM *cllm)
|
||||
: m_cllm(cllm) {}
|
||||
|
||||
void onSplitIntoTwo(const QString &startTag, const QString &firstBuffer, const QString &secondBuffer) override
|
||||
{ /* no-op */ }
|
||||
|
||||
void onSplitIntoThree(const QString &secondBuffer, const QString &thirdBuffer) override
|
||||
{ /* no-op */ }
|
||||
|
||||
void onOldResponseChunk(const QByteArray &chunk) override
|
||||
{ m_response.append(chunk); }
|
||||
|
||||
bool onBufferResponse(const QString &response, int bufferIdx) override
|
||||
{
|
||||
if (bufferIdx == 1)
|
||||
return true; // ignore "think" content
|
||||
return onSimpleResponse(response);
|
||||
}
|
||||
|
||||
bool onRegularResponse() override
|
||||
{ return onBufferResponse(QString::fromUtf8(m_response), 0); }
|
||||
|
||||
bool getStopGenerating() const override
|
||||
{ return m_cllm->m_stopGenerating; }
|
||||
|
||||
protected:
|
||||
virtual bool onSimpleResponse(const QString &response) = 0;
|
||||
|
||||
protected:
|
||||
ChatLLM *m_cllm;
|
||||
QByteArray m_response;
|
||||
};
|
||||
|
||||
class NameResponseHandler : public SimpleResponseHandler {
|
||||
private:
|
||||
// max length of chat names, in words
|
||||
static constexpr qsizetype MAX_WORDS = 3;
|
||||
|
||||
public:
|
||||
using SimpleResponseHandler::SimpleResponseHandler;
|
||||
|
||||
protected:
|
||||
bool onSimpleResponse(const QString &response) override
|
||||
{
|
||||
QTextStream stream(const_cast<QString *>(&response), QIODeviceBase::ReadOnly);
|
||||
QStringList words;
|
||||
while (!stream.atEnd() && words.size() < MAX_WORDS) {
|
||||
QString word;
|
||||
stream >> word;
|
||||
words << word;
|
||||
}
|
||||
|
||||
emit m_cllm->generatedNameChanged(words.join(u' '));
|
||||
return words.size() < MAX_WORDS || stream.atEnd();
|
||||
}
|
||||
};
|
||||
|
||||
void ChatLLM::generateName()
|
||||
{
|
||||
Q_ASSERT(isModelLoaded());
|
||||
@@ -1097,23 +1239,15 @@ void ChatLLM::generateName()
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray response; // raw UTF-8
|
||||
|
||||
auto handleResponse = [this, &response](LLModel::Token token, std::string_view piece) -> bool {
|
||||
Q_UNUSED(token)
|
||||
|
||||
response.append(piece.data(), piece.size());
|
||||
QStringList words = QString::fromUtf8(response).simplified().split(u' ', Qt::SkipEmptyParts);
|
||||
emit generatedNameChanged(words.join(u' '));
|
||||
return words.size() <= 3;
|
||||
};
|
||||
NameResponseHandler respHandler(this);
|
||||
|
||||
try {
|
||||
m_llModelInfo.model->prompt(
|
||||
applyJinjaTemplate(forkConversation(chatNamePrompt)),
|
||||
[this](auto &&...) { return !m_stopGenerating; },
|
||||
handleResponse,
|
||||
promptContextFromSettings(m_modelInfo)
|
||||
promptModelWithTools(
|
||||
m_llModelInfo.model.get(),
|
||||
/*promptCallback*/ [this](auto &&...) { return !m_stopGenerating; },
|
||||
respHandler, promptContextFromSettings(m_modelInfo),
|
||||
applyJinjaTemplate(forkConversation(chatNamePrompt)).c_str(),
|
||||
{ ToolCallConstants::ThinkTagName }
|
||||
);
|
||||
} catch (const std::exception &e) {
|
||||
qWarning() << "ChatLLM failed to generate name:" << e.what();
|
||||
@@ -1125,13 +1259,43 @@ void ChatLLM::handleChatIdChanged(const QString &id)
|
||||
m_llmThread.setObjectName(id);
|
||||
}
|
||||
|
||||
void ChatLLM::generateQuestions(qint64 elapsed)
|
||||
{
|
||||
class QuestionResponseHandler : public SimpleResponseHandler {
|
||||
public:
|
||||
using SimpleResponseHandler::SimpleResponseHandler;
|
||||
|
||||
protected:
|
||||
bool onSimpleResponse(const QString &response) override
|
||||
{
|
||||
auto responseUtf8Bytes = response.toUtf8().slice(m_offset);
|
||||
auto responseUtf8 = std::string(responseUtf8Bytes.begin(), responseUtf8Bytes.end());
|
||||
// extract all questions from response
|
||||
ptrdiff_t lastMatchEnd = -1;
|
||||
auto it = std::sregex_iterator(responseUtf8.begin(), responseUtf8.end(), s_reQuestion);
|
||||
auto end = std::sregex_iterator();
|
||||
for (; it != end; ++it) {
|
||||
auto pos = it->position();
|
||||
auto len = it->length();
|
||||
lastMatchEnd = pos + len;
|
||||
emit m_cllm->generatedQuestionFinished(QString::fromUtf8(&responseUtf8[pos], len));
|
||||
}
|
||||
|
||||
// remove processed input from buffer
|
||||
if (lastMatchEnd != -1)
|
||||
m_offset += lastMatchEnd;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
// FIXME: This only works with response by the model in english which is not ideal for a multi-language
|
||||
// model.
|
||||
// match whole question sentences
|
||||
static const std::regex reQuestion(R"(\b(?:What|Where|How|Why|When|Who|Which|Whose|Whom)\b[^?]*\?)");
|
||||
static inline const std::regex s_reQuestion { R"(\b(?:What|Where|How|Why|When|Who|Which|Whose|Whom)\b[^?]*\?)" };
|
||||
|
||||
qsizetype m_offset = 0;
|
||||
};
|
||||
|
||||
void ChatLLM::generateQuestions(qint64 elapsed)
|
||||
{
|
||||
Q_ASSERT(isModelLoaded());
|
||||
if (!isModelLoaded()) {
|
||||
emit responseStopped(elapsed);
|
||||
@@ -1149,39 +1313,17 @@ void ChatLLM::generateQuestions(qint64 elapsed)
|
||||
|
||||
emit generatingQuestions();
|
||||
|
||||
std::string response; // raw UTF-8
|
||||
|
||||
auto handleResponse = [this, &response](LLModel::Token token, std::string_view piece) -> bool {
|
||||
Q_UNUSED(token)
|
||||
|
||||
// add token to buffer
|
||||
response.append(piece);
|
||||
|
||||
// extract all questions from response
|
||||
ptrdiff_t lastMatchEnd = -1;
|
||||
auto it = std::sregex_iterator(response.begin(), response.end(), reQuestion);
|
||||
auto end = std::sregex_iterator();
|
||||
for (; it != end; ++it) {
|
||||
auto pos = it->position();
|
||||
auto len = it->length();
|
||||
lastMatchEnd = pos + len;
|
||||
emit generatedQuestionFinished(QString::fromUtf8(&response[pos], len));
|
||||
}
|
||||
|
||||
// remove processed input from buffer
|
||||
if (lastMatchEnd != -1)
|
||||
response.erase(0, lastMatchEnd);
|
||||
return true;
|
||||
};
|
||||
QuestionResponseHandler respHandler(this);
|
||||
|
||||
QElapsedTimer totalTime;
|
||||
totalTime.start();
|
||||
try {
|
||||
m_llModelInfo.model->prompt(
|
||||
applyJinjaTemplate(forkConversation(suggestedFollowUpPrompt)),
|
||||
[this](auto &&...) { return !m_stopGenerating; },
|
||||
handleResponse,
|
||||
promptContextFromSettings(m_modelInfo)
|
||||
promptModelWithTools(
|
||||
m_llModelInfo.model.get(),
|
||||
/*promptCallback*/ [this](auto &&...) { return !m_stopGenerating; },
|
||||
respHandler, promptContextFromSettings(m_modelInfo),
|
||||
applyJinjaTemplate(forkConversation(suggestedFollowUpPrompt)).c_str(),
|
||||
{ ToolCallConstants::ThinkTagName }
|
||||
);
|
||||
} catch (const std::exception &e) {
|
||||
qWarning() << "ChatLLM failed to generate follow-up questions:" << e.what();
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
using namespace Qt::Literals::StringLiterals;
|
||||
|
||||
class ChatViewResponseHandler;
|
||||
class QDataStream;
|
||||
|
||||
// NOTE: values serialized to disk, do not change or reuse
|
||||
@@ -285,6 +286,8 @@ private:
|
||||
bool m_isServer;
|
||||
bool m_forceMetal;
|
||||
bool m_reloadingToChangeVariant;
|
||||
friend class ChatViewResponseHandler;
|
||||
friend class SimpleResponseHandler;
|
||||
};
|
||||
|
||||
#endif // CHATLLM_H
|
||||
|
||||
@@ -41,6 +41,12 @@ void ChatItem::serializeText(QDataStream &stream, int version)
|
||||
stream << value;
|
||||
}
|
||||
|
||||
void ChatItem::serializeThink(QDataStream &stream, int version)
|
||||
{
|
||||
stream << value;
|
||||
stream << thinkingTime;
|
||||
}
|
||||
|
||||
void ChatItem::serializeSubItems(QDataStream &stream, int version)
|
||||
{
|
||||
stream << name;
|
||||
@@ -50,6 +56,7 @@ void ChatItem::serializeSubItems(QDataStream &stream, int version)
|
||||
case ToolCall: { serializeToolCall(stream, version); break; }
|
||||
case ToolResponse: { serializeToolResponse(stream, version); break; }
|
||||
case Text: { serializeText(stream, version); break; }
|
||||
case Think: { serializeThink(stream, version); break; }
|
||||
case System:
|
||||
case Prompt:
|
||||
throw std::invalid_argument(fmt::format("cannot serialize subitem type {}", int(typ)));
|
||||
@@ -162,6 +169,13 @@ bool ChatItem::deserializeResponse(QDataStream &stream, int version)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ChatItem::deserializeThink(QDataStream &stream, int version)
|
||||
{
|
||||
stream >> value;
|
||||
stream >> thinkingTime;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ChatItem::deserializeSubItems(QDataStream &stream, int version)
|
||||
{
|
||||
stream >> name;
|
||||
@@ -177,6 +191,7 @@ bool ChatItem::deserializeSubItems(QDataStream &stream, int version)
|
||||
case ToolCall: { deserializeToolCall(stream, version); break; }
|
||||
case ToolResponse: { deserializeToolResponse(stream, version); break; }
|
||||
case Text: { deserializeText(stream, version); break; }
|
||||
case Think: { deserializeThink(stream, version); break; }
|
||||
case System:
|
||||
case Prompt:
|
||||
throw std::invalid_argument(fmt::format("cannot serialize subitem type {}", int(typ)));
|
||||
|
||||
@@ -94,11 +94,28 @@ class MessageItem
|
||||
public:
|
||||
enum class Type { System, Prompt, Response, ToolResponse };
|
||||
|
||||
MessageItem(Type type, QString content)
|
||||
: m_type(type), m_content(std::move(content)) {}
|
||||
struct system_tag_t { explicit system_tag_t() = default; };
|
||||
static inline constexpr system_tag_t system_tag = system_tag_t{};
|
||||
|
||||
MessageItem(Type type, QString content, const QList<ResultInfo> &sources, const QList<PromptAttachment> &promptAttachments)
|
||||
: m_type(type), m_content(std::move(content)), m_sources(sources), m_promptAttachments(promptAttachments) {}
|
||||
MessageItem(qsizetype index, Type type, QString content)
|
||||
: m_index(index), m_type(type), m_content(std::move(content))
|
||||
{
|
||||
Q_ASSERT(type != Type::System); // use system_tag constructor
|
||||
}
|
||||
|
||||
// Construct a system message with no index, since they are never stored in the chat
|
||||
MessageItem(system_tag_t, QString content)
|
||||
: m_type(Type::System), m_content(std::move(content)) {}
|
||||
|
||||
MessageItem(qsizetype index, Type type, QString content, const QList<ResultInfo> &sources, const QList<PromptAttachment> &promptAttachments)
|
||||
: m_index(index)
|
||||
, m_type(type)
|
||||
, m_content(std::move(content))
|
||||
, m_sources(sources)
|
||||
, m_promptAttachments(promptAttachments) {}
|
||||
|
||||
// index of the parent ChatItem (system, prompt, response) in its container
|
||||
std::optional<qsizetype> index() const { return m_index; }
|
||||
|
||||
Type type() const { return m_type; }
|
||||
const QString &content() const { return m_content; }
|
||||
@@ -126,10 +143,11 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
Type m_type;
|
||||
QString m_content;
|
||||
QList<ResultInfo> m_sources;
|
||||
QList<PromptAttachment> m_promptAttachments;
|
||||
std::optional<qsizetype> m_index;
|
||||
Type m_type;
|
||||
QString m_content;
|
||||
QList<ResultInfo> m_sources;
|
||||
QList<PromptAttachment> m_promptAttachments;
|
||||
};
|
||||
Q_DECLARE_METATYPE(MessageItem)
|
||||
|
||||
@@ -159,8 +177,11 @@ class ChatItem : public QObject
|
||||
Q_PROPERTY(bool thumbsUpState MEMBER thumbsUpState )
|
||||
Q_PROPERTY(bool thumbsDownState MEMBER thumbsDownState)
|
||||
|
||||
// thinking
|
||||
Q_PROPERTY(int thinkingTime MEMBER thinkingTime NOTIFY thinkingTimeChanged)
|
||||
|
||||
public:
|
||||
enum class Type { System, Prompt, Response, Text, ToolCall, ToolResponse };
|
||||
enum class Type { System, Prompt, Response, Text, ToolCall, ToolResponse, Think };
|
||||
|
||||
// tags for constructing ChatItems
|
||||
struct prompt_tag_t { explicit prompt_tag_t () = default; };
|
||||
@@ -169,12 +190,14 @@ public:
|
||||
struct text_tag_t { explicit text_tag_t () = default; };
|
||||
struct tool_call_tag_t { explicit tool_call_tag_t () = default; };
|
||||
struct tool_response_tag_t { explicit tool_response_tag_t() = default; };
|
||||
struct think_tag_t { explicit think_tag_t () = default; };
|
||||
static inline constexpr prompt_tag_t prompt_tag = prompt_tag_t {};
|
||||
static inline constexpr response_tag_t response_tag = response_tag_t {};
|
||||
static inline constexpr system_tag_t system_tag = system_tag_t {};
|
||||
static inline constexpr text_tag_t text_tag = text_tag_t {};
|
||||
static inline constexpr tool_call_tag_t tool_call_tag = tool_call_tag_t {};
|
||||
static inline constexpr tool_response_tag_t tool_response_tag = tool_response_tag_t {};
|
||||
static inline constexpr think_tag_t think_tag = think_tag_t {};
|
||||
|
||||
public:
|
||||
ChatItem(QObject *parent)
|
||||
@@ -220,6 +243,10 @@ public:
|
||||
: ChatItem(parent)
|
||||
{ this->name = u"ToolResponse: "_s; this->value = value; }
|
||||
|
||||
ChatItem(QObject *parent, think_tag_t, const QString &value)
|
||||
: ChatItem(parent)
|
||||
{ this->name = u"Think: "_s; this->value = value; }
|
||||
|
||||
Type type() const
|
||||
{
|
||||
if (name == u"System: "_s)
|
||||
@@ -234,6 +261,8 @@ public:
|
||||
return Type::ToolCall;
|
||||
if (name == u"ToolResponse: "_s)
|
||||
return Type::ToolResponse;
|
||||
if (name == u"Think: "_s)
|
||||
return Type::Think;
|
||||
throw std::invalid_argument(fmt::format("Chat item has unknown label: {:?}", name));
|
||||
}
|
||||
|
||||
@@ -254,7 +283,7 @@ public:
|
||||
if (type() == Type::Response) {
|
||||
// We parse if this contains any part of a partial toolcall
|
||||
ToolCallParser parser;
|
||||
parser.update(value);
|
||||
parser.update(value.toUtf8());
|
||||
|
||||
// If no tool call is detected, return the original value
|
||||
if (parser.startIndex() < 0)
|
||||
@@ -265,9 +294,11 @@ public:
|
||||
return beforeToolCall;
|
||||
}
|
||||
|
||||
// For tool calls we only return content if it is the code interpreter
|
||||
if (type() == Type::Think)
|
||||
return thinkContent(value);
|
||||
|
||||
if (type() == Type::ToolCall)
|
||||
return codeInterpreterContent(value);
|
||||
return toolCallContent(value);
|
||||
|
||||
// We don't show any of content from the tool response in the GUI
|
||||
if (type() == Type::ToolResponse)
|
||||
@@ -276,10 +307,21 @@ public:
|
||||
return value;
|
||||
}
|
||||
|
||||
QString codeInterpreterContent(const QString &value) const
|
||||
QString thinkContent(const QString &value) const
|
||||
{
|
||||
ToolCallParser parser;
|
||||
parser.update(value);
|
||||
parser.update(value.toUtf8());
|
||||
|
||||
// Extract the content
|
||||
QString content = parser.toolCall();
|
||||
content = content.trimmed();
|
||||
return content;
|
||||
}
|
||||
|
||||
QString toolCallContent(const QString &value) const
|
||||
{
|
||||
ToolCallParser parser;
|
||||
parser.update(value.toUtf8());
|
||||
|
||||
// Extract the code
|
||||
QString code = parser.toolCall();
|
||||
@@ -357,6 +399,12 @@ public:
|
||||
return toolCallInfo.error != ToolEnums::Error::NoError;
|
||||
}
|
||||
|
||||
void setThinkingTime(int t)
|
||||
{
|
||||
thinkingTime = t;
|
||||
emit thinkingTimeChanged();
|
||||
}
|
||||
|
||||
// NB: Assumes response is not current.
|
||||
static ChatItem *fromMessageInput(QObject *parent, const MessageInput &message)
|
||||
{
|
||||
@@ -369,7 +417,7 @@ public:
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
|
||||
MessageItem asMessageItem() const
|
||||
MessageItem asMessageItem(qsizetype index) const
|
||||
{
|
||||
MessageItem::Type msgType;
|
||||
switch (auto typ = type()) {
|
||||
@@ -380,9 +428,10 @@ public:
|
||||
case ToolResponse: msgType = MessageItem::Type::ToolResponse; break;
|
||||
case Text:
|
||||
case ToolCall:
|
||||
case Think:
|
||||
throw std::invalid_argument(fmt::format("cannot convert ChatItem type {} to message item", int(typ)));
|
||||
}
|
||||
return { msgType, flattenedContent(), sources, promptAttachments };
|
||||
return { index, msgType, flattenedContent(), sources, promptAttachments };
|
||||
}
|
||||
|
||||
static QList<ResultInfo> consolidateSources(const QList<ResultInfo> &sources);
|
||||
@@ -391,6 +440,7 @@ public:
|
||||
void serializeToolCall(QDataStream &stream, int version);
|
||||
void serializeToolResponse(QDataStream &stream, int version);
|
||||
void serializeText(QDataStream &stream, int version);
|
||||
void serializeThink(QDataStream &stream, int version);
|
||||
void serializeSubItems(QDataStream &stream, int version); // recursive
|
||||
void serialize(QDataStream &stream, int version);
|
||||
|
||||
@@ -399,6 +449,7 @@ public:
|
||||
bool deserializeToolCall(QDataStream &stream, int version);
|
||||
bool deserializeToolResponse(QDataStream &stream, int version);
|
||||
bool deserializeText(QDataStream &stream, int version);
|
||||
bool deserializeThink(QDataStream &stream, int version);
|
||||
bool deserializeSubItems(QDataStream &stream, int version); // recursive
|
||||
bool deserialize(QDataStream &stream, int version);
|
||||
|
||||
@@ -406,6 +457,7 @@ Q_SIGNALS:
|
||||
void contentChanged();
|
||||
void isTooCallErrorChanged();
|
||||
void isCurrentResponseChanged();
|
||||
void thinkingTimeChanged();
|
||||
|
||||
public:
|
||||
|
||||
@@ -429,6 +481,9 @@ public:
|
||||
bool stopped = false;
|
||||
bool thumbsUpState = false;
|
||||
bool thumbsDownState = false;
|
||||
|
||||
// thinking time in ms
|
||||
int thinkingTime = 0;
|
||||
};
|
||||
|
||||
class ChatModel : public QAbstractListModel
|
||||
@@ -500,6 +555,7 @@ private:
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// FIXME(jared): this should really be done at the parent level, not the sub-item level
|
||||
static std::optional<qsizetype> getPeerInternal(const MessageItem *arr, qsizetype size, qsizetype index)
|
||||
{
|
||||
qsizetype peer;
|
||||
@@ -879,6 +935,70 @@ public:
|
||||
if (changed) emit dataChanged(createIndex(index, 0), createIndex(index, 0), {NewResponseRole});
|
||||
}
|
||||
|
||||
Q_INVOKABLE void splitThinking(const QPair<QString, QString> &split)
|
||||
{
|
||||
qsizetype index;
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (m_chatItems.isEmpty() || m_chatItems.cend()[-1]->type() != ChatItem::Type::Response)
|
||||
throw std::logic_error("can only set thinking on a chat that ends with a response");
|
||||
|
||||
index = m_chatItems.count() - 1;
|
||||
ChatItem *currentResponse = m_chatItems.back();
|
||||
Q_ASSERT(currentResponse->isCurrentResponse);
|
||||
|
||||
// Create a new response container for any text and the thinking
|
||||
ChatItem *newResponse = new ChatItem(this, ChatItem::response_tag);
|
||||
|
||||
// Add preceding text if any
|
||||
if (!split.first.isEmpty()) {
|
||||
ChatItem *textItem = new ChatItem(this, ChatItem::text_tag, split.first);
|
||||
newResponse->subItems.push_back(textItem);
|
||||
}
|
||||
|
||||
// Add the thinking item
|
||||
Q_ASSERT(!split.second.isEmpty());
|
||||
ChatItem *thinkingItem = new ChatItem(this, ChatItem::think_tag, split.second);
|
||||
thinkingItem->isCurrentResponse = true;
|
||||
newResponse->subItems.push_back(thinkingItem);
|
||||
|
||||
// Add new response and reset our value
|
||||
currentResponse->subItems.push_back(newResponse);
|
||||
currentResponse->value = QString();
|
||||
}
|
||||
|
||||
emit dataChanged(createIndex(index, 0), createIndex(index, 0), {ChildItemsRole, ContentRole});
|
||||
}
|
||||
|
||||
Q_INVOKABLE void endThinking(const QPair<QString, QString> &split, int thinkingTime)
|
||||
{
|
||||
qsizetype index;
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (m_chatItems.isEmpty() || m_chatItems.cend()[-1]->type() != ChatItem::Type::Response)
|
||||
throw std::logic_error("can only end thinking on a chat that ends with a response");
|
||||
|
||||
index = m_chatItems.count() - 1;
|
||||
ChatItem *currentResponse = m_chatItems.back();
|
||||
Q_ASSERT(currentResponse->isCurrentResponse);
|
||||
|
||||
ChatItem *subResponse = currentResponse->subItems.back();
|
||||
Q_ASSERT(subResponse->type() == ChatItem::Type::Response);
|
||||
Q_ASSERT(subResponse->isCurrentResponse);
|
||||
subResponse->setCurrentResponse(false);
|
||||
|
||||
ChatItem *thinkingItem = subResponse->subItems.back();
|
||||
Q_ASSERT(thinkingItem->type() == ChatItem::Type::Think);
|
||||
thinkingItem->setCurrentResponse(false);
|
||||
thinkingItem->setValue(split.first);
|
||||
thinkingItem->setThinkingTime(thinkingTime);
|
||||
|
||||
currentResponse->setValue(split.second);
|
||||
}
|
||||
|
||||
emit dataChanged(createIndex(index, 0), createIndex(index, 0), {ChildItemsRole, ContentRole});
|
||||
}
|
||||
|
||||
Q_INVOKABLE void splitToolCall(const QPair<QString, QString> &split)
|
||||
{
|
||||
qsizetype index;
|
||||
@@ -1013,10 +1133,12 @@ public:
|
||||
// A flattened version of the chat item tree used by the backend and jinja
|
||||
QMutexLocker locker(&m_mutex);
|
||||
std::vector<MessageItem> chatItems;
|
||||
for (const ChatItem *item : m_chatItems) {
|
||||
chatItems.reserve(chatItems.size() + item->subItems.size() + 1);
|
||||
ranges::copy(item->subItems | views::transform(&ChatItem::asMessageItem), std::back_inserter(chatItems));
|
||||
chatItems.push_back(item->asMessageItem());
|
||||
for (qsizetype i : views::iota(0, m_chatItems.size())) {
|
||||
auto *parent = m_chatItems.at(i);
|
||||
chatItems.reserve(chatItems.size() + parent->subItems.size() + 1);
|
||||
ranges::copy(parent->subItems | views::transform([&](auto *s) { return s->asMessageItem(i); }),
|
||||
std::back_inserter(chatItems));
|
||||
chatItems.push_back(parent->asMessageItem(i));
|
||||
}
|
||||
return chatItems;
|
||||
}
|
||||
|
||||
@@ -7,8 +7,15 @@
|
||||
|
||||
using namespace Qt::Literals::StringLiterals;
|
||||
|
||||
CodeInterpreter::CodeInterpreter()
|
||||
: Tool()
|
||||
, m_error(ToolEnums::Error::NoError)
|
||||
{
|
||||
m_worker = new CodeInterpreterWorker;
|
||||
connect(this, &CodeInterpreter::request, m_worker, &CodeInterpreterWorker::request, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
QString CodeInterpreter::run(const QList<ToolParam> ¶ms, qint64 timeout)
|
||||
void CodeInterpreter::run(const QList<ToolParam> ¶ms)
|
||||
{
|
||||
m_error = ToolEnums::Error::NoError;
|
||||
m_errorString = QString();
|
||||
@@ -18,27 +25,24 @@ QString CodeInterpreter::run(const QList<ToolParam> ¶ms, qint64 timeout)
|
||||
&& params.first().type == ToolEnums::ParamType::String);
|
||||
|
||||
const QString code = params.first().value.toString();
|
||||
|
||||
QThread workerThread;
|
||||
CodeInterpreterWorker worker;
|
||||
worker.moveToThread(&workerThread);
|
||||
connect(&worker, &CodeInterpreterWorker::finished, &workerThread, &QThread::quit, Qt::DirectConnection);
|
||||
connect(&workerThread, &QThread::started, [&worker, code]() {
|
||||
worker.request(code);
|
||||
connect(m_worker, &CodeInterpreterWorker::finished, [this, params] {
|
||||
m_error = m_worker->error();
|
||||
m_errorString = m_worker->errorString();
|
||||
emit runComplete({
|
||||
ToolCallConstants::CodeInterpreterFunction,
|
||||
params,
|
||||
m_worker->response(),
|
||||
m_error,
|
||||
m_errorString
|
||||
});
|
||||
});
|
||||
workerThread.start();
|
||||
bool timedOut = !workerThread.wait(timeout);
|
||||
if (timedOut) {
|
||||
worker.interrupt(timeout); // thread safe
|
||||
m_error = ToolEnums::Error::TimeoutError;
|
||||
}
|
||||
workerThread.quit();
|
||||
workerThread.wait();
|
||||
if (!timedOut) {
|
||||
m_error = worker.error();
|
||||
m_errorString = worker.errorString();
|
||||
}
|
||||
return worker.response();
|
||||
|
||||
emit request(code);
|
||||
}
|
||||
|
||||
bool CodeInterpreter::interrupt()
|
||||
{
|
||||
return m_worker->interrupt();
|
||||
}
|
||||
|
||||
QList<ToolParamInfo> CodeInterpreter::parameters() const
|
||||
@@ -89,20 +93,20 @@ QString CodeInterpreter::exampleReply() const
|
||||
|
||||
CodeInterpreterWorker::CodeInterpreterWorker()
|
||||
: QObject(nullptr)
|
||||
, m_engine(new QJSEngine(this))
|
||||
{
|
||||
}
|
||||
moveToThread(&m_thread);
|
||||
|
||||
void CodeInterpreterWorker::request(const QString &code)
|
||||
{
|
||||
JavaScriptConsoleCapture consoleCapture;
|
||||
QJSValue consoleInternalObject = m_engine.newQObject(&consoleCapture);
|
||||
m_engine.globalObject().setProperty("console_internal", consoleInternalObject);
|
||||
QJSValue consoleInternalObject = m_engine->newQObject(&m_consoleCapture);
|
||||
m_engine->globalObject().setProperty("console_internal", consoleInternalObject);
|
||||
|
||||
// preprocess console.log args in JS since Q_INVOKE doesn't support varargs
|
||||
auto consoleObject = m_engine.evaluate(uR"(
|
||||
auto consoleObject = m_engine->evaluate(uR"(
|
||||
class Console {
|
||||
log(...args) {
|
||||
if (args.length && typeof args[0] === 'string')
|
||||
if (args.length == 0)
|
||||
return;
|
||||
if (args.length >= 2 && typeof args[0] === 'string')
|
||||
throw new Error('console.log string formatting not supported');
|
||||
let cat = '';
|
||||
for (const arg of args) {
|
||||
@@ -114,15 +118,28 @@ void CodeInterpreterWorker::request(const QString &code)
|
||||
|
||||
new Console();
|
||||
)"_s);
|
||||
m_engine.globalObject().setProperty("console", consoleObject);
|
||||
m_engine->globalObject().setProperty("console", consoleObject);
|
||||
m_thread.start();
|
||||
}
|
||||
|
||||
const QJSValue result = m_engine.evaluate(code);
|
||||
void CodeInterpreterWorker::reset()
|
||||
{
|
||||
m_response.clear();
|
||||
m_error = ToolEnums::Error::NoError;
|
||||
m_errorString.clear();
|
||||
m_consoleCapture.output.clear();
|
||||
m_engine->setInterrupted(false);
|
||||
}
|
||||
|
||||
void CodeInterpreterWorker::request(const QString &code)
|
||||
{
|
||||
reset();
|
||||
const QJSValue result = m_engine->evaluate(code);
|
||||
QString resultString;
|
||||
|
||||
if (m_engine.isInterrupted()) {
|
||||
resultString = QString("Error: code execution was timed out as it exceeded %1 ms. Code must be written to ensure execution does not timeout.").arg(m_timeout);
|
||||
} else if (result.isError()) {
|
||||
if (m_engine->isInterrupted()) {
|
||||
resultString = QString("Error: code execution was interrupted or timed out.");
|
||||
} else if (result.isError()) {
|
||||
// NOTE: We purposely do not set the m_error or m_errorString for the code interpreter since
|
||||
// we *want* the model to see the response has an error so it can hopefully correct itself. The
|
||||
// error member variables are intended for tools that have error conditions that cannot be corrected.
|
||||
@@ -143,9 +160,16 @@ void CodeInterpreterWorker::request(const QString &code)
|
||||
}
|
||||
|
||||
if (resultString.isEmpty())
|
||||
resultString = consoleCapture.output;
|
||||
else if (!consoleCapture.output.isEmpty())
|
||||
resultString += "\n" + consoleCapture.output;
|
||||
resultString = m_consoleCapture.output;
|
||||
else if (!m_consoleCapture.output.isEmpty())
|
||||
resultString += "\n" + m_consoleCapture.output;
|
||||
m_response = resultString;
|
||||
emit finished();
|
||||
}
|
||||
|
||||
bool CodeInterpreterWorker::interrupt()
|
||||
{
|
||||
m_error = ToolEnums::Error::TimeoutError;
|
||||
m_engine->setInterrupted(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QtGlobal>
|
||||
#include <QThread>
|
||||
|
||||
class JavaScriptConsoleCapture : public QObject
|
||||
{
|
||||
@@ -39,32 +40,37 @@ public:
|
||||
CodeInterpreterWorker();
|
||||
virtual ~CodeInterpreterWorker() {}
|
||||
|
||||
void reset();
|
||||
QString response() const { return m_response; }
|
||||
|
||||
void request(const QString &code);
|
||||
void interrupt(qint64 timeout) { m_timeout = timeout; m_engine.setInterrupted(true); }
|
||||
ToolEnums::Error error() const { return m_error; }
|
||||
QString errorString() const { return m_errorString; }
|
||||
bool interrupt();
|
||||
|
||||
public Q_SLOTS:
|
||||
void request(const QString &code);
|
||||
|
||||
Q_SIGNALS:
|
||||
void finished();
|
||||
|
||||
private:
|
||||
qint64 m_timeout = 0;
|
||||
QJSEngine m_engine;
|
||||
QString m_response;
|
||||
ToolEnums::Error m_error = ToolEnums::Error::NoError;
|
||||
QString m_errorString;
|
||||
QThread m_thread;
|
||||
JavaScriptConsoleCapture m_consoleCapture;
|
||||
QJSEngine *m_engine = nullptr;
|
||||
};
|
||||
|
||||
class CodeInterpreter : public Tool
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit CodeInterpreter() : Tool(), m_error(ToolEnums::Error::NoError) {}
|
||||
explicit CodeInterpreter();
|
||||
virtual ~CodeInterpreter() {}
|
||||
|
||||
QString run(const QList<ToolParam> ¶ms, qint64 timeout = 2000) override;
|
||||
void run(const QList<ToolParam> ¶ms) override;
|
||||
bool interrupt() override;
|
||||
|
||||
ToolEnums::Error error() const override { return m_error; }
|
||||
QString errorString() const override { return m_errorString; }
|
||||
|
||||
@@ -77,9 +83,13 @@ public:
|
||||
QString exampleCall() const override;
|
||||
QString exampleReply() const override;
|
||||
|
||||
Q_SIGNALS:
|
||||
void request(const QString &code);
|
||||
|
||||
private:
|
||||
ToolEnums::Error m_error = ToolEnums::Error::NoError;
|
||||
QString m_errorString;
|
||||
CodeInterpreterWorker *m_worker;
|
||||
};
|
||||
|
||||
#endif // CODEINTERPRETER_H
|
||||
|
||||
7
gpt4all-chat/src/config.h.in
Normal file
7
gpt4all-chat/src/config.h.in
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#define APP_VERSION "@APP_VERSION@"
|
||||
|
||||
#define G4A_CONFIG(name) (1/G4A_CONFIG_##name == 1)
|
||||
|
||||
#define G4A_CONFIG_force_d3d12 @GPT4ALL_CONFIG_FORCE_D3D12@
|
||||
@@ -1209,7 +1209,6 @@ public:
|
||||
FPDF_ClosePage(m_page);
|
||||
if (m_doc)
|
||||
FPDF_CloseDocument(m_doc);
|
||||
FPDF_DestroyLibrary();
|
||||
}
|
||||
|
||||
int page() const override { return m_currentPage; }
|
||||
@@ -1224,7 +1223,7 @@ private:
|
||||
return std::nullopt;
|
||||
|
||||
if (m_page)
|
||||
FPDF_ClosePage(m_page);
|
||||
FPDF_ClosePage(std::exchange(m_page, nullptr));
|
||||
m_page = FPDF_LoadPage(m_doc, m_currentPage++);
|
||||
if (!m_page)
|
||||
throw std::runtime_error("Failed to load page.");
|
||||
|
||||
@@ -7,111 +7,75 @@
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
#include <memory>
|
||||
#include <iterator>
|
||||
#include <map>
|
||||
#include <ranges>
|
||||
#include <vector>
|
||||
|
||||
using namespace std::literals::string_view_literals;
|
||||
namespace views = std::views;
|
||||
using json = nlohmann::ordered_json;
|
||||
|
||||
|
||||
JinjaResultInfo::~JinjaResultInfo() = default;
|
||||
|
||||
const JinjaFieldMap<ResultInfo> JinjaResultInfo::s_fields = {
|
||||
{ "collection", [](auto &s) { return s.collection.toStdString(); } },
|
||||
{ "path", [](auto &s) { return s.path .toStdString(); } },
|
||||
{ "file", [](auto &s) { return s.file .toStdString(); } },
|
||||
{ "title", [](auto &s) { return s.title .toStdString(); } },
|
||||
{ "author", [](auto &s) { return s.author .toStdString(); } },
|
||||
{ "date", [](auto &s) { return s.date .toStdString(); } },
|
||||
{ "text", [](auto &s) { return s.text .toStdString(); } },
|
||||
{ "page", [](auto &s) { return s.page; } },
|
||||
{ "file_uri", [](auto &s) { return s.fileUri() .toStdString(); } },
|
||||
};
|
||||
|
||||
JinjaPromptAttachment::~JinjaPromptAttachment() = default;
|
||||
|
||||
const JinjaFieldMap<PromptAttachment> JinjaPromptAttachment::s_fields = {
|
||||
{ "url", [](auto &s) { return s.url.toString() .toStdString(); } },
|
||||
{ "file", [](auto &s) { return s.file() .toStdString(); } },
|
||||
{ "processed_content", [](auto &s) { return s.processedContent().toStdString(); } },
|
||||
};
|
||||
|
||||
std::vector<std::string> JinjaMessage::GetKeys() const
|
||||
json::object_t JinjaResultInfo::AsJson() const
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
auto &keys = this->keys();
|
||||
result.reserve(keys.size());
|
||||
result.assign(keys.begin(), keys.end());
|
||||
return result;
|
||||
return {
|
||||
{ "collection", m_source->collection.toStdString() },
|
||||
{ "path", m_source->path .toStdString() },
|
||||
{ "file", m_source->file .toStdString() },
|
||||
{ "title", m_source->title .toStdString() },
|
||||
{ "author", m_source->author .toStdString() },
|
||||
{ "date", m_source->date .toStdString() },
|
||||
{ "text", m_source->text .toStdString() },
|
||||
{ "page", m_source->page },
|
||||
{ "file_uri", m_source->fileUri() .toStdString() },
|
||||
};
|
||||
}
|
||||
|
||||
auto JinjaMessage::keys() const -> const std::unordered_set<std::string_view> &
|
||||
json::object_t JinjaPromptAttachment::AsJson() const
|
||||
{
|
||||
static const std::unordered_set<std::string_view> baseKeys
|
||||
{ "role", "content" };
|
||||
static const std::unordered_set<std::string_view> userKeys
|
||||
{ "role", "content", "sources", "prompt_attachments" };
|
||||
switch (m_item->type()) {
|
||||
using enum MessageItem::Type;
|
||||
case System:
|
||||
case Response:
|
||||
case ToolResponse:
|
||||
return baseKeys;
|
||||
case Prompt:
|
||||
return userKeys;
|
||||
break;
|
||||
}
|
||||
Q_UNREACHABLE();
|
||||
return {
|
||||
{ "url", m_attachment->url.toString() .toStdString() },
|
||||
{ "file", m_attachment->file() .toStdString() },
|
||||
{ "processed_content", m_attachment->processedContent().toStdString() },
|
||||
};
|
||||
}
|
||||
|
||||
bool operator==(const JinjaMessage &a, const JinjaMessage &b)
|
||||
json::object_t JinjaMessage::AsJson() const
|
||||
{
|
||||
if (a.m_item == b.m_item)
|
||||
return true;
|
||||
const auto &[ia, ib] = std::tie(*a.m_item, *b.m_item);
|
||||
auto type = ia.type();
|
||||
if (type != ib.type() || ia.content() != ib.content())
|
||||
return false;
|
||||
|
||||
switch (type) {
|
||||
using enum MessageItem::Type;
|
||||
case System:
|
||||
case Response:
|
||||
case ToolResponse:
|
||||
return true;
|
||||
case Prompt:
|
||||
return ia.sources() == ib.sources() && ia.promptAttachments() == ib.promptAttachments();
|
||||
break;
|
||||
}
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
|
||||
const JinjaFieldMap<JinjaMessage> JinjaMessage::s_fields = {
|
||||
{ "role", [](auto &m) {
|
||||
switch (m.item().type()) {
|
||||
json::object_t obj;
|
||||
{
|
||||
json::string_t role;
|
||||
switch (m_item->type()) {
|
||||
using enum MessageItem::Type;
|
||||
case System: return "system"sv;
|
||||
case Prompt: return "user"sv;
|
||||
case Response: return "assistant"sv;
|
||||
case ToolResponse: return "tool"sv;
|
||||
break;
|
||||
case System: role = "system"; break;
|
||||
case Prompt: role = "user"; break;
|
||||
case Response: role = "assistant"; break;
|
||||
case ToolResponse: role = "tool"; break;
|
||||
}
|
||||
Q_UNREACHABLE();
|
||||
} },
|
||||
{ "content", [](auto &m) {
|
||||
if (m.version() == 0 && m.item().type() == MessageItem::Type::Prompt)
|
||||
return m.item().bakedPrompt().toStdString();
|
||||
return m.item().content().toStdString();
|
||||
} },
|
||||
{ "sources", [](auto &m) {
|
||||
auto sources = m.item().sources() | views::transform([](auto &r) {
|
||||
return jinja2::GenericMap([map = std::make_shared<JinjaResultInfo>(r)] { return map.get(); });
|
||||
});
|
||||
return jinja2::ValuesList(sources.begin(), sources.end());
|
||||
} },
|
||||
{ "prompt_attachments", [](auto &m) {
|
||||
auto attachments = m.item().promptAttachments() | views::transform([](auto &pa) {
|
||||
return jinja2::GenericMap([map = std::make_shared<JinjaPromptAttachment>(pa)] { return map.get(); });
|
||||
});
|
||||
return jinja2::ValuesList(attachments.begin(), attachments.end());
|
||||
} },
|
||||
};
|
||||
obj.emplace_back("role", std::move(role));
|
||||
}
|
||||
{
|
||||
QString content;
|
||||
if (m_version == 0 && m_item->type() == MessageItem::Type::Prompt) {
|
||||
content = m_item->bakedPrompt();
|
||||
} else {
|
||||
content = m_item->content();
|
||||
}
|
||||
obj.emplace_back("content", content.toStdString());
|
||||
}
|
||||
if (m_item->type() == MessageItem::Type::Prompt) {
|
||||
{
|
||||
auto sources = m_item->sources() | views::transform([](auto &r) {
|
||||
return JinjaResultInfo(r).AsJson();
|
||||
});
|
||||
obj.emplace("sources", json::array_t(sources.begin(), sources.end()));
|
||||
}
|
||||
{
|
||||
auto attachments = m_item->promptAttachments() | views::transform([](auto &pa) {
|
||||
return JinjaPromptAttachment(pa).AsJson();
|
||||
});
|
||||
obj.emplace("prompt_attachments", json::array_t(attachments.begin(), attachments.end()));
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -3,47 +3,17 @@
|
||||
#include "chatmodel.h"
|
||||
#include "database.h"
|
||||
|
||||
#include <jinja2cpp/value.h>
|
||||
|
||||
#include <functional>
|
||||
#include <ranges>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
namespace views = std::views;
|
||||
using json = nlohmann::ordered_json;
|
||||
|
||||
|
||||
template <typename T>
|
||||
using JinjaFieldMap = std::unordered_map<std::string_view, std::function<jinja2::Value (const T &)>>;
|
||||
|
||||
template <typename Derived>
|
||||
class JinjaComparable : public jinja2::IMapItemAccessor {
|
||||
class JinjaHelper {
|
||||
public:
|
||||
JinjaComparable() = default;
|
||||
|
||||
bool IsEqual(const jinja2::IComparable &other) const override;
|
||||
|
||||
private:
|
||||
Q_DISABLE_COPY_MOVE(JinjaComparable)
|
||||
};
|
||||
|
||||
template <typename Derived>
|
||||
class JinjaHelper : public JinjaComparable<Derived> {
|
||||
public:
|
||||
size_t GetSize() const override
|
||||
{ return Derived::s_fields.size(); }
|
||||
|
||||
bool HasValue(const std::string &name) const override
|
||||
{ return Derived::s_fields.contains(name); }
|
||||
|
||||
jinja2::Value GetValueByName(const std::string &name) const override;
|
||||
|
||||
std::vector<std::string> GetKeys() const override
|
||||
{ auto keys = views::elements<0>(Derived::s_fields); return { keys.begin(), keys.end() }; }
|
||||
json::object_t AsJson() const { return static_cast<const Derived *>(this)->AsJson(); }
|
||||
};
|
||||
|
||||
class JinjaResultInfo : public JinjaHelper<JinjaResultInfo> {
|
||||
@@ -51,18 +21,10 @@ public:
|
||||
explicit JinjaResultInfo(const ResultInfo &source) noexcept
|
||||
: m_source(&source) {}
|
||||
|
||||
~JinjaResultInfo() override;
|
||||
|
||||
const ResultInfo &value() const { return *m_source; }
|
||||
|
||||
friend bool operator==(const JinjaResultInfo &a, const JinjaResultInfo &b)
|
||||
{ return a.m_source == b.m_source || *a.m_source == *b.m_source; }
|
||||
json::object_t AsJson() const;
|
||||
|
||||
private:
|
||||
static const JinjaFieldMap<ResultInfo> s_fields;
|
||||
const ResultInfo *m_source;
|
||||
|
||||
friend class JinjaHelper<JinjaResultInfo>;
|
||||
};
|
||||
|
||||
class JinjaPromptAttachment : public JinjaHelper<JinjaPromptAttachment> {
|
||||
@@ -70,18 +32,10 @@ public:
|
||||
explicit JinjaPromptAttachment(const PromptAttachment &attachment) noexcept
|
||||
: m_attachment(&attachment) {}
|
||||
|
||||
~JinjaPromptAttachment() override;
|
||||
|
||||
const PromptAttachment &value() const { return *m_attachment; }
|
||||
|
||||
friend bool operator==(const JinjaPromptAttachment &a, const JinjaPromptAttachment &b)
|
||||
{ return a.m_attachment == b.m_attachment || *a.m_attachment == *b.m_attachment; }
|
||||
json::object_t AsJson() const;
|
||||
|
||||
private:
|
||||
static const JinjaFieldMap<PromptAttachment> s_fields;
|
||||
const PromptAttachment *m_attachment;
|
||||
|
||||
friend class JinjaHelper<JinjaPromptAttachment>;
|
||||
};
|
||||
|
||||
class JinjaMessage : public JinjaHelper<JinjaMessage> {
|
||||
@@ -89,28 +43,9 @@ public:
|
||||
explicit JinjaMessage(uint version, const MessageItem &item) noexcept
|
||||
: m_version(version), m_item(&item) {}
|
||||
|
||||
const JinjaMessage &value () const { return *this; }
|
||||
uint version() const { return m_version; }
|
||||
const MessageItem &item () const { return *m_item; }
|
||||
|
||||
size_t GetSize() const override { return keys().size(); }
|
||||
bool HasValue(const std::string &name) const override { return keys().contains(name); }
|
||||
|
||||
jinja2::Value GetValueByName(const std::string &name) const override
|
||||
{ return HasValue(name) ? JinjaHelper::GetValueByName(name) : jinja2::EmptyValue(); }
|
||||
|
||||
std::vector<std::string> GetKeys() const override;
|
||||
json::object_t AsJson() const;
|
||||
|
||||
private:
|
||||
auto keys() const -> const std::unordered_set<std::string_view> &;
|
||||
|
||||
private:
|
||||
static const JinjaFieldMap<JinjaMessage> s_fields;
|
||||
uint m_version;
|
||||
uint m_version;
|
||||
const MessageItem *m_item;
|
||||
|
||||
friend class JinjaHelper<JinjaMessage>;
|
||||
friend bool operator==(const JinjaMessage &a, const JinjaMessage &b);
|
||||
};
|
||||
|
||||
#include "jinja_helpers.inl"
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
template <typename D>
|
||||
bool JinjaComparable<D>::IsEqual(const jinja2::IComparable &other) const
|
||||
{
|
||||
if (auto *omsg = dynamic_cast<const D *>(&other))
|
||||
return *static_cast<const D *>(this) == *omsg;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename D>
|
||||
jinja2::Value JinjaHelper<D>::GetValueByName(const std::string &name) const
|
||||
{
|
||||
if (auto it = D::s_fields.find(name); it != D::s_fields.end()) {
|
||||
auto [_, func] = *it;
|
||||
return func(static_cast<const D *>(this)->value());
|
||||
}
|
||||
return jinja2::EmptyValue();
|
||||
}
|
||||
@@ -5,26 +5,9 @@
|
||||
// This is a list of prompt templates known to GPT4All and their associated replacements which are automatically used
|
||||
// instead when loading the chat template from GGUF. These exist for two primary reasons:
|
||||
// - HuggingFace model authors make ugly chat templates because they do not expect the end user to see them;
|
||||
// - and our Jinja2Cpp-based template parsing is not fully compatible with HuggingFace transformers and jinja2.
|
||||
|
||||
// Below is a list of known incompatibilities with the official HF jinja2 implementation. These are not all necessarily
|
||||
// reflected in the below substitution list, and this cannot be an exhaustive list because there are a plethora of edge
|
||||
// cases in template parsing in which jinja2 and Jinja2Cpp differ. These are differences that could be reasonably
|
||||
// expected to affect chat templates that could be seen in the wild, or that cause a crash:
|
||||
// - Jinja2Cpp crashes (in debug builds) if given the template `a[""(`
|
||||
// - Jinja2Cpp does not support these jinja2 constructs:
|
||||
// - `is not none`
|
||||
// - list slicing, e.g. `messages[1:]`
|
||||
// - the jinja2.ext.loopcontrols extension, which HF enables by default
|
||||
// - a missing space after a quote in substitution (e.g. `{{ 'foo'}}`), which *has* been seen in the wild
|
||||
// - GPT4All does not currently support these HuggingFace template features:
|
||||
// - customized "tojson" filter (we provide the built-in Jinja2Cpp one)
|
||||
// - the AssistantTracker extension
|
||||
|
||||
// - and chat templates occasionally use features we do not support. This is less true now that we use minja.
|
||||
|
||||
// The substitution list.
|
||||
// For templates that apply to models listed in models3.json, these should be copied there as well for best
|
||||
// compatibility with older versions of GPT4All.
|
||||
|
||||
const std::unordered_map<std::string_view, std::string_view> CHAT_TEMPLATE_SUBSTITUTIONS {
|
||||
// calme-2.1-phi3.5-4b.Q6_K.gguf (reported by ThilotE on Discord), Phi-3.5-mini-instruct-Q4_0.gguf (nomic-ai/gpt4all#3345)
|
||||
@@ -52,6 +35,30 @@ const std::unordered_map<std::string_view, std::string_view> CHAT_TEMPLATE_SUBST
|
||||
{{- '<|assistant|>\n' }}
|
||||
{%- else %}
|
||||
{{- eos_token }}
|
||||
{%- endif %})TEMPLATE",
|
||||
},
|
||||
// DeepSeek-R1-Distill-Qwen-7B-Q4_0.gguf
|
||||
{
|
||||
// original
|
||||
R"TEMPLATE({% if not add_generation_prompt is defined %}{% set add_generation_prompt = false %}{% endif %}{% set ns = namespace(is_first=false, is_tool=false, is_output_first=true, system_prompt='') %}{%- for message in messages %}{%- if message['role'] == 'system' %}{% set ns.system_prompt = message['content'] %}{%- endif %}{%- endfor %}{{bos_token}}{{ns.system_prompt}}{%- for message in messages %}{%- if message['role'] == 'user' %}{%- set ns.is_tool = false -%}{{'<|User|>' + message['content']}}{%- endif %}{%- if message['role'] == 'assistant' and message['content'] is none %}{%- set ns.is_tool = false -%}{%- for tool in message['tool_calls']%}{%- if not ns.is_first %}{{'<|Assistant|><|tool▁calls▁begin|><|tool▁call▁begin|>' + tool['type'] + '<|tool▁sep|>' + tool['function']['name'] + '\n' + '```json' + '\n' + tool['function']['arguments'] + '\n' + '```' + '<|tool▁call▁end|>'}}{%- set ns.is_first = true -%}{%- else %}{{'\n' + '<|tool▁call▁begin|>' + tool['type'] + '<|tool▁sep|>' + tool['function']['name'] + '\n' + '```json' + '\n' + tool['function']['arguments'] + '\n' + '```' + '<|tool▁call▁end|>'}}{{'<|tool▁calls▁end|><|end▁of▁sentence|>'}}{%- endif %}{%- endfor %}{%- endif %}{%- if message['role'] == 'assistant' and message['content'] is not none %}{%- if ns.is_tool %}{{'<|tool▁outputs▁end|>' + message['content'] + '<|end▁of▁sentence|>'}}{%- set ns.is_tool = false -%}{%- else %}{% set content = message['content'] %}{% if '</think>' in content %}{% set content = content.split('</think>')[-1] %}{% endif %}{{'<|Assistant|>' + content + '<|end▁of▁sentence|>'}}{%- endif %}{%- endif %}{%- if message['role'] == 'tool' %}{%- set ns.is_tool = true -%}{%- if ns.is_output_first %}{{'<|tool▁outputs▁begin|><|tool▁output▁begin|>' + message['content'] + '<|tool▁output▁end|>'}}{%- set ns.is_output_first = false %}{%- else %}{{'\n<|tool▁output▁begin|>' + message['content'] + '<|tool▁output▁end|>'}}{%- endif %}{%- endif %}{%- endfor -%}{% if ns.is_tool %}{{'<|tool▁outputs▁end|>'}}{% endif %}{% if add_generation_prompt and not ns.is_tool %}{{'<|Assistant|>'}}{% endif %})TEMPLATE",
|
||||
// replacement
|
||||
R"TEMPLATE({%- if not add_generation_prompt is defined %}
|
||||
{%- set add_generation_prompt = false %}
|
||||
{%- endif %}
|
||||
{%- if messages[0]['role'] == 'system' %}
|
||||
{{- messages[0]['content'] }}
|
||||
{%- endif %}
|
||||
{%- for message in messages %}
|
||||
{%- if message['role'] == 'user' %}
|
||||
{{- '<|User|>' + message['content'] }}
|
||||
{%- endif %}
|
||||
{%- if message['role'] == 'assistant' %}
|
||||
{%- set content = message['content'] | regex_replace('^[\\s\\S]*</think>', '') %}
|
||||
{{- '<|Assistant|>' + content + '<|end▁of▁sentence|>' }}
|
||||
{%- endif %}
|
||||
{%- endfor -%}
|
||||
{%- if add_generation_prompt %}
|
||||
{{- '<|Assistant|>' }}
|
||||
{%- endif %})TEMPLATE",
|
||||
},
|
||||
// gemma-2-9b-it-Q4_0.gguf (nomic-ai/gpt4all#3282)
|
||||
@@ -626,7 +633,7 @@ const std::unordered_map<std::string_view, std::string_view> CHAT_TEMPLATE_SUBST
|
||||
// replacement
|
||||
R"TEMPLATE({%- for message in messages %}
|
||||
{%- if message['role'] == 'system' %}
|
||||
{{-'<|system|>\n' + message['content'] + '<|end|>\n'}}
|
||||
{{- '<|system|>\n' + message['content'] + '<|end|>\n' }}
|
||||
{%- elif message['role'] == 'user' %}
|
||||
{{- '<|user|>\n' + message['content'] + '<|end|>\n' }}
|
||||
{%- elif message['role'] == 'assistant' %}
|
||||
|
||||
@@ -25,6 +25,10 @@
|
||||
#include <QVariant>
|
||||
#include <Qt>
|
||||
|
||||
#if G4A_CONFIG(force_d3d12)
|
||||
# include <QSGRendererInterface>
|
||||
#endif
|
||||
|
||||
#ifndef GPT4ALL_USE_QTPDF
|
||||
# include <fpdfview.h>
|
||||
#endif
|
||||
@@ -83,24 +87,27 @@ int main(int argc, char *argv[])
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if G4A_CONFIG(force_d3d12)
|
||||
QQuickWindow::setGraphicsApi(QSGRendererInterface::Direct3D12);
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
app.setWindowIcon(QIcon(":/gpt4all/icons/gpt4all.svg"));
|
||||
#endif
|
||||
|
||||
// set search path before constructing the MySettings instance, which relies on this
|
||||
QString llmodelSearchPaths = QCoreApplication::applicationDirPath();
|
||||
const QString libDir = QCoreApplication::applicationDirPath() + "/../lib/";
|
||||
if (LLM::directoryExists(libDir))
|
||||
llmodelSearchPaths += ";" + libDir;
|
||||
#if defined(Q_OS_MAC)
|
||||
const QString binDir = QCoreApplication::applicationDirPath() + "/../../../";
|
||||
if (LLM::directoryExists(binDir))
|
||||
llmodelSearchPaths += ";" + binDir;
|
||||
const QString frameworksDir = QCoreApplication::applicationDirPath() + "/../Frameworks/";
|
||||
if (LLM::directoryExists(frameworksDir))
|
||||
llmodelSearchPaths += ";" + frameworksDir;
|
||||
{
|
||||
auto appDirPath = QCoreApplication::applicationDirPath();
|
||||
QStringList searchPaths {
|
||||
#ifdef Q_OS_DARWIN
|
||||
u"%1/../Frameworks"_s.arg(appDirPath),
|
||||
#else
|
||||
appDirPath,
|
||||
u"%1/../lib"_s.arg(appDirPath),
|
||||
#endif
|
||||
LLModel::Implementation::setImplementationsSearchPath(llmodelSearchPaths.toStdString());
|
||||
};
|
||||
LLModel::Implementation::setImplementationsSearchPath(searchPaths.join(u';').toStdString());
|
||||
}
|
||||
|
||||
// Set the local and language translation before the qml engine has even been started. This will
|
||||
// use the default system locale unless the user has explicitly set it to use a different one.
|
||||
@@ -174,5 +181,9 @@ int main(int argc, char *argv[])
|
||||
// Otherwise, we can get a heap-use-after-free inside of llama.cpp.
|
||||
ChatListModel::globalInstance()->destroyChats();
|
||||
|
||||
#ifndef GPT4ALL_USE_QTPDF
|
||||
FPDF_DestroyLibrary();
|
||||
#endif
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -1621,7 +1621,6 @@ void ModelList::parseModelsJsonFile(const QByteArray &jsonData, bool save)
|
||||
QString requiresVersion = obj["requires"].toString();
|
||||
QString versionRemoved = obj["removedIn"].toString();
|
||||
QString url = obj["url"].toString();
|
||||
QByteArray modelHash = obj["md5sum"].toString().toLatin1();
|
||||
bool isDefault = obj.contains("isDefault") && obj["isDefault"] == u"true"_s;
|
||||
bool disableGUI = obj.contains("disableGUI") && obj["disableGUI"] == u"true"_s;
|
||||
QString description = obj["description"].toString();
|
||||
@@ -1632,6 +1631,16 @@ void ModelList::parseModelsJsonFile(const QByteArray &jsonData, bool save)
|
||||
QString type = obj["type"].toString();
|
||||
bool isEmbeddingModel = obj["embeddingModel"].toBool();
|
||||
|
||||
QByteArray modelHash;
|
||||
ModelInfo::HashAlgorithm hashAlgorithm;
|
||||
if (auto it = obj.find("sha256sum"_L1); it != obj.end()) {
|
||||
modelHash = it->toString().toLatin1();
|
||||
hashAlgorithm = ModelInfo::Sha256;
|
||||
} else {
|
||||
modelHash = obj["md5sum"].toString().toLatin1();
|
||||
hashAlgorithm = ModelInfo::Md5;
|
||||
}
|
||||
|
||||
// Some models aren't supported in the GUI at all
|
||||
if (disableGUI)
|
||||
continue;
|
||||
@@ -1660,7 +1669,7 @@ void ModelList::parseModelsJsonFile(const QByteArray &jsonData, bool save)
|
||||
{ ModelList::FilenameRole, modelFilename },
|
||||
{ ModelList::FilesizeRole, modelFilesize },
|
||||
{ ModelList::HashRole, modelHash },
|
||||
{ ModelList::HashAlgorithmRole, ModelInfo::Md5 },
|
||||
{ ModelList::HashAlgorithmRole, hashAlgorithm },
|
||||
{ ModelList::DefaultRole, isDefault },
|
||||
{ ModelList::DescriptionRole, description },
|
||||
{ ModelList::RequiresVersionRole, requiresVersion },
|
||||
|
||||
@@ -269,7 +269,7 @@ private:
|
||||
std::optional<QString> m_chatTemplate;
|
||||
mutable std::optional<QString> m_modelChatTemplate;
|
||||
QString m_systemMessage;
|
||||
QString m_chatNamePrompt = "Describe the above conversation in seven words or less.";
|
||||
QString m_chatNamePrompt = "Describe the above conversation. Your entire response must be three words or less.";
|
||||
QString m_suggestedFollowUpPrompt = "Suggest three very short factual follow-up questions that have not been answered yet or cannot be found inspired by the previous conversation and excerpts.";
|
||||
friend class MySettings;
|
||||
friend class ModelList;
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
#include "tool.h"
|
||||
|
||||
#include <jinja2cpp/value.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
jinja2::Value Tool::jinjaValue() const
|
||||
using json = nlohmann::ordered_json;
|
||||
|
||||
|
||||
json::object_t Tool::jinjaValue() const
|
||||
{
|
||||
jinja2::ValuesList paramList;
|
||||
json::array_t paramList;
|
||||
const QList<ToolParamInfo> p = parameters();
|
||||
for (auto &info : p) {
|
||||
std::string typeStr;
|
||||
@@ -20,26 +21,24 @@ jinja2::Value Tool::jinjaValue() const
|
||||
case Boolean: typeStr = "boolean"; break;
|
||||
case Null: typeStr = "null"; break;
|
||||
}
|
||||
jinja2::ValuesMap infoMap {
|
||||
{ "name", info.name.toStdString() },
|
||||
{ "type", typeStr},
|
||||
paramList.emplace_back(json::initializer_list_t {
|
||||
{ "name", info.name.toStdString() },
|
||||
{ "type", typeStr },
|
||||
{ "description", info.description.toStdString() },
|
||||
{ "required", info.required }
|
||||
};
|
||||
paramList.push_back(infoMap);
|
||||
{ "required", info.required },
|
||||
});
|
||||
}
|
||||
|
||||
jinja2::ValuesMap params {
|
||||
{ "name", name().toStdString() },
|
||||
{ "description", description().toStdString() },
|
||||
{ "function", function().toStdString() },
|
||||
{ "parameters", paramList },
|
||||
return {
|
||||
{ "name", name().toStdString() },
|
||||
{ "description", description().toStdString() },
|
||||
{ "function", function().toStdString() },
|
||||
{ "parameters", paramList },
|
||||
{ "symbolicFormat", symbolicFormat().toStdString() },
|
||||
{ "examplePrompt", examplePrompt().toStdString() },
|
||||
{ "exampleCall", exampleCall().toStdString() },
|
||||
{ "exampleReply", exampleReply().toStdString() }
|
||||
{ "examplePrompt", examplePrompt().toStdString() },
|
||||
{ "exampleCall", exampleCall().toStdString() },
|
||||
{ "exampleReply", exampleReply().toStdString() },
|
||||
};
|
||||
return params;
|
||||
}
|
||||
|
||||
void ToolCallInfo::serialize(QDataStream &stream, int version)
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
#ifndef TOOL_H
|
||||
#define TOOL_H
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QVariant>
|
||||
#include <QtGlobal>
|
||||
|
||||
#include <jinja2cpp/value.h>
|
||||
using json = nlohmann::ordered_json;
|
||||
|
||||
|
||||
namespace ToolEnums
|
||||
{
|
||||
@@ -25,6 +28,7 @@ namespace ToolEnums
|
||||
|
||||
enum class ParseState {
|
||||
None,
|
||||
InTagChoice,
|
||||
InStart,
|
||||
Partial,
|
||||
Complete,
|
||||
@@ -87,7 +91,8 @@ public:
|
||||
Tool() : QObject(nullptr) {}
|
||||
virtual ~Tool() {}
|
||||
|
||||
virtual QString run(const QList<ToolParam> ¶ms, qint64 timeout = 2000) = 0;
|
||||
virtual void run(const QList<ToolParam> ¶ms) = 0;
|
||||
virtual bool interrupt() = 0;
|
||||
|
||||
// Tools should set these if they encounter errors. For instance, a tool depending upon the network
|
||||
// might set these error variables if the network is not available.
|
||||
@@ -121,7 +126,10 @@ public:
|
||||
|
||||
bool operator==(const Tool &other) const { return function() == other.function(); }
|
||||
|
||||
jinja2::Value jinjaValue() const;
|
||||
json::object_t jinjaValue() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void runComplete(const ToolCallInfo &info);
|
||||
};
|
||||
|
||||
#endif // TOOL_H
|
||||
|
||||
@@ -1,16 +1,29 @@
|
||||
#include "toolcallparser.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include "tool.h"
|
||||
|
||||
#include <QSet>
|
||||
#include <QtGlobal>
|
||||
#include <QtLogging>
|
||||
|
||||
#include <cstddef>
|
||||
#include <stdexcept>
|
||||
|
||||
static const QString ToolCallStart = ToolCallConstants::CodeInterpreterTag;
|
||||
static const QString ToolCallEnd = ToolCallConstants::CodeInterpreterEndTag;
|
||||
|
||||
ToolCallParser::ToolCallParser()
|
||||
: ToolCallParser(ToolCallConstants::AllTagNames)
|
||||
{}
|
||||
|
||||
ToolCallParser::ToolCallParser(const QStringList &tagNames)
|
||||
{
|
||||
QSet<QChar> firstChars;
|
||||
for (auto &name : tagNames) {
|
||||
if (name.isEmpty())
|
||||
throw std::invalid_argument("ToolCallParser(): tag names must not be empty");
|
||||
if (firstChars.contains(name.at(0)))
|
||||
throw std::invalid_argument("ToolCallParser(): tag names must not share any prefix");
|
||||
firstChars << name.at(0);
|
||||
m_possibleStartTags << makeStartTag(name).toUtf8();
|
||||
m_possibleEndTags << makeEndTag (name).toUtf8();
|
||||
}
|
||||
reset();
|
||||
}
|
||||
|
||||
@@ -20,36 +33,68 @@ void ToolCallParser::reset()
|
||||
resetSearchState();
|
||||
|
||||
// These are global states maintained between update calls
|
||||
m_buffer.clear();
|
||||
m_hasSplit = false;
|
||||
m_buffers.clear();
|
||||
m_buffers << QByteArray();
|
||||
}
|
||||
|
||||
void ToolCallParser::resetSearchState()
|
||||
{
|
||||
m_expected = ToolCallStart.at(0);
|
||||
m_expected = {'<'};
|
||||
m_expectedIndex = 0;
|
||||
m_state = ToolEnums::ParseState::None;
|
||||
|
||||
m_toolCall.clear();
|
||||
m_startTagBuffer.clear();
|
||||
m_endTagBuffer.clear();
|
||||
|
||||
m_currentTagIndex = -1;
|
||||
m_startIndex = -1;
|
||||
m_endIndex = -1;
|
||||
}
|
||||
|
||||
bool ToolCallParser::isExpected(char c) const
|
||||
{
|
||||
return m_expected.isEmpty() || m_expected.contains(c);
|
||||
}
|
||||
|
||||
void ToolCallParser::setExpected(const QList<QByteArray> &tags)
|
||||
{
|
||||
m_expected.clear();
|
||||
for (const auto &tag : tags) {
|
||||
Q_ASSERT(tag.size() > m_expectedIndex);
|
||||
m_expected << tag.at(m_expectedIndex);
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray ToolCallParser::startTag() const
|
||||
{
|
||||
if (m_currentTagIndex < 0)
|
||||
return {};
|
||||
return m_possibleStartTags.at(m_currentTagIndex);
|
||||
}
|
||||
|
||||
QByteArray ToolCallParser::endTag() const
|
||||
{
|
||||
if (m_currentTagIndex < 0)
|
||||
return {};
|
||||
return m_possibleEndTags.at(m_currentTagIndex);
|
||||
}
|
||||
|
||||
QByteArray &ToolCallParser::currentBuffer()
|
||||
{
|
||||
return m_buffers.last();
|
||||
}
|
||||
|
||||
// This method is called with an arbitrary string and a current state. This method should take the
|
||||
// current state into account and then parse through the update character by character to arrive at
|
||||
// the new state.
|
||||
void ToolCallParser::update(const QString &update)
|
||||
void ToolCallParser::update(const QByteArray &update)
|
||||
{
|
||||
Q_ASSERT(m_state != ToolEnums::ParseState::Complete);
|
||||
if (m_state == ToolEnums::ParseState::Complete) {
|
||||
qWarning() << "ERROR: ToolCallParser::update already found a complete toolcall!";
|
||||
return;
|
||||
}
|
||||
currentBuffer().append(update);
|
||||
|
||||
m_buffer.append(update);
|
||||
|
||||
for (size_t i = m_buffer.size() - update.size(); i < m_buffer.size(); ++i) {
|
||||
const QChar c = m_buffer[i];
|
||||
const bool foundMatch = m_expected.isNull() || c == m_expected;
|
||||
for (qsizetype i = currentBuffer().size() - update.size(); i < currentBuffer().size(); ++i) {
|
||||
const char c = currentBuffer()[i];
|
||||
const bool foundMatch = isExpected(c);
|
||||
if (!foundMatch) {
|
||||
resetSearchState();
|
||||
continue;
|
||||
@@ -59,34 +104,58 @@ void ToolCallParser::update(const QString &update)
|
||||
case ToolEnums::ParseState::None:
|
||||
{
|
||||
m_expectedIndex = 1;
|
||||
m_expected = ToolCallStart.at(1);
|
||||
m_state = ToolEnums::ParseState::InStart;
|
||||
setExpected(m_possibleStartTags);
|
||||
m_state = ToolEnums::ParseState::InTagChoice;
|
||||
m_startIndex = i;
|
||||
break;
|
||||
}
|
||||
case ToolEnums::ParseState::InTagChoice:
|
||||
{
|
||||
for (int i = 0; i < m_possibleStartTags.size(); ++i) {
|
||||
const auto &tag = m_possibleStartTags.at(i);
|
||||
if (c == tag.at(1)) m_currentTagIndex = i;
|
||||
}
|
||||
if (m_currentTagIndex >= 0) {
|
||||
m_expectedIndex = 2;
|
||||
setExpected({m_possibleStartTags.at(m_currentTagIndex)});
|
||||
m_state = ToolEnums::ParseState::InStart;
|
||||
} else
|
||||
resetSearchState();
|
||||
break;
|
||||
}
|
||||
case ToolEnums::ParseState::InStart:
|
||||
{
|
||||
if (m_expectedIndex == ToolCallStart.size() - 1) {
|
||||
m_startTagBuffer.append(c);
|
||||
|
||||
const auto startTag = this->startTag();
|
||||
Q_ASSERT(!startTag.isEmpty());
|
||||
if (m_expectedIndex == startTag.size() - 1) {
|
||||
m_expectedIndex = 0;
|
||||
m_expected = QChar();
|
||||
setExpected({});
|
||||
m_state = ToolEnums::ParseState::Partial;
|
||||
} else {
|
||||
++m_expectedIndex;
|
||||
m_expected = ToolCallStart.at(m_expectedIndex);
|
||||
Q_ASSERT(m_currentTagIndex >= 0);
|
||||
setExpected({startTag});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ToolEnums::ParseState::Partial:
|
||||
{
|
||||
Q_ASSERT(m_currentTagIndex >= 0);
|
||||
const auto endTag = this->endTag();
|
||||
Q_ASSERT(!endTag.isEmpty());
|
||||
m_toolCall.append(c);
|
||||
m_endTagBuffer.append(c);
|
||||
if (m_endTagBuffer.size() > ToolCallEnd.size())
|
||||
if (m_endTagBuffer.size() > endTag.size())
|
||||
m_endTagBuffer.remove(0, 1);
|
||||
if (m_endTagBuffer == ToolCallEnd) {
|
||||
m_toolCall.chop(ToolCallEnd.size());
|
||||
if (m_endTagBuffer == endTag) {
|
||||
m_endIndex = i + 1;
|
||||
m_toolCall.chop(endTag.size());
|
||||
m_state = ToolEnums::ParseState::Complete;
|
||||
m_endTagBuffer.clear();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ToolEnums::ParseState::Complete:
|
||||
{
|
||||
@@ -97,15 +166,35 @@ void ToolCallParser::update(const QString &update)
|
||||
}
|
||||
}
|
||||
|
||||
QPair<QString, QString> ToolCallParser::split()
|
||||
bool ToolCallParser::splitIfPossible()
|
||||
{
|
||||
Q_ASSERT(m_state == ToolEnums::ParseState::Partial
|
||||
|| m_state == ToolEnums::ParseState::Complete);
|
||||
// The first split happens when we're in a partial state
|
||||
if (m_buffers.size() < 2 && m_state == ToolEnums::ParseState::Partial) {
|
||||
Q_ASSERT(m_startIndex >= 0);
|
||||
const auto beforeToolCall = currentBuffer().left(m_startIndex);
|
||||
const auto toolCall = currentBuffer().mid (m_startIndex);
|
||||
m_buffers = { beforeToolCall, toolCall };
|
||||
return true;
|
||||
}
|
||||
|
||||
Q_ASSERT(m_startIndex >= 0);
|
||||
m_hasSplit = true;
|
||||
const QString beforeToolCall = m_buffer.left(m_startIndex);
|
||||
m_buffer = m_buffer.mid(m_startIndex);
|
||||
m_startIndex = 0;
|
||||
return { beforeToolCall, m_buffer };
|
||||
// The second split happens when we're in the complete state
|
||||
if (m_buffers.size() < 3 && m_state == ToolEnums::ParseState::Complete) {
|
||||
Q_ASSERT(m_endIndex >= 0);
|
||||
const auto &beforeToolCall = m_buffers.first();
|
||||
const auto toolCall = currentBuffer().left(m_endIndex);
|
||||
const auto afterToolCall = currentBuffer().mid (m_endIndex);
|
||||
m_buffers = { beforeToolCall, toolCall, afterToolCall };
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QStringList ToolCallParser::buffers() const
|
||||
{
|
||||
QStringList result;
|
||||
result.reserve(m_buffers.size());
|
||||
for (const auto &buffer : m_buffers)
|
||||
result << QString::fromUtf8(buffer);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,47 +1,73 @@
|
||||
#ifndef TOOLCALLPARSER_H
|
||||
#define TOOLCALLPARSER_H
|
||||
|
||||
#include "tool.h"
|
||||
|
||||
#include <QChar>
|
||||
#include <QByteArray>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QPair>
|
||||
#include <QStringList>
|
||||
|
||||
namespace ToolEnums { enum class ParseState; }
|
||||
|
||||
using namespace Qt::Literals::StringLiterals;
|
||||
|
||||
namespace ToolCallConstants
|
||||
{
|
||||
const QString CodeInterpreterFunction = R"(javascript_interpret)";
|
||||
const QString CodeInterpreterTag = R"(<)" + CodeInterpreterFunction + R"(>)";
|
||||
const QString CodeInterpreterEndTag = R"(</)" + CodeInterpreterFunction + R"(>)";
|
||||
const QString CodeInterpreterPrefix = CodeInterpreterTag + "\n```javascript\n";
|
||||
const QString CodeInterpreterSuffix = "```\n" + CodeInterpreterEndTag;
|
||||
}
|
||||
|
||||
class ToolCallParser
|
||||
{
|
||||
public:
|
||||
ToolCallParser();
|
||||
ToolCallParser(const QStringList &tagNames);
|
||||
|
||||
void reset();
|
||||
void update(const QString &update);
|
||||
QString buffer() const { return m_buffer; }
|
||||
QString toolCall() const { return m_toolCall; }
|
||||
void update(const QByteArray &update);
|
||||
QString toolCall() const { return QString::fromUtf8(m_toolCall); }
|
||||
int startIndex() const { return m_startIndex; }
|
||||
ToolEnums::ParseState state() const { return m_state; }
|
||||
QByteArray startTag() const;
|
||||
QByteArray endTag() const;
|
||||
|
||||
// Splits
|
||||
QPair<QString, QString> split();
|
||||
bool hasSplit() const { return m_hasSplit; }
|
||||
bool splitIfPossible();
|
||||
QStringList buffers() const;
|
||||
int numberOfBuffers() const { return m_buffers.size(); }
|
||||
|
||||
static QString makeStartTag(const QString &name) { return u"<%1>"_s .arg(name); }
|
||||
static QString makeEndTag (const QString &name) { return u"</%1>"_s.arg(name); }
|
||||
|
||||
private:
|
||||
QByteArray ¤tBuffer();
|
||||
void resetSearchState();
|
||||
bool isExpected(char c) const;
|
||||
void setExpected(const QList<QByteArray> &tags);
|
||||
|
||||
QChar m_expected;
|
||||
QList<QByteArray> m_possibleStartTags;
|
||||
QList<QByteArray> m_possibleEndTags;
|
||||
QByteArray m_startTagBuffer;
|
||||
QByteArray m_endTagBuffer;
|
||||
int m_currentTagIndex;
|
||||
|
||||
QList<char> m_expected;
|
||||
int m_expectedIndex;
|
||||
ToolEnums::ParseState m_state;
|
||||
QString m_buffer;
|
||||
QString m_toolCall;
|
||||
QString m_endTagBuffer;
|
||||
QList<QByteArray> m_buffers;
|
||||
QByteArray m_toolCall;
|
||||
int m_startIndex;
|
||||
bool m_hasSplit;
|
||||
int m_endIndex;
|
||||
};
|
||||
|
||||
namespace ToolCallConstants
|
||||
{
|
||||
// NB: the parsing code assumes the first char of the various tags differ
|
||||
|
||||
inline const QString CodeInterpreterFunction = u"javascript_interpret"_s;
|
||||
inline const QString CodeInterpreterStartTag = ToolCallParser::makeStartTag(CodeInterpreterFunction);
|
||||
inline const QString CodeInterpreterEndTag = ToolCallParser::makeEndTag (CodeInterpreterFunction);
|
||||
inline const QString CodeInterpreterPrefix = u"%1\n```javascript\n"_s.arg(CodeInterpreterStartTag);
|
||||
inline const QString CodeInterpreterSuffix = u"```\n%1"_s .arg(CodeInterpreterEndTag );
|
||||
|
||||
inline const QString ThinkTagName = u"think"_s;
|
||||
inline const QString ThinkStartTag = ToolCallParser::makeStartTag(ThinkTagName);
|
||||
inline const QString ThinkEndTag = ToolCallParser::makeEndTag (ThinkTagName);
|
||||
|
||||
inline const QStringList AllTagNames { CodeInterpreterFunction, ThinkTagName };
|
||||
}
|
||||
|
||||
#endif // TOOLCALLPARSER_H
|
||||
|
||||
Reference in New Issue
Block a user