├── .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 | 
2 | [](https://discord.gg/tPWjMwK)
3 | [](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 |
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 |
--------------------------------------------------------------------------------