├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── bin ├── deepseek-r1.txt └── prompts.txt ├── examples └── testbed │ ├── Testbed.dpr │ ├── Testbed.dproj │ ├── Testbed.res │ ├── Testbed_Icon.ico │ └── UTestbed.pas ├── media ├── delphi.png └── sophora.png ├── src ├── Sophora.CLibs.pas ├── Sophora.CLibs.res ├── Sophora.Common.pas ├── Sophora.Console.pas ├── Sophora.Defines.inc ├── Sophora.Inference.pas ├── Sophora.Messages.pas ├── Sophora.RAG.pas ├── Sophora.Tools.pas ├── Sophora.Utils.pas ├── Sophora.groupproj └── Sophora.pas └── virustotal.txt /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: tinyBigGAMES # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | thanks_dev: # Replace with a single thanks.dev username 15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Uncomment these types if you want even more clean repository. But be careful. 2 | # It can make harm to an existing project source. Read explanations below. 3 | # 4 | # Resource files are binaries containing manifest, project icon and version info. 5 | # They can not be viewed as text or compared by diff-tools. Consider replacing them with .rc files. 6 | #*.res 7 | # 8 | # Type library file (binary). In old Delphi versions it should be stored. 9 | # Since Delphi 2009 it is produced from .ridl file and can safely be ignored. 10 | #*.tlb 11 | # 12 | # Diagram Portfolio file. Used by the diagram editor up to Delphi 7. 13 | # Uncomment this if you are not using diagrams or use newer Delphi version. 14 | #*.ddp 15 | # 16 | # Visual LiveBindings file. Added in Delphi XE2. 17 | # Uncomment this if you are not using LiveBindings Designer. 18 | #*.vlb 19 | # 20 | # Deployment Manager configuration file for your project. Added in Delphi XE2. 21 | # Uncomment this if it is not mobile development and you do not use remote debug feature. 22 | #*.deployproj 23 | # 24 | # C++ object files produced when C/C++ Output file generation is configured. 25 | # Uncomment this if you are not using external objects (zlib library for example). 26 | #*.obj 27 | # 28 | 29 | # Default Delphi compiler directories 30 | # Content of this directories are generated with each Compile/Construct of a project. 31 | # Most of the time, files here have not there place in a code repository. 32 | #Win32/ 33 | #Win64/ 34 | #OSX64/ 35 | #OSXARM64/ 36 | #Android/ 37 | #Android64/ 38 | #iOSDevice64/ 39 | #Linux64/ 40 | 41 | # Delphi compiler-generated binaries (safe to delete) 42 | *.exe 43 | *.dll 44 | *.bpl 45 | *.bpi 46 | *.dcp 47 | *.so 48 | *.apk 49 | *.drc 50 | *.map 51 | *.dres 52 | *.rsm 53 | *.tds 54 | *.dcu 55 | *.lib 56 | *.a 57 | *.o 58 | *.ocx 59 | 60 | # Delphi autogenerated files (duplicated info) 61 | *.cfg 62 | *.hpp 63 | *Resource.rc 64 | 65 | # Delphi local files (user-specific info) 66 | *.local 67 | *.identcache 68 | *.projdata 69 | *.tvsconfig 70 | *.dsk 71 | 72 | # Delphi history and backups 73 | __history/ 74 | __recovery/ 75 | *.~* 76 | 77 | # Castalia statistics file (since XE7 Castalia is distributed with Delphi) 78 | *.stat 79 | 80 | # Boss dependency manager vendor folder https://github.com/HashLoad/boss 81 | modules/ 82 | 83 | # Sophora 84 | *.dsv 85 | zip_latest_commit.cmd 86 | Sophora.zip 87 | 88 | bin/articles.db 89 | bin/vectors.db 90 | bin/deepseek-r1.db 91 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2025-present, tinyBigGAMES LLC 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Sophora](media/sophora.png) 2 | [![Chat on Discord](https://img.shields.io/discord/754884471324672040?style=for-the-badge)](https://discord.gg/tPWjMwK) 3 | [![Follow on Bluesky](https://img.shields.io/badge/Bluesky-tinyBigGAMES-blue?style=for-the-badge&logo=bluesky)](https://bsky.app/profile/tinybiggames.com) 4 | 5 | **Sophora** is a local generative AI toolkit for **Delphi**, powered by the **DeepHermes-3** model and the latest **llama.cpp** optimizations. It enables fast, efficient, and unified reasoning, making it ideal for AI-driven applications that require **high-performance local inference** without relying on external cloud services. With features like **function calling**, **embedding generation**, **retrieval-augmented generation (RAG)**, and deep inference capabilities, Sophora provides developers with a versatile and powerful toolset for integrating AI into their Delphi projects. By supporting **optimized execution on modern hardware**, including **compute capability 5.0+ GPUs** via **Vulkan** for acceleration, it ensures smooth and efficient model operations. 6 | 7 | ## 🚀 Key Features 8 | - **Local AI Inference**: Run **DeepHermes-3** (Llama 3-based) entirely on your machine, enabling fully offline AI capabilities. 9 | - **Fast Token Streaming**: Supports both **non-thinking** (fast response) and **thinking** (deep reasoning) modes. 10 | - **Function Calling & Embeddings**: Execute **function calls** and perform **vector-based search** for advanced AI-driven workflows. 11 | - **Retrieval-Augmented Generation (RAG)**: Enhances AI-generated responses using structured database lookups. 12 | - **SQL and Vector Databases**: Works with **SQLite3** and vector stores, making structured and semantic searches more efficient. 13 | - **Optimized with llama.cpp**: Leverages the latest optimizations for **high performance and reduced memory usage**. 14 | - **Flexible Model Deployment**: Supports various model configurations, letting users balance between performance and accuracy. 15 | 16 | ## 📥 Getting Started 17 | 18 | ### 1️⃣ Download and Install Sophora 19 | Get the latest version of Sophora and set up the toolkit: 20 | 21 | - Download the latest version from: [Sophora Main ZIP](https://github.com/tinyBigGAMES/Sophora/archive/refs/heads/main.zip) or clone the repository: 22 | ```sh 23 | git clone https://github.com/tinyBigGAMES/Sophora.git 24 | ``` 25 | - Extract the contents to your preferred directory. 26 | - Open the project in **Delphi**, and run the provided examples to explore the toolkit. Be sure to reference the **Usage Notes** in `UTestbed.pas` for insights about setup and using the toolkit. 27 | - Ensure your system meets the minimum requirements for running large language models efficiently. Your device will need enough RAM/VRAM to hold the model plus context. Your GPU must have compute capability 5.0+ and support Vulkan for acceleration. 28 | 29 | ### 2️⃣ Download the Model 30 | Sophora requires **DeepHermes-3**, which can be downloaded from **Hugging Face**: 31 | - [DeepHermes-3-Llama-3-8B-Preview-abliterated-Q4_K_M-GGUF](https://huggingface.co/tinybiggames/DeepHermes-3-Llama-3-8B-Preview-abliterated-Q4_K_M-GGUF/resolve/main/deephermes-3-llama-3-8b-preview-abliterated-q4_k_m.gguf?download=true) (General, Reasoning, Tools) 32 | - [bge-m3-Q8_0-GGUF](https://huggingface.co/tinybiggames/bge-m3-Q8_0-GGUF/resolve/main/bge-m3-q8_0.gguf?download=true) (Embeddings) 33 | - Place the downloaded model in the desired location (default: `C:/LLM/GGUF`). 34 | - Ensure the model file is correctly placed before running the inference engine. 35 | 36 | ### 3️⃣ Setup Search API (Optional) 37 | To enable web-augmented search capabilities, obtain an API key from [Tavily](https://tavily.com/). 38 | 39 | - You receive **1000 free API credits per month**. 40 | - Set an environment variable: 41 | ```sh 42 | TAVILY_API_KEY="your_api_key_here" 43 | ``` 44 | - This API can be used for enhanced external queries via tool calls when needed. 45 | 46 | ## 🛠️ Usage Examples 47 | 48 | ### 🔹 Basic AI Query (Non-Thinking Mode) 49 | Sophora can generate **fast responses** without deep reasoning. 50 | ```delphi 51 | LMsg := TsoMessages.Create(); 52 | LInf := TsoInference.Create(); 53 | if not LInf.LoadModel() then Exit; 54 | LMsg.Add(soUser, 'Who is Bill Gates?'); 55 | if not LInf.Run(LMsg) then 56 | soConsole.PrintLn(LInf.GetError()); 57 | ``` 58 | 59 | ### 🔹 Deep Thinking Mode 60 | Sophora enables **multi-step AI reasoning** for complex problem-solving. 61 | ```delphi 62 | LMsg.Add(soSystem, 'You are a deep-thinking AI...'); 63 | LMsg.Add(soUser, 'Solve this riddle: I walk on four legs in the morning...'); 64 | LInf.Run(LMsg); 65 | ``` 66 | 67 | ### 🔹 Embedding Generation 68 | Sophora supports **vector search** using LLM embeddings. 69 | ```delphi 70 | LEmb := TsoEmbeddings.Create(); 71 | LEmb.LoadModel(); 72 | LResult := LEmb.Generate('Explain data analysis in ML'); 73 | ``` 74 | 75 | ### 🔹 Retrieval-Augmented Generation (RAG) 76 | Store and retrieve **articles** from an SQLite database. 77 | ```delphi 78 | LDb := TsoDatabase.Create(); 79 | LDb.Open('articles.db'); 80 | LDb.ExecuteSQL('INSERT INTO articles VALUES (''AI is transforming industries.'')'); 81 | LDb.ExecuteSQL('SELECT * FROM articles'); 82 | ``` 83 | 84 | ### 🔹 Vector Database Search 85 | Sophora supports **semantic search** over stored documents. 86 | ```delphi 87 | LEmb := TsoEmbeddings.Create(); 88 | LEmb.LoadModel(); 89 | LVectorDB := TsoVectorDatabase.Create(); 90 | LVectorDB.Open(LEmb, 'vectors.db'); 91 | LVectorDB.AddDocument('doc1', 'AI and deep learning research.'); 92 | LSearchResults := LVectorDB.Search('machine learning', 3); 93 | ``` 94 | 95 | ## 📊 Performance Metrics 96 | Sophora provides **detailed performance tracking**: 97 | - **Input Tokens**: Number of tokens processed. 98 | - **Output Tokens**: Tokens generated by the model. 99 | - **Speed**: Processing speed in tokens per second. 100 | 101 | ### ✅ Example Performance Output: 102 | ```plaintext 103 | Performance: 104 | Input : 15 tokens 105 | Output: 156 tokens 106 | Speed : 49.68 tokens/sec 107 | ``` 108 | 109 | ## ⚠️ Repository Status 110 | 🚧 **Note:** This repository is currently in the **setup phase**, and full documentation is not yet available. However, the code is fully functional and generally stable. Additional **examples, guides, and API documentation** will be added soon. Stay tuned—this README, along with other resources, will be continuously updated! 🚀 111 | 112 | ## 📺 Media 113 | 🌊 Deep Dive Podcast 114 | Discover in-depth discussions and insights about Sophora and its innovative features. 🚀✨ 115 | 116 | 117 | https://github.com/user-attachments/assets/6e82bf55-34fc-4085-8f97-0e0faca50a47 118 | 119 | 120 | 121 | ## 🛠️ Support and Resources 122 | 123 | - 🐞 **Report issues** via the [Issue Tracker](https://github.com/tinyBigGAMES/Sophora/issues). 124 | - 💬 **Engage in discussions** on the [Forum](https://github.com/tinyBigGAMES/Sophora/discussions) and [Discord](https://discord.gg/tPWjMwK). 125 | - 📚 **Learn more** at [Learn Delphi](https://learndelphi.org). 126 | 127 | ## 🤝 Contributing 128 | 129 | Contributions to **✨ Sophora** are highly encouraged! 🌟 130 | - 🐛 **Report Issues:** Submit issues if you encounter bugs or need help. 131 | - 💡 **Suggest Features:** Share your ideas to make **Sophora** even better. 132 | - 🔧 **Create Pull Requests:** Help expand the capabilities and robustness of the library. 133 | 134 | Your contributions make a difference! 🙌✨ 135 | 136 | #### Contributors 👥🤝 137 |
138 | 139 | 140 | 141 | 142 | 143 | ## 📜 Licensing 144 | 145 | **Sophora** is distributed under the **🆓 BSD-3-Clause License**, allowing for redistribution and use in both source and binary forms, with or without modification, under specific conditions. 146 | See the [📜 LICENSE](https://github.com/tinyBigGAMES/Sophora?tab=BSD-3-Clause-1-ov-file#BSD-3-Clause-1-ov-file) file for more details. 147 | 148 | ## 💖 Sponsoring 149 | 150 | If you find this project useful, please consider [sponsoring this project](https://github.com/sponsors/tinyBigGAMES). Your support helps sustain development, improve features, and keep the project thriving. 151 | 152 | If you're unable to support financially, there are many other ways to contribute: 153 | - ⭐ **Star the repo** – It helps increase visibility and shows appreciation. 154 | - 📢 **Spread the word** – Share the project with others who might find it useful. 155 | - 🐛 **Report bugs** – Help improve the project by identifying and reporting issues. 156 | - 🔧 **Submit fixes** – Found a bug? Fix it and contribute! 157 | - 💡 **Make suggestions** – Share ideas for improvements and new features. 158 | 159 | Every contribution, big or small, helps make this project better. Thank you for your support! 🚀 160 | 161 | 162 | --- 163 | 164 | 🛠️ Sophora AI Toolkit – A Powerful Local AI Framework for Delphi with Fast Token Streaming, Deep Reasoning, RAG, and Vector Search! 🚀🤖 165 | 166 |

167 | Delphi 168 |

169 |
170 | 171 | Made with ❤️ in Delphi 172 | 173 | 174 | -------------------------------------------------------------------------------- /bin/deepseek-r1.txt: -------------------------------------------------------------------------------- 1 | DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning 2 | **DeepSeek-AI** 3 | research@deepseek.com 4 | 5 | ## Abstract 6 | We introduce our first-generation reasoning models, **DeepSeek-R1-Zero** and **DeepSeek-R1**. DeepSeek-R1-Zero is trained via **large-scale reinforcement learning (RL) without supervised fine-tuning (SFT)** and exhibits remarkable reasoning capabilities. However, it faces challenges such as **poor readability and language mixing**. To address these, we introduce **DeepSeek-R1**, incorporating **multi-stage training and cold-start data before RL**. DeepSeek-R1 achieves performance **comparable to OpenAI-o1-1217** on reasoning tasks. To support the research community, we **open-source DeepSeek-R1-Zero, DeepSeek-R1, and six dense models** (1.5B, 7B, 8B, 14B, 32B, 70B) distilled from DeepSeek-R1 based on **Qwen and Llama**. 7 | 8 | ## 1. Introduction 9 | Large Language Models (LLMs) have evolved significantly, narrowing the gap toward **Artificial General Intelligence (AGI)**. Post-training has become an essential step in improving **reasoning accuracy, alignment with social values, and adaptation to user preferences** while using fewer computational resources than pre-training. 10 | 11 | OpenAI’s **o1 series models** introduced **inference-time scaling** via extended **Chain-of-Thought (CoT) reasoning**, significantly improving **mathematics, coding, and scientific reasoning**. However, effective **test-time scaling** remains an open problem. 12 | 13 | We take a step forward by improving **LLM reasoning capabilities through pure reinforcement learning (RL)**. Specifically, we: 14 | - Use **DeepSeek-V3-Base** as the base model. 15 | - Employ **Group Relative Policy Optimization (GRPO)** as the RL framework. 16 | - Observe that **DeepSeek-R1-Zero** exhibits strong reasoning capabilities after thousands of RL steps. 17 | - Introduce **DeepSeek-R1**, incorporating **cold-start fine-tuning** and a **multi-stage training pipeline** to improve readability and performance. 18 | - Achieve **performance on par with OpenAI-o1-1217** in reasoning tasks. 19 | 20 | Additionally, we explore **distillation from DeepSeek-R1 to smaller dense models**, showing that distilled **14B, 32B, and 70B models** outperform **QwQ-32B-Preview** and set **new benchmarks** for dense models. 21 | 22 | ## 1.1 Contributions 23 | ### **Post-Training: Large-Scale Reinforcement Learning on the Base Model** 24 | - **Pure RL Training:** DeepSeek-R1-Zero is trained **without supervised fine-tuning (SFT)**, allowing it to develop **Chain-of-Thought (CoT) reasoning** independently. 25 | - **First-of-Its-Kind Research:** DeepSeek-R1-Zero proves that **LLM reasoning capabilities can be developed purely through RL**, without SFT. 26 | 27 | ### **Distillation: Smaller Models Can Be Powerful Too** 28 | - **Distilled reasoning patterns:** The **reasoning strategies of large models** can be **transferred to smaller models**, improving their performance. 29 | - **Open-source impact:** We open-source **1.5B, 7B, 8B, 14B, 32B, and 70B models** based on **Qwen2.5 and Llama3**. 30 | 31 | ## 2. Approach 32 | ### **2.1 Overview** 33 | Most previous methods relied on **large-scale supervised data**. We show that **reasoning capabilities can be significantly improved through large-scale RL alone**. Our key contributions: 34 | 1. **DeepSeek-R1-Zero:** RL applied **directly to a base model**, without supervised fine-tuning. 35 | 2. **DeepSeek-R1:** RL applied to a model that has been **cold-start fine-tuned with reasoning data**. 36 | 3. **Distilled models:** Transferring reasoning capability from DeepSeek-R1 to **smaller dense models**. 37 | 38 | ### **2.2 DeepSeek-R1-Zero: Reinforcement Learning on the Base Model** 39 | #### **Reinforcement Learning Algorithm** 40 | We use **Group Relative Policy Optimization (GRPO)** to reduce training costs. GRPO eliminates the need for a critic model by estimating policy performance using a **group of sampled outputs**. 41 | 42 | #### **Reward Modeling** 43 | - **Accuracy rewards:** Evaluate correctness in **math and coding tasks** via automated verification. 44 | - **Format rewards:** Ensure structured **reasoning processes** with special tags ` reasoning process ` and ` final answer `. 45 | 46 | #### **Performance of DeepSeek-R1-Zero** 47 | - **Pass@1 on AIME 2024 improved from 15.6% to 71.0%.** 48 | - **With majority voting, performance increased to 86.7%, exceeding OpenAI-o1-0912.** 49 | - Shows that **LLMs can develop powerful reasoning behaviors through RL alone**. 50 | 51 | ### **2.3 DeepSeek-R1: Reinforcement Learning with Cold Start** 52 | DeepSeek-R1-Zero suffers from **poor readability and language mixing**. To fix this, **DeepSeek-R1** integrates **cold-start fine-tuning** before RL. 53 | 54 | #### **Benefits of Cold-Start Fine-Tuning** 55 | 1. **Improved Readability:** Outputs follow a **structured format**. 56 | 2. **Faster RL Convergence:** Cold-start training provides **better initialization** for reasoning tasks. 57 | 3. **Language Consistency:** A reward function enforces **consistent language use** in Chain-of-Thought (CoT) reasoning. 58 | 59 | ### **2.4 Distillation: Empowering Small Models with Reasoning Capabilities** 60 | We fine-tune **Qwen and Llama models** using **800K reasoning samples from DeepSeek-R1**, significantly improving small model reasoning ability. 61 | 62 | ## 3. Experiment 63 | ### **3.1 DeepSeek-R1 Evaluation** 64 | **Benchmarks & Performance:** 65 | - **Mathematics:** **97.3% Pass@1 on MATH-500**, matching OpenAI **o1-1217**. 66 | - **Coding:** **2,029 Elo rating on Codeforces**, slightly below OpenAI-o1-1217’s **2061 Elo rating**. 67 | - **Knowledge:** **DeepSeek-R1 scores 90.8% on MMLU**, slightly behind OpenAI-o1-1217’s **91.8%**. 68 | - **Reasoning-intensive tasks:** **79.8% Pass@1 on AIME 2024**, slightly **outperforming OpenAI-o1-1217**. 69 | 70 | ### **3.2 Distilled Model Evaluation** 71 | - **DeepSeek-R1-14B and 32B outperform QwQ-32B-Preview.** 72 | - **Distilled models perform better than RL-trained small models.** 73 | 74 | ## 4. Discussion 75 | ### **4.1 Distillation vs. Reinforcement Learning** 76 | - **Distillation is more effective than RL training for small models.** 77 | - **RL is still necessary to surpass distillation performance limits.** 78 | 79 | ### **4.2 Unsuccessful Attempts** 80 | - **Process Reward Models (PRM):** Failed due to **reward hacking**. 81 | - **Monte Carlo Tree Search (MCTS):** Struggled with **large search spaces and poor value model training**. 82 | - **RL on small models:** Required **too much compute** and performed **worse than distillation**. 83 | 84 | ## 5. Conclusion and Future Work 85 | - **DeepSeek-R1-Zero proves that reasoning capabilities can emerge through RL alone.** 86 | - **DeepSeek-R1 achieves near OpenAI-o1-1217 performance on reasoning tasks.** 87 | - **Future Directions:** Expanding beyond STEM reasoning, addressing **language mixing**, improving **prompt engineering**, and enhancing **software engineering tasks**. 88 | 89 | ## Acknowledgments 90 | Core contributors: **Daya Guo, Dejian Yang, Haowei Zhang, Junxiao Song, Ruoyu Zhang, Runxin Xu, Qihao Zhu, and many others.** 91 | 92 | -------------------------------------------------------------------------------- /bin/prompts.txt: -------------------------------------------------------------------------------- 1 | ;---------------------------------------------------------------------------- 2 | ; ___ _ 3 | ; / __| ___ _ __| |_ ___ _ _ __ _ ™ 4 | ; \__ \/ _ \ '_ \ ' \/ _ \ '_/ _` | 5 | ; |___/\___/ .__/_||_\___/_| \__,_| 6 | ; |_| 7 | ; AI Reasoning, Function-calling & 8 | ; Knowledge Retrieval 9 | ; 10 | ; Copyright © 2025-present tinyBigGAMES™ LLC 11 | ; All Rights Reserved. 12 | ; 13 | ; https://github.com/tinyBigGAMES/Sophora 14 | ; 15 | ; See LICENSE file for license information 16 | ;---------------------------------------------------------------------------- 17 | 18 | ; This is a prompt database for storing all project-related prompts. Use 19 | ; TsoPromptDatabase to load and save prompts within your application. The 20 | ; text format allows for easy editing in external text editors. 21 | ; 22 | ; Just follow the format below, including the case for the fields and you 23 | ; be able to manage all your AI prompts from a centeral external file. 24 | 25 | --- 26 | ID: DeepThink 27 | Description: Puts the LLM into deep thinking mode using structured reasoning. 28 | Prompt: | 29 | You are a deep thinking AI that uses structured reasoning to analyze problems thoroughly before providing conclusions. Follow this process: 30 | 31 | 1. Enclose your thinking process inside XML tags. 32 | 2. Limit your thinking to a maximum of 5-7 distinct points or steps of analysis. 33 | 3. For each step in your thinking, first state what you're considering, then analyze it. 34 | 4. After completing your analysis, summarize your key insights. 35 | 5. Once your thinking is complete, explicitly transition with "Based on my analysis, I conclude:" 36 | 6. Then provide your final answer or conclusion outside the XML tags in a clear, concise format. 37 | 38 | Remember: Your primary goal is to reach a well-reasoned conclusion, not just to demonstrate your thinking process. 39 | 40 | --- 41 | ID: ToolCall 42 | Description: Calls external tools when needed, formatting responses correctly. %1 = datetime string, %2 = JSON tools schema 43 | Prompt: | 44 | The present date and time is: %s 45 | 46 | You are provided with function signatures within `tools` JSON array. 47 | 48 | You may call one or more functions to assist with the user query. 49 | 50 | IMPORTANT: If you know about the use query, just answer it directly and DO NOT USE a tool. 51 | 52 | Available tools: 53 | [ 54 | %s 55 | ] 56 | 57 | When requests correspond to these tools, respond by outputting a list of function calls, one per line, in the following structure: 58 | [func_name1(params_name1=params_value1, params_name2=params_value2...), func_name2(params_name1=params_value1...), func_name3(params...), {additional function calls as needed, one per line}] 59 | 60 | --- 61 | ID: ToolResponse 62 | Description: Formats and refines a given question into a structured response. %1 = query text, %2 = query response text 63 | Prompt: | 64 | %s 65 | %s 66 | 67 | Your task is to provide an answer using ONLY information that is EXPLICITLY and DIRECTLY stated in the function_result. This is CRITICAL. 68 | 69 | ABSOLUTE REQUIREMENTS: 70 | 1. QUOTE EXACT PHRASES from the text whenever possible. 71 | 2. NEVER state or imply ANY causal relationships unless they appear verbatim in the text with explicit causal language. 72 | 3. If the question asks "why" but the text only describes "what," state clearly: "[what happens] is described, but no explicit explanation for why is provided." 73 | 4. If any aspect of the question cannot be answered with direct statements from the text, explicitly acknowledge: "[specific aspect] is not explicitly stated." 74 | 5. DO NOT connect facts together unless the connection is explicitly stated in the text. 75 | 6. DO NOT use phrases like "the text states," "the document mentions," "according to the provided information," etc. 76 | 7. Present information directly as facts, not as reporting on what "the text says." 77 | 8. If information is missing, simply state what IS known and what IS NOT known without meta-commentary. 78 | 79 | Formatting Guidelines: 80 | - Structure: Organize the answer into logical sections. Use Markdown headings (e.g., `#`, `##`, `###`) to title different sections as appropriate, so the content is easy to scan. 81 | - Paragraphs: Write short, focused paragraphs (approximately 3-5 sentences each) to maintain clarity and readability. 82 | - Lists: When presenting multiple items, steps, or key points, use lists. Use bullet points (`-` or `*`) for unordered lists and numbers (`1.`, `2.`, ...) for ordered lists. 83 | - Clarity: Ensure the answer directly addresses the question without ambiguity. Provide information in a straightforward manner and maintain a user-friendly tone. 84 | - User Preferences: If the question or any context provides specific instructions about the desired output format or style, follow those instructions first, even if they differ from the guidelines here. 85 | - Images/Charts: Ignore any requests to produce or embed images, charts, or graphs. Do not attempt to generate or reference such visual elements, as they are not supported in this environment. 86 | - Citations: Preserve all citation references in their original format (e.g., `【source†L...】`) if they are present in the provided text. Include them appropriately in the answer to support relevant statements. 87 | - Final Output Only: Do not include any reasoning steps, tool usage information, or other extraneous content. Only output the final formatted answer encapsulated in the `` tags. 88 | 89 | EXAMPLE: 90 | 91 | Question: "Why did X happen?" 92 | Text: "X happened. Later, Y was implemented." 93 | 94 | INCORRECT: "X happened because of Z." (Z isn't mentioned) 95 | INCORRECT: "X happened due to the absence of Y." (causal relationship not stated) 96 | INCORRECT: "The text states that X happened. It does not explicitly explain why X happened." 97 | CORRECT: "X happened. No explicit explanation for why X happened is provided." 98 | 99 | Your answer should be incomplete rather than speculative. Acknowledge gaps in the information rather than filling them with reasonable-sounding explanations. 100 | 101 | Output format: 102 | 103 | ...your strictly fact-based answer here... 104 | 105 | 106 | --- 107 | ID: VectorSearchResponse 108 | Description: Formats and refines a given vector search response question into a structured response. 109 | Prompt: | 110 | %s 111 | %s 112 | 113 | Your task is to provide an answer using ONLY information that is EXPLICITLY and DIRECTLY stated in the function_result. This is CRITICAL. 114 | 115 | ABSOLUTE REQUIREMENTS: 116 | 1. QUOTE EXACT PHRASES from the text whenever possible. 117 | 2. NEVER state or imply ANY causal relationships unless they appear verbatim in the text with explicit causal language. 118 | 3. If the question asks "why" but the text only describes "what," state clearly: "The provided information describes [what happens] but does not explicitly explain why." 119 | 4. If any aspect of the question cannot be answered with direct statements from the text, explicitly acknowledge: "The text does not explicitly state [specific aspect]." 120 | 5. DO NOT connect facts together unless the connection is explicitly stated in the text. 121 | 122 | Formatting Guidelines: 123 | [Your existing formatting guidelines] 124 | 125 | EXAMPLE: 126 | 127 | Question: "Why did X happen?" 128 | Text: "X happened. Later, Y was implemented." 129 | 130 | INCORRECT: "X happened because of Z." (Z isn't mentioned) 131 | INCORRECT: "X happened due to the absence of Y." (causal relationship not stated) 132 | CORRECT: "The text states that X happened. It does not explicitly explain why X happened." 133 | 134 | Your answer should be incomplete rather than speculative. Acknowledge gaps in the information rather than filling them with reasonable-sounding explanations. 135 | 136 | Output format: 137 | 138 | ...your strictly fact-based answer here... 139 | 140 | 141 | -------------------------------------------------------------------------------- /examples/testbed/Testbed.dpr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinyBigGAMES/Sophora/6af6e97bcc71cdef42571553f0614b1c02676119/examples/testbed/Testbed.dpr -------------------------------------------------------------------------------- /examples/testbed/Testbed.dproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | {66ED1055-6B0E-4499-9201-6FBCA16E07F1} 4 | 20.3 5 | None 6 | True 7 | Release 8 | Win64 9 | Testbed 10 | 2 11 | Console 12 | Testbed.dpr 13 | 14 | 15 | true 16 | 17 | 18 | true 19 | Base 20 | true 21 | 22 | 23 | true 24 | Base 25 | true 26 | 27 | 28 | true 29 | Base 30 | true 31 | 32 | 33 | true 34 | Cfg_1 35 | true 36 | true 37 | 38 | 39 | true 40 | Cfg_1 41 | true 42 | true 43 | 44 | 45 | true 46 | Base 47 | true 48 | 49 | 50 | true 51 | Cfg_2 52 | true 53 | true 54 | 55 | 56 | .\$(Platform)\$(Config) 57 | .\$(Platform)\$(Config) 58 | false 59 | false 60 | false 61 | false 62 | false 63 | System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) 64 | Testbed 65 | 66 | 67 | RaizeComponentsVcl;vclwinx;DataSnapServer;fmx;emshosting;vclie;DbxCommonDriver;bindengine;IndyIPCommon;VCLRESTComponents;DBXMSSQLDriver;FireDACCommonODBC;emsclient;FireDACCommonDriver;appanalytics;IndyProtocols;vclx;Skia.Package.RTL;IndyIPClient;dbxcds;vcledge;bindcompvclwinx;emsedge;bindcompfmx;DBXFirebirdDriver;inetdb;FireDACSqliteDriver;DbxClientDriver;FireDACASADriver;soapmidas;vclactnband;fmxFireDAC;dbexpress;FireDACInfxDriver;DBXMySQLDriver;VclSmp;inet;DataSnapCommon;vcltouch;fmxase;DBXOdbcDriver;dbrtl;FireDACDBXDriver;Skia.Package.FMX;FireDACOracleDriver;fmxdae;FireDACMSAccDriver;CustomIPTransport;FireDACMSSQLDriver;DataSnapIndy10ServerTransport;DataSnapConnectors;vcldsnap;DBXInterBaseDriver;FireDACMongoDBDriver;IndySystem;FireDACTDataDriver;Skia.Package.VCL;vcldb;vclFireDAC;bindcomp;FireDACCommon;DataSnapServerMidas;FireDACODBCDriver;emsserverresource;inetstn;IndyCore;RESTBackendComponents;bindcompdbx;rtl;FireDACMySQLDriver;FireDACADSDriver;RaizeComponentsVclDb;RESTComponents;DBXSqliteDriver;vcl;IndyIPServer;dsnapxml;dsnapcon;DataSnapClient;DataSnapProviderClient;adortl;DBXSybaseASEDriver;DBXDb2Driver;vclimg;DataSnapFireDAC;emsclientfiredac;FireDACPgDriver;FireDAC;FireDACDSDriver;inetdbxpress;xmlrtl;tethering;bindcompvcl;dsnap;CloudService;DBXSybaseASADriver;DBXOracleDriver;FireDACDb2Driver;DBXInformixDriver;fmxobj;bindcompvclsmp;DataSnapNativeClient;DatasnapConnectorsFreePascal;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) 68 | Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) 69 | Debug 70 | CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= 71 | 1033 72 | true 73 | $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png 74 | $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png 75 | 76 | 77 | RaizeComponentsVcl;vclwinx;DataSnapServer;fmx;emshosting;vclie;DbxCommonDriver;bindengine;IndyIPCommon;VCLRESTComponents;DBXMSSQLDriver;FireDACCommonODBC;emsclient;FireDACCommonDriver;appanalytics;IndyProtocols;vclx;IndyIPClient;dbxcds;vcledge;bindcompvclwinx;emsedge;bindcompfmx;DBXFirebirdDriver;inetdb;FireDACSqliteDriver;DbxClientDriver;FireDACASADriver;soapmidas;vclactnband;fmxFireDAC;dbexpress;FireDACInfxDriver;DBXMySQLDriver;VclSmp;inet;DataSnapCommon;vcltouch;fmxase;DBXOdbcDriver;dbrtl;FireDACDBXDriver;FireDACOracleDriver;fmxdae;FireDACMSAccDriver;CustomIPTransport;FireDACMSSQLDriver;DataSnapIndy10ServerTransport;DataSnapConnectors;vcldsnap;DBXInterBaseDriver;FireDACMongoDBDriver;IndySystem;FireDACTDataDriver;Skia.Package.VCL;vcldb;vclFireDAC;bindcomp;FireDACCommon;DataSnapServerMidas;FireDACODBCDriver;emsserverresource;inetstn;IndyCore;RESTBackendComponents;bindcompdbx;rtl;FireDACMySQLDriver;FireDACADSDriver;RaizeComponentsVclDb;RESTComponents;DBXSqliteDriver;vcl;IndyIPServer;dsnapxml;dsnapcon;DataSnapClient;DataSnapProviderClient;adortl;DBXSybaseASEDriver;DBXDb2Driver;vclimg;DataSnapFireDAC;emsclientfiredac;FireDACPgDriver;FireDAC;FireDACDSDriver;inetdbxpress;xmlrtl;tethering;bindcompvcl;dsnap;CloudService;DBXSybaseASADriver;DBXOracleDriver;FireDACDb2Driver;DBXInformixDriver;fmxobj;bindcompvclsmp;DataSnapNativeClient;DatasnapConnectorsFreePascal;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) 78 | true 79 | $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png 80 | $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png 81 | Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) 82 | Debug 83 | CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= 84 | 1033 85 | ..\..\bin 86 | $(BDS)\bin\default_app.manifest 87 | PerMonitorV2 88 | Testbed_Icon.ico 89 | 90 | 91 | DEBUG;$(DCC_Define) 92 | true 93 | false 94 | true 95 | true 96 | true 97 | true 98 | true 99 | 100 | 101 | false 102 | 103 | 104 | 1033 105 | 106 | 107 | false 108 | RELEASE;$(DCC_Define) 109 | 0 110 | 0 111 | 112 | 113 | 1033 114 | 115 | 116 | 117 | MainSource 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | Base 131 | 132 | 133 | Cfg_1 134 | Base 135 | 136 | 137 | Cfg_2 138 | Base 139 | 140 | 141 | 142 | Delphi.Personality.12 143 | Application 144 | 145 | 146 | 147 | Testbed.dpr 148 | 149 | 150 | Embarcadero C++Builder Office 2000 Servers Package 151 | Embarcadero C++Builder Office XP Servers Package 152 | Microsoft Office 2000 Sample Automation Server Wrapper Components 153 | Microsoft Office XP Sample Automation Server Wrapper Components 154 | 155 | 156 | 157 | 158 | 159 | true 160 | 161 | 162 | 163 | 164 | true 165 | 166 | 167 | 168 | 169 | true 170 | 171 | 172 | 173 | 174 | Testbed.exe 175 | true 176 | 177 | 178 | 179 | 180 | Testbed.exe 181 | true 182 | 183 | 184 | 185 | 186 | Testbed.exe 187 | true 188 | 189 | 190 | 191 | 192 | 1 193 | 194 | 195 | Contents\MacOS 196 | 1 197 | 198 | 199 | 0 200 | 201 | 202 | 203 | 204 | res\xml 205 | 1 206 | 207 | 208 | res\xml 209 | 1 210 | 211 | 212 | 213 | 214 | library\lib\armeabi 215 | 1 216 | 217 | 218 | library\lib\armeabi 219 | 1 220 | 221 | 222 | 223 | 224 | library\lib\armeabi-v7a 225 | 1 226 | 227 | 228 | 229 | 230 | library\lib\mips 231 | 1 232 | 233 | 234 | library\lib\mips 235 | 1 236 | 237 | 238 | 239 | 240 | library\lib\armeabi-v7a 241 | 1 242 | 243 | 244 | library\lib\arm64-v8a 245 | 1 246 | 247 | 248 | 249 | 250 | library\lib\armeabi-v7a 251 | 1 252 | 253 | 254 | 255 | 256 | res\drawable 257 | 1 258 | 259 | 260 | res\drawable 261 | 1 262 | 263 | 264 | 265 | 266 | res\drawable-anydpi-v21 267 | 1 268 | 269 | 270 | res\drawable-anydpi-v21 271 | 1 272 | 273 | 274 | 275 | 276 | res\values 277 | 1 278 | 279 | 280 | res\values 281 | 1 282 | 283 | 284 | 285 | 286 | res\values-v21 287 | 1 288 | 289 | 290 | res\values-v21 291 | 1 292 | 293 | 294 | 295 | 296 | res\values-v31 297 | 1 298 | 299 | 300 | res\values-v31 301 | 1 302 | 303 | 304 | 305 | 306 | res\values-v35 307 | 1 308 | 309 | 310 | res\values-v35 311 | 1 312 | 313 | 314 | 315 | 316 | res\drawable-anydpi-v26 317 | 1 318 | 319 | 320 | res\drawable-anydpi-v26 321 | 1 322 | 323 | 324 | 325 | 326 | res\drawable 327 | 1 328 | 329 | 330 | res\drawable 331 | 1 332 | 333 | 334 | 335 | 336 | res\drawable 337 | 1 338 | 339 | 340 | res\drawable 341 | 1 342 | 343 | 344 | 345 | 346 | res\drawable 347 | 1 348 | 349 | 350 | res\drawable 351 | 1 352 | 353 | 354 | 355 | 356 | res\drawable-anydpi-v33 357 | 1 358 | 359 | 360 | res\drawable-anydpi-v33 361 | 1 362 | 363 | 364 | 365 | 366 | res\values 367 | 1 368 | 369 | 370 | res\values 371 | 1 372 | 373 | 374 | 375 | 376 | res\values-night-v21 377 | 1 378 | 379 | 380 | res\values-night-v21 381 | 1 382 | 383 | 384 | 385 | 386 | res\drawable 387 | 1 388 | 389 | 390 | res\drawable 391 | 1 392 | 393 | 394 | 395 | 396 | res\drawable-xxhdpi 397 | 1 398 | 399 | 400 | res\drawable-xxhdpi 401 | 1 402 | 403 | 404 | 405 | 406 | res\drawable-xxxhdpi 407 | 1 408 | 409 | 410 | res\drawable-xxxhdpi 411 | 1 412 | 413 | 414 | 415 | 416 | res\drawable-ldpi 417 | 1 418 | 419 | 420 | res\drawable-ldpi 421 | 1 422 | 423 | 424 | 425 | 426 | res\drawable-mdpi 427 | 1 428 | 429 | 430 | res\drawable-mdpi 431 | 1 432 | 433 | 434 | 435 | 436 | res\drawable-hdpi 437 | 1 438 | 439 | 440 | res\drawable-hdpi 441 | 1 442 | 443 | 444 | 445 | 446 | res\drawable-xhdpi 447 | 1 448 | 449 | 450 | res\drawable-xhdpi 451 | 1 452 | 453 | 454 | 455 | 456 | res\drawable-mdpi 457 | 1 458 | 459 | 460 | res\drawable-mdpi 461 | 1 462 | 463 | 464 | 465 | 466 | res\drawable-hdpi 467 | 1 468 | 469 | 470 | res\drawable-hdpi 471 | 1 472 | 473 | 474 | 475 | 476 | res\drawable-xhdpi 477 | 1 478 | 479 | 480 | res\drawable-xhdpi 481 | 1 482 | 483 | 484 | 485 | 486 | res\drawable-xxhdpi 487 | 1 488 | 489 | 490 | res\drawable-xxhdpi 491 | 1 492 | 493 | 494 | 495 | 496 | res\drawable-xxxhdpi 497 | 1 498 | 499 | 500 | res\drawable-xxxhdpi 501 | 1 502 | 503 | 504 | 505 | 506 | res\drawable-small 507 | 1 508 | 509 | 510 | res\drawable-small 511 | 1 512 | 513 | 514 | 515 | 516 | res\drawable-normal 517 | 1 518 | 519 | 520 | res\drawable-normal 521 | 1 522 | 523 | 524 | 525 | 526 | res\drawable-large 527 | 1 528 | 529 | 530 | res\drawable-large 531 | 1 532 | 533 | 534 | 535 | 536 | res\drawable-xlarge 537 | 1 538 | 539 | 540 | res\drawable-xlarge 541 | 1 542 | 543 | 544 | 545 | 546 | res\values 547 | 1 548 | 549 | 550 | res\values 551 | 1 552 | 553 | 554 | 555 | 556 | res\drawable-anydpi-v24 557 | 1 558 | 559 | 560 | res\drawable-anydpi-v24 561 | 1 562 | 563 | 564 | 565 | 566 | res\drawable 567 | 1 568 | 569 | 570 | res\drawable 571 | 1 572 | 573 | 574 | 575 | 576 | res\drawable-night-anydpi-v21 577 | 1 578 | 579 | 580 | res\drawable-night-anydpi-v21 581 | 1 582 | 583 | 584 | 585 | 586 | res\drawable-anydpi-v31 587 | 1 588 | 589 | 590 | res\drawable-anydpi-v31 591 | 1 592 | 593 | 594 | 595 | 596 | res\drawable-night-anydpi-v31 597 | 1 598 | 599 | 600 | res\drawable-night-anydpi-v31 601 | 1 602 | 603 | 604 | 605 | 606 | 1 607 | 608 | 609 | Contents\MacOS 610 | 1 611 | 612 | 613 | 0 614 | 615 | 616 | 617 | 618 | Contents\MacOS 619 | 1 620 | .framework 621 | 622 | 623 | Contents\MacOS 624 | 1 625 | .framework 626 | 627 | 628 | Contents\MacOS 629 | 1 630 | .framework 631 | 632 | 633 | 0 634 | 635 | 636 | 637 | 638 | 1 639 | .dylib 640 | 641 | 642 | 1 643 | .dylib 644 | 645 | 646 | 1 647 | .dylib 648 | 649 | 650 | Contents\MacOS 651 | 1 652 | .dylib 653 | 654 | 655 | Contents\MacOS 656 | 1 657 | .dylib 658 | 659 | 660 | Contents\MacOS 661 | 1 662 | .dylib 663 | 664 | 665 | 0 666 | .dll;.bpl 667 | 668 | 669 | 670 | 671 | 1 672 | .dylib 673 | 674 | 675 | 1 676 | .dylib 677 | 678 | 679 | 1 680 | .dylib 681 | 682 | 683 | Contents\MacOS 684 | 1 685 | .dylib 686 | 687 | 688 | Contents\MacOS 689 | 1 690 | .dylib 691 | 692 | 693 | Contents\MacOS 694 | 1 695 | .dylib 696 | 697 | 698 | 0 699 | .bpl 700 | 701 | 702 | 703 | 704 | 0 705 | 706 | 707 | 0 708 | 709 | 710 | 0 711 | 712 | 713 | 0 714 | 715 | 716 | 0 717 | 718 | 719 | Contents\Resources\StartUp\ 720 | 0 721 | 722 | 723 | Contents\Resources\StartUp\ 724 | 0 725 | 726 | 727 | Contents\Resources\StartUp\ 728 | 0 729 | 730 | 731 | 0 732 | 733 | 734 | 735 | 736 | 1 737 | 738 | 739 | 1 740 | 741 | 742 | 743 | 744 | ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 745 | 1 746 | 747 | 748 | ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 749 | 1 750 | 751 | 752 | 753 | 754 | ..\ 755 | 1 756 | 757 | 758 | ..\ 759 | 1 760 | 761 | 762 | ..\ 763 | 1 764 | 765 | 766 | 767 | 768 | Contents 769 | 1 770 | 771 | 772 | Contents 773 | 1 774 | 775 | 776 | Contents 777 | 1 778 | 779 | 780 | 781 | 782 | Contents\Resources 783 | 1 784 | 785 | 786 | Contents\Resources 787 | 1 788 | 789 | 790 | Contents\Resources 791 | 1 792 | 793 | 794 | 795 | 796 | library\lib\armeabi-v7a 797 | 1 798 | 799 | 800 | library\lib\arm64-v8a 801 | 1 802 | 803 | 804 | 1 805 | 806 | 807 | 1 808 | 809 | 810 | 1 811 | 812 | 813 | 1 814 | 815 | 816 | Contents\MacOS 817 | 1 818 | 819 | 820 | Contents\MacOS 821 | 1 822 | 823 | 824 | Contents\MacOS 825 | 1 826 | 827 | 828 | 0 829 | 830 | 831 | 832 | 833 | library\lib\armeabi-v7a 834 | 1 835 | 836 | 837 | 838 | 839 | 1 840 | 841 | 842 | 1 843 | 844 | 845 | 1 846 | 847 | 848 | 849 | 850 | ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 851 | 1 852 | 853 | 854 | ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 855 | 1 856 | 857 | 858 | ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 859 | 1 860 | 861 | 862 | 863 | 864 | ..\ 865 | 1 866 | 867 | 868 | ..\ 869 | 1 870 | 871 | 872 | ..\ 873 | 1 874 | 875 | 876 | 877 | 878 | 1 879 | 880 | 881 | 1 882 | 883 | 884 | 1 885 | 886 | 887 | 888 | 889 | ..\$(PROJECTNAME).launchscreen 890 | 64 891 | 892 | 893 | ..\$(PROJECTNAME).launchscreen 894 | 64 895 | 896 | 897 | 898 | 899 | 1 900 | 901 | 902 | 1 903 | 904 | 905 | 1 906 | 907 | 908 | 909 | 910 | Assets 911 | 1 912 | 913 | 914 | Assets 915 | 1 916 | 917 | 918 | 919 | 920 | Assets 921 | 1 922 | 923 | 924 | Assets 925 | 1 926 | 927 | 928 | 929 | 930 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 931 | 1 932 | 933 | 934 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 935 | 1 936 | 937 | 938 | 939 | 940 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 941 | 1 942 | 943 | 944 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 945 | 1 946 | 947 | 948 | 949 | 950 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 951 | 1 952 | 953 | 954 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 955 | 1 956 | 957 | 958 | 959 | 960 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 961 | 1 962 | 963 | 964 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 965 | 1 966 | 967 | 968 | 969 | 970 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 971 | 1 972 | 973 | 974 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 975 | 1 976 | 977 | 978 | 979 | 980 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 981 | 1 982 | 983 | 984 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 985 | 1 986 | 987 | 988 | 989 | 990 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 991 | 1 992 | 993 | 994 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 995 | 1 996 | 997 | 998 | 999 | 1000 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1001 | 1 1002 | 1003 | 1004 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1005 | 1 1006 | 1007 | 1008 | 1009 | 1010 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1011 | 1 1012 | 1013 | 1014 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1015 | 1 1016 | 1017 | 1018 | 1019 | 1020 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1021 | 1 1022 | 1023 | 1024 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1025 | 1 1026 | 1027 | 1028 | 1029 | 1030 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1031 | 1 1032 | 1033 | 1034 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1035 | 1 1036 | 1037 | 1038 | 1039 | 1040 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1041 | 1 1042 | 1043 | 1044 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1045 | 1 1046 | 1047 | 1048 | 1049 | 1050 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1051 | 1 1052 | 1053 | 1054 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1055 | 1 1056 | 1057 | 1058 | 1059 | 1060 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1061 | 1 1062 | 1063 | 1064 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1065 | 1 1066 | 1067 | 1068 | 1069 | 1070 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1071 | 1 1072 | 1073 | 1074 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1075 | 1 1076 | 1077 | 1078 | 1079 | 1080 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1081 | 1 1082 | 1083 | 1084 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1085 | 1 1086 | 1087 | 1088 | 1089 | 1090 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1091 | 1 1092 | 1093 | 1094 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1095 | 1 1096 | 1097 | 1098 | 1099 | 1100 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1101 | 1 1102 | 1103 | 1104 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1105 | 1 1106 | 1107 | 1108 | 1109 | 1110 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1111 | 1 1112 | 1113 | 1114 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1115 | 1 1116 | 1117 | 1118 | 1119 | 1120 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1121 | 1 1122 | 1123 | 1124 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1125 | 1 1126 | 1127 | 1128 | 1129 | 1130 | 1131 | 1132 | 1133 | 1134 | 1135 | 1136 | 1137 | 1138 | 1139 | 1140 | 1141 | 1142 | False 1143 | True 1144 | 1145 | 1146 | 12 1147 | 1148 | 1149 | 1150 | 1151 | 1152 | -------------------------------------------------------------------------------- /examples/testbed/Testbed.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinyBigGAMES/Sophora/6af6e97bcc71cdef42571553f0614b1c02676119/examples/testbed/Testbed.res -------------------------------------------------------------------------------- /examples/testbed/Testbed_Icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinyBigGAMES/Sophora/6af6e97bcc71cdef42571553f0614b1c02676119/examples/testbed/Testbed_Icon.ico -------------------------------------------------------------------------------- /examples/testbed/UTestbed.pas: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinyBigGAMES/Sophora/6af6e97bcc71cdef42571553f0614b1c02676119/examples/testbed/UTestbed.pas -------------------------------------------------------------------------------- /media/delphi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinyBigGAMES/Sophora/6af6e97bcc71cdef42571553f0614b1c02676119/media/delphi.png -------------------------------------------------------------------------------- /media/sophora.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinyBigGAMES/Sophora/6af6e97bcc71cdef42571553f0614b1c02676119/media/sophora.png -------------------------------------------------------------------------------- /src/Sophora.CLibs.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinyBigGAMES/Sophora/6af6e97bcc71cdef42571553f0614b1c02676119/src/Sophora.CLibs.res -------------------------------------------------------------------------------- /src/Sophora.Common.pas: -------------------------------------------------------------------------------- 1 | {=============================================================================== 2 | ___ _ 3 | / __| ___ _ __| |_ ___ _ _ __ _ ™ 4 | \__ \/ _ \ '_ \ ' \/ _ \ '_/ _` | 5 | |___/\___/ .__/_||_\___/_| \__,_| 6 | |_| 7 | AI Reasoning, Function-calling & 8 | Knowledge Retrieval 9 | 10 | Copyright © 2025-present tinyBigGAMES™ LLC 11 | All Rights Reserved. 12 | 13 | https://github.com/tinyBigGAMES/Sophora 14 | 15 | See LICENSE file for license information 16 | ===============================================================================} 17 | 18 | unit Sophora.Common; 19 | 20 | {$I Sophora.Defines.inc} 21 | 22 | interface 23 | 24 | uses 25 | WinApi.Windows, 26 | System.SysUtils, 27 | System.Classes, 28 | System.Math, 29 | System.IOUtils, 30 | Sophora.CLibs, 31 | Sophora.Utils, 32 | Sophora.Console; 33 | 34 | const 35 | CsoSophoraVersion = '0.1.0'; 36 | 37 | CsoDefaultModelPath = 'C:/LLM/GGUF'; 38 | 39 | CsoDefaultModelFilename = 'deephermes-3-llama-3-8b-preview-abliterated-q4_k_m.gguf'; 40 | csoDefaultEmbeddingsModelFilename = 'bge-m3-q8_0.gguf'; 41 | 42 | CsoDefaultMainGPU = -1; 43 | CsoDefaultGPULayers = -1; 44 | CsoDefaultMaxContext = 1024*4; 45 | CsoDefaultMaxThreads = -1; 46 | 47 | type 48 | 49 | { TsoBaseObject } 50 | TsoBaseObject = class 51 | protected 52 | FError: string; 53 | public 54 | constructor Create(); virtual; 55 | destructor Destroy(); override; 56 | procedure SetError(const AText: string; const AArgs: array of const); virtual; 57 | function GetError(): string; virtual; 58 | end; 59 | 60 | implementation 61 | 62 | { TsoBaseObject } 63 | constructor TsoBaseObject.Create(); 64 | begin 65 | inherited; 66 | end; 67 | 68 | destructor TsoBaseObject.Destroy(); 69 | begin 70 | inherited; 71 | end; 72 | 73 | procedure TsoBaseObject.SetError(const AText: string; const AArgs: array of const); 74 | begin 75 | FError := Format(AText, AArgs); 76 | end; 77 | 78 | function TsoBaseObject.GetError(): string; 79 | begin 80 | Result := FError; 81 | end; 82 | 83 | initialization 84 | begin 85 | ReportMemoryLeaksOnShutdown := True; 86 | 87 | soUtils.UnitInit(); 88 | soConsole.UnitInit(); 89 | end; 90 | 91 | finalization 92 | begin 93 | end; 94 | 95 | end. 96 | -------------------------------------------------------------------------------- /src/Sophora.Console.pas: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinyBigGAMES/Sophora/6af6e97bcc71cdef42571553f0614b1c02676119/src/Sophora.Console.pas -------------------------------------------------------------------------------- /src/Sophora.Defines.inc: -------------------------------------------------------------------------------- 1 | {=============================================================================== 2 | ___ _ 3 | / __| ___ _ __| |_ ___ _ _ __ _ ™ 4 | \__ \/ _ \ '_ \ ' \/ _ \ '_/ _` | 5 | |___/\___/ .__/_||_\___/_| \__,_| 6 | |_| 7 | AI Reasoning, Function-calling & 8 | Knowledge Retrieval 9 | 10 | Copyright © 2025-present tinyBigGAMES™ LLC 11 | All Rights Reserved. 12 | 13 | https://github.com/tinyBigGAMES/Sophora 14 | 15 | See LICENSE file for license information 16 | ===============================================================================} 17 | 18 | {$WARN SYMBOL_DEPRECATED OFF} 19 | {$WARN SYMBOL_PLATFORM OFF} 20 | 21 | {$WARN UNIT_PLATFORM OFF} 22 | {$WARN UNIT_DEPRECATED OFF} 23 | 24 | {$Z4} 25 | {$A8} 26 | 27 | {$INLINE AUTO} 28 | 29 | {$IFNDEF WIN64} 30 | {$MESSAGE Error 'Unsupported platform'} 31 | {$ENDIF} 32 | 33 | {$IF (CompilerVersion < 36.0)} 34 | {$IFNDEF WIN64} 35 | {$MESSAGE Error 'Must use Delphi 12 or higher'} 36 | {$ENDIF} 37 | {$IFEND} 38 | -------------------------------------------------------------------------------- /src/Sophora.Inference.pas: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinyBigGAMES/Sophora/6af6e97bcc71cdef42571553f0614b1c02676119/src/Sophora.Inference.pas -------------------------------------------------------------------------------- /src/Sophora.Messages.pas: -------------------------------------------------------------------------------- 1 | {=============================================================================== 2 | ___ _ 3 | / __| ___ _ __| |_ ___ _ _ __ _ ™ 4 | \__ \/ _ \ '_ \ ' \/ _ \ '_/ _` | 5 | |___/\___/ .__/_||_\___/_| \__,_| 6 | |_| 7 | AI Reasoning, Function-calling & 8 | Knowledge Retrieval 9 | 10 | Copyright © 2025-present tinyBigGAMES™ LLC 11 | All Rights Reserved. 12 | 13 | https://github.com/tinyBigGAMES/Sophora 14 | 15 | See LICENSE file for license information 16 | ===============================================================================} 17 | 18 | unit Sophora.Messages; 19 | 20 | {$I Sophora.Defines.inc} 21 | 22 | interface 23 | 24 | uses 25 | System.SysUtils, 26 | System.StrUtils, 27 | System.Classes, 28 | Sophora.CLibs, 29 | Sophora.Utils, 30 | Sophora.Common; 31 | 32 | const 33 | soSystem = 'system'; 34 | soUser = 'user'; 35 | soAssistant = 'assistant'; 36 | soTool = 'tool'; 37 | 38 | type 39 | 40 | { TsoMessages } 41 | TsoMessages = class(TsoBaseObject) 42 | protected 43 | FList: TStringList; 44 | FLastUser: string; 45 | FAddEnd: Boolean; 46 | public 47 | constructor Create(); override; 48 | destructor Destroy(); override; 49 | procedure Clear(); virtual; 50 | procedure AddRaw(const AContent: string); virtual; 51 | procedure Add(const ARole, AContent: string); virtual; 52 | procedure AddEnd(); virtual; 53 | function Prompt(): string; virtual; 54 | function IsEmpty: Boolean; virtual; 55 | function LastUser(): string; 56 | end; 57 | 58 | implementation 59 | 60 | { TsoMessages } 61 | constructor TsoMessages.Create(); 62 | begin 63 | inherited; 64 | FList := TStringList.Create(); 65 | Clear(); 66 | end; 67 | 68 | destructor TsoMessages.Destroy(); 69 | begin 70 | FList.Free(); 71 | inherited; 72 | end; 73 | 74 | procedure TsoMessages.Clear(); 75 | begin 76 | FList.Clear(); 77 | FLastUser := ''; 78 | FAddEnd := False; 79 | end; 80 | 81 | procedure TsoMessages.AddRaw(const AContent: string); 82 | var 83 | LContent: string; 84 | begin 85 | LContent := AContent.Trim(); 86 | if LContent.IsEmpty then Exit; 87 | 88 | FList.Add(AContent.Trim); 89 | end; 90 | 91 | procedure TsoMessages.Add(const ARole, AContent: string); 92 | var 93 | LRole: string; 94 | LContent: string; 95 | LMsg: string; 96 | begin 97 | LRole := ARole.Trim(); 98 | LContent := AContent.Trim(); 99 | 100 | if LRole.IsEmpty then Exit; 101 | if LContent.IsEmpty then Exit; 102 | 103 | if SameText(LRole, soSystem) then 104 | begin 105 | LMsg := '<|start_header_id|>system<|end_header_id|>\n' + LContent + '<|eot_id|>'; 106 | FAddEnd := False; 107 | end 108 | else 109 | if SameText(LRole, soUser) then 110 | begin 111 | LMsg := '<|start_header_id|>user<|end_header_id|>\n' + LContent + '<|eot_id|>'; 112 | FLastUser := LContent; 113 | FAddEnd := True; 114 | end 115 | else 116 | if SameText(LRole, soAssistant) then 117 | begin 118 | LMsg := '<|start_header_id|>assistant<|end_header_id|>\n' + LContent + '<|eot_id|>'; 119 | FAddEnd := False; 120 | end 121 | else 122 | if SameText(LRole, soTool) then 123 | begin 124 | LMsg := '<|start_header_id|>tool<|end_header_id|>\n' + LContent + '<|eot_id|>'; 125 | FAddEnd := True; 126 | end; 127 | 128 | AddRaw(LMsg); 129 | end; 130 | 131 | procedure TsoMessages.AddEnd(); 132 | begin 133 | if FAddEnd then 134 | AddRaw('<|start_header_id|>assistant<|end_header_id|>'); 135 | end; 136 | 137 | function TsoMessages.Prompt(): string; 138 | var 139 | LItem: string; 140 | begin 141 | Result := ''; 142 | for LItem in FList do 143 | begin 144 | Result := Result + LItem + ' '; 145 | end; 146 | Result := Result.Trim(); 147 | end; 148 | 149 | function TsoMessages.IsEmpty: Boolean; 150 | begin 151 | Result := Prompt().IsEmpty; 152 | end; 153 | 154 | function TsoMessages.LastUser(): string; 155 | begin 156 | Result := FLastUser; 157 | end; 158 | 159 | 160 | end. 161 | -------------------------------------------------------------------------------- /src/Sophora.RAG.pas: -------------------------------------------------------------------------------- 1 | {=============================================================================== 2 | ___ _ 3 | / __| ___ _ __| |_ ___ _ _ __ _ ™ 4 | \__ \/ _ \ '_ \ ' \/ _ \ '_/ _` | 5 | |___/\___/ .__/_||_\___/_| \__,_| 6 | |_| 7 | AI Reasoning, Function-calling & 8 | Knowledge Retrieval 9 | 10 | Copyright © 2025-present tinyBigGAMES™ LLC 11 | All Rights Reserved. 12 | 13 | https://github.com/tinyBigGAMES/Sophora 14 | 15 | See LICENSE file for license information 16 | ===============================================================================} 17 | 18 | unit Sophora.RAG; 19 | 20 | {$I Sophora.Defines.inc} 21 | 22 | interface 23 | 24 | uses 25 | System.Generics.Collections, 26 | System.Generics.Defaults, 27 | System.SysUtils, 28 | System.IOUtils, 29 | System.Classes, 30 | System.JSON, 31 | System.Math, 32 | System.NetEncoding, 33 | Sophora.CLibs, 34 | Sophora.Utils, 35 | Sophora.Common, 36 | Sophora.Inference, 37 | Sophora.Console; 38 | 39 | type 40 | 41 | { TsoEmbeddings } 42 | TsoEmbeddings = class(TsoModel) 43 | protected 44 | public 45 | constructor Create(); override; 46 | destructor Destroy(); override; 47 | function LoadModel(const AMainGPU: Integer=0; const AGPULayers: Integer=0; const AFilename: string=csoDefaultEmbeddingsModelFilename): Boolean; override; 48 | function Generate(const APrompt: string; const AMaxContext: Cardinal=CsoDefaultMaxContext; const AMaxThreads: Integer=CsoDefaultMaxThreads): TArray; virtual; 49 | end; 50 | 51 | { TsoDatabase } 52 | TsoDatabase = class(TsoBaseObject) 53 | protected 54 | FDatabase: string; 55 | FResponseText: string; 56 | FSQL: TStringList; 57 | FPrepairedSQL: string; 58 | FJSON: TJSONObject; 59 | FDataset: TJSONArray; 60 | FMacros: TDictionary; 61 | FParams: TDictionary; 62 | FHandle: PSQLite3; 63 | FStmt: Psqlite3_stmt; 64 | procedure SetMacroValue(const AName, AValue: string); 65 | procedure SetParamValue(const AName, AValue: string); 66 | procedure Prepair(); 67 | function ExecuteSQLInternal(const ASQL: string): Boolean; 68 | public 69 | property Handle: PSQLite3 read FHandle; 70 | constructor Create(); override; 71 | destructor Destroy(); override; 72 | function IsOpen(): Boolean; 73 | function Open(const AFilename: string): Boolean; 74 | procedure Close(); 75 | procedure ClearSQLText(); 76 | procedure AddSQLText(const AText: string); 77 | function GetSQLText(): string; 78 | procedure SetSQLText(const AText: string); 79 | function GetPrepairedSQL(): string; 80 | procedure ClearMacros(); 81 | function GetMacro(const AName: string): string; 82 | procedure SetMacro(const AName, AValue: string); 83 | procedure ClearParams(); 84 | function GetParam(const AName: string): string; 85 | procedure SetParam(const AName, AValue: string); 86 | function RecordCount(): Integer; 87 | function GetField(const AIndex: Cardinal; const AName: string): string; 88 | function Execute(): Boolean; 89 | function ExecuteSQL(const ASQL: string): Boolean; 90 | function GetResponseText(): string; 91 | end; 92 | 93 | { TsoVectorDatabaseDocument } 94 | TsoVectorDatabaseDocument = record 95 | ID: string; 96 | Score: Single; 97 | Text: string; 98 | end; 99 | 100 | { TsoVectorDatabase } 101 | TsoVectorDatabase = class(TsoBaseObject) 102 | private 103 | FDatabase: TsoDatabase; 104 | FEmbeddings: TsoEmbeddings; 105 | procedure CreateTableIfNotExists(); 106 | function CosineSimilarity(const AVec1, AVec2: TArray): Double; 107 | public 108 | constructor Create(); override; 109 | destructor Destroy; override; 110 | 111 | function Open(const AEmbeddings: TsoEmbeddings; const AFilename: string): Boolean; 112 | procedure Close(); 113 | function AddDocument(const ADocID: string; const AText: string; const AMaxContext: Cardinal=1024*4; const AMaxThreads: Integer=-1): Boolean; 114 | function AddLargeDocument(const ADocID, ATitle, AText: string; const AChunkSize: Integer; const AMaxContext: Cardinal=1024*4; const AMaxThreads: Integer=-1): Boolean; 115 | function Search(const AQuery: string; const ATopK: Integer = 5; const AQueryLimit: Integer = 1000; const ASimilarityThreshold: Single=0.1; const AMaxContext: Cardinal=1024*4; const AMaxThreads: Integer=-1): TJSONArray; 116 | function Search2(const AQuery: string; const ATopK: Integer = 5; const AQueryLimit: Integer = 1000; const ASimilarityThreshold: Single=0.1; const AMaxContext: Cardinal=1024*4; const AMaxThreads: Integer=-1): TArray; 117 | end; 118 | 119 | implementation 120 | 121 | { TsoEmbeddings } 122 | constructor TsoEmbeddings.Create(); 123 | begin 124 | inherited; 125 | end; 126 | 127 | destructor TsoEmbeddings.Destroy(); 128 | begin 129 | inherited; 130 | end; 131 | 132 | function TsoEmbeddings.LoadModel(const AMainGPU: Integer; const AGPULayers: Integer; const AFilename: string): Boolean; 133 | begin 134 | Result := inherited LoadModel(AMainGPU, AGPULayers, AFilename); 135 | end; 136 | 137 | function TsoEmbeddings.Generate(const APrompt: string; const AMaxContext: Cardinal=1024*4; const AMaxThreads: Integer=-1): TArray; 138 | var 139 | LContextParams: llama_context_params; 140 | LContext: Pllama_context; 141 | LVocab: Pllama_vocab; 142 | LTokens: TArray; 143 | LTokenCount, LEmbeddingSize, I: Integer; 144 | LBatch: llama_batch; 145 | LSourceEmbeddings: TArray; 146 | LEmbeddingsP: PSingle; 147 | LInputText: UTF8String; 148 | LSeqId: Integer; 149 | LNumPredict: Integer; 150 | LMaxContext: Cardinal; 151 | 152 | procedure Normalize(AInVectors, LOutVectors: PSingle; const ACount: Integer); 153 | var 154 | LNorm: Single; 155 | I: Integer; 156 | begin 157 | LNorm := 0; 158 | 159 | // Compute the squared sum 160 | for I := 0 to ACount - 1 do 161 | //norm := norm + vec[i] * vec[i]; 162 | LNorm := LNorm + TsoPointerArray1D.GetValue(AInVectors, I) * TsoPointerArray1D.GetValue(AInVectors, I); 163 | 164 | // Take the square root to get the norm 165 | LNorm := Sqrt(LNorm); 166 | 167 | // Normalize each element 168 | if LNorm <> 0 then 169 | for I := 0 to ACount - 1 do 170 | //outVec[i] := vec[i] / norm; 171 | TsoPointerArray1D.SetValue(LOutVectors, I, TsoPointerArray1D.GetValue(AInVectors, I) / LNorm); 172 | end; 173 | 174 | begin 175 | Result := nil; 176 | 177 | // check if model not loaded 178 | if not ModelLoaded() then 179 | begin 180 | SetError('[%s] Model not loaded', ['RunInference']); 181 | Exit; 182 | end; 183 | 184 | if APrompt.IsEmpty then 185 | begin 186 | SetError('Not messages was found', []); 187 | Exit; 188 | end; 189 | 190 | if LMaxContext > 0 then 191 | LNumPredict := EnsureRange(AMaxContext, 512, LMaxContext) 192 | else 193 | LNumPredict := 512; 194 | 195 | 196 | // Get model vocab 197 | LVocab := llama_model_get_vocab(FModel); 198 | if LVocab = nil then 199 | begin 200 | SetError('Failed to get vocab', []); 201 | Exit; 202 | end; 203 | 204 | // Tokenize input text 205 | LInputText := UTF8String(APrompt); 206 | LTokenCount := -llama_tokenize(LVocab, PUTF8Char(LInputText), Length(LInputText), nil, 0, true, true); 207 | SetLength(LTokens, LTokenCount); 208 | 209 | // Initialize context parameters 210 | LContextParams := llama_context_default_params(); 211 | LContextParams.embeddings := True; // Enable embedding mode 212 | LContextParams.n_ctx := LTokenCount + LNumPredict - 1; 213 | LContextParams.n_batch := LTokenCount; 214 | LContextParams.no_perf := false; 215 | if AMaxThreads = -1 then 216 | LContextParams.n_threads := soUtils.GetPhysicalProcessorCount() 217 | else 218 | LContextParams.n_threads := EnsureRange(AMaxThreads, 1, soUtils.GetPhysicalProcessorCount()); 219 | LContextParams.n_threads_batch := LContextParams.n_threads; 220 | LContextParams.flash_attn := False; 221 | 222 | // Create a new context 223 | LContext := llama_new_context_with_model(FModel, LContextParams); 224 | if LContext = nil then 225 | begin 226 | SetError('Failed to create LLaMA context', []); 227 | Exit; 228 | end; 229 | 230 | if llama_tokenize(LVocab, PUTF8Char(LInputText), Length(LInputText), @LTokens[0], Length(LTokens), True, True) < 0 then 231 | begin 232 | SetError('Tokenization failed!', []); 233 | llama_free(LContext); 234 | Exit; 235 | end; 236 | 237 | // Initialize batch 238 | LBatch := llama_batch_init(LTokenCount, 0, 1); 239 | 240 | LSeqId := 0; 241 | // llama_batch_add(batch, tokens, 0, ) 242 | for I := 0 to LTokenCount - 1 do 243 | begin 244 | TsoPointerArray1D.SetValue(LBatch.token, LBatch.n_tokens, LTokens[I]); 245 | TsoPointerArray1D.SetValue(LBatch.pos, LBatch.n_tokens, I); 246 | TsoPointerArray1D.SetValue(LBatch.n_seq_id, LBatch.n_tokens, 1); 247 | TsoPointerArray2D.SetValue(LBatch.seq_id, LBatch.n_tokens, 0, LSeqId); 248 | TsoPointerArray1D.SetValue(LBatch.logits, LBatch.n_tokens, Ord(I = (LTokenCount - 1))); 249 | Inc(LBatch.n_tokens); 250 | end; 251 | 252 | // Allocate storage for embeddings 253 | LEmbeddingSize := llama_model_n_embd(FModel); 254 | SetLength(LSourceEmbeddings, LEmbeddingSize); 255 | SetLength(Result, LEmbeddingSize); 256 | 257 | // Process batch 258 | llama_kv_cache_clear(LContext); 259 | if llama_decode(LContext, LBatch) < 0 then 260 | begin 261 | SetError('LLaMA encoding failed!', []); 262 | llama_batch_free(LBatch); 263 | llama_free(LContext); 264 | Exit; 265 | end; 266 | 267 | if llama_pooling_type_rtn(LContext) = LLAMA_POOLING_TYPE_NONE then 268 | LEmbeddingsP := PSingle(llama_get_embeddings(LContext)) 269 | else 270 | LEmbeddingsP := PSingle(llama_get_embeddings_seq(LContext, TsoPointerArray2D.GetValue(LBatch.seq_id, 0, 0))); 271 | 272 | for i := 0 to LEmbeddingSize-1 do 273 | begin 274 | LSourceEmbeddings[i] := TsoPointerArray1D.GetValue(LEmbeddingsP, i); 275 | end; 276 | 277 | // Normalizes an embedding vector to unit length 278 | Normalize(@LSourceEmbeddings[0], @Result[0], LEmbeddingSize); 279 | 280 | // Cleanup 281 | llama_batch_free(LBatch); 282 | llama_free(LContext); 283 | end; 284 | 285 | 286 | { TsoDatabase } 287 | procedure TsoDatabase.SetMacroValue(const AName, AValue: string); 288 | begin 289 | FPrepairedSQL := FPrepairedSQL.Replace('&'+AName, AValue); 290 | end; 291 | 292 | procedure TsoDatabase.SetParamValue(const AName, AValue: string); 293 | begin 294 | FPrepairedSQL := FPrepairedSQL.Replace(':'+AName, ''''+AValue+''''); 295 | end; 296 | 297 | procedure TsoDatabase.Prepair(); 298 | var 299 | LKey: string; 300 | begin 301 | FPrepairedSQL := FSQL.Text; 302 | 303 | // Substitute macros 304 | for LKey in FMacros.Keys do 305 | begin 306 | SetMacroValue(LKey, FMacros.Items[LKey]); 307 | end; 308 | 309 | // Substitute field params 310 | for LKey in FParams.Keys do 311 | begin 312 | SetParamValue(LKey, FParams.Items[LKey]); 313 | end; 314 | end; 315 | 316 | constructor TsoDatabase.Create(); 317 | begin 318 | inherited; 319 | 320 | FSQL := TStringList.Create; 321 | FMacros := TDictionary.Create; 322 | FParams := TDictionary.Create; 323 | end; 324 | 325 | destructor TsoDatabase.Destroy(); 326 | begin 327 | Close(); 328 | FParams.Free(); 329 | FMacros.Free(); 330 | FSQL.Free(); 331 | 332 | inherited; 333 | end; 334 | 335 | function TsoDatabase.IsOpen(): Boolean; 336 | begin 337 | Result := Assigned(FHandle); 338 | end; 339 | 340 | function TsoDatabase.Open(const AFilename: string): Boolean; 341 | begin 342 | Result := False; 343 | 344 | if IsOpen() then 345 | begin 346 | SetError('Database already open', []); 347 | Exit; 348 | end; 349 | 350 | FDatabase := TPath.ChangeExtension(AFilename, 'db'); 351 | if sqlite3_open(PAnsiChar(AnsiString(FDatabase)), @FHandle) <> SQLITE_OK then 352 | begin 353 | SetError(string(sqlite3_errmsg(FHandle)), []); 354 | sqlite3_close(FHandle); 355 | FHandle := nil; 356 | end; 357 | 358 | Result := IsOpen(); 359 | end; 360 | 361 | procedure TsoDatabase.Close(); 362 | begin 363 | if not IsOpen() then 364 | begin 365 | SetError('Database was not open', []); 366 | Exit; 367 | end; 368 | 369 | if Assigned(FJSON) then 370 | begin 371 | FJSON.Free(); 372 | FJSON := nil; 373 | end; 374 | 375 | if Assigned(FStmt) then 376 | begin 377 | sqlite3_finalize(FStmt); 378 | FStmt := nil; 379 | end; 380 | 381 | if Assigned(FHandle) then 382 | begin 383 | sqlite3_close(FHandle); 384 | FHandle := nil; 385 | end; 386 | 387 | ClearMacros(); 388 | ClearParams(); 389 | ClearSQLText(); 390 | 391 | FDatabase := ''; 392 | FResponseText := ''; 393 | FError := ''; 394 | FPrepairedSQL := ''; 395 | end; 396 | 397 | procedure TsoDatabase.ClearSQLText(); 398 | begin 399 | FSQL.Clear; 400 | end; 401 | 402 | procedure TsoDatabase.AddSQLText(const AText: string); 403 | begin 404 | FSQL.Add(AText); 405 | end; 406 | 407 | function TsoDatabase.GetSQLText(): string; 408 | begin 409 | Result := FSQL.Text; 410 | end; 411 | 412 | procedure TsoDatabase.SetSQLText(const AText: string); 413 | begin 414 | FSQL.Text := AText; 415 | end; 416 | 417 | function TsoDatabase.GetPrepairedSQL(): string; 418 | begin 419 | Result := FPrepairedSQL; 420 | end; 421 | 422 | procedure TsoDatabase.ClearMacros(); 423 | begin 424 | FMacros.Clear(); 425 | end; 426 | 427 | function TsoDatabase.GetMacro(const AName: string): string; 428 | begin 429 | FMacros.TryGetValue(AName, Result); 430 | end; 431 | 432 | procedure TsoDatabase.SetMacro(const AName, AValue: string); 433 | begin 434 | FMacros.AddOrSetValue(AName, AValue); 435 | end; 436 | 437 | procedure TsoDatabase.ClearParams(); 438 | begin 439 | FParams.Clear(); 440 | end; 441 | 442 | function TsoDatabase.GetParam(const AName: string): string; 443 | begin 444 | FParams.TryGetValue(AName, Result); 445 | end; 446 | 447 | procedure TsoDatabase.SetParam(const AName, AValue: string); 448 | begin 449 | FParams.AddOrSetValue(AName, AValue); 450 | end; 451 | 452 | function TsoDatabase.RecordCount(): Integer; 453 | begin 454 | Result := 0; 455 | if not Assigned(FDataset) then Exit; 456 | Result := FDataset.Count; 457 | end; 458 | 459 | function TsoDatabase.GetField(const AIndex: Cardinal; const AName: string): string; 460 | begin 461 | Result := ''; 462 | if not Assigned(FDataset) then Exit; 463 | if AIndex > Cardinal(FDataset.Count-1) then Exit; 464 | Result := FDataset.Items[AIndex].GetValue(AName); 465 | end; 466 | 467 | function TsoDatabase.Execute(): Boolean; 468 | begin 469 | Prepair; 470 | Result := ExecuteSQL(FPrepairedSQL); 471 | end; 472 | 473 | function TsoDatabase.ExecuteSQL(const ASQL: string): Boolean; 474 | begin 475 | Result := ExecuteSQLInternal(ASQL); 476 | end; 477 | 478 | function TsoDatabase.ExecuteSQLInternal(const ASQL: string): Boolean; 479 | var 480 | LRes: Integer; 481 | I: Integer; 482 | LName: string; 483 | LValue: string; 484 | LRow: TJSONObject; 485 | 486 | function GetTypeAsString(AStmt: Psqlite3_stmt; AColumn: Integer): string; 487 | begin 488 | case sqlite3_column_type(AStmt, AColumn) of 489 | SQLITE_INTEGER: Result := IntToStr(sqlite3_column_int(AStmt, AColumn)); 490 | SQLITE_FLOAT: Result := FloatToStr(sqlite3_column_double(AStmt, AColumn)); 491 | SQLITE_TEXT: Result := string(PWideChar(sqlite3_column_text16(AStmt, AColumn))); // Fixed AColumn usage 492 | SQLITE_BLOB: Result := '[Blob Data]'; 493 | SQLITE_NULL: Result := 'NULL'; 494 | else 495 | Result := 'Unknown'; 496 | end; 497 | end; 498 | 499 | begin 500 | Result := False; 501 | if not Assigned(FHandle) then Exit; 502 | 503 | LRes := sqlite3_prepare16_v2(FHandle, PChar(ASQL), -1, @FStmt, nil); 504 | if LRes <> SQLITE_OK then 505 | begin 506 | SetError(string(PWideChar(sqlite3_errmsg16(FHandle))), []); 507 | Exit; 508 | end; 509 | 510 | LRes := sqlite3_step(FStmt); 511 | if (LRes <> SQLITE_DONE) and (LRes <> SQLITE_ROW) then 512 | begin 513 | SetError(string(PWideChar(sqlite3_errmsg16(FHandle))), []); 514 | sqlite3_finalize(FStmt); 515 | FStmt := nil; 516 | Exit; 517 | end; 518 | 519 | FResponseText := ''; 520 | if LRes = SQLITE_ROW then 521 | begin 522 | try 523 | // Free the current Dataset 524 | if Assigned(FJSON) then 525 | begin 526 | FJSON.Free(); 527 | FJSON := nil; 528 | end; 529 | 530 | FDataset := TJSONArray.Create; 531 | while LRes = SQLITE_ROW do 532 | begin 533 | LRow := TJSONObject.Create; 534 | for I := 0 to sqlite3_column_count(FStmt) - 1 do 535 | begin 536 | LName := string(PWideChar(sqlite3_column_name16(FStmt, I))); 537 | LValue := GetTypeAsString(FStmt, I); 538 | LRow.AddPair(LName, LValue); 539 | end; 540 | FDataset.AddElement(LRow); 541 | LRes := sqlite3_step(FStmt); 542 | end; 543 | FJSON := TJSONObject.Create; 544 | FJSON.AddPair('response', FDataset); 545 | FResponseText := FJSON.Format(); 546 | except 547 | on E: Exception do 548 | begin 549 | FreeAndNil(FDataset); 550 | FreeAndNil(FJSON); 551 | raise; // Re-raise to ensure the caller handles it 552 | end; 553 | end; 554 | end; 555 | 556 | FError := ''; 557 | Result := True; 558 | sqlite3_reset(FStmt); // Reset instead of immediate finalize 559 | sqlite3_finalize(FStmt); 560 | FStmt := nil; 561 | end; 562 | 563 | function TsoDatabase.GetResponseText(): string; 564 | begin 565 | Result := FResponseText; 566 | end; 567 | 568 | 569 | { TsoVectorDatabase } 570 | constructor TsoVectorDatabase.Create(); 571 | begin 572 | inherited; 573 | FDatabase := TsoDatabase.Create; 574 | end; 575 | 576 | destructor TsoVectorDatabase.Destroy(); 577 | begin 578 | Close(); 579 | 580 | FDatabase.Free(); 581 | 582 | inherited; 583 | end; 584 | 585 | function TsoVectorDatabase.Open(const AEmbeddings: TsoEmbeddings; const AFilename: string): Boolean; 586 | begin 587 | Result := False; 588 | if not Assigned(AEmbeddings) then Exit; 589 | FEmbeddings := AEmbeddings; 590 | 591 | if not FDatabase.Open(AFilename) then Exit; 592 | 593 | CreateTableIfNotExists(); 594 | 595 | Result := True; 596 | end; 597 | 598 | procedure TsoVectorDatabase.Close(); 599 | begin 600 | FDatabase.Close(); 601 | end; 602 | 603 | procedure TsoVectorDatabase.CreateTableIfNotExists(); 604 | begin 605 | FDatabase.ClearSQLText(); 606 | FDatabase.SetMacro('table', 'vectors'); 607 | FDatabase.AddSQLText('CREATE TABLE IF NOT EXISTS &table (' + 608 | 'id TEXT PRIMARY KEY, ' + 609 | 'text TEXT, ' + 610 | 'embedding BLOB)'); 611 | FDatabase.Execute(); 612 | end; 613 | 614 | function TsoVectorDatabase.CosineSimilarity(const AVec1, AVec2: TArray): Double; 615 | var 616 | LDotProduct, LNorm1, LNorm2: Double; 617 | I: Integer; 618 | begin 619 | LDotProduct := 0; 620 | LNorm1 := 0; 621 | LNorm2 := 0; 622 | 623 | for I := Low(AVec1) to High(AVec1) do 624 | begin 625 | LDotProduct := LDotProduct + (AVec1[I] * AVec2[I]); 626 | LNorm1 := LNorm1 + (AVec1[I] * AVec1[I]); 627 | LNorm2 := LNorm2 + (AVec2[I] * AVec2[I]); 628 | end; 629 | 630 | LNorm1 := Sqrt(LNorm1); 631 | LNorm2 := Sqrt(LNorm2); 632 | 633 | // ✅ Prevent divide by zero 634 | if (LNorm1 < 1e-6) or (LNorm2 < 1e-6) then 635 | Exit(0.0); 636 | 637 | Result := LDotProduct / (LNorm1 * LNorm2); 638 | 639 | // ✅ Ensure result stays between -1 and 1 640 | if Result > 1 then Result := 1; 641 | if Result < -1 then Result := -1; 642 | end; 643 | 644 | function TsoVectorDatabase.AddDocument(const ADocID: string; const AText: string; const AMaxContext: Cardinal; const AMaxThreads: Integer): Boolean; 645 | var 646 | LEmbedding: TArray; 647 | LEmbeddingBlob: TBytes; 648 | LEncodedEmbedding: string; 649 | LSize: Integer; 650 | begin 651 | Result := False; 652 | if ADocID.IsEmpty then Exit; 653 | if AText.IsEmpty then Exit; 654 | 655 | LEmbedding := FEmbeddings.Generate(AText, AMaxContext, AMaxThreads); 656 | LSize := Length(LEmbedding) * SizeOf(Single); 657 | SetLength(LEmbeddingBlob, LSize); 658 | 659 | // Optimized memory move 660 | Move(LEmbedding[0], LEmbeddingBlob[0], LSize); 661 | 662 | // Encode BLOB as Base64 string 663 | LEncodedEmbedding := TNetEncoding.Base64.EncodeBytesToString(LEmbeddingBlob); 664 | 665 | FDatabase.ClearSQLText; 666 | FDatabase.SetMacro('table', 'vectors'); 667 | FDatabase.AddSQLText('INSERT OR REPLACE INTO &table (id, text, embedding) VALUES (:id, :text, :embedding)'); 668 | FDatabase.SetParam('id', ADocID); 669 | FDatabase.SetParam('text', AText); 670 | FDatabase.SetParam('embedding', LEncodedEmbedding); // Base64-encoded BLOB 671 | 672 | Result := FDatabase.Execute; 673 | end; 674 | 675 | function TsoVectorDatabase.AddLargeDocument(const ADocID, ATitle, AText: string; const AChunkSize: Integer; const AMaxContext: Cardinal; const AMaxThreads: Integer): Boolean; 676 | var 677 | LWords, LParagraphs: TArray; 678 | LChunkText, LCurrentChunk: string; 679 | LStartIdx, LChunkCounter, LWordCount, I: Integer; 680 | begin 681 | Result := False; 682 | 683 | // Validate input 684 | if AText.Trim.IsEmpty then 685 | begin 686 | SetError('Cannot add an empty document', []); 687 | Exit; 688 | end; 689 | 690 | if AChunkSize <= 0 then 691 | begin 692 | SetError('Chunk size must be greater than zero', []); 693 | Exit; 694 | end; 695 | 696 | // Try to split by paragraphs first to maintain context 697 | LParagraphs := AText.Split([#13#10+#13#10, #10+#10], TStringSplitOptions.None); 698 | 699 | // If we have multiple paragraphs, process paragraph by paragraph 700 | if Length(LParagraphs) > 1 then 701 | begin 702 | LChunkCounter := 1; 703 | LCurrentChunk := ''; 704 | LWordCount := 0; 705 | 706 | for I := 0 to High(LParagraphs) do 707 | begin 708 | if LParagraphs[I].Trim = '' then Continue; // Skip empty paragraphs 709 | 710 | // Count words in current paragraph 711 | LWords := LParagraphs[I].Split([' ']); 712 | 713 | // If adding this paragraph would exceed chunk size, store current chunk 714 | if (LWordCount > 0) and (LWordCount + Length(LWords) > AChunkSize) then 715 | begin 716 | // Add chunk with context from title 717 | LChunkText := Format('[%s - Part %d] %s', [ATitle, LChunkCounter, LCurrentChunk.Trim]); 718 | 719 | // Store chunk 720 | if not AddDocument(Format('%s_chunk%d', [ADocID, LChunkCounter]), 721 | LChunkText, AMaxContext, AMaxThreads) then 722 | begin 723 | SetError('Failed to add chunk %d', [LChunkCounter]); 724 | Exit; 725 | end; 726 | 727 | // Reset for next chunk 728 | Inc(LChunkCounter); 729 | LCurrentChunk := ''; 730 | LWordCount := 0; 731 | end; 732 | 733 | // Add paragraph to current chunk 734 | if LCurrentChunk <> '' then 735 | LCurrentChunk := LCurrentChunk + #13#10#13#10; 736 | LCurrentChunk := LCurrentChunk + LParagraphs[I]; 737 | Inc(LWordCount, Length(LWords)); 738 | end; 739 | 740 | // Add final chunk if not empty 741 | if LCurrentChunk <> '' then 742 | begin 743 | LChunkText := Format('[%s - Part %d] %s', [ATitle, LChunkCounter, LCurrentChunk.Trim]); 744 | if not AddDocument(Format('%s_chunk%d', [ADocID, LChunkCounter]), 745 | LChunkText, AMaxContext, AMaxThreads) then 746 | begin 747 | SetError('Failed to add final chunk', []); 748 | Exit; 749 | end; 750 | end; 751 | end 752 | else 753 | begin 754 | // Fall back to original word-based chunking for single paragraphs 755 | LWords := AText.Split([' ']); 756 | LChunkCounter := 1; 757 | LCurrentChunk := ''; 758 | LWordCount := 0; 759 | 760 | for LStartIdx := 0 to High(LWords) do 761 | begin 762 | // Add next word to chunk 763 | if LCurrentChunk <> '' then 764 | LCurrentChunk := LCurrentChunk + ' '; 765 | 766 | LCurrentChunk := LCurrentChunk + LWords[LStartIdx]; 767 | Inc(LWordCount); 768 | 769 | // If chunk size is reached or end of document, store chunk 770 | if (LWordCount >= AChunkSize) or (LStartIdx = High(LWords)) then 771 | begin 772 | // Add title to each chunk for better context 773 | LChunkText := Format('[%s - Part %d] %s', [ATitle, LChunkCounter, LCurrentChunk]); 774 | 775 | // Store chunk separately 776 | if not AddDocument(Format('%s_chunk%d', [ADocID, LChunkCounter]), 777 | LChunkText, AMaxContext, AMaxThreads) then 778 | begin 779 | SetError('Failed to add chunk: %s', [LChunkText]); 780 | Exit; 781 | end; 782 | 783 | // Reset for next chunk 784 | Inc(LChunkCounter); 785 | LCurrentChunk := ''; 786 | LWordCount := 0; 787 | end; 788 | end; 789 | end; 790 | 791 | Result := True; 792 | end; 793 | 794 | function TsoVectorDatabase.Search(const AQuery: string; const ATopK: Integer; const AQueryLimit: Integer; const ASimilarityThreshold: Single; const AMaxContext: Cardinal; const AMaxThreads: Integer): TJSONArray; 795 | var 796 | LQueryEmbedding: TArray; 797 | LDocID, LTextData, LEncodedEmbedding: string; 798 | LDBEmbedding: TArray; 799 | LEmbeddingBlob: TBytes; 800 | LSimilarityScores: TDictionary; 801 | LScore, LExistingScore: Single; 802 | I, LVectorSize: Integer; 803 | LJSONObject: TJSONObject; 804 | LSortedScores: TList>; 805 | LPair: TPair; 806 | LTextMap: TDictionary; 807 | LStartTime: TDateTime; 808 | begin 809 | LStartTime := Now; 810 | Result := TJSONArray.Create; // Always return an empty array if no results 811 | 812 | // Exit if model is not loaded or empty query 813 | if (not FEmbeddings.ModelLoaded()) then 814 | begin 815 | SetError('Model not loaded for search', []); 816 | Exit; 817 | end; 818 | 819 | if (Trim(AQuery) = '') then 820 | begin 821 | SetError('Empty search query', []); 822 | Exit; 823 | end; 824 | 825 | // Generate embedding for query 826 | LQueryEmbedding := FEmbeddings.Generate(AQuery, AMaxContext, AMaxThreads); 827 | if Length(LQueryEmbedding) = 0 then 828 | begin 829 | SetError('Failed to generate embedding for query', []); 830 | Exit; 831 | end; 832 | 833 | LSimilarityScores := TDictionary.Create; 834 | LTextMap := TDictionary.Create; 835 | try 836 | LSortedScores := TList>.Create; 837 | try 838 | FDatabase.ClearSQLText; 839 | FDatabase.SetMacro('table', 'vectors'); 840 | 841 | // Improved SQL - add ORDER BY id for consistent results 842 | FDatabase.AddSQLText('SELECT id, text, embedding FROM &table ORDER BY id ASC LIMIT :limit'); 843 | FDatabase.SetParam('limit', AQueryLimit.ToString); 844 | 845 | if not FDatabase.Execute then 846 | begin 847 | SetError('Database query failed', []); 848 | Exit; 849 | end; 850 | 851 | // Preallocate for better performance if RecordCount is large 852 | if FDatabase.RecordCount > 100 then 853 | begin 854 | LSimilarityScores.Capacity := FDatabase.RecordCount; 855 | LTextMap.Capacity := FDatabase.RecordCount; 856 | end; 857 | 858 | for I := 0 to FDatabase.RecordCount - 1 do 859 | begin 860 | LDocID := FDatabase.GetField(I, 'id'); 861 | if LDocID = '' then Continue; // Skip records with empty IDs 862 | 863 | LTextData := FDatabase.GetField(I, 'text'); 864 | 865 | // Retrieve and decode Base64-encoded embedding 866 | LEncodedEmbedding := FDatabase.GetField(I, 'embedding'); 867 | if LEncodedEmbedding = '' then Continue; // Skip records with empty embeddings 868 | 869 | try 870 | LEmbeddingBlob := TNetEncoding.Base64.DecodeStringToBytes(LEncodedEmbedding); 871 | 872 | // Convert byte array back to float array 873 | LVectorSize := Length(LEmbeddingBlob) div SizeOf(Single); 874 | if LVectorSize = 0 then Continue; // Skip invalid embeddings 875 | 876 | SetLength(LDBEmbedding, LVectorSize); 877 | Move(LEmbeddingBlob[0], LDBEmbedding[0], Length(LEmbeddingBlob)); 878 | 879 | // Compute similarity only if vectors have same dimensions 880 | if Length(LDBEmbedding) = Length(LQueryEmbedding) then 881 | begin 882 | LScore := CosineSimilarity(LQueryEmbedding, LDBEmbedding); 883 | 884 | // Only process scores above threshold 885 | if LScore > ASimilarityThreshold then 886 | begin 887 | // Store highest score per chunk 888 | if LSimilarityScores.TryGetValue(LDocID, LExistingScore) then 889 | LSimilarityScores[LDocID] := Max(LExistingScore, LScore) 890 | else 891 | LSimilarityScores.Add(LDocID, LScore); 892 | 893 | // Store text for retrieval 894 | LTextMap.AddOrSetValue(LDocID, LTextData); 895 | end; 896 | end; 897 | except 898 | on E: Exception do 899 | begin 900 | // Log error but continue to next record 901 | // soConsole.PrintLn('Error processing embedding for %s: %s', [LDocID, E.Message]); 902 | Continue; 903 | end; 904 | end; 905 | end; 906 | 907 | // Early exit if no results 908 | if LSimilarityScores.Count = 0 then Exit; 909 | 910 | // Optimization: Only sort as many items as needed 911 | LSortedScores.Capacity := LSimilarityScores.Count; 912 | for LPair in LSimilarityScores do 913 | LSortedScores.Add(LPair); 914 | 915 | LSortedScores.Sort( 916 | TComparer>.Construct( 917 | function(const Left, Right: TPair): Integer 918 | begin 919 | Result := CompareValue(Right.Value, Left.Value); // Higher scores first 920 | end 921 | ) 922 | ); 923 | 924 | // Return top chunks separately (not merged) 925 | for I := 0 to Min(ATopK - 1, LSortedScores.Count - 1) do 926 | begin 927 | LJSONObject := TJSONObject.Create; 928 | try 929 | LJSONObject.AddPair('id', LSortedScores[I].Key); 930 | LJSONObject.AddPair('score', TJSONNumber.Create(LSortedScores[I].Value)); 931 | if LTextMap.ContainsKey(LSortedScores[I].Key) then 932 | LJSONObject.AddPair('text', LTextMap[LSortedScores[I].Key]); 933 | Result.AddElement(LJSONObject); 934 | except 935 | LJSONObject.Free; 936 | Continue; // Continue to next result on error 937 | end; 938 | end; 939 | 940 | // soConsole.PrintLn('Search completed in %d ms with %d results', [MilliSecondsBetween(Now, LStartTime), Result.Count]); 941 | finally 942 | LSortedScores.Free; 943 | end; 944 | finally 945 | LSimilarityScores.Free; 946 | LTextMap.Free; 947 | end; 948 | end; 949 | 950 | function TsoVectorDatabase.Search2(const AQuery: string; const ATopK: Integer; const AQueryLimit: Integer; const ASimilarityThreshold: Single; const AMaxContext: Cardinal; const AMaxThreads: Integer): TArray; 951 | 952 | var 953 | LJson: TJSONArray; 954 | 955 | function ParseJSONToDocuments(const JSONArray: TJSONArray): TArray; 956 | var 957 | DocumentList: TList; 958 | JSONObject: TJSONObject; 959 | Doc: TsoVectorDatabaseDocument; 960 | i: Integer; 961 | begin 962 | Result := nil; 963 | if not Assigned(JSONArray) then 964 | Exit; 965 | 966 | DocumentList := TList.Create; 967 | try 968 | for i := 0 to JSONArray.Count - 1 do 969 | begin 970 | JSONObject := JSONArray.Items[i] as TJSONObject; 971 | if Assigned(JSONObject) then 972 | begin 973 | Doc.ID := JSONObject.GetValue('id'); 974 | Doc.Score := JSONObject.GetValue('score'); // JSON numbers are treated as Double 975 | Doc.Text := JSONObject.GetValue('text'); 976 | DocumentList.Add(Doc); 977 | end; 978 | end; 979 | Result := DocumentList.ToArray; 980 | finally 981 | DocumentList.Free; 982 | end; 983 | end; 984 | 985 | begin 986 | Result := nil; 987 | 988 | LJson := Search(AQuery, ATopK, AQueryLimit, ASimilarityThreshold, AMaxContext, AMaxThreads); 989 | try 990 | if not Assigned(LJson) then Exit; 991 | 992 | Result := ParseJSONToDocuments(LJson); 993 | finally 994 | LJson.Free(); 995 | end; 996 | 997 | 998 | end; 999 | 1000 | end. 1001 | -------------------------------------------------------------------------------- /src/Sophora.Tools.pas: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinyBigGAMES/Sophora/6af6e97bcc71cdef42571553f0614b1c02676119/src/Sophora.Tools.pas -------------------------------------------------------------------------------- /src/Sophora.Utils.pas: -------------------------------------------------------------------------------- 1 | {=============================================================================== 2 | ___ _ 3 | / __| ___ _ __| |_ ___ _ _ __ _ ™ 4 | \__ \/ _ \ '_ \ ' \/ _ \ '_/ _` | 5 | |___/\___/ .__/_||_\___/_| \__,_| 6 | |_| 7 | AI Reasoning, Function-calling & 8 | Knowledge Retrieval 9 | 10 | Copyright © 2025-present tinyBigGAMES™ LLC 11 | All Rights Reserved. 12 | 13 | https://github.com/tinyBigGAMES/Sophora 14 | 15 | See LICENSE file for license information 16 | ===============================================================================} 17 | 18 | unit Sophora.Utils; 19 | 20 | {$I Sophora.Defines.inc} 21 | 22 | interface 23 | 24 | uses 25 | WinApi.Windows, 26 | System.SysUtils, 27 | System.IOUtils, 28 | System.DateUtils, 29 | System.StrUtils, 30 | System.Classes, 31 | System.Math, 32 | System.JSON, 33 | System.TypInfo, 34 | System.Rtti, 35 | System.Net.HttpClient, 36 | System.Net.URLClient, 37 | System.NetConsts; 38 | 39 | type 40 | 41 | PsoGenericPointer = ^Pointer; 42 | PPsoGenericPointer = ^PsoGenericPointer; 43 | 44 | TsoPointerArray1D = record 45 | class function GetValue(P: Pointer; Index: Integer): T; static; 46 | class procedure SetValue(P: Pointer; Index: Integer; const Value: T); static; 47 | end; 48 | 49 | // Generic Helper for Pointer-Based 2D Access 50 | TsoPointerArray2D = record 51 | class function GetValue(P: Pointer; Row, Col: Integer): T; static; 52 | class procedure SetValue(P: Pointer; Row, Col: Integer; const Value: T); static; 53 | end; 54 | 55 | 56 | // Custom Attribute to store descriptions for functions and parameters 57 | soSchemaDescription = class(TCustomAttribute) 58 | private 59 | FDescription: string; 60 | public 61 | constructor Create(const ADescription: string); 62 | property Description: string read FDescription; 63 | end; 64 | 65 | { soUtils } 66 | soUtils = class 67 | private 68 | class constructor Create(); 69 | class destructor Destroy(); 70 | public 71 | class procedure UnitInit(); 72 | class function AsUTF8(const AText: string): Pointer; static; 73 | class function GetPhysicalProcessorCount(): DWORD; static; 74 | class function EnableVirtualTerminalProcessing(): DWORD; static; 75 | class function GetEnvVarValue(const AVarName: string): string; static; 76 | class function IsStartedFromDelphiIDE(): Boolean; static; 77 | class procedure ProcessMessages(); static; 78 | class function RandomRange(const AMin, AMax: Integer): Integer; static; 79 | class function RandomRangef(const AMin, AMax: Single): Single; static; 80 | class function RandomBool(): Boolean; static; 81 | class function GetRandomSeed(): Integer; static; 82 | class procedure SetRandomSeed(const AVaLue: Integer); static; 83 | class procedure Wait(const AMilliseconds: Double); static; 84 | class function SanitizeFromJson(const aText: string): string; static; 85 | class function SanitizeToJson(const aText: string): string; static; 86 | class function GetJsonSchema(const AClass: TClass; const AMethodName: string): string; static; 87 | class function GetJsonSchemas(AClass: TClass): string; static; 88 | class function CallStaticMethod(const AClass: TClass; const AMethodName: string; const AArgs: array of TValue): TValue; 89 | class function GetISO8601DateTime(): string; 90 | class function GetISO8601DateTimeLocal(): string; 91 | class function GetLocalDateTime(): string; 92 | class function HasEnoughDiskSpace(const AFilePath: string; ARequiredSize: Int64): Boolean; 93 | class function GetRandomThinkingResult(): string; 94 | class function TavilyWebSearch(const AAPIKey, AQuery: string): string; static; 95 | end; 96 | 97 | type 98 | { TsoTokenPrintAction } 99 | TsoTokenPrintAction = (tpaWait, tpaAppend, tpaNewline); 100 | 101 | { TsoTokenResponse } 102 | TsoTokenResponse = record 103 | private 104 | FRaw: string; // Full response as is 105 | FTokens: array of string; // Actual tokens 106 | FMaxLineLength: Integer; // Define confined space, in chars for fixed width font 107 | FWordBreaks: array of char; // What is considered a logical word-break 108 | FLineBreaks: array of char; // What is considered a logical line-break 109 | FWords: array of String; // Response but as array of "words" 110 | FWord: string; // Current word accumulating 111 | FLine: string; // Current line accumulating 112 | FFinalized: Boolean; // Know the finalization is done 113 | FRightMargin: Integer; 114 | function HandleLineBreaks(const AToken: string): Boolean; 115 | function SplitWord(const AWord: string; var APrefix, ASuffix: string): Boolean; 116 | function GetLineLengthMax(): Integer; 117 | public 118 | procedure Initialize; 119 | property RightMargin: Integer read FRightMargin; 120 | property MaxLineLength: Integer read FMaxLineLength; 121 | function GetRightMargin(): Integer; 122 | procedure SetRightMargin(const AMargin: Integer); 123 | function GetMaxLineLength(): Integer; 124 | procedure SetMaxLineLength(const ALength: Integer); 125 | function AddToken(const aToken: string): TsoTokenPrintAction; 126 | function LastWord(const ATrimLeft: Boolean=False): string; 127 | function Finalize: Boolean; 128 | procedure Clear(); 129 | end; 130 | 131 | implementation 132 | 133 | uses 134 | Sophora.Console; 135 | 136 | var 137 | LMarshaller: TMarshaller; 138 | 139 | class function TsoPointerArray1D.GetValue(P: Pointer; Index: Integer): T; 140 | var 141 | Ptr: PByte; 142 | begin 143 | Ptr := PByte(P); 144 | Inc(Ptr, Index * SizeOf(T)); 145 | Move(Ptr^, Result, SizeOf(T)); 146 | end; 147 | 148 | class procedure TsoPointerArray1D.SetValue(P: Pointer; Index: Integer; const Value: T); 149 | var 150 | Ptr: PByte; 151 | begin 152 | Ptr := PByte(P); 153 | Inc(Ptr, Index * SizeOf(T)); 154 | Move(Value, Ptr^, SizeOf(T)); 155 | end; 156 | 157 | class function TsoPointerArray2D.GetValue(P: Pointer; Row, Col: Integer): T; 158 | var 159 | PP: PPointer; 160 | Ptr: PByte; 161 | begin 162 | PP := PPointer(P); 163 | Inc(PP, Row); 164 | Ptr := PByte(PP^); 165 | Inc(Ptr, Col * SizeOf(T)); 166 | Move(Ptr^, Result, SizeOf(T)); 167 | end; 168 | 169 | class procedure TsoPointerArray2D.SetValue(P: Pointer; Row, Col: Integer; const Value: T); 170 | var 171 | PP: PPointer; 172 | Ptr: PByte; 173 | begin 174 | PP := PPointer(P); 175 | Inc(PP, Row); 176 | Ptr := PByte(PP^); 177 | Inc(Ptr, Col * SizeOf(T)); 178 | Move(Value, Ptr^, SizeOf(T)); 179 | end; 180 | 181 | { TsoSchemaDescription } 182 | constructor soSchemaDescription.Create(const ADescription: string); 183 | begin 184 | FDescription := ADescription; 185 | end; 186 | 187 | { soUtils } 188 | class constructor soUtils.Create(); 189 | begin 190 | Randomize(); 191 | end; 192 | 193 | class destructor soUtils.Destroy(); 194 | begin 195 | end; 196 | 197 | class procedure soUtils.UnitInit(); 198 | begin 199 | // force constructor 200 | end; 201 | 202 | class function soUtils.AsUTF8(const AText: string): Pointer; 203 | begin 204 | Result := LMarshaller.AsUtf8(AText).ToPointer; 205 | end; 206 | 207 | class function soUtils.GetPhysicalProcessorCount(): DWORD; 208 | var 209 | BufferSize: DWORD; 210 | Buffer: PSYSTEM_LOGICAL_PROCESSOR_INFORMATION; 211 | ProcessorInfo: PSYSTEM_LOGICAL_PROCESSOR_INFORMATION; 212 | Offset: DWORD; 213 | begin 214 | Result := 0; 215 | BufferSize := 0; 216 | 217 | // Call GetLogicalProcessorInformation with buffer size set to 0 to get required buffer size 218 | if not GetLogicalProcessorInformation(nil, BufferSize) and (WinApi.Windows.GetLastError() = ERROR_INSUFFICIENT_BUFFER) then 219 | begin 220 | // Allocate buffer 221 | GetMem(Buffer, BufferSize); 222 | try 223 | // Call GetLogicalProcessorInformation again with allocated buffer 224 | if GetLogicalProcessorInformation(Buffer, BufferSize) then 225 | begin 226 | ProcessorInfo := Buffer; 227 | Offset := 0; 228 | 229 | // Loop through processor information to count physical processors 230 | while Offset + SizeOf(SYSTEM_LOGICAL_PROCESSOR_INFORMATION) <= BufferSize do 231 | begin 232 | if ProcessorInfo.Relationship = RelationProcessorCore then 233 | Inc(Result); 234 | 235 | Inc(ProcessorInfo); 236 | Inc(Offset, SizeOf(SYSTEM_LOGICAL_PROCESSOR_INFORMATION)); 237 | end; 238 | end; 239 | finally 240 | FreeMem(Buffer); 241 | end; 242 | end; 243 | end; 244 | 245 | class function soUtils.EnableVirtualTerminalProcessing(): DWORD; 246 | var 247 | HOut: THandle; 248 | LMode: DWORD; 249 | begin 250 | HOut := GetStdHandle(STD_OUTPUT_HANDLE); 251 | if HOut = INVALID_HANDLE_VALUE then 252 | begin 253 | Result := GetLastError; 254 | Exit; 255 | end; 256 | 257 | if not GetConsoleMode(HOut, LMode) then 258 | begin 259 | Result := GetLastError; 260 | Exit; 261 | end; 262 | 263 | LMode := LMode or ENABLE_VIRTUAL_TERMINAL_PROCESSING; 264 | if not SetConsoleMode(HOut, LMode) then 265 | begin 266 | Result := GetLastError; 267 | Exit; 268 | end; 269 | 270 | Result := 0; // Success 271 | end; 272 | 273 | class function soUtils.GetEnvVarValue(const AVarName: string): string; 274 | var 275 | LBufSize: Integer; 276 | begin 277 | LBufSize := GetEnvironmentVariable(PChar(AVarName), nil, 0); 278 | if LBufSize > 0 then 279 | begin 280 | SetLength(Result, LBufSize - 1); 281 | GetEnvironmentVariable(PChar(AVarName), PChar(Result), LBufSize); 282 | end 283 | else 284 | Result := ''; 285 | end; 286 | 287 | class function soUtils.IsStartedFromDelphiIDE(): Boolean; 288 | begin 289 | // Check if the IDE environment variable is present 290 | Result := (GetEnvironmentVariable('BDS') <> ''); 291 | end; 292 | 293 | class procedure soUtils.ProcessMessages(); 294 | var 295 | LMsg: TMsg; 296 | begin 297 | while Integer(PeekMessage(LMsg, 0, 0, 0, PM_REMOVE)) <> 0 do 298 | begin 299 | TranslateMessage(LMsg); 300 | DispatchMessage(LMsg); 301 | end; 302 | end; 303 | 304 | function _RandomRange(const aFrom, aTo: Integer): Integer; 305 | var 306 | LFrom: Integer; 307 | LTo: Integer; 308 | begin 309 | LFrom := aFrom; 310 | LTo := aTo; 311 | 312 | if AFrom > ATo then 313 | Result := Random(LFrom - LTo) + ATo 314 | else 315 | Result := Random(LTo - LFrom) + AFrom; 316 | end; 317 | 318 | class function soUtils.RandomRange(const AMin, AMax: Integer): Integer; 319 | begin 320 | Result := _RandomRange(AMin, AMax + 1); 321 | end; 322 | 323 | class function soUtils.RandomRangef(const AMin, AMax: Single): Single; 324 | var 325 | LNum: Single; 326 | begin 327 | LNum := _RandomRange(0, MaxInt) / MaxInt; 328 | Result := AMin + (LNum * (AMax - AMin)); 329 | end; 330 | 331 | class function soUtils.RandomBool(): Boolean; 332 | begin 333 | Result := Boolean(_RandomRange(0, 2) = 1); 334 | end; 335 | 336 | class function soUtils.GetRandomSeed(): Integer; 337 | begin 338 | Result := System.RandSeed; 339 | end; 340 | 341 | class procedure soUtils.SetRandomSeed(const AVaLue: Integer); 342 | begin 343 | System.RandSeed := AVaLue; 344 | end; 345 | 346 | class procedure soUtils.Wait(const AMilliseconds: Double); 347 | var 348 | LFrequency, LStartCount, LCurrentCount: Int64; 349 | LElapsedTime: Double; 350 | begin 351 | // Get the high-precision frequency of the system's performance counter 352 | QueryPerformanceFrequency(LFrequency); 353 | 354 | // Get the starting value of the performance counter 355 | QueryPerformanceCounter(LStartCount); 356 | 357 | // Convert milliseconds to seconds for precision timing 358 | repeat 359 | QueryPerformanceCounter(LCurrentCount); 360 | LElapsedTime := (LCurrentCount - LStartCount) / LFrequency * 1000.0; // Convert to milliseconds 361 | until LElapsedTime >= AMilliseconds; 362 | end; 363 | 364 | class function soUtils.SanitizeToJson(const aText: string): string; 365 | var 366 | i: Integer; 367 | begin 368 | Result := ''; 369 | for i := 1 to Length(aText) do 370 | begin 371 | case aText[i] of 372 | '\': Result := Result + '\\'; 373 | '"': Result := Result + '\"'; 374 | '/': Result := Result + '\/'; 375 | #8: Result := Result + '\b'; 376 | #9: Result := Result + '\t'; 377 | #10: Result := Result + '\n'; 378 | #12: Result := Result + '\f'; 379 | #13: Result := Result + '\r'; 380 | else 381 | Result := Result + aText[i]; 382 | end; 383 | end; 384 | Result := Result; 385 | end; 386 | 387 | class function soUtils.SanitizeFromJson(const aText: string): string; 388 | var 389 | LText: string; 390 | begin 391 | LText := aText; 392 | LText := LText.Replace('\n', #10); 393 | LText := LText.Replace('\r', #13); 394 | LText := LText.Replace('\b', #8); 395 | LText := LText.Replace('\t', #9); 396 | LText := LText.Replace('\f', #12); 397 | LText := LText.Replace('\/', '/'); 398 | LText := LText.Replace('\"', '"'); 399 | LText := LText.Replace('\\', '\'); 400 | Result := LText; 401 | end; 402 | 403 | // Helper function to map Delphi types to JSON Schema types 404 | function GetJsonType(DelphiType: TRttiType): string; 405 | begin 406 | if not Assigned(DelphiType) then 407 | Exit('null'); 408 | 409 | case DelphiType.TypeKind of 410 | tkInteger, tkInt64: Result := 'integer'; 411 | tkFloat: Result := 'number'; 412 | tkChar, tkWChar, tkString, tkLString, tkWString, tkUString: Result := 'string'; 413 | tkEnumeration: 414 | begin 415 | if SameText(DelphiType.Name, 'Boolean') then 416 | Result := 'boolean' 417 | else 418 | Result := 'string'; 419 | end; 420 | tkClass, tkRecord: Result := 'object'; 421 | tkSet, tkDynArray, tkArray: Result := 'array'; 422 | else 423 | Result := 'unknown'; 424 | end; 425 | end; 426 | 427 | class function soUtils.GetJsonSchema(const AClass: TClass; const AMethodName: string): string; 428 | var 429 | JsonRoot, JsonFunction, JsonParams, JsonProperties, JsonParamObj: TJSONObject; 430 | RequiredArray: TJSONArray; 431 | Context: TRttiContext; 432 | RttiType: TRttiType; 433 | Method: TRttiMethod; 434 | Param: TRttiParameter; 435 | Attr: TCustomAttribute; 436 | ParamDescription: string; 437 | begin 438 | Result := ''; 439 | Context := TRttiContext.Create; 440 | try 441 | RttiType := Context.GetType(AClass.ClassInfo); 442 | if not Assigned(RttiType) then Exit; 443 | 444 | Method := RttiType.GetMethod(AMethodName); 445 | if not Assigned(Method) then Exit; 446 | 447 | // Ensure the method is a STATIC method 448 | if not (Method.MethodKind in [mkClassFunction, mkClassProcedure]) then 449 | Exit; // Return empty result if it's not a static method 450 | 451 | // Root JSON Object 452 | JsonRoot := TJSONObject.Create; 453 | JsonRoot.AddPair('type', 'function'); 454 | 455 | // Function JSON Object 456 | JsonFunction := TJSONObject.Create; 457 | JsonFunction.AddPair('name', AMethodName); 458 | 459 | // Extract method description (if available) 460 | for Attr in Method.GetAttributes do 461 | if Attr is soSchemaDescription then 462 | JsonFunction.AddPair('description', soSchemaDescription(Attr).Description); 463 | 464 | // Parameter Section 465 | JsonParams := TJSONObject.Create; 466 | JsonParams.AddPair('type', 'object'); 467 | 468 | JsonProperties := TJSONObject.Create; 469 | RequiredArray := TJSONArray.Create; 470 | 471 | for Param in Method.GetParameters do 472 | begin 473 | JsonParamObj := TJSONObject.Create; 474 | JsonParamObj.AddPair('type', GetJsonType(Param.ParamType)); 475 | 476 | // Extract parameter description (if available) 477 | ParamDescription := ''; 478 | for Attr in Param.GetAttributes do 479 | if Attr is soSchemaDescription then 480 | ParamDescription := soSchemaDescription(Attr).Description; 481 | 482 | if ParamDescription <> '' then 483 | JsonParamObj.AddPair('description', ParamDescription); 484 | 485 | JsonProperties.AddPair(Param.Name, JsonParamObj); 486 | RequiredArray.AddElement(TJSONString.Create(Param.Name)); 487 | end; 488 | 489 | JsonParams.AddPair('properties', JsonProperties); 490 | JsonParams.AddPair('required', RequiredArray); 491 | JsonFunction.AddPair('parameters', JsonParams); 492 | 493 | // Return Type 494 | if Assigned(Method.ReturnType) then 495 | JsonFunction.AddPair('return_type', GetJsonType(Method.ReturnType)) 496 | else 497 | JsonFunction.AddPair('return_type', 'void'); 498 | 499 | JsonRoot.AddPair('function', JsonFunction); 500 | 501 | Result := JsonRoot.Format(); 502 | JsonRoot.Free(); 503 | 504 | finally 505 | Context.Free; 506 | end; 507 | end; 508 | 509 | class function soUtils.CallStaticMethod(const AClass: TClass; const AMethodName: string; const AArgs: array of TValue): TValue; 510 | var 511 | Context: TRttiContext; 512 | RttiType: TRttiType; 513 | Method: TRttiMethod; 514 | begin 515 | Context := TRttiContext.Create; 516 | try 517 | RttiType := Context.GetType(AClass.ClassInfo); 518 | if not Assigned(RttiType) then 519 | raise Exception.Create('Class RTTI not found.'); 520 | 521 | Method := RttiType.GetMethod(AMethodName); 522 | if not Assigned(Method) then 523 | raise Exception.CreateFmt('Method "%s" not found.', [AMethodName]); 524 | 525 | // Ensure the method is a class method (STATIC method) 526 | if not (Method.MethodKind in [mkClassFunction, mkClassProcedure]) then 527 | raise Exception.CreateFmt('Method "%s" is not a static class method.', [AMethodName]); 528 | 529 | // Invoke the method dynamically 530 | Result := Method.Invoke(nil, AArgs); 531 | finally 532 | Context.Free; 533 | end; 534 | end; 535 | 536 | class function soUtils.GetJsonSchemas(AClass: TClass): string; 537 | var 538 | JsonRoot, JsonTool: TJSONObject; 539 | JsonToolsArray: TJSONArray; 540 | Context: TRttiContext; 541 | RttiType: TRttiType; 542 | Method: TRttiMethod; 543 | begin 544 | Result := ''; 545 | JsonRoot := TJSONObject.Create; 546 | JsonToolsArray := TJSONArray.Create; 547 | Context := TRttiContext.Create; 548 | try 549 | RttiType := Context.GetType(AClass.ClassInfo); 550 | if not Assigned(RttiType) then Exit; 551 | 552 | // Loop through all published methods 553 | for Method in RttiType.GetMethods do 554 | begin 555 | // Ensure the method is published and static 556 | if (Method.Visibility = mvPublished) and 557 | (Method.MethodKind in [mkClassFunction, mkClassProcedure]) then 558 | begin 559 | // Get the JSON schema for the method 560 | JsonTool := TJSONObject.ParseJSONValue(GetJsonSchema(AClass, Method.Name)) as TJSONObject; 561 | if Assigned(JsonTool) then 562 | JsonToolsArray.AddElement(JsonTool); 563 | end; 564 | end; 565 | 566 | // Add tools array to the root JSON object 567 | JsonRoot.AddPair('tools', JsonToolsArray); 568 | Result := JsonRoot.Format(); 569 | JsonRoot.Free(); 570 | finally 571 | Context.Free; 572 | end; 573 | end; 574 | 575 | class function soUtils.GetISO8601DateTime(): string; 576 | begin 577 | Result := FormatDateTime('yyyy-mm-dd"T"hh:nn:ss"Z"', Now); 578 | end; 579 | 580 | class function soUtils.GetISO8601DateTimeLocal(): string; 581 | var 582 | TZI: TTimeZoneInformation; 583 | Bias, HoursOffset, MinsOffset: Integer; 584 | TimeZoneStr: string; 585 | begin 586 | case GetTimeZoneInformation(TZI) of 587 | TIME_ZONE_ID_STANDARD, TIME_ZONE_ID_DAYLIGHT: 588 | Bias := TZI.Bias + TZI.DaylightBias; // Adjust for daylight saving time 589 | else 590 | Bias := 0; // Default to UTC if timezone is unknown 591 | end; 592 | 593 | HoursOffset := Abs(Bias) div 60; 594 | MinsOffset := Abs(Bias) mod 60; 595 | 596 | if Bias = 0 then 597 | TimeZoneStr := 'Z' 598 | else 599 | TimeZoneStr := Format('%s%.2d:%.2d', [IfThen(Bias > 0, '-', '+'), HoursOffset, MinsOffset]); 600 | 601 | Result := FormatDateTime('yyyy-mm-dd"T"hh:nn:ss', Now) + TimeZoneStr; 602 | end; 603 | 604 | class function soUtils.GetLocalDateTime(): string; 605 | begin 606 | Result := FormatDateTime('dddd, dd mmmm yyyy hh:nn:ss AM/PM', Now); 607 | end; 608 | 609 | class function soUtils.HasEnoughDiskSpace(const AFilePath: string; ARequiredSize: Int64): Boolean; 610 | var 611 | LFreeAvailable, LTotalSpace, LTotalFree: Int64; 612 | LDrive: string; 613 | begin 614 | Result := False; 615 | 616 | // Resolve the absolute path in case of a relative path 617 | LDrive := ExtractFileDrive(TPath.GetFullPath(AFilePath)); 618 | 619 | // If there is no drive letter, use the current drive 620 | if LDrive = '' then 621 | LDrive := ExtractFileDrive(TDirectory.GetCurrentDirectory); 622 | 623 | // Ensure drive has a trailing backslash 624 | if LDrive <> '' then 625 | LDrive := LDrive + '\'; 626 | 627 | if GetDiskFreeSpaceEx(PChar(LDrive), LFreeAvailable, LTotalSpace, @LTotalFree) then 628 | Result := LFreeAvailable >= ARequiredSize; 629 | end; 630 | 631 | class function soUtils.GetRandomThinkingResult(): string; 632 | const 633 | CMessages: array[0..9] of string = ( 634 | 'Here’s what I came up with:', 635 | 'This is what I found:', 636 | 'Here’s my answer:', 637 | 'Done! Here’s the result:', 638 | 'Here’s my response:', 639 | 'I’ve worked it out:', 640 | 'Processing complete. Here’s my output:', 641 | 'Here’s what I think:', 642 | 'After thinking it through, here’s my take:', 643 | 'Solution ready! Check this out:' 644 | ); 645 | begin 646 | Result := CMessages[Random(Length(CMessages))]; 647 | end; 648 | 649 | class function soUtils.TavilyWebSearch(const AAPIKey, AQuery: string): string; 650 | var 651 | HttpClient: THTTPClient; 652 | Response: IHTTPResponse; 653 | JsonRequest, JsonResponse: TJSONObject; 654 | StringContent: TStringStream; 655 | Url: string; 656 | begin 657 | Result := ''; 658 | if AAPIKey.IsEmpty then Exit; 659 | 660 | HttpClient := THTTPClient.Create; 661 | try 662 | // Set the API URL 663 | Url := 'https://api.tavily.com/search'; 664 | 665 | // Create JSON request body 666 | JsonRequest := TJSONObject.Create; 667 | try 668 | JsonRequest.AddPair('api_key', AAPIKey); 669 | JsonRequest.AddPair('query', AQuery); 670 | JsonRequest.AddPair('include_answer', 'advanced'); // Include 'include_answer' parameter 671 | JsonRequest.AddPair('include_answer', TJSONBool.Create(True)); 672 | JsonRequest.AddPair('include_images', TJSONBool.Create(False)); 673 | JsonRequest.AddPair('include_image_descriptions', TJSONBool.Create(False)); 674 | JsonRequest.AddPair('include_raw_content', TJSONBool.Create(False)); 675 | JsonRequest.AddPair('max_results', TJSONNumber.Create(1)); 676 | JsonRequest.AddPair('include_domains', TJSONArray.Create); // Empty array 677 | JsonRequest.AddPair('exclude_domains', TJSONArray.Create); // Empty array 678 | 679 | // Convert JSON to string stream 680 | StringContent := TStringStream.Create(JsonRequest.ToString, TEncoding.UTF8); 681 | try 682 | // Set content type to application/json 683 | HttpClient.ContentType := 'application/json'; 684 | 685 | // Perform the POST request 686 | Response := HttpClient.Post(Url, StringContent); 687 | 688 | // Check if the response is successful 689 | if Response.StatusCode = 200 then 690 | begin 691 | // Parse the JSON response 692 | JsonResponse := TJSONObject.ParseJSONValue(Response.ContentAsString(TEncoding.UTF8)) as TJSONObject; 693 | try 694 | // Extract the 'answer' field from the response 695 | if JsonResponse.TryGetValue('answer', Result) then 696 | begin 697 | // 'Result' now contains the answer from the API 698 | end 699 | else 700 | begin 701 | raise Exception.Create('The "answer" field is missing in the API response.'); 702 | end; 703 | finally 704 | JsonResponse.Free; 705 | end; 706 | end 707 | else 708 | begin 709 | raise Exception.CreateFmt('Error: %d - %s', [Response.StatusCode, Response.StatusText]); 710 | end; 711 | finally 712 | StringContent.Free; 713 | end; 714 | finally 715 | JsonRequest.Free; 716 | end; 717 | finally 718 | HttpClient.Free; 719 | end; 720 | end; 721 | 722 | { TatTokenResponse } 723 | procedure TsoTokenResponse.Initialize; 724 | var 725 | LSize: Integer; 726 | begin 727 | // Defaults 728 | FRaw := ''; 729 | SetLength(FTokens, 0); 730 | SetLength(FWordBreaks, 0); 731 | SetLength(FLineBreaks, 0); 732 | SetLength(FWords, 0); 733 | FWord := ''; 734 | FLine := ''; 735 | FFinalized := False; 736 | FRightMargin := 10; 737 | 738 | // If stream output is sent to a destination without wordwrap, 739 | // the TatTokenResponse will find wordbreaks and split into lines by full words 740 | 741 | // Stream is tabulated into full words based on these break characters 742 | // !Syntax requires at least one! 743 | SetLength(FWordBreaks, 4); 744 | FWordBreaks[0] := ' '; 745 | FWordBreaks[1] := '-'; 746 | FWordBreaks[2] := ','; 747 | FWordBreaks[3] := '.'; 748 | 749 | // Stream may contain forced line breaks 750 | // !Syntax requires at least one! 751 | SetLength(FLineBreaks, 2); 752 | FLineBreaks[0] := #13; 753 | FLineBreaks[1] := #10; 754 | 755 | SetRightMargin(10); 756 | 757 | LSize := 120; 758 | soConsole.GetSize(@LSize, nil); 759 | SetMaxLineLength(LSize); 760 | end; 761 | 762 | function TsoTokenResponse.AddToken(const aToken: string): TsoTokenPrintAction; 763 | var 764 | LPrefix, LSuffix: string; 765 | begin 766 | // Keep full original response 767 | FRaw := FRaw + aToken; // As continuous string 768 | Setlength(FTokens, Length(FTokens)+1); // Make space 769 | FTokens[Length(FTokens)-1] := aToken; // As an array 770 | 771 | // Accumulate "word" 772 | FWord := FWord + aToken; 773 | 774 | // If stream contains linebreaks, print token out without added linebreaks 775 | if HandleLineBreaks(aToken) then 776 | exit(TsoTokenPrintAction.tpaAppend) 777 | 778 | // Check if a natural break exists, also split if word is longer than the allowed space 779 | // and print out token with or without linechange as needed 780 | else if SplitWord(FWord, LPrefix, LSuffix) or FFinalized then 781 | begin 782 | // On last call when Finalized we want access to the line change logic only 783 | // Bad design (fix on top of a fix) Would be better to separate word slipt and line logic from eachother 784 | if not FFinalized then 785 | begin 786 | Setlength(FWords, Length(FWords)+1); // Make space 787 | FWords[Length(FWords)-1] := LPrefix; // Add new word to array 788 | FWord := LSuffix; // Keep the remainder of the split 789 | end; 790 | 791 | // Word was split, so there is something that can be printed 792 | 793 | // Need for a new line? 794 | if Length(FLine) + Length(LastWord) > GetLineLengthMax() then 795 | begin 796 | Result := TsoTokenPrintAction.tpaNewline; 797 | FLine := LastWord; // Reset Line (will be new line and then the word) 798 | end 799 | else 800 | begin 801 | Result := TsoTokenPrintAction.tpaAppend; 802 | FLine := FLine + LastWord; // Append to the line 803 | end; 804 | end 805 | else 806 | begin 807 | Result := TsoTokenPrintAction.tpaWait; 808 | end; 809 | end; 810 | 811 | function TsoTokenResponse.HandleLineBreaks(const AToken: string): Boolean; 812 | var 813 | LLetter, LLineBreak: Integer; 814 | begin 815 | Result := false; 816 | 817 | for LLetter := Length(AToken) downto 1 do // We are interested in the last possible linebreak 818 | begin 819 | for LLineBReak := 0 to Length(Self.FLineBreaks)-1 do // Iterate linebreaks 820 | begin 821 | if AToken[LLetter] = FLineBreaks[LLineBreak] then // If linebreak was found 822 | begin 823 | // Split into a word by last found linechange (do note the stored word may have more linebreak) 824 | Setlength(FWords, Length(FWords)+1); // Make space 825 | FWords[Length(FWords)-1] := FWord + LeftStr(AToken, Length(AToken)-LLetter); // Add new word to array 826 | 827 | // In case aToken did not end after last LF 828 | // Word and new line will have whatever was after the last linebreak 829 | FWord := RightStr(AToken, Length(AToken)-LLetter); 830 | FLine := FWord; 831 | 832 | // No need to go further 833 | exit(true); 834 | end; 835 | end; 836 | end; 837 | end; 838 | 839 | function TsoTokenResponse.Finalize: Boolean; 840 | begin 841 | // Buffer may contain something, if so make it into a word 842 | if FWord <> '' then 843 | begin 844 | Setlength(FWords, Length(FWords)+1); // Make space 845 | FWords[Length(FWords)-1] := FWord; // Add new word to array 846 | Self.FFinalized := True; // Remember Finalize was done (affects how last AddToken-call behaves) 847 | exit(true); 848 | end 849 | else 850 | Result := false; 851 | end; 852 | 853 | procedure TsoTokenResponse.Clear(); 854 | begin 855 | FRaw := ''; 856 | SetLength(FTokens, 0); 857 | SetLength(FWords, 0); 858 | FWord := ''; 859 | FLine := ''; 860 | FFinalized := False; 861 | end; 862 | 863 | function TsoTokenResponse.LastWord(const ATrimLeft: Boolean): string; 864 | begin 865 | Result := FWords[Length(FWords)-1]; 866 | if ATrimLeft then 867 | Result := Result.TrimLeft; 868 | end; 869 | 870 | function TsoTokenResponse.SplitWord(const AWord: string; var APrefix, ASuffix: string): Boolean; 871 | var 872 | LLetter, LSeparator: Integer; 873 | begin 874 | Result := false; 875 | 876 | for LLetter := 1 to Length(AWord) do // Iterate whole word 877 | begin 878 | for LSeparator := 0 to Length(FWordBreaks)-1 do // Iterate all separating characters 879 | begin 880 | if AWord[LLetter] = FWordBreaks[LSeparator] then // check for natural break 881 | begin 882 | // Let the world know there's stuff that can be a reason for a line change 883 | Result := True; 884 | 885 | APrefix := LeftStr(AWord, LLetter); 886 | ASuffix := RightStr(AWord, Length(AWord)-LLetter); 887 | end; 888 | end; 889 | end; 890 | 891 | // Maybe the word is too long but there was no natural break, then cut it to LineLengthMax 892 | if Length(AWord) > GetLineLengthMax() then 893 | begin 894 | Result := True; 895 | APrefix := LeftStr(AWord, GetLineLengthMax()); 896 | ASuffix := RightStr(AWord, Length(AWord)-GetLineLengthMax()); 897 | end; 898 | end; 899 | 900 | function TsoTokenResponse.GetLineLengthMax(): Integer; 901 | begin 902 | Result := FMaxLineLength - FRightMargin; 903 | end; 904 | 905 | function TsoTokenResponse.GetRightMargin(): Integer; 906 | begin 907 | Result := FRightMargin; 908 | end; 909 | 910 | procedure TsoTokenResponse.SetRightMargin(const AMargin: Integer); 911 | begin 912 | FRightMargin := AMargin; 913 | end; 914 | 915 | function TsoTokenResponse.GetMaxLineLength(): Integer; 916 | begin 917 | Result := FMaxLineLength; 918 | end; 919 | 920 | procedure TsoTokenResponse.SetMaxLineLength(const ALength: Integer); 921 | begin 922 | FMaxLineLength := ALength; 923 | end; 924 | 925 | initialization 926 | begin 927 | ReportMemoryLeaksOnShutdown := True; 928 | SetExceptionMask(GetExceptionMask + [exOverflow, exInvalidOp]); 929 | Randomize(); 930 | end; 931 | 932 | finalization 933 | begin 934 | end; 935 | 936 | end. 937 | -------------------------------------------------------------------------------- /src/Sophora.groupproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | {1B40AEF7-B69E-47B6-9129-CF9EAFEEDF80} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Default.Personality.12 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/Sophora.pas: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinyBigGAMES/Sophora/6af6e97bcc71cdef42571553f0614b1c02676119/src/Sophora.pas -------------------------------------------------------------------------------- /virustotal.txt: -------------------------------------------------------------------------------- 1 | VirtusTotal report for Sophora Project 2 | ============================================================================= 3 | 4 | We are pleased to include a VirusTotal report for this project to ensure 5 | transparency and trust. Every effort is made to deliver software that is both 6 | safe and free of viruses. However, despite our rigorous testing and security 7 | measures, there is always a possibility that virus scanners may flag the 8 | files as a false positive. We appreciate your understanding and encourage you 9 | to review the VirusTotal report for your assurance. Should you have any 10 | concerns or reservations, please do not hesitate to reach out to us. 11 | 12 | Sophora.CLibs.res - https://www.virustotal.com/gui/file/bafd9c66b4dbc6d81090dc7e106a67ac892f3c5ffe789714a5675733f762d1a1 13 | 14 | --------------------------------------------------------------------------------