├── .github └── workflows │ └── cicd.yaml ├── .gitignore ├── LICENSE ├── README.MD ├── data ├── AboutScott.pdf ├── AboutScott.txt ├── JupyterGoBoom.txt ├── LLamaSharpExecutors.pdf └── NewAgeOfQrss.html ├── dev └── screenshots │ ├── document-qa.png │ ├── nuget-llamasharp.png │ ├── planets.png │ ├── planets2.png │ ├── scott.png │ └── winforms.png └── src ├── Chat ├── Chat.csproj └── Program.cs ├── ChatWinForms ├── ChatBot.cs ├── ChatWinForms.csproj ├── Form1.Designer.cs ├── Form1.cs ├── Form1.resx └── Program.cs ├── DocumentQa ├── DocumentQa.csproj └── Program.cs ├── DocumentQaWithStorage ├── DocumentChatBot.cs ├── DocumentQaWithStorage.csproj └── Program.cs ├── DocumentSearch ├── DocumentSearch.csproj └── Program.cs ├── KernelMemory ├── KernelMemory.csproj └── Program.cs └── LLM.sln /.github/workflows/cicd.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | paths: 9 | - src/** 10 | 11 | env: 12 | DOTNET_VERSION: "8.0.x" 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | name: Build 18 | steps: 19 | - name: 🛒 Checkout 20 | uses: actions/checkout@v4 21 | - name: ✨ Setup .NET ${{ env.DOTNET_VERSION }} 22 | uses: actions/setup-dotnet@v4 23 | with: 24 | dotnet-version: ${{ env.DOTNET_VERSION }} 25 | - name: 🚚 Restore 26 | run: dotnet restore src 27 | - name: 🛠️ Build 28 | run: dotnet build src 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # ASP.NET Scaffolding 66 | ScaffoldingReadMe.txt 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.tlog 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 298 | *.vbp 299 | 300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 301 | *.dsw 302 | *.dsp 303 | 304 | # Visual Studio 6 technical files 305 | *.ncb 306 | *.aps 307 | 308 | # Visual Studio LightSwitch build output 309 | **/*.HTMLClient/GeneratedArtifacts 310 | **/*.DesktopClient/GeneratedArtifacts 311 | **/*.DesktopClient/ModelManifest.xml 312 | **/*.Server/GeneratedArtifacts 313 | **/*.Server/ModelManifest.xml 314 | _Pvt_Extensions 315 | 316 | # Paket dependency manager 317 | .paket/paket.exe 318 | paket-files/ 319 | 320 | # FAKE - F# Make 321 | .fake/ 322 | 323 | # CodeRush personal settings 324 | .cr/personal 325 | 326 | # Python Tools for Visual Studio (PTVS) 327 | __pycache__/ 328 | *.pyc 329 | 330 | # Cake - Uncomment if you are using it 331 | # tools/** 332 | # !tools/packages.config 333 | 334 | # Tabs Studio 335 | *.tss 336 | 337 | # Telerik's JustMock configuration file 338 | *.jmconfig 339 | 340 | # BizTalk build output 341 | *.btp.cs 342 | *.btm.cs 343 | *.odx.cs 344 | *.xsd.cs 345 | 346 | # OpenCover UI analysis results 347 | OpenCover/ 348 | 349 | # Azure Stream Analytics local run output 350 | ASALocalRun/ 351 | 352 | # MSBuild Binary and Structured Log 353 | *.binlog 354 | 355 | # NVidia Nsight GPU debugger configuration file 356 | *.nvuser 357 | 358 | # MFractors (Xamarin productivity tool) working folder 359 | .mfractor/ 360 | 361 | # Local History for Visual Studio 362 | .localhistory/ 363 | 364 | # Visual Studio History (VSHistory) files 365 | .vshistory/ 366 | 367 | # BeatPulse healthcheck temp database 368 | healthchecksdb 369 | 370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 371 | MigrationBackup/ 372 | 373 | # Ionide (cross platform F# VS Code tools) working folder 374 | .ionide/ 375 | 376 | # Fody - auto-generated XML schema 377 | FodyWeavers.xsd 378 | 379 | # VS Code files for those working on multiple tools 380 | .vscode/* 381 | !.vscode/settings.json 382 | !.vscode/tasks.json 383 | !.vscode/launch.json 384 | !.vscode/extensions.json 385 | *.code-workspace 386 | 387 | # Local History for Visual Studio Code 388 | .history/ 389 | 390 | # Windows Installer files from build outputs 391 | *.cab 392 | *.msi 393 | *.msix 394 | *.msm 395 | *.msp 396 | 397 | # JetBrains Rider 398 | *.sln.iml 399 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Scott W Harden 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # Local LLM with C# 2 | 3 | [![CI](https://github.com/swharden/Local-LLM-csharp/actions/workflows/cicd.yaml/badge.svg)](https://github.com/swharden/Local-LLM-csharp/actions/workflows/cicd.yaml) 4 | 5 | This repository contains notes and sample projects demonstrating how to run a large language model (LLM) locally to provide AI chat and answer questions about local documents. -------------------------------------------------------------------------------- /data/AboutScott.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swharden/Local-LLM-csharp/5f6dda4e4ed14278c5e173faaa4e528d18057abc/data/AboutScott.pdf -------------------------------------------------------------------------------- /data/AboutScott.txt: -------------------------------------------------------------------------------- 1 | Scott William Harden is an open-source software developer. He is the primary author of ScottPlot, pyabf, FftSharp, Spectrogram, and several other open-source packages. Scott’s favorite color is dark blue despite the fact that he is colorblind. Scott’s advocacy for those with color vision deficiency (CVD) leads him to recommend perceptually uniform color maps like Viridis instead of traditional color maps like Spectrum and Jet. -------------------------------------------------------------------------------- /data/JupyterGoBoom.txt: -------------------------------------------------------------------------------- 1 | "JupyterGoBoom" is the name of a Python package for creating unmaintainable Jupyter notebooks. It is no longer actively developed and is now considered obsolete because modern software developers have come to realize that Jupyter notebooks grow to become unmaintainable all by themselves. -------------------------------------------------------------------------------- /data/LLamaSharpExecutors.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swharden/Local-LLM-csharp/5f6dda4e4ed14278c5e173faaa4e528d18057abc/data/LLamaSharpExecutors.pdf -------------------------------------------------------------------------------- /data/NewAgeOfQrss.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The New Age of QRSS 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 | 15 |
16 | 17 | 54 | 55 |
56 | 57 |

58 | 59 | SWHarden.com 60 | 61 |

62 | 63 |
64 | The personal website of Scott W Harden 65 |
66 | 67 |
68 | 69 | 70 | 71 |
72 | 73 |

The New Age of QRSS

74 |
75 |
76 | 77 |
78 | 79 | October 3, 2020 80 | 81 |
82 | 83 |
84 | 85 | 86 | 87 | 88 | qrss 89 | 90 | 91 | 92 | 93 | amateur radio 94 | 95 |
96 |
97 | 98 | 99 | 100 |

QRSS is an experimental radio mode that uses frequency-shift-keyed (FSK) continuous wave (CW) Morse code to transmit messages that can be decoded visually by inspecting the radio frequency spectrogram. The name “QRSS” is a derivation of the Q code “QRS”, a phrase Morse code operators send to indicate the transmitter needs to slow down. The extra “S” means slow way, way down, and at the typical speed of 6 second dots and 18 second dashes most QRSS operators have just enough time to send their call sign once every ten minutes (as required by federal law). These slow Morse code messages can be decoded by visual inspection of spectrograms created by computer software processing the received audio. A QRSS grabber is a radio/computer setup configured to upload the latest radio spectrogram to the internet every 10 minutes. QRSS Plus is an automatically-updating list of active QRSS grabbers around the world, allowing the QRSS community to see QRSS transmitters being detected all over the world.

101 |
102 |

TLDR: Get Started with QRSS

103 |
    104 |
  • Tune your radio to 10.140 MHz (10.1387 MHz USB)
  • 105 |
  • Install spectrogram software like FSKview
  • 106 |
  • Inspect the spectrogram to decode callsigns visually
  • 107 |
  • Join the QRSS Knights mailing list to learn what’s new
  • 108 |
  • Go to QRSS Plus to see QRSS signals around the world
  • 109 |
  • Design and build a circuit (or buy a kit) to transmit QRSS
  • 110 |
111 |
112 |

What is QRSS?

QRSS allows miniscule amounts of power to send messages enormous distances. For example, 200 mW QRSS transmitters are routinely spotted on QRSS grabbers thousands of miles away. The key to this resilience lies in the fact that spectrograms can be designed which average several seconds of audio into each pixel. By averaging audio in this way, the level of the noise (which is random and averages toward zero) falls below the level of the signal, allowing visualization of signals on the spectrogram which are too deep in the noise to be heard by ear.

113 |
114 |

115 | 116 |

117 |
118 |

If you have a radio and a computer, you can view QRSS! Connect your radio to your computer’s microphone, then run a spectrogram like FSKview to visualize that audio as a spectrogram. The most QRSS activity is on 30m within 100 Hz of 10.140 MHz, so set your radio to upper sideband (USB) mode and tune to 10.1387 MHz so QRSS audio will be captured as 1.4 kHz audio tones.

119 |

FSKview is radio frequency spectrogram software for viewing QRSS and WSPR simultaneously. I wrote FSKview to be simple and easy to use, but it’s worth noting that Spectrum Lab, Argo, LOPORA, and QRSSpig are also popular spectrogram software projects used for QRSS, with the last two supporting Linux and suitable for use on the Raspberry Pi.

120 |
121 |

122 | 123 |

124 |
125 |

QRSS Transmitter Design

QRSS transmitters can be extraordinarily simple because they just transmit a single tone which shifts between two frequencies. The simplicity of QRSS transmitters makes them easy to assemble as a kits, or inexpensively designed and built by those first learning about RF circuit design. The simplest designs use a crystal oscillator (typically a Colpitts configuration) followed by a buffer stage and a final amplifier (often Class C configuration using a 2N7000 N-channel MOSFET or 2N2222 NPN transistor). Manual frequency adjustments are achieved using a variable capacitor, supplemented in this case with twisted wire to act as a simple but effective variable capacitor for fine frequency tuning within the 100 Hz QRSS band. Frequency shift keying to transmit call signs is typically achieved using a microcontroller to adjust voltage on a reverse-biased diode (acting as a varactor) to modulate capacitance and shift resonant frequency of the oscillator. Following a low-pass filter (typically a 3-pole Chebyshev design) the signal is then sent to an antenna.

126 |
127 |

128 | 129 |

130 |
131 |

QRP Labs is a great source for QRSS kits. The kit pictured above and below is one of their earliest kits (the 30/40/80/160m QRSS Kit), but they have created many impressive new products in the last several years. Some of their more advanced QRSS kits leverage things like direct digital synthesis (DDS), GPS time synchronization, and the ability to transmit additional digital modes like Hellschreiber and WSPR.

132 |
133 |

134 | 135 |

136 |
137 |

Radio Frequency Spectral Phenomena

Atmospheric phenomena and other special conditions can often be spotted in QRSS spectrograms. One of the most common special cases are radio frequency reflections off of airplanes resulting in the radio waves arriving at the receiver simultaneously via two different paths (a form of multipath propagation). Due to the Doppler shift from the airplane approaching the receiver the signal from the reflected path appears higher frequency than the direct path, and as the airplane flies over and begins heading away the signal from the reflected path decreases in frequency relative to the signal of the direct path. The image below is one of my favorites, captured by Andy (G0FTD) in the 10m QRSS band. QRSS de W4HBK is a website that has many blog posts about rare and special grabs, demonstrating effects of meteors and coronal mass ejections on QRSS signals.

138 |
139 |

140 | 141 |

142 |
143 |
144 |

145 | 146 |

147 |
148 |

QRSS Transmitters are Not Beacons

Radio beacons send continuous, automated, unattended, one-way transmissions without specific reception targets. In contrast, QRSS transmitters are only intended to be transmitting when the control operator is available to control them, and the recipients are known QRSS grabbers around the world. To highlight the distinction from radio beacons, QRSS transmitters are termed Manned Experimental Propagation Transmitters (MEPTs). Users in the United States will recall that the FCC (in Part 97.203) confines operation of radio beacons to specific regions of the radio spectrum and disallows operation of beacons below 28 MHz. Note that amateur radio beacons typically operate up to 100 W which is a power level multiple orders of magnitude greater than QRSS transmitters. MEPTs, in contrast, can transmit in any portion of the radio frequency spectrum where CW operation is permitted.

149 |

The New Age of QRSS

QRSS was first mentioned in epsisode 28 of The Soldersmoke Podcast on July 30, 2006. It was discussed in several episodes over the next few years, and a 2009 post about QRSS on Hack-A-Day brought it to my attention. In the early days of QRSS the only way to transmit QRSS was to design and build your own transmitter. David Hassall (WA5DJJ), Bill Houghton (W4HBK), Hans Summers (G0UPL), and others would post their designs on their personal websites along with notes about where their transmitters had been spotted. In the following years the act of creating QRSS grabbers became streamlined, and websites like I2NDT’s QRSS Grabber Compendium and QRSS Plus made it easier to see QRSS signals around the world. Hans Summers (G0UPL) began selling QRSS transmitter kits at amateur radio conventions, then later through the QRP Labs website. As more people started selling and buying kits (and documenting their experiences) it became easier and easier to get started with QRSS. Before QRSS kits were easy to obtain the only way to participate in the hobby was to design and build a transmitter from scratch, representing a high barrier to entry for those potentially interested in this fascinating hobby. Now with the availability of high quality QRSS transmitter kits and the ubiquity of internet tools and software to facilitate QRSS reception, it’s easier than ever to get involved in this exciting field! For these reasons I believe we have entered into a New Age of QRSS.

150 |

QRSS Frequency Bands

To receive QRSS, configure your radio for uper sideband using a dial frequency from the table below and QRSS will be audible as 1-2 kHz audio tones. This radio configuration is identical to the recommendation by WSPRnet for receiving WSPR using WSJT-X software, allowing QRSS and WSPR to be monitored simultaneously.

151 |

The entire QRSS band is approximately 200 Hz wide centered on the QRSS frequencies listed in the table below. Testing and experimentation is encouraged in the 100 Hz below the listed frequency, and the upper portion is typically used for more stable transmitters. An exception is the 10m band where the QRSS band is 400 Hz wide, extending ±200 Hz from the center frequency listed below.

152 |
153 |
154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 |
BandQRSS Frequency (±100 Hz)USB Dial Frequency (Hz)Audio Frequency (Hz)
600m476,100474,2001,900
160m1,837,9001,836,6001,300
80m3,569,900 ⭐ popular3,568,6001,300
60m5,288,5505,287,2001,350
40m7,039,900 ⭐ popular7,038,6001,300
30m10,140,000 🌟 most popular10,138,7001,300
22m13,555,40013,553,9001,300
20m14,096,900 ⭐ popular14,095,6001,300
17m18,105,90018,104,6001,300
15m21,095,90021,094,6001,300
12m24,925,90024,924,6001,300
10m28,125,70028,124,6001,100
6m50,294,30050,293,0001,300
244 |
245 |
246 |

Similar QRSS frequency tables:

247 | 255 |
256 |

⚠️ WARNING: It may not be legal for you to transmit on these frequencies. Check license requirements and regulations for your region before transmitting QRSS.

257 |
258 |
259 |

⚠️ WARNING: These frequencies sometimes change based upon community discussion. Frequency tables can be found on the Knights QRSS Wiki. Outdated or alternate frequencies include 160m (1,843,200 Hz), 80m (3,593,900 Hz), 12m (24,890,800 Hz), 10m (28,000,800 Hz and 28,322,000 ±500 Hz), and 6m (50,000,900 Hz). Experimentation on 10m is encouraged in the 100Hz above the band.

260 |
261 |

When tuning your radio your dial frequency may be lower than the QRSS frequency. If you are using upper-sideband (USB) mode, you have to tune your radio dial 1.4 kHz below the QRSS band to hear QRSS signals as a 1.4 kHz tone. Recommended dial frequencies in the table above are suitable for receiving QRSS and WSPR.

262 |

QRSS Knights

The QRSS Knights is a group of QRSS enthusiasts who coordinate events and discuss experiments over email. The group is kind and welcoming to newcomers, and those interested in learning more about QRSS are encouraged to join the mailing list.

263 |

Resources

304 |
305 |

306 | 307 |

308 |
309 | 310 | 311 | 312 | 313 | 340 | 341 | 342 |
343 | 344 |
345 | 346 | 347 | 375 | 376 | 377 | 378 | 379 | 402 | 403 |
404 | 405 | -------------------------------------------------------------------------------- /dev/screenshots/document-qa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swharden/Local-LLM-csharp/5f6dda4e4ed14278c5e173faaa4e528d18057abc/dev/screenshots/document-qa.png -------------------------------------------------------------------------------- /dev/screenshots/nuget-llamasharp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swharden/Local-LLM-csharp/5f6dda4e4ed14278c5e173faaa4e528d18057abc/dev/screenshots/nuget-llamasharp.png -------------------------------------------------------------------------------- /dev/screenshots/planets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swharden/Local-LLM-csharp/5f6dda4e4ed14278c5e173faaa4e528d18057abc/dev/screenshots/planets.png -------------------------------------------------------------------------------- /dev/screenshots/planets2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swharden/Local-LLM-csharp/5f6dda4e4ed14278c5e173faaa4e528d18057abc/dev/screenshots/planets2.png -------------------------------------------------------------------------------- /dev/screenshots/scott.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swharden/Local-LLM-csharp/5f6dda4e4ed14278c5e173faaa4e528d18057abc/dev/screenshots/scott.png -------------------------------------------------------------------------------- /dev/screenshots/winforms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swharden/Local-LLM-csharp/5f6dda4e4ed14278c5e173faaa4e528d18057abc/dev/screenshots/winforms.png -------------------------------------------------------------------------------- /src/Chat/Chat.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Chat/Program.cs: -------------------------------------------------------------------------------- 1 | /* This code example demonstrates how to use the LLamaSharp package 2 | * to engage in an AI chat session with a large language model. 3 | */ 4 | 5 | using LLama.Common; 6 | using LLama; 7 | 8 | // Indicate where the GGUF model file is 9 | string modelPath = @"C:\Users\scott\Documents\important\LLM-models\llama-2-7b-chat.Q5_K_M.gguf"; 10 | 11 | // Load the model into memory 12 | Console.ForegroundColor = ConsoleColor.DarkGray; 13 | ModelParams modelParams = new(modelPath); 14 | using LLamaWeights weights = LLamaWeights.LoadFromFile(modelParams); 15 | 16 | // Setup a chat session 17 | using LLamaContext context = weights.CreateContext(modelParams); 18 | InteractiveExecutor ex = new(context); 19 | ChatSession session = new(ex); 20 | var hideWords = new LLamaTransforms.KeywordTextOutputStreamTransform(["User:", "Bot: "]); 21 | session.WithOutputTransform(hideWords); 22 | InferenceParams infParams = new() 23 | { 24 | Temperature = 0.6f, // higher values give more "creative" answers 25 | AntiPrompts = ["User:"] 26 | }; 27 | 28 | while (true) 29 | { 30 | // Get a question from the user 31 | Console.ForegroundColor = ConsoleColor.Green; 32 | Console.Write("\nQuestion: "); 33 | string userInput = Console.ReadLine() ?? string.Empty; 34 | ChatHistory.Message msg = new(AuthorRole.User, "Question: " + userInput); 35 | 36 | // Display answer text as it is being generated 37 | Console.ForegroundColor = ConsoleColor.Yellow; 38 | await foreach (string text in session.ChatAsync(msg, infParams)) 39 | { 40 | Console.Write(text); 41 | } 42 | } -------------------------------------------------------------------------------- /src/ChatWinForms/ChatBot.cs: -------------------------------------------------------------------------------- 1 | using LLama.Common; 2 | using LLama; 3 | 4 | namespace ChatWinForms; 5 | 6 | /// 7 | /// This class wraps a LLamaSharp chat session and exposes events 8 | /// to make it easier to engage in chat with a model while invoking 9 | /// events to update the GUI at various points along the way. 10 | /// 11 | internal class ChatBot 12 | { 13 | readonly ChatSession Session; 14 | 15 | public EventHandler TextGenerated = delegate { }; 16 | public EventHandler ResponseStarted = delegate { }; 17 | public EventHandler ResponseEnded = delegate { }; 18 | 19 | public ChatBot(string modelPath) 20 | { 21 | if (!File.Exists(modelPath)) 22 | throw new FileNotFoundException(modelPath); 23 | 24 | ModelParams modelParams = new(modelPath); 25 | LLamaWeights weights = LLamaWeights.LoadFromFile(modelParams); 26 | 27 | LLamaContext context = weights.CreateContext(modelParams); 28 | InteractiveExecutor ex = new(context); 29 | Session = new ChatSession(ex); 30 | 31 | var hideWords = new LLamaTransforms.KeywordTextOutputStreamTransform(["User:", "Bot: "]); 32 | Session.WithOutputTransform(hideWords); 33 | } 34 | 35 | public async Task AskAsync(string userInput, float temperature = 0.6f) 36 | { 37 | ResponseStarted.Invoke(this, EventArgs.Empty); 38 | 39 | InferenceParams infParams = new() 40 | { 41 | Temperature = temperature, 42 | AntiPrompts = ["User:"] 43 | }; 44 | 45 | // Get a question from the user 46 | ChatHistory.Message msg = new(AuthorRole.User, "Question: " + userInput); 47 | 48 | // Display answer text as it is being generated 49 | await foreach (string text in Session.ChatAsync(msg, infParams)) 50 | { 51 | TextGenerated.Invoke(this, text); 52 | } 53 | 54 | ResponseEnded.Invoke(this, EventArgs.Empty); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/ChatWinForms/ChatWinForms.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net8.0-windows 6 | enable 7 | true 8 | enable 9 | true 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/ChatWinForms/Form1.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace ChatWinForms; 2 | 3 | partial class Form1 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | rtbOutput = new RichTextBox(); 32 | tbInput = new TextBox(); 33 | btnSend = new Button(); 34 | nudTemperature = new NumericUpDown(); 35 | label1 = new Label(); 36 | label2 = new Label(); 37 | ((System.ComponentModel.ISupportInitialize)nudTemperature).BeginInit(); 38 | SuspendLayout(); 39 | // 40 | // rtbOutput 41 | // 42 | rtbOutput.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; 43 | rtbOutput.BackColor = SystemColors.Control; 44 | rtbOutput.BorderStyle = BorderStyle.None; 45 | rtbOutput.Font = new Font("Consolas", 11.25F, FontStyle.Regular, GraphicsUnit.Point, 0); 46 | rtbOutput.Location = new Point(24, 97); 47 | rtbOutput.Name = "rtbOutput"; 48 | rtbOutput.ReadOnly = true; 49 | rtbOutput.Size = new Size(626, 235); 50 | rtbOutput.TabIndex = 0; 51 | rtbOutput.Text = ""; 52 | // 53 | // tbInput 54 | // 55 | tbInput.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; 56 | tbInput.Location = new Point(24, 44); 57 | tbInput.Name = "tbInput"; 58 | tbInput.Size = new Size(411, 23); 59 | tbInput.TabIndex = 1; 60 | tbInput.Text = "What are the names of the planets?"; 61 | // 62 | // btnSend 63 | // 64 | btnSend.Anchor = AnchorStyles.Top | AnchorStyles.Right; 65 | btnSend.Location = new Point(575, 26); 66 | btnSend.Name = "btnSend"; 67 | btnSend.Size = new Size(75, 49); 68 | btnSend.TabIndex = 3; 69 | btnSend.Text = "Send"; 70 | btnSend.UseVisualStyleBackColor = true; 71 | // 72 | // nudTemperature 73 | // 74 | nudTemperature.Anchor = AnchorStyles.Top | AnchorStyles.Right; 75 | nudTemperature.DecimalPlaces = 1; 76 | nudTemperature.Increment = new decimal(new int[] { 1, 0, 0, 65536 }); 77 | nudTemperature.Location = new Point(475, 45); 78 | nudTemperature.Maximum = new decimal(new int[] { 1, 0, 0, 0 }); 79 | nudTemperature.Name = "numericUpDown1"; 80 | nudTemperature.Size = new Size(58, 23); 81 | nudTemperature.TabIndex = 5; 82 | nudTemperature.Value = new decimal(new int[] { 6, 0, 0, 65536 }); 83 | // 84 | // label1 85 | // 86 | label1.AutoSize = true; 87 | label1.Location = new Point(24, 26); 88 | label1.Name = "label1"; 89 | label1.Size = new Size(47, 15); 90 | label1.TabIndex = 6; 91 | label1.Text = "Prompt"; 92 | // 93 | // label2 94 | // 95 | label2.Anchor = AnchorStyles.Top | AnchorStyles.Right; 96 | label2.AutoSize = true; 97 | label2.Location = new Point(467, 26); 98 | label2.Name = "label2"; 99 | label2.Size = new Size(73, 15); 100 | label2.TabIndex = 7; 101 | label2.Text = "Temperature"; 102 | // 103 | // Form1 104 | // 105 | AutoScaleDimensions = new SizeF(7F, 15F); 106 | AutoScaleMode = AutoScaleMode.Font; 107 | ClientSize = new Size(676, 358); 108 | Controls.Add(label2); 109 | Controls.Add(label1); 110 | Controls.Add(btnSend); 111 | Controls.Add(tbInput); 112 | Controls.Add(nudTemperature); 113 | Controls.Add(rtbOutput); 114 | Name = "Form1"; 115 | StartPosition = FormStartPosition.CenterScreen; 116 | Text = "Form1"; 117 | ((System.ComponentModel.ISupportInitialize)nudTemperature).EndInit(); 118 | ResumeLayout(false); 119 | PerformLayout(); 120 | } 121 | 122 | #endregion 123 | 124 | private RichTextBox rtbOutput; 125 | private TextBox tbInput; 126 | private Button btnSend; 127 | private NumericUpDown nudTemperature; 128 | private Label label1; 129 | private Label label2; 130 | } 131 | -------------------------------------------------------------------------------- /src/ChatWinForms/Form1.cs: -------------------------------------------------------------------------------- 1 | /* This code example demonstrates how to use the LLamaSharp package 2 | to engage in an AI chat session with a large language model 3 | using Windows Forms. Note that ChatBot is a class in this project 4 | that makes it easy to asynchronously interact with a chat session. 5 | */ 6 | 7 | namespace ChatWinForms; 8 | 9 | public partial class Form1 : Form 10 | { 11 | public Form1() 12 | { 13 | InitializeComponent(); 14 | 15 | string path = GetModelPath(); 16 | Text = Path.GetFileNameWithoutExtension(path); 17 | 18 | ChatBot chatter = new(path); 19 | chatter.TextGenerated += (s, e) => { rtbOutput.AppendText(e); Application.DoEvents(); }; 20 | chatter.ResponseStarted += (s, e) => EnableInputs(false); 21 | chatter.ResponseEnded += (s, e) => EnableInputs(true); 22 | 23 | btnSend.Click += async (s, e) => await chatter.AskAsync(tbInput.Text!, (float)nudTemperature.Value); 24 | } 25 | 26 | public static string GetModelPath() 27 | { 28 | OpenFileDialog dialog = new() 29 | { 30 | Filter = "GGUF files (*.gguf)|*.gguf|All files (*.*)|*.*", 31 | Title = "Select a GGUF model", 32 | }; 33 | 34 | DialogResult result = dialog.ShowDialog(); 35 | 36 | return result == DialogResult.OK 37 | ? dialog.FileName 38 | : throw new FileNotFoundException(); 39 | } 40 | 41 | public void EnableInputs(bool enabled) 42 | { 43 | if (enabled == false) 44 | rtbOutput.Clear(); 45 | 46 | btnSend.Enabled = enabled; 47 | nudTemperature.Enabled = enabled; 48 | tbInput.Enabled = enabled; 49 | Application.DoEvents(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/ChatWinForms/Form1.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /src/ChatWinForms/Program.cs: -------------------------------------------------------------------------------- 1 | namespace ChatWinForms; 2 | 3 | static class Program 4 | { 5 | /// 6 | /// The main entry point for the application. 7 | /// 8 | [STAThread] 9 | static void Main() 10 | { 11 | // To customize application configuration such as set high DPI settings or default font, 12 | // see https://aka.ms/applicationconfiguration. 13 | ApplicationConfiguration.Initialize(); 14 | Application.Run(new Form1()); 15 | } 16 | } -------------------------------------------------------------------------------- /src/DocumentQa/DocumentQa.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/DocumentQa/Program.cs: -------------------------------------------------------------------------------- 1 | /* This code example demonstrates how to pair the LLamaSharp package 2 | * with Microsoft's KernelMemory package to analyze the content of 3 | * various documents (PDF, Markdown, text files, etc.) and answer 4 | * questions about them in an interactive chat session with a LLM. 5 | */ 6 | 7 | using LLamaSharp.KernelMemory; 8 | using Microsoft.KernelMemory.Configuration; 9 | using Microsoft.KernelMemory; 10 | using System.Diagnostics; 11 | 12 | // Setup the kernel memory with the LLM model 13 | string modelPath = @"C:\Users\scott\Documents\important\LLM-models\llama-2-7b-chat.Q5_K_M.gguf"; 14 | 15 | LLama.Common.InferenceParams infParams = new() { AntiPrompts = ["\n\n"] }; 16 | 17 | LLamaSharpConfig lsConfig = new(modelPath) { DefaultInferenceParams = infParams }; 18 | 19 | SearchClientConfig searchClientConfig = new() 20 | { 21 | MaxMatchesCount = 1, 22 | AnswerTokens = 100, 23 | }; 24 | 25 | TextPartitioningOptions parseOptions = new() 26 | { 27 | MaxTokensPerParagraph = 300, 28 | MaxTokensPerLine = 100, 29 | OverlappingTokens = 30 30 | }; 31 | 32 | Console.ForegroundColor = ConsoleColor.DarkGray; 33 | IKernelMemory memory = new KernelMemoryBuilder() 34 | .WithLLamaSharpDefaults(lsConfig) 35 | .WithSearchClientConfig(searchClientConfig) 36 | .With(parseOptions) 37 | .Build(); 38 | 39 | // Ingest documents (format is automatically detected from the filename) 40 | string documentFolder = "../../../../../data"; 41 | string[] documentPaths = Directory.GetFiles(documentFolder, "*.txt"); 42 | for (int i = 0; i < documentPaths.Length; i++) 43 | { 44 | string path = documentPaths[i]; 45 | Stopwatch sw = Stopwatch.StartNew(); 46 | Console.ForegroundColor = ConsoleColor.Blue; 47 | Console.WriteLine($"Importing {i + 1} of {documentPaths.Length}: {Path.GetFileName(path)}"); 48 | await memory.ImportDocumentAsync(path, steps: Constants.PipelineWithoutSummary); 49 | Console.WriteLine($"Completed in {sw.Elapsed}\n"); 50 | } 51 | 52 | // Allow the user to ask questions 53 | while (true) 54 | { 55 | Console.ForegroundColor = ConsoleColor.Green; 56 | Console.Write("\nQuestion: "); 57 | string question = Console.ReadLine() ?? string.Empty; 58 | 59 | Stopwatch sw = Stopwatch.StartNew(); 60 | Console.ForegroundColor = ConsoleColor.DarkGray; 61 | Console.WriteLine($"Generating answer..."); 62 | 63 | MemoryAnswer answer = await memory.AskAsync(question); 64 | Console.WriteLine($"Answer generated in {sw.Elapsed}"); 65 | 66 | Console.ForegroundColor = ConsoleColor.Gray; 67 | Console.WriteLine($"Answer: {answer.Result}"); 68 | foreach (var source in answer.RelevantSources) 69 | { 70 | Console.WriteLine($"Source: {source.SourceName}"); 71 | } 72 | } -------------------------------------------------------------------------------- /src/DocumentQaWithStorage/DocumentChatBot.cs: -------------------------------------------------------------------------------- 1 | using LLamaSharp.KernelMemory; 2 | using Microsoft.KernelMemory.Configuration; 3 | using Microsoft.KernelMemory; 4 | using System.Diagnostics; 5 | using LLama.Common; 6 | using Microsoft.KernelMemory.ContentStorage.DevTools; 7 | using Microsoft.KernelMemory.FileSystem.DevTools; 8 | using Microsoft.KernelMemory.MemoryStorage.DevTools; 9 | 10 | namespace DocumentQaWithStorage; 11 | 12 | /// 13 | /// This class is a wrapper for KernelMemory and LLamaSharp which 14 | /// allows documents to be ingested and their information to be stored 15 | /// locally such that the information can be recalled when the program 16 | /// restarts instead of requiring slow re-analysis of documents. 17 | /// 18 | public class DocumentChatBot 19 | { 20 | public string StorageFolder => Path.GetFullPath($"./storage-{nameof(DocumentChatBot)}"); 21 | public bool StorageFolderExists => Directory.Exists(StorageFolder) && Directory.GetDirectories(StorageFolder).Length > 0; 22 | 23 | readonly IKernelMemory Memory; 24 | 25 | public DocumentChatBot(string modelPath) 26 | { 27 | if (!File.Exists(modelPath)) 28 | throw new FileNotFoundException(modelPath); 29 | 30 | InferenceParams infParams = new() { AntiPrompts = ["\n\n"] }; 31 | 32 | LLamaSharpConfig lsConfig = new(modelPath) { DefaultInferenceParams = infParams }; 33 | 34 | SearchClientConfig searchClientConfig = new() 35 | { 36 | MaxMatchesCount = 1, 37 | AnswerTokens = 500, 38 | }; 39 | 40 | TextPartitioningOptions parseOptions = new() 41 | { 42 | MaxTokensPerParagraph = 300, 43 | MaxTokensPerLine = 100, 44 | OverlappingTokens = 30 45 | }; 46 | 47 | SimpleFileStorageConfig storageConfig = new() 48 | { 49 | Directory = StorageFolder, 50 | StorageType = FileSystemTypes.Disk, 51 | }; 52 | 53 | SimpleVectorDbConfig vectorDbConfig = new() 54 | { 55 | Directory = StorageFolder, 56 | StorageType = FileSystemTypes.Disk, 57 | }; 58 | 59 | Console.ForegroundColor = ConsoleColor.Blue; 60 | Console.WriteLine($"Kernel memory folder: {StorageFolder}"); 61 | 62 | Console.ForegroundColor = ConsoleColor.DarkGray; 63 | Memory = new KernelMemoryBuilder() 64 | .WithSimpleFileStorage(storageConfig) 65 | .WithSimpleVectorDb(vectorDbConfig) 66 | .WithLLamaSharpDefaults(lsConfig) 67 | .WithSearchClientConfig(searchClientConfig) 68 | .With(parseOptions) 69 | .Build(); 70 | } 71 | 72 | public async Task ImportFiles(string folderPath, string searchPattern) 73 | { 74 | string[] filePaths = Directory.GetFiles(folderPath, searchPattern); 75 | for (int i = 0; i < filePaths.Length; i++) 76 | { 77 | await ImportFile(filePaths[i]); 78 | } 79 | } 80 | 81 | public async Task ImportFile(string filePath) 82 | { 83 | Stopwatch sw = Stopwatch.StartNew(); 84 | Console.ForegroundColor = ConsoleColor.Blue; 85 | Console.WriteLine($"Importing file: {Path.GetFileName(filePath)}"); 86 | await Memory.ImportDocumentAsync(filePath, steps: Constants.PipelineWithoutSummary); 87 | Console.WriteLine($"Completed in {sw.Elapsed}\n"); 88 | } 89 | 90 | public async Task ImportText(string text) 91 | { 92 | Stopwatch sw = Stopwatch.StartNew(); 93 | Console.ForegroundColor = ConsoleColor.Blue; 94 | Console.WriteLine($"Importing text ({text.Length} bytes)..."); 95 | await Memory.ImportTextAsync(text, steps: Constants.PipelineWithoutSummary); 96 | Console.WriteLine($"Completed in {sw.Elapsed}\n"); 97 | } 98 | 99 | public async Task AskAsync(string question) 100 | { 101 | Stopwatch sw = Stopwatch.StartNew(); 102 | Console.ForegroundColor = ConsoleColor.DarkGray; 103 | Console.WriteLine($"Generating answer..."); 104 | 105 | MemoryAnswer answer = await Memory.AskAsync(question); 106 | Console.WriteLine($"Answer generated in {sw.Elapsed}"); 107 | 108 | Console.ForegroundColor = ConsoleColor.Gray; 109 | Console.WriteLine($"Answer: {answer.Result}"); 110 | foreach (var source in answer.RelevantSources) 111 | { 112 | Console.WriteLine($"Source: {source.SourceName}"); 113 | } 114 | Console.WriteLine(); 115 | } 116 | 117 | public async Task ShowAnswer(string question) 118 | { 119 | Stopwatch sw = Stopwatch.StartNew(); 120 | Console.ForegroundColor = ConsoleColor.DarkGray; 121 | Console.WriteLine($"Generating answer..."); 122 | 123 | MemoryAnswer answer = await Memory.AskAsync(question); 124 | Console.WriteLine($"Answer generated in {sw.Elapsed}"); 125 | 126 | Console.ForegroundColor = ConsoleColor.Gray; 127 | Console.WriteLine($"Answer: {answer.Result}"); 128 | foreach (var source in answer.RelevantSources) 129 | { 130 | Console.WriteLine($"Source: {source.SourceName}"); 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/DocumentQaWithStorage/DocumentQaWithStorage.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/DocumentQaWithStorage/Program.cs: -------------------------------------------------------------------------------- 1 | /* This code example demonstrates how to pair the LLamaSharp package 2 | * with Microsoft's KernelMemory package to analyze the content of 3 | * various documents (PDF, Markdown, text files, etc.) and answer 4 | * questions about them in an interactive chat session with a LLM. 5 | * 6 | * Unlike the other code example in this folder, this code example wraps 7 | * document ingestion and Q&A functionality in a class that also stores 8 | * ingested document information to disk, meaning that subsequent launches 9 | * of this application are very fast since the documents do not need 10 | * to be re-analyzed every time the application starts. 11 | */ 12 | 13 | using DocumentQaWithStorage; 14 | 15 | string modelPath = @"C:\Users\scott\Documents\important\LLM-models\llama-2-7b-chat.Q5_K_M.gguf"; 16 | DocumentChatBot chat = new(modelPath); 17 | 18 | // only import files if they have not been imported before 19 | if (!chat.StorageFolderExists) 20 | { 21 | // information learned from documents is saved to disk 22 | await chat.ImportFiles("../../../../../data/", "*.*"); 23 | } 24 | 25 | while (true) 26 | { 27 | Console.ForegroundColor = ConsoleColor.Green; 28 | Console.Write("\nQuestion: "); 29 | string question = Console.ReadLine() ?? string.Empty; 30 | 31 | await chat.ShowAnswer(question); 32 | } -------------------------------------------------------------------------------- /src/DocumentSearch/DocumentSearch.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/DocumentSearch/Program.cs: -------------------------------------------------------------------------------- 1 | // See https://aka.ms/new-console-template for more information 2 | Console.WriteLine("Hello, World!"); 3 | -------------------------------------------------------------------------------- /src/KernelMemory/KernelMemory.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/KernelMemory/Program.cs: -------------------------------------------------------------------------------- 1 | /* This code example demonstrates Microsoft's KernelMemory package 2 | * and the "LLamaSharp text generator" it comes with. Although 3 | * it technically works, it's stateless (resetting context every time), 4 | * and is pretty limited. For most applications users will be best 5 | * served by interacting with the LLamaSharp classes directly and 6 | * avoiding Microsoft's repackaged wrappers around their functionality. 7 | */ 8 | 9 | using Microsoft.KernelMemory; 10 | using Microsoft.KernelMemory.AI; 11 | using Microsoft.KernelMemory.AI.LlamaSharp; 12 | 13 | string modelPath = @"C:\Users\scott\Documents\important\LLM-models\llama-2-7b-chat.Q5_K_M.gguf"; 14 | LlamaSharpConfig lsConfig = new() { ModelPath = modelPath }; 15 | LlamaSharpTextGenerator txtGen = new(lsConfig, loggerFactory: null); 16 | TextGenerationOptions options = new() 17 | { 18 | MaxTokens = 1000, 19 | Temperature = 0.0, 20 | StopSequences = ["Question"] 21 | }; 22 | 23 | while (true) 24 | { 25 | Console.ForegroundColor = ConsoleColor.Green; 26 | Console.Write("\nQuestion: "); 27 | string prompt = Console.ReadLine() ?? string.Empty; 28 | 29 | Console.ForegroundColor = ConsoleColor.Yellow; 30 | await foreach (string token in txtGen.GenerateTextAsync(prompt, options)) 31 | { 32 | Console.Write(token); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/LLM.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.9.34607.119 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Chat", "Chat\Chat.csproj", "{7A5A2278-5C6D-4214-AA29-D3EC7952913D}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChatWinForms", "ChatWinForms\ChatWinForms.csproj", "{51B132EA-61AF-40FD-93CE-881A3922A0EA}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DocumentQa", "DocumentQa\DocumentQa.csproj", "{78DE9615-409D-428C-A28A-D7A7EF460324}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DocumentQaWithStorage", "DocumentQaWithStorage\DocumentQaWithStorage.csproj", "{6BFC5079-676B-43EB-9400-91D56164D72F}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KernelMemory", "KernelMemory\KernelMemory.csproj", "{571AE03B-1B75-405F-A2EF-8B9B86255FEA}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Release|Any CPU = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {7A5A2278-5C6D-4214-AA29-D3EC7952913D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {7A5A2278-5C6D-4214-AA29-D3EC7952913D}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {7A5A2278-5C6D-4214-AA29-D3EC7952913D}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {7A5A2278-5C6D-4214-AA29-D3EC7952913D}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {51B132EA-61AF-40FD-93CE-881A3922A0EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {51B132EA-61AF-40FD-93CE-881A3922A0EA}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {51B132EA-61AF-40FD-93CE-881A3922A0EA}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {51B132EA-61AF-40FD-93CE-881A3922A0EA}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {78DE9615-409D-428C-A28A-D7A7EF460324}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {78DE9615-409D-428C-A28A-D7A7EF460324}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {78DE9615-409D-428C-A28A-D7A7EF460324}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {78DE9615-409D-428C-A28A-D7A7EF460324}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {6BFC5079-676B-43EB-9400-91D56164D72F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {6BFC5079-676B-43EB-9400-91D56164D72F}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {6BFC5079-676B-43EB-9400-91D56164D72F}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {6BFC5079-676B-43EB-9400-91D56164D72F}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {571AE03B-1B75-405F-A2EF-8B9B86255FEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {571AE03B-1B75-405F-A2EF-8B9B86255FEA}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {571AE03B-1B75-405F-A2EF-8B9B86255FEA}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {571AE03B-1B75-405F-A2EF-8B9B86255FEA}.Release|Any CPU.Build.0 = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | GlobalSection(ExtensibilityGlobals) = postSolution 47 | SolutionGuid = {1720FF14-FD52-4F07-ACB6-4CFE8C6BEB96} 48 | EndGlobalSection 49 | EndGlobal 50 | --------------------------------------------------------------------------------