mirror of
				https://github.com/amithkoujalgi/ollama4j.git
				synced 2025-10-30 16:10:40 +01:00 
			
		
		
		
	Compare commits
	
		
			56 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | c9db51a71e | ||
|   | 681a692ca9 | ||
|   | 9a6065fdb3 | ||
|   | e245d9633f | ||
|   | 590364dd53 | ||
|   | bb4e7477bd | ||
|   | c33c1c8315 | ||
|   | 05eecdccaa | ||
|   | 26bb2f9bab | ||
|   | bbafc95577 | ||
|   | bee09aa626 | ||
|   | 8aa6e3b066 | ||
|   | d40912c638 | ||
|   | ba0444194f | ||
|   | ac3f505aa6 | ||
|   | 7e5ca53fda | ||
|   | 2b0238b9e8 | ||
|   | 469a0fe491 | ||
|   | 983a3617f0 | ||
|   | b638b981c9 | ||
|   | fe5078891f | ||
|   | 44b4de9ed9 | ||
|   | 854c0b4acf | ||
|   | 18c5d06a6c | ||
|   | 22b403d0b0 | ||
|   | ee0493eb57 | ||
|   | f966b4b74e | ||
|   | 1dadbacd2c | ||
|   | 714c16c216 | ||
|   | cf2c510b23 | ||
|   | a0bcc47b2e | ||
|   | 57ecbc2572 | ||
|   | 99beb3e6d0 | ||
|   | 7756eed9a0 | ||
|   | b795117f0a | ||
|   | 0d091d1826 | ||
|   | 9fd77a6743 | ||
|   | 57a962148b | ||
|   | 3c64f2099f | ||
|   | a9c7f4e5e0 | ||
|   | e7f58d4e0d | ||
|   | 138497b30f | ||
|   | 3a792090e2 | ||
|   | 7ef859bba5 | ||
|   | 3c30593e1e | ||
|   | 98b794ca2b | ||
|   | bc90a15a68 | ||
|   | bb4689e94b | ||
|   | 6739c93edc | ||
|   | c8c30d703b | ||
|   | 419b0369c9 | ||
|   | d9a94b95e1 | ||
|   | db1db948c8 | ||
|   | 41ad780224 | ||
|   | cb58c6a9b0 | ||
|   | d1115e0b35 | 
| @@ -1,21 +1,20 @@ | ||||
| # This workflow will build a package using Maven and then publish it to GitHub packages when a release is created | ||||
| # For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#apache-maven-with-a-settings-path | ||||
| 
 | ||||
| name: Build on PR Create | ||||
| name: Run Tests | ||||
| 
 | ||||
| on: | ||||
|   pull_request: | ||||
|     types: [ opened, reopened ] | ||||
| #    types: [opened, reopened, synchronize, edited] | ||||
|     branches: [ "main" ] | ||||
| 
 | ||||
| concurrency: | ||||
|   group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | ||||
|   cancel-in-progress: true | ||||
| 
 | ||||
| jobs: | ||||
|   build: | ||||
|   run-tests: | ||||
| 
 | ||||
|     runs-on: ubuntu-latest | ||||
|     permissions: | ||||
|       contents: read | ||||
|       packages: write | ||||
| 
 | ||||
|     steps: | ||||
|       - uses: actions/checkout@v3 | ||||
| @@ -30,5 +29,8 @@ jobs: | ||||
|       - name: Build with Maven | ||||
|         run: mvn --file pom.xml -U clean package | ||||
| 
 | ||||
|       - name: Run Tests | ||||
|         run: mvn --file pom.xml -U clean test -Punit-tests | ||||
|       - name: Run unit tests | ||||
|         run: mvn --file pom.xml -U clean test -Punit-tests | ||||
| 
 | ||||
|       - name: Run integration tests | ||||
|         run: mvn --file pom.xml -U clean verify -Pintegration-tests | ||||
							
								
								
									
										2
									
								
								.github/workflows/publish-docs.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/publish-docs.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| # Simple workflow for deploying static content to GitHub Pages | ||||
| name: Deploy Docs to GH Pages | ||||
| name: Publish Docs to GH Pages | ||||
|  | ||||
| on: | ||||
|   release: | ||||
|   | ||||
							
								
								
									
										12
									
								
								.github/workflows/run-tests.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								.github/workflows/run-tests.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,15 +1,18 @@ | ||||
| name: Run Tests | ||||
| name: Run Unit and Integration Tests | ||||
|  | ||||
| on: | ||||
| #  push: | ||||
| #    branches: | ||||
| #      - main | ||||
|   workflow_dispatch: | ||||
|     inputs: | ||||
|       branch: | ||||
|         description: 'Branch name to run tests on' | ||||
|         description: 'Branch name to run the tests on' | ||||
|         required: true | ||||
|         default: 'main' | ||||
|  | ||||
| jobs: | ||||
|   build: | ||||
|   run-tests: | ||||
|     runs-on: ubuntu-latest | ||||
|  | ||||
|     steps: | ||||
| @@ -27,3 +30,6 @@ jobs: | ||||
|  | ||||
|       - name: Run unit tests | ||||
|         run: mvn clean test -Punit-tests | ||||
|  | ||||
|       - name: Run integration tests | ||||
|         run: mvn clean verify -Pintegration-tests | ||||
							
								
								
									
										38
									
								
								.pre-commit-config.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								.pre-commit-config.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| repos: | ||||
|  | ||||
|   # pre-commit hooks | ||||
|   - repo: https://github.com/pre-commit/pre-commit-hooks | ||||
|     rev: "v5.0.0" | ||||
|     hooks: | ||||
|       - id: no-commit-to-branch | ||||
|         args: ['--branch', 'main'] | ||||
|       - id: check-merge-conflict | ||||
|       - id: check-added-large-files | ||||
|       - id: check-yaml | ||||
|       - id: check-xml | ||||
|       - id: check-json | ||||
|       - id: pretty-format-json | ||||
|         args: ['--no-sort-keys', '--autofix', '--indent=4'] | ||||
|       - id: end-of-file-fixer | ||||
|         exclude: \.json$ | ||||
|         files: \.java$|\.xml$ | ||||
|       - id: trailing-whitespace | ||||
|       - id: mixed-line-ending | ||||
|  | ||||
|   # for commit message formatting | ||||
|   - repo: https://github.com/commitizen-tools/commitizen | ||||
|     rev: v4.4.1 | ||||
|     hooks: | ||||
|       - id: commitizen | ||||
|         stages: [commit-msg] | ||||
|  | ||||
| #  # for java code quality | ||||
| #  - repo: https://github.com/gherynos/pre-commit-java | ||||
| #    rev: v0.6.10 | ||||
| #    hooks: | ||||
| #      - id: pmd | ||||
| #        exclude: /test/ | ||||
| #      - id: cpd | ||||
| #        exclude: /test/ | ||||
| #      - id: checkstyle | ||||
| #        exclude: /test/ | ||||
							
								
								
									
										8
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								Makefile
									
									
									
									
									
								
							| @@ -1,3 +1,11 @@ | ||||
| dev: | ||||
| 	@echo "Setting up dev environment..." | ||||
| 	@command -v pre-commit >/dev/null 2>&1 || { echo "Error: pre-commit is not installed. Please install it first."; exit 1; } | ||||
| 	@command -v docker >/dev/null 2>&1 || { echo "Error: docker is not installed. Please install it first."; exit 1; } | ||||
| 	pre-commit install | ||||
| 	pre-commit autoupdate | ||||
| 	pre-commit install --install-hooks | ||||
|  | ||||
| build: | ||||
| 	mvn -B clean install | ||||
|  | ||||
|   | ||||
							
								
								
									
										125
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										125
									
								
								README.md
									
									
									
									
									
								
							| @@ -31,8 +31,11 @@ Find more details on the [website](https://ollama4j.github.io/ollama4j/). | ||||
|  | ||||
|  | ||||
| [](https://codecov.io/gh/ollama4j/ollama4j) | ||||
|  | ||||
| [](https://github.com/ollama4j/ollama4j/actions/workflows/run-tests.yml) | ||||
|  | ||||
|  | ||||
|  | ||||
| </div> | ||||
|  | ||||
| [//]: # () | ||||
| @@ -45,6 +48,7 @@ Find more details on the [website](https://ollama4j.github.io/ollama4j/). | ||||
| - [Requirements](#requirements) | ||||
| - [Installation](#installation) | ||||
| - [API Spec](https://ollama4j.github.io/ollama4j/category/apis---model-management) | ||||
| - [Examples](#examples) | ||||
| - [Javadoc](https://ollama4j.github.io/ollama4j/apidocs/) | ||||
| - [Development](#development) | ||||
| - [Contributions](#get-involved) | ||||
| @@ -73,61 +77,6 @@ Find more details on the [website](https://ollama4j.github.io/ollama4j/). | ||||
|   <img src="https://img.shields.io/badge/v0.3.0-green.svg?style=for-the-badge&labelColor=gray&label=Ollama&color=blue" alt=""/> | ||||
| </a> | ||||
|  | ||||
| <table> | ||||
| <tr> | ||||
| <td>  | ||||
|  | ||||
| <a href="https://ollama.ai/" target="_blank">Local Installation</a> | ||||
|  | ||||
| </td>  | ||||
|  | ||||
| <td>  | ||||
|  | ||||
| <a href="https://hub.docker.com/r/ollama/ollama" target="_blank">Docker Installation</a> | ||||
|  | ||||
| </td> | ||||
| </tr> | ||||
| <tr> | ||||
| <td> | ||||
|  | ||||
| <a href="https://ollama.com/download/Ollama-darwin.zip" target="_blank">Download for macOS</a> | ||||
|  | ||||
| <a href="https://ollama.com/download/OllamaSetup.exe" target="_blank">Download for Windows</a> | ||||
|  | ||||
| Install on Linux | ||||
|  | ||||
| ```shell  | ||||
| curl -fsSL https://ollama.com/install.sh | sh | ||||
| ``` | ||||
|  | ||||
| </td> | ||||
| <td> | ||||
|  | ||||
|  | ||||
|  | ||||
| CPU only | ||||
|  | ||||
| ```shell | ||||
| docker run -d -p 11434:11434 \ | ||||
|   -v ollama:/root/.ollama \ | ||||
|   --name ollama \ | ||||
|   ollama/ollama | ||||
| ``` | ||||
|  | ||||
| NVIDIA GPU | ||||
|  | ||||
| ```shell | ||||
| docker run -d -p 11434:11434 \ | ||||
|   --gpus=all \ | ||||
|   -v ollama:/root/.ollama \ | ||||
|   --name ollama \ | ||||
|   ollama/ollama | ||||
| ``` | ||||
|  | ||||
| </td> | ||||
| </tr> | ||||
| </table> | ||||
|  | ||||
| ## Installation | ||||
|  | ||||
| > [!NOTE] | ||||
| @@ -242,49 +191,77 @@ dependencies { | ||||
| > [!TIP] | ||||
| > Find the full API specifications on the [website](https://ollama4j.github.io/ollama4j/). | ||||
|  | ||||
| #### Development | ||||
| ### Development | ||||
|  | ||||
| Build: | ||||
| Make sure you have `pre-commit` installed. | ||||
|  | ||||
| With `brew`: | ||||
|  | ||||
| ```shell | ||||
| brew install pre-commit | ||||
| ``` | ||||
|  | ||||
| With `pip`: | ||||
|  | ||||
| ```shell | ||||
| pip install pre-commit | ||||
| ``` | ||||
|  | ||||
| #### Setup dev environment | ||||
|  | ||||
| ```shell | ||||
| make dev | ||||
| ``` | ||||
|  | ||||
| #### Build | ||||
|  | ||||
| ```shell | ||||
| make build | ||||
| ``` | ||||
|  | ||||
| Run unit tests: | ||||
| #### Run unit tests | ||||
|  | ||||
| ```shell | ||||
| make unit-tests | ||||
| ``` | ||||
|  | ||||
| Run integration tests: | ||||
| #### Run integration tests | ||||
|  | ||||
| Make sure you have Docker running as this uses [testcontainers](https://testcontainers.com/) to run the integration | ||||
| tests on Ollama Docker container. | ||||
|  | ||||
| ```shell | ||||
| make integration-tests | ||||
| ``` | ||||
|  | ||||
| #### Releases | ||||
| ### Releases | ||||
|  | ||||
| Newer artifacts are published via GitHub Actions CI workflow when a new release is created from `main` branch. | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| The `ollama4j-examples` repository contains examples for using the Ollama4j library. You can explore | ||||
| it [here](https://github.com/ollama4j/ollama4j-examples). | ||||
|  | ||||
| ## ⭐ Give us a Star! | ||||
|  | ||||
| If you like or are using this project to build your own, please give us a star. It's a free way to show your support. | ||||
|  | ||||
| ## Who's using Ollama4j? | ||||
|  | ||||
| | # | Project Name      | Description                                                                                                   | Link                                                                                                                                           | | ||||
| |---|-------------------|---------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------| | ||||
| | 1 | Datafaker         | A library to generate fake data                                                                               | [GitHub](https://github.com/datafaker-net/datafaker-experimental/tree/main/ollama-api)                                                         | | ||||
| | 2 | Vaadin Web UI     | UI-Tester for interactions with Ollama via ollama4j                                                           | [GitHub](https://github.com/TEAMPB/ollama4j-vaadin-ui)                                                                                         | | ||||
| | 3 | ollama-translator | A Minecraft 1.20.6 Spigot plugin that translates all messages into a specific target language via Ollama      | [GitHub](https://github.com/liebki/ollama-translator)                                                                                          | | ||||
| | 4 | AI Player         | A Minecraft mod that adds an intelligent "second player" to the game                                          | [GitHub](https://github.com/shasankp000/AI-Player), <br/> [Reddit Thread](https://www.reddit.com/r/fabricmc/comments/1e65x5s/comment/ldr2vcf/) | | ||||
| | 5 | Ollama4j Web UI   | A web UI for Ollama written in Java using Spring Boot, Vaadin, and Ollama4j                                   | [GitHub](https://github.com/ollama4j/ollama4j-web-ui)                                                                                          | | ||||
| | 6 | JnsCLI            | A command-line tool for Jenkins that manages jobs, builds, and configurations, with AI-powered error analysis | [GitHub](https://github.com/mirum8/jnscli)                                                                                                     | | ||||
| | 7 | Katie Backend     | An open-source AI-based question-answering platform for accessing private domain knowledge                    | [GitHub](https://github.com/wyona/katie-backend)                                                                                               | | ||||
| | 8 | TeleLlama3 Bot    | A question-answering Telegram bot                                                                             | [Repo](https://git.hiast.edu.sy/mohamadbashar.disoki/telellama3-bot)                                                                           | | ||||
| | 9 | moqui-wechat      | A moqui-wechat component                                                                                      | [GitHub](https://github.com/heguangyong/moqui-wechat)                                                                                          | | ||||
| | 10 | B4X      | A set of simple and powerful RAD tool for Desktop and Server development                                              | [Website](https://www.b4x.com/android/forum/threads/ollama4j-library-pnd_ollama4j-your-local-offline-llm-like-chatgpt.165003/)     |                                                               | ||||
|  | ||||
| | #  | Project Name      | Description                                                                                                                                                        | Link                                                                                                                                                                                          | | ||||
| |----|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | ||||
| | 1  | Datafaker         | A library to generate fake data                                                                                                                                    | [GitHub](https://github.com/datafaker-net/datafaker-experimental/tree/main/ollama-api)                                                                                                        | | ||||
| | 2  | Vaadin Web UI     | UI-Tester for interactions with Ollama via ollama4j                                                                                                                | [GitHub](https://github.com/TEAMPB/ollama4j-vaadin-ui)                                                                                                                                        | | ||||
| | 3  | ollama-translator | A Minecraft 1.20.6 Spigot plugin that translates all messages into a specific target language via Ollama                                                           | [GitHub](https://github.com/liebki/ollama-translator)                                                                                                                                         | | ||||
| | 4  | AI Player         | A Minecraft mod that adds an intelligent "second player" to the game                                                                                               | [Website](https://modrinth.com/mod/ai-player), [GitHub](https://github.com/shasankp000/AI-Player), <br/> [Reddit Thread](https://www.reddit.com/r/fabricmc/comments/1e65x5s/comment/ldr2vcf/) | | ||||
| | 5  | Ollama4j Web UI   | A web UI for Ollama written in Java using Spring Boot, Vaadin, and Ollama4j                                                                                        | [GitHub](https://github.com/ollama4j/ollama4j-web-ui)                                                                                                                                         | | ||||
| | 6  | JnsCLI            | A command-line tool for Jenkins that manages jobs, builds, and configurations, with AI-powered error analysis                                                      | [GitHub](https://github.com/mirum8/jnscli)                                                                                                                                                    | | ||||
| | 7  | Katie Backend     | An open-source AI-based question-answering platform for accessing private domain knowledge                                                                         | [GitHub](https://github.com/wyona/katie-backend)                                                                                                                                              | | ||||
| | 8  | TeleLlama3 Bot    | A question-answering Telegram bot                                                                                                                                  | [Repo](https://git.hiast.edu.sy/mohamadbashar.disoki/telellama3-bot)                                                                                                                          | | ||||
| | 9  | moqui-wechat      | A moqui-wechat component                                                                                                                                           | [GitHub](https://github.com/heguangyong/moqui-wechat)                                                                                                                                         | | ||||
| | 10 | B4X               | A set of simple and powerful RAD tool for Desktop and Server development                                                                                           | [Website](https://www.b4x.com/android/forum/threads/ollama4j-library-pnd_ollama4j-your-local-offline-llm-like-chatgpt.165003/)                                                                | | ||||
| | 11 | Research Article  | Article: `Large language model based mutations in genetic improvement` - published on National Library of Medicine (National Center for Biotechnology Information) | [Website](https://pmc.ncbi.nlm.nih.gov/articles/PMC11750896/)                                                                                                                                 | | ||||
|  | ||||
| ## Traction | ||||
|  | ||||
| @@ -351,7 +328,7 @@ project. | ||||
|   </a> | ||||
| </p> | ||||
|  | ||||
| ### Appreciate my work? | ||||
| ### Appreciate the work? | ||||
|  | ||||
| <p align="center"> | ||||
|   <a href="https://www.buymeacoffee.com/amithkoujalgi" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a> | ||||
|   | ||||
| @@ -7,6 +7,8 @@ sidebar_position: 7 | ||||
| This API lets you create a conversation with LLMs. Using this API enables you to ask questions to the model including | ||||
| information using the history of already asked questions and the respective answers. | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Create a new conversation and use chat history to augment follow up questions | ||||
|  | ||||
| ```java | ||||
| @@ -269,4 +271,12 @@ You will get a response similar to: | ||||
| > Second Answer: Based on the image, it's difficult to definitively determine the breed of the dog. However, the dog | ||||
| > appears to be medium-sized with a short coat and a brown coloration, which might suggest that it is a Golden Retriever | ||||
| > or a similar breed. Without more details like ear shape and tail length, it's not possible to identify the exact breed | ||||
| > confidently. | ||||
| > confidently. | ||||
|  | ||||
|  | ||||
| [//]: # (Generated using: https://emgithub.com/) | ||||
| <iframe style={{ width: '100%', height: '919px', border: 'none' }} allow="clipboard-write" src="https://emgithub.com/iframe.html?target=https%3A%2F%2Fgithub.com%2Follama4j%2Follama4j-examples%2Fblob%2Fmain%2Fsrc%2Fmain%2Fjava%2Fio%2Fgithub%2Follama4j%2Fexamples%2FChatExample.java&style=default&type=code&showBorder=on&showLineNumbers=on&showFileMeta=on&showFullPath=on&showCopy=on" /> | ||||
|  | ||||
| <a href="https://github.com/ollama4j/ollama4j-examples/blob/main/src/main/java/io/github/ollama4j/examples/ChatExample.java" target="_blank"> | ||||
|   View ChatExample.java on GitHub | ||||
| </a> | ||||
| @@ -84,6 +84,7 @@ const config = { | ||||
|                         position: 'left', | ||||
|                         label: 'Docs', | ||||
|                     }, | ||||
|                     {to: 'https://github.com/ollama4j/ollama4j-examples', label: 'Examples', position: 'left'}, | ||||
|                     {to: 'https://ollama4j.github.io/ollama4j/apidocs/', label: 'Javadoc', position: 'left'}, | ||||
|                     {to: 'https://ollama4j.github.io/ollama4j/doxygen/html/', label: 'Doxygen', position: 'left'}, | ||||
|                     {to: '/blog', label: 'Blog', position: 'left'}, | ||||
| @@ -106,6 +107,15 @@ const config = { | ||||
|                             }, | ||||
|                         ], | ||||
|                     }, | ||||
|                     { | ||||
|                         title: 'Usage', | ||||
|                         items: [ | ||||
|                             { | ||||
|                                 label: 'Examples', | ||||
|                                 to: 'https://github.com/ollama4j/ollama4j-examples', | ||||
|                             }, | ||||
|                         ], | ||||
|                     }, | ||||
|                     { | ||||
|                         title: 'Community', | ||||
|                         items: [ | ||||
|   | ||||
							
								
								
									
										492
									
								
								docs/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										492
									
								
								docs/package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -4242,9 +4242,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/body-parser": { | ||||
|       "version": "1.20.2", | ||||
|       "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", | ||||
|       "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", | ||||
|       "version": "1.20.3", | ||||
|       "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", | ||||
|       "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "bytes": "3.1.2", | ||||
| @@ -4255,7 +4255,7 @@ | ||||
|         "http-errors": "2.0.0", | ||||
|         "iconv-lite": "0.4.24", | ||||
|         "on-finished": "2.4.1", | ||||
|         "qs": "6.11.0", | ||||
|         "qs": "6.13.0", | ||||
|         "raw-body": "2.5.2", | ||||
|         "type-is": "~1.6.18", | ||||
|         "unpipe": "1.0.0" | ||||
| @@ -4446,6 +4446,35 @@ | ||||
|         "url": "https://github.com/sponsors/ljharb" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/call-bind-apply-helpers": { | ||||
|       "version": "1.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", | ||||
|       "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "es-errors": "^1.3.0", | ||||
|         "function-bind": "^1.1.2" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">= 0.4" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/call-bound": { | ||||
|       "version": "1.0.4", | ||||
|       "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", | ||||
|       "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "call-bind-apply-helpers": "^1.0.2", | ||||
|         "get-intrinsic": "^1.3.0" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">= 0.4" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://github.com/sponsors/ljharb" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/callsites": { | ||||
|       "version": "3.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", | ||||
| @@ -4945,9 +4974,9 @@ | ||||
|       "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" | ||||
|     }, | ||||
|     "node_modules/cookie": { | ||||
|       "version": "0.6.0", | ||||
|       "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", | ||||
|       "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", | ||||
|       "version": "0.7.1", | ||||
|       "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", | ||||
|       "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">= 0.6" | ||||
| @@ -5103,9 +5132,10 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/cross-spawn": { | ||||
|       "version": "7.0.3", | ||||
|       "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", | ||||
|       "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", | ||||
|       "version": "7.0.6", | ||||
|       "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", | ||||
|       "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "path-key": "^3.1.0", | ||||
|         "shebang-command": "^2.0.0", | ||||
| @@ -5419,30 +5449,6 @@ | ||||
|         "cytoscape": "^3.2.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/cytoscape-fcose": { | ||||
|       "version": "2.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", | ||||
|       "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", | ||||
|       "dependencies": { | ||||
|         "cose-base": "^2.2.0" | ||||
|       }, | ||||
|       "peerDependencies": { | ||||
|         "cytoscape": "^3.2.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/cytoscape-fcose/node_modules/cose-base": { | ||||
|       "version": "2.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", | ||||
|       "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", | ||||
|       "dependencies": { | ||||
|         "layout-base": "^2.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/cytoscape-fcose/node_modules/layout-base": { | ||||
|       "version": "2.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", | ||||
|       "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==" | ||||
|     }, | ||||
|     "node_modules/d3": { | ||||
|       "version": "7.8.5", | ||||
|       "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.5.tgz", | ||||
| @@ -6048,6 +6054,7 @@ | ||||
|       "version": "2.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", | ||||
|       "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">= 0.8" | ||||
|       } | ||||
| @@ -6064,6 +6071,7 @@ | ||||
|       "version": "1.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", | ||||
|       "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">= 0.8", | ||||
|         "npm": "1.2.8000 || >= 1.4.16" | ||||
| @@ -6210,9 +6218,10 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/dompurify": { | ||||
|       "version": "3.0.6", | ||||
|       "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.6.tgz", | ||||
|       "integrity": "sha512-ilkD8YEnnGh1zJ240uJsW7AzE+2qpbOUYjacomn3AvJ6J4JhKGSZ2nh4wUIXPZrEPppaCLx5jFe8T89Rk8tQ7w==" | ||||
|       "version": "3.1.6", | ||||
|       "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.6.tgz", | ||||
|       "integrity": "sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==", | ||||
|       "license": "(MPL-2.0 OR Apache-2.0)" | ||||
|     }, | ||||
|     "node_modules/domutils": { | ||||
|       "version": "3.1.0", | ||||
| @@ -6258,6 +6267,20 @@ | ||||
|         "node": ">=8" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/dunder-proto": { | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", | ||||
|       "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "call-bind-apply-helpers": "^1.0.1", | ||||
|         "es-errors": "^1.3.0", | ||||
|         "gopd": "^1.2.0" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">= 0.4" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/duplexer": { | ||||
|       "version": "0.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", | ||||
| @@ -6271,7 +6294,8 @@ | ||||
|     "node_modules/ee-first": { | ||||
|       "version": "1.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", | ||||
|       "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" | ||||
|       "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/electron-to-chromium": { | ||||
|       "version": "1.4.827", | ||||
| @@ -6279,9 +6303,10 @@ | ||||
|       "integrity": "sha512-VY+J0e4SFcNfQy19MEoMdaIcZLmDCprqvBtkii1WTCTQHpRvf5N8+3kTYCgL/PcntvwQvmMJWTuDPsq+IlhWKQ==" | ||||
|     }, | ||||
|     "node_modules/elkjs": { | ||||
|       "version": "0.8.2", | ||||
|       "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.8.2.tgz", | ||||
|       "integrity": "sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ==" | ||||
|       "version": "0.9.3", | ||||
|       "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.9.3.tgz", | ||||
|       "integrity": "sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==", | ||||
|       "license": "EPL-2.0" | ||||
|     }, | ||||
|     "node_modules/emoji-regex": { | ||||
|       "version": "9.2.2", | ||||
| @@ -6311,9 +6336,10 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/encodeurl": { | ||||
|       "version": "1.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", | ||||
|       "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", | ||||
|       "version": "2.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", | ||||
|       "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">= 0.8" | ||||
|       } | ||||
| @@ -6351,13 +6377,10 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/es-define-property": { | ||||
|       "version": "1.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", | ||||
|       "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", | ||||
|       "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "get-intrinsic": "^1.2.4" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">= 0.4" | ||||
|       } | ||||
| @@ -6376,6 +6399,18 @@ | ||||
|       "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", | ||||
|       "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==" | ||||
|     }, | ||||
|     "node_modules/es-object-atoms": { | ||||
|       "version": "1.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", | ||||
|       "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "es-errors": "^1.3.0" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">= 0.4" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/escalade": { | ||||
|       "version": "3.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", | ||||
| @@ -6567,6 +6602,7 @@ | ||||
|       "version": "1.8.1", | ||||
|       "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", | ||||
|       "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">= 0.6" | ||||
|       } | ||||
| @@ -6619,37 +6655,37 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/express": { | ||||
|       "version": "4.19.2", | ||||
|       "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", | ||||
|       "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", | ||||
|       "version": "4.21.2", | ||||
|       "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", | ||||
|       "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "accepts": "~1.3.8", | ||||
|         "array-flatten": "1.1.1", | ||||
|         "body-parser": "1.20.2", | ||||
|         "body-parser": "1.20.3", | ||||
|         "content-disposition": "0.5.4", | ||||
|         "content-type": "~1.0.4", | ||||
|         "cookie": "0.6.0", | ||||
|         "cookie": "0.7.1", | ||||
|         "cookie-signature": "1.0.6", | ||||
|         "debug": "2.6.9", | ||||
|         "depd": "2.0.0", | ||||
|         "encodeurl": "~1.0.2", | ||||
|         "encodeurl": "~2.0.0", | ||||
|         "escape-html": "~1.0.3", | ||||
|         "etag": "~1.8.1", | ||||
|         "finalhandler": "1.2.0", | ||||
|         "finalhandler": "1.3.1", | ||||
|         "fresh": "0.5.2", | ||||
|         "http-errors": "2.0.0", | ||||
|         "merge-descriptors": "1.0.1", | ||||
|         "merge-descriptors": "1.0.3", | ||||
|         "methods": "~1.1.2", | ||||
|         "on-finished": "2.4.1", | ||||
|         "parseurl": "~1.3.3", | ||||
|         "path-to-regexp": "0.1.7", | ||||
|         "path-to-regexp": "0.1.12", | ||||
|         "proxy-addr": "~2.0.7", | ||||
|         "qs": "6.11.0", | ||||
|         "qs": "6.13.0", | ||||
|         "range-parser": "~1.2.1", | ||||
|         "safe-buffer": "5.2.1", | ||||
|         "send": "0.18.0", | ||||
|         "serve-static": "1.15.0", | ||||
|         "send": "0.19.0", | ||||
|         "serve-static": "1.16.2", | ||||
|         "setprototypeof": "1.2.0", | ||||
|         "statuses": "2.0.1", | ||||
|         "type-is": "~1.6.18", | ||||
| @@ -6658,6 +6694,10 @@ | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">= 0.10.0" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "type": "opencollective", | ||||
|         "url": "https://opencollective.com/express" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/express/node_modules/array-flatten": { | ||||
| @@ -6690,9 +6730,10 @@ | ||||
|       "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" | ||||
|     }, | ||||
|     "node_modules/express/node_modules/path-to-regexp": { | ||||
|       "version": "0.1.7", | ||||
|       "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", | ||||
|       "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" | ||||
|       "version": "0.1.12", | ||||
|       "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", | ||||
|       "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/express/node_modules/range-parser": { | ||||
|       "version": "1.2.1", | ||||
| @@ -6743,14 +6784,6 @@ | ||||
|       "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", | ||||
|       "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" | ||||
|     }, | ||||
|     "node_modules/fast-url-parser": { | ||||
|       "version": "1.1.3", | ||||
|       "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", | ||||
|       "integrity": "sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==", | ||||
|       "dependencies": { | ||||
|         "punycode": "^1.3.2" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/fastq": { | ||||
|       "version": "1.16.0", | ||||
|       "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz", | ||||
| @@ -6878,12 +6911,13 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/finalhandler": { | ||||
|       "version": "1.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", | ||||
|       "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", | ||||
|       "version": "1.3.1", | ||||
|       "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", | ||||
|       "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "debug": "2.6.9", | ||||
|         "encodeurl": "~1.0.2", | ||||
|         "encodeurl": "~2.0.0", | ||||
|         "escape-html": "~1.0.3", | ||||
|         "on-finished": "2.4.1", | ||||
|         "parseurl": "~1.3.3", | ||||
| @@ -6898,6 +6932,7 @@ | ||||
|       "version": "2.6.9", | ||||
|       "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", | ||||
|       "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "ms": "2.0.0" | ||||
|       } | ||||
| @@ -6905,7 +6940,8 @@ | ||||
|     "node_modules/finalhandler/node_modules/ms": { | ||||
|       "version": "2.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", | ||||
|       "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" | ||||
|       "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/find-cache-dir": { | ||||
|       "version": "4.0.0", | ||||
| @@ -7125,6 +7161,7 @@ | ||||
|       "version": "0.5.2", | ||||
|       "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", | ||||
|       "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">= 0.6" | ||||
|       } | ||||
| @@ -7182,16 +7219,21 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/get-intrinsic": { | ||||
|       "version": "1.2.4", | ||||
|       "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", | ||||
|       "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", | ||||
|       "version": "1.3.0", | ||||
|       "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", | ||||
|       "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "call-bind-apply-helpers": "^1.0.2", | ||||
|         "es-define-property": "^1.0.1", | ||||
|         "es-errors": "^1.3.0", | ||||
|         "es-object-atoms": "^1.1.1", | ||||
|         "function-bind": "^1.1.2", | ||||
|         "has-proto": "^1.0.1", | ||||
|         "has-symbols": "^1.0.3", | ||||
|         "hasown": "^2.0.0" | ||||
|         "get-proto": "^1.0.1", | ||||
|         "gopd": "^1.2.0", | ||||
|         "has-symbols": "^1.1.0", | ||||
|         "hasown": "^2.0.2", | ||||
|         "math-intrinsics": "^1.1.0" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">= 0.4" | ||||
| @@ -7205,6 +7247,19 @@ | ||||
|       "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", | ||||
|       "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==" | ||||
|     }, | ||||
|     "node_modules/get-proto": { | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", | ||||
|       "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "dunder-proto": "^1.0.1", | ||||
|         "es-object-atoms": "^1.0.0" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">= 0.4" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/get-stream": { | ||||
|       "version": "6.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", | ||||
| @@ -7342,11 +7397,12 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/gopd": { | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", | ||||
|       "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", | ||||
|       "dependencies": { | ||||
|         "get-intrinsic": "^1.1.3" | ||||
|       "version": "1.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", | ||||
|       "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">= 0.4" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://github.com/sponsors/ljharb" | ||||
| @@ -7465,21 +7521,11 @@ | ||||
|         "url": "https://github.com/sponsors/ljharb" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/has-proto": { | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", | ||||
|       "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", | ||||
|       "engines": { | ||||
|         "node": ">= 0.4" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://github.com/sponsors/ljharb" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/has-symbols": { | ||||
|       "version": "1.0.3", | ||||
|       "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", | ||||
|       "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", | ||||
|       "version": "1.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", | ||||
|       "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">= 0.4" | ||||
|       }, | ||||
| @@ -7499,9 +7545,10 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/hasown": { | ||||
|       "version": "2.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", | ||||
|       "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", | ||||
|       "version": "2.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", | ||||
|       "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "function-bind": "^1.1.2" | ||||
|       }, | ||||
| @@ -7912,6 +7959,7 @@ | ||||
|       "version": "2.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", | ||||
|       "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "depd": "2.0.0", | ||||
|         "inherits": "2.0.4", | ||||
| @@ -7942,9 +7990,10 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/http-proxy-middleware": { | ||||
|       "version": "2.0.6", | ||||
|       "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", | ||||
|       "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", | ||||
|       "version": "2.0.7", | ||||
|       "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", | ||||
|       "integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@types/http-proxy": "^1.17.8", | ||||
|         "http-proxy": "^1.18.1", | ||||
| @@ -8562,6 +8611,31 @@ | ||||
|         "graceful-fs": "^4.1.6" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/katex": { | ||||
|       "version": "0.16.21", | ||||
|       "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.21.tgz", | ||||
|       "integrity": "sha512-XvqR7FgOHtWupfMiigNzmh+MgUVmDGU2kXZm899ZkPfcuoPuFxyHmXsgATDpFZDAXCI8tvinaVcDo8PIIJSo4A==", | ||||
|       "funding": [ | ||||
|         "https://opencollective.com/katex", | ||||
|         "https://github.com/sponsors/katex" | ||||
|       ], | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "commander": "^8.3.0" | ||||
|       }, | ||||
|       "bin": { | ||||
|         "katex": "cli.js" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/katex/node_modules/commander": { | ||||
|       "version": "8.3.0", | ||||
|       "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", | ||||
|       "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">= 12" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/keyv": { | ||||
|       "version": "4.5.4", | ||||
|       "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", | ||||
| @@ -8770,6 +8844,15 @@ | ||||
|         "url": "https://github.com/sponsors/wooorm" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/math-intrinsics": { | ||||
|       "version": "1.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", | ||||
|       "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">= 0.4" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/mdast-util-directive": { | ||||
|       "version": "3.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.0.0.tgz", | ||||
| @@ -9174,9 +9257,13 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/merge-descriptors": { | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", | ||||
|       "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" | ||||
|       "version": "1.0.3", | ||||
|       "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", | ||||
|       "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", | ||||
|       "license": "MIT", | ||||
|       "funding": { | ||||
|         "url": "https://github.com/sponsors/sindresorhus" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/merge-stream": { | ||||
|       "version": "2.0.0", | ||||
| @@ -9192,22 +9279,23 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/mermaid": { | ||||
|       "version": "10.6.1", | ||||
|       "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-10.6.1.tgz", | ||||
|       "integrity": "sha512-Hky0/RpOw/1il9X8AvzOEChfJtVvmXm+y7JML5C//ePYMy0/9jCEmW1E1g86x9oDfW9+iVEdTV/i+M6KWRNs4A==", | ||||
|       "version": "10.9.3", | ||||
|       "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-10.9.3.tgz", | ||||
|       "integrity": "sha512-V80X1isSEvAewIL3xhmz/rVmc27CVljcsbWxkxlWJWY/1kQa4XOABqpDl2qQLGKzpKm6WbTfUEKImBlUfFYArw==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@braintree/sanitize-url": "^6.0.1", | ||||
|         "@types/d3-scale": "^4.0.3", | ||||
|         "@types/d3-scale-chromatic": "^3.0.0", | ||||
|         "cytoscape": "^3.23.0", | ||||
|         "cytoscape": "^3.28.1", | ||||
|         "cytoscape-cose-bilkent": "^4.1.0", | ||||
|         "cytoscape-fcose": "^2.1.0", | ||||
|         "d3": "^7.4.0", | ||||
|         "d3-sankey": "^0.12.3", | ||||
|         "dagre-d3-es": "7.0.10", | ||||
|         "dayjs": "^1.11.7", | ||||
|         "dompurify": "^3.0.5", | ||||
|         "elkjs": "^0.8.2", | ||||
|         "dompurify": "^3.0.5 <3.1.7", | ||||
|         "elkjs": "^0.9.0", | ||||
|         "katex": "^0.16.9", | ||||
|         "khroma": "^2.0.0", | ||||
|         "lodash-es": "^4.17.21", | ||||
|         "mdast-util-from-markdown": "^1.3.0", | ||||
| @@ -11354,6 +11442,7 @@ | ||||
|       "version": "1.6.0", | ||||
|       "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", | ||||
|       "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", | ||||
|       "license": "MIT", | ||||
|       "bin": { | ||||
|         "mime": "cli.js" | ||||
|       }, | ||||
| @@ -11475,15 +11564,16 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/nanoid": { | ||||
|       "version": "3.3.7", | ||||
|       "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", | ||||
|       "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", | ||||
|       "version": "3.3.9", | ||||
|       "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.9.tgz", | ||||
|       "integrity": "sha512-SppoicMGpZvbF1l3z4x7No3OlIjP7QJvC9XR7AhZr1kL133KHnKPztkKDc+Ir4aJ/1VhTySrtKhrsycmrMQfvg==", | ||||
|       "funding": [ | ||||
|         { | ||||
|           "type": "github", | ||||
|           "url": "https://github.com/sponsors/ai" | ||||
|         } | ||||
|       ], | ||||
|       "license": "MIT", | ||||
|       "bin": { | ||||
|         "nanoid": "bin/nanoid.cjs" | ||||
|       }, | ||||
| @@ -11597,9 +11687,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/object-inspect": { | ||||
|       "version": "1.13.2", | ||||
|       "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", | ||||
|       "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", | ||||
|       "version": "1.13.4", | ||||
|       "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", | ||||
|       "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">= 0.4" | ||||
| @@ -11642,6 +11732,7 @@ | ||||
|       "version": "2.4.1", | ||||
|       "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", | ||||
|       "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "ee-first": "1.1.1" | ||||
|       }, | ||||
| @@ -11931,9 +12022,10 @@ | ||||
|       "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" | ||||
|     }, | ||||
|     "node_modules/path-to-regexp": { | ||||
|       "version": "1.8.0", | ||||
|       "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", | ||||
|       "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", | ||||
|       "version": "1.9.0", | ||||
|       "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", | ||||
|       "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "isarray": "0.0.1" | ||||
|       } | ||||
| @@ -12719,11 +12811,6 @@ | ||||
|         "node": ">= 0.10" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/punycode": { | ||||
|       "version": "1.4.1", | ||||
|       "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", | ||||
|       "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" | ||||
|     }, | ||||
|     "node_modules/pupa": { | ||||
|       "version": "3.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.1.0.tgz", | ||||
| @@ -12739,12 +12826,12 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/qs": { | ||||
|       "version": "6.11.0", | ||||
|       "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", | ||||
|       "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", | ||||
|       "version": "6.13.0", | ||||
|       "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", | ||||
|       "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", | ||||
|       "license": "BSD-3-Clause", | ||||
|       "dependencies": { | ||||
|         "side-channel": "^1.0.4" | ||||
|         "side-channel": "^1.0.6" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=0.6" | ||||
| @@ -13786,9 +13873,10 @@ | ||||
|       "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" | ||||
|     }, | ||||
|     "node_modules/send": { | ||||
|       "version": "0.18.0", | ||||
|       "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", | ||||
|       "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", | ||||
|       "version": "0.19.0", | ||||
|       "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", | ||||
|       "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "debug": "2.6.9", | ||||
|         "depd": "2.0.0", | ||||
| @@ -13812,6 +13900,7 @@ | ||||
|       "version": "2.6.9", | ||||
|       "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", | ||||
|       "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "ms": "2.0.0" | ||||
|       } | ||||
| @@ -13819,48 +13908,62 @@ | ||||
|     "node_modules/send/node_modules/debug/node_modules/ms": { | ||||
|       "version": "2.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", | ||||
|       "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" | ||||
|       "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/send/node_modules/encodeurl": { | ||||
|       "version": "1.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", | ||||
|       "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">= 0.8" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/send/node_modules/ms": { | ||||
|       "version": "2.1.3", | ||||
|       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", | ||||
|       "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" | ||||
|       "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/send/node_modules/range-parser": { | ||||
|       "version": "1.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", | ||||
|       "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">= 0.6" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/serialize-javascript": { | ||||
|       "version": "6.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", | ||||
|       "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", | ||||
|       "version": "6.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", | ||||
|       "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", | ||||
|       "license": "BSD-3-Clause", | ||||
|       "dependencies": { | ||||
|         "randombytes": "^2.1.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/serve-handler": { | ||||
|       "version": "6.1.5", | ||||
|       "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.5.tgz", | ||||
|       "integrity": "sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg==", | ||||
|       "version": "6.1.6", | ||||
|       "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz", | ||||
|       "integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "bytes": "3.0.0", | ||||
|         "content-disposition": "0.5.2", | ||||
|         "fast-url-parser": "1.1.3", | ||||
|         "mime-types": "2.1.18", | ||||
|         "minimatch": "3.1.2", | ||||
|         "path-is-inside": "1.0.2", | ||||
|         "path-to-regexp": "2.2.1", | ||||
|         "path-to-regexp": "3.3.0", | ||||
|         "range-parser": "1.2.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/serve-handler/node_modules/path-to-regexp": { | ||||
|       "version": "2.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz", | ||||
|       "integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==" | ||||
|       "version": "3.3.0", | ||||
|       "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", | ||||
|       "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/serve-index": { | ||||
|       "version": "1.9.1", | ||||
| @@ -13933,14 +14036,15 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/serve-static": { | ||||
|       "version": "1.15.0", | ||||
|       "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", | ||||
|       "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", | ||||
|       "version": "1.16.2", | ||||
|       "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", | ||||
|       "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "encodeurl": "~1.0.2", | ||||
|         "encodeurl": "~2.0.0", | ||||
|         "escape-html": "~1.0.3", | ||||
|         "parseurl": "~1.3.3", | ||||
|         "send": "0.18.0" | ||||
|         "send": "0.19.0" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">= 0.8.0" | ||||
| @@ -13966,7 +14070,8 @@ | ||||
|     "node_modules/setprototypeof": { | ||||
|       "version": "1.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", | ||||
|       "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" | ||||
|       "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", | ||||
|       "license": "ISC" | ||||
|     }, | ||||
|     "node_modules/shallow-clone": { | ||||
|       "version": "3.0.1", | ||||
| @@ -14028,15 +14133,69 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/side-channel": { | ||||
|       "version": "1.0.6", | ||||
|       "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", | ||||
|       "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", | ||||
|       "version": "1.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", | ||||
|       "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "call-bind": "^1.0.7", | ||||
|         "es-errors": "^1.3.0", | ||||
|         "get-intrinsic": "^1.2.4", | ||||
|         "object-inspect": "^1.13.1" | ||||
|         "object-inspect": "^1.13.3", | ||||
|         "side-channel-list": "^1.0.0", | ||||
|         "side-channel-map": "^1.0.1", | ||||
|         "side-channel-weakmap": "^1.0.2" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">= 0.4" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://github.com/sponsors/ljharb" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/side-channel-list": { | ||||
|       "version": "1.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", | ||||
|       "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "es-errors": "^1.3.0", | ||||
|         "object-inspect": "^1.13.3" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">= 0.4" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://github.com/sponsors/ljharb" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/side-channel-map": { | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", | ||||
|       "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "call-bound": "^1.0.2", | ||||
|         "es-errors": "^1.3.0", | ||||
|         "get-intrinsic": "^1.2.5", | ||||
|         "object-inspect": "^1.13.3" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">= 0.4" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://github.com/sponsors/ljharb" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/side-channel-weakmap": { | ||||
|       "version": "1.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", | ||||
|       "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "call-bound": "^1.0.2", | ||||
|         "es-errors": "^1.3.0", | ||||
|         "get-intrinsic": "^1.2.5", | ||||
|         "object-inspect": "^1.13.3", | ||||
|         "side-channel-map": "^1.0.1" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">= 0.4" | ||||
| @@ -14227,6 +14386,7 @@ | ||||
|       "version": "2.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", | ||||
|       "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">= 0.8" | ||||
|       } | ||||
| @@ -14616,6 +14776,7 @@ | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", | ||||
|       "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">=0.6" | ||||
|       } | ||||
| @@ -14906,6 +15067,7 @@ | ||||
|       "version": "1.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", | ||||
|       "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">= 0.8" | ||||
|       } | ||||
|   | ||||
							
								
								
									
										92
									
								
								pom.xml
									
									
									
									
									
								
							
							
						
						
									
										92
									
								
								pom.xml
									
									
									
									
									
								
							| @@ -13,8 +13,8 @@ | ||||
|     <packaging>jar</packaging> | ||||
|  | ||||
|     <properties> | ||||
|         <maven.compiler.source>11</maven.compiler.source> | ||||
|         <maven.compiler.target>11</maven.compiler.target> | ||||
|         <maven.compiler.release>11</maven.compiler.release> | ||||
|         <project.build.outputTimestamp>${git.commit.time}</project.build.outputTimestamp><!-- populated via git-commit-id-plugin --> | ||||
|         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||||
|         <maven-surefire-plugin.version>3.0.0-M5</maven-surefire-plugin.version> | ||||
|         <maven-failsafe-plugin.version>3.0.0-M5</maven-failsafe-plugin.version> | ||||
| @@ -49,7 +49,7 @@ | ||||
|             <plugin> | ||||
|                 <groupId>org.apache.maven.plugins</groupId> | ||||
|                 <artifactId>maven-source-plugin</artifactId> | ||||
|                 <version>3.3.0</version> | ||||
|                 <version>3.3.1</version> | ||||
|                 <executions> | ||||
|                     <execution> | ||||
|                         <id>attach-sources</id> | ||||
| @@ -62,7 +62,7 @@ | ||||
|             <plugin> | ||||
|                 <groupId>org.apache.maven.plugins</groupId> | ||||
|                 <artifactId>maven-javadoc-plugin</artifactId> | ||||
|                 <version>3.5.0</version> | ||||
|                 <version>3.11.2</version> | ||||
|                 <configuration> | ||||
|                     <!-- to disable the "missing" warnings. Remove the doclint to enable warnings--> | ||||
|                     <doclint>all,-missing</doclint> | ||||
| @@ -70,9 +70,13 @@ | ||||
|                 <executions> | ||||
|                     <execution> | ||||
|                         <id>attach-javadocs</id> | ||||
|                         <phase>package</phase> | ||||
|                         <goals> | ||||
|                             <goal>jar</goal> | ||||
|                         </goals> | ||||
|                         <configuration> | ||||
|                             <outputDirectory>${project.build.directory}</outputDirectory> | ||||
|                         </configuration> | ||||
|                     </execution> | ||||
|                 </executions> | ||||
|             </plugin> | ||||
| @@ -114,7 +118,6 @@ | ||||
|                 </executions> | ||||
|             </plugin> | ||||
|  | ||||
|  | ||||
|             <plugin> | ||||
|                 <groupId>org.apache.maven.plugins</groupId> | ||||
|                 <artifactId>maven-gpg-plugin</artifactId> | ||||
| @@ -130,7 +133,36 @@ | ||||
|                 </executions> | ||||
|             </plugin> | ||||
|  | ||||
|             <plugin> | ||||
|                 <groupId>io.github.git-commit-id</groupId> | ||||
|                 <artifactId>git-commit-id-maven-plugin</artifactId> | ||||
|                 <version>9.0.1</version> | ||||
|                 <executions> | ||||
|                     <execution> | ||||
|                         <goals> | ||||
|                             <goal>revision</goal> | ||||
|                         </goals> | ||||
|                     </execution> | ||||
|                 </executions> | ||||
|                 <configuration> | ||||
|                     <dateFormat>yyyy-MM-dd'T'HH:mm:ss'Z'</dateFormat> | ||||
| 		    <dateFormatTimeZone>Etc/UTC</dateFormatTimeZone> | ||||
|                 </configuration> | ||||
|             </plugin> | ||||
|         </plugins> | ||||
|  | ||||
|         <pluginManagement> | ||||
|             <plugins> | ||||
|                 <plugin> | ||||
|                     <artifactId>maven-compiler-plugin</artifactId> | ||||
|                     <version>3.14.0</version> | ||||
|                 </plugin> | ||||
|                 <plugin> | ||||
|                     <artifactId>maven-jar-plugin</artifactId> | ||||
|                     <version>3.4.2</version> | ||||
|                 </plugin> | ||||
|             </plugins> | ||||
|         </pluginManagement> | ||||
|     </build> | ||||
|  | ||||
|     <dependencies> | ||||
| @@ -184,6 +216,13 @@ | ||||
|             <version>20240205</version> | ||||
|             <scope>test</scope> | ||||
|         </dependency> | ||||
|  | ||||
|         <dependency> | ||||
|             <groupId>org.testcontainers</groupId> | ||||
|             <artifactId>ollama</artifactId> | ||||
|             <version>1.20.2</version> | ||||
|             <scope>test</scope> | ||||
|         </dependency> | ||||
|     </dependencies> | ||||
|  | ||||
|     <distributionManagement> | ||||
| @@ -225,6 +264,7 @@ | ||||
|                 <test.env>unit</test.env> | ||||
|                 <skipUnitTests>false</skipUnitTests> | ||||
|                 <skipIntegrationTests>true</skipIntegrationTests> | ||||
|                 <skipGpgPluginDuringTests>true</skipGpgPluginDuringTests> | ||||
|             </properties> | ||||
|             <activation> | ||||
|                 <activeByDefault>false</activeByDefault> | ||||
| @@ -250,6 +290,23 @@ | ||||
|                             </execution> | ||||
|                         </executions> | ||||
|                     </plugin> | ||||
|                     <plugin> | ||||
|                         <groupId>org.apache.maven.plugins</groupId> | ||||
|                         <artifactId>maven-gpg-plugin</artifactId> | ||||
|                         <version>1.5</version> | ||||
|                         <executions> | ||||
|                             <execution> | ||||
|                                 <id>sign-artifacts</id> | ||||
|                                 <phase>verify</phase> | ||||
|                                 <goals> | ||||
|                                     <goal>sign</goal> | ||||
|                                 </goals> | ||||
|                                 <configuration> | ||||
|                                     <skip>${skipGpgPluginDuringTests}</skip> | ||||
|                                 </configuration> | ||||
|                             </execution> | ||||
|                         </executions> | ||||
|                     </plugin> | ||||
|                 </plugins> | ||||
|             </build> | ||||
|         </profile> | ||||
| @@ -259,7 +316,29 @@ | ||||
|                 <test.env>integration</test.env> | ||||
|                 <skipUnitTests>true</skipUnitTests> | ||||
|                 <skipIntegrationTests>false</skipIntegrationTests> | ||||
|                 <skipGpgPluginDuringTests>true</skipGpgPluginDuringTests> | ||||
|             </properties> | ||||
|             <build> | ||||
|                 <plugins> | ||||
|                     <plugin> | ||||
|                         <groupId>org.apache.maven.plugins</groupId> | ||||
|                         <artifactId>maven-gpg-plugin</artifactId> | ||||
|                         <version>1.5</version> | ||||
|                         <executions> | ||||
|                             <execution> | ||||
|                                 <id>sign-artifacts</id> | ||||
|                                 <phase>verify</phase> | ||||
|                                 <goals> | ||||
|                                     <goal>sign</goal> | ||||
|                                 </goals> | ||||
|                                 <configuration> | ||||
|                                     <skip>${skipGpgPluginDuringTests}</skip> | ||||
|                                 </configuration> | ||||
|                             </execution> | ||||
|                         </executions> | ||||
|                     </plugin> | ||||
|                 </plugins> | ||||
|             </build> | ||||
|         </profile> | ||||
|         <profile> | ||||
|             <id>ci-cd</id> | ||||
| @@ -303,7 +382,6 @@ | ||||
|                             <autoReleaseAfterClose>true</autoReleaseAfterClose> | ||||
|                         </configuration> | ||||
|                     </plugin> | ||||
|  | ||||
|                     <plugin> | ||||
|                         <groupId>org.jacoco</groupId> | ||||
|                         <artifactId>jacoco-maven-plugin</artifactId> | ||||
| @@ -328,4 +406,4 @@ | ||||
|         </profile> | ||||
|     </profiles> | ||||
|  | ||||
| </project> | ||||
| </project> | ||||
|   | ||||
| @@ -1,14 +1,17 @@ | ||||
| package io.github.ollama4j; | ||||
|  | ||||
| import com.fasterxml.jackson.core.JsonParseException; | ||||
| import com.fasterxml.jackson.databind.JsonNode; | ||||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||||
| import io.github.ollama4j.exceptions.OllamaBaseException; | ||||
| import io.github.ollama4j.exceptions.RoleNotFoundException; | ||||
| import io.github.ollama4j.exceptions.ToolInvocationException; | ||||
| import io.github.ollama4j.exceptions.ToolNotFoundException; | ||||
| import io.github.ollama4j.models.chat.*; | ||||
| import io.github.ollama4j.models.embeddings.OllamaEmbedRequestModel; | ||||
| import io.github.ollama4j.models.embeddings.OllamaEmbedResponseModel; | ||||
| import io.github.ollama4j.models.embeddings.OllamaEmbeddingResponseModel; | ||||
| import io.github.ollama4j.models.embeddings.OllamaEmbeddingsRequestModel; | ||||
| import io.github.ollama4j.models.embeddings.OllamaEmbedResponseModel; | ||||
| import io.github.ollama4j.models.generate.OllamaGenerateRequest; | ||||
| import io.github.ollama4j.models.generate.OllamaStreamHandler; | ||||
| import io.github.ollama4j.models.generate.OllamaTokenHandler; | ||||
| @@ -22,6 +25,12 @@ import io.github.ollama4j.tools.annotations.ToolSpec; | ||||
| import io.github.ollama4j.utils.Options; | ||||
| import io.github.ollama4j.utils.Utils; | ||||
| import lombok.Setter; | ||||
| import org.jsoup.Jsoup; | ||||
| import org.jsoup.nodes.Document; | ||||
| import org.jsoup.nodes.Element; | ||||
| import org.jsoup.select.Elements; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| import java.io.*; | ||||
| import java.lang.reflect.InvocationTargetException; | ||||
| @@ -39,13 +48,6 @@ import java.time.Duration; | ||||
| import java.util.*; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.jsoup.Jsoup; | ||||
| import org.jsoup.nodes.Document; | ||||
| import org.jsoup.nodes.Element; | ||||
| import org.jsoup.select.Elements; | ||||
|  | ||||
| /** | ||||
|  * The base Ollama API class. | ||||
|  */ | ||||
| @@ -70,7 +72,7 @@ public class OllamaAPI { | ||||
|     @Setter | ||||
|     private int maxChatToolCallRetries = 3; | ||||
|  | ||||
|     private BasicAuth basicAuth; | ||||
|     private Auth auth; | ||||
|  | ||||
|     private final ToolRegistry toolRegistry = new ToolRegistry(); | ||||
|  | ||||
| @@ -92,6 +94,9 @@ public class OllamaAPI { | ||||
|         } else { | ||||
|             this.host = host; | ||||
|         } | ||||
|         if (this.verbose) { | ||||
|             logger.info("Ollama API initialized with host: " + this.host); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -101,7 +106,16 @@ public class OllamaAPI { | ||||
|      * @param password the password | ||||
|      */ | ||||
|     public void setBasicAuth(String username, String password) { | ||||
|         this.basicAuth = new BasicAuth(username, password); | ||||
|         this.auth = new BasicAuth(username, password); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Set Bearer authentication for accessing Ollama server that's behind a reverse-proxy/gateway. | ||||
|      * | ||||
|      * @param bearerToken the Bearer authentication token to provide | ||||
|      */ | ||||
|     public void setBearerAuth(String bearerToken) { | ||||
|         this.auth = new BearerAuth(bearerToken); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -318,26 +332,58 @@ public class OllamaAPI { | ||||
|     public void pullModel(String modelName) throws OllamaBaseException, IOException, URISyntaxException, InterruptedException { | ||||
|         String url = this.host + "/api/pull"; | ||||
|         String jsonData = new ModelRequest(modelName).toString(); | ||||
|         HttpRequest request = getRequestBuilderDefault(new URI(url)).POST(HttpRequest.BodyPublishers.ofString(jsonData)).header("Accept", "application/json").header("Content-type", "application/json").build(); | ||||
|         HttpRequest request = getRequestBuilderDefault(new URI(url)) | ||||
|                 .POST(HttpRequest.BodyPublishers.ofString(jsonData)) | ||||
|                 .header("Accept", "application/json") | ||||
|                 .header("Content-type", "application/json") | ||||
|                 .build(); | ||||
|         HttpClient client = HttpClient.newHttpClient(); | ||||
|         HttpResponse<InputStream> response = client.send(request, HttpResponse.BodyHandlers.ofInputStream()); | ||||
|         int statusCode = response.statusCode(); | ||||
|         InputStream responseBodyStream = response.body(); | ||||
|         String responseString = ""; | ||||
|         boolean success = false; // Flag to check the pull success. | ||||
|         try (BufferedReader reader = new BufferedReader(new InputStreamReader(responseBodyStream, StandardCharsets.UTF_8))) { | ||||
|             String line; | ||||
|             while ((line = reader.readLine()) != null) { | ||||
|                 ModelPullResponse modelPullResponse = Utils.getObjectMapper().readValue(line, ModelPullResponse.class); | ||||
|                 if (verbose) { | ||||
|                     logger.info(modelPullResponse.getStatus()); | ||||
|                 if (modelPullResponse != null && modelPullResponse.getStatus() != null) { | ||||
|                     if (verbose) { | ||||
|                         logger.info(modelName + ": " + modelPullResponse.getStatus()); | ||||
|                     } | ||||
|                     // Check if status is "success" and set success flag to true. | ||||
|                     if ("success".equalsIgnoreCase(modelPullResponse.getStatus())) { | ||||
|                         success = true; | ||||
|                     } | ||||
|                 } else { | ||||
|                     logger.error("Received null or invalid status for model pull."); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         if (!success) { | ||||
|             logger.error("Model pull failed or returned invalid status."); | ||||
|             throw new OllamaBaseException("Model pull failed or returned invalid status."); | ||||
|         } | ||||
|         if (statusCode != 200) { | ||||
|             throw new OllamaBaseException(statusCode + " - " + responseString); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     public String getVersion() throws URISyntaxException, IOException, InterruptedException, OllamaBaseException { | ||||
|         String url = this.host + "/api/version"; | ||||
|         HttpClient httpClient = HttpClient.newHttpClient(); | ||||
|         HttpRequest httpRequest = getRequestBuilderDefault(new URI(url)).header("Accept", "application/json").header("Content-type", "application/json").GET().build(); | ||||
|         HttpResponse<String> response = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString()); | ||||
|         int statusCode = response.statusCode(); | ||||
|         String responseString = response.body(); | ||||
|         if (statusCode == 200) { | ||||
|             return Utils.getObjectMapper().readValue(responseString, OllamaVersion.class).getVersion(); | ||||
|         } else { | ||||
|             throw new OllamaBaseException(statusCode + " - " + responseString); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Pulls a model using the specified Ollama library model tag. | ||||
|      * The model is identified by a name and a tag, which are combined into a single identifier | ||||
| @@ -658,7 +704,22 @@ public class OllamaAPI { | ||||
|             toolsResponse = toolsResponse.replace("[TOOL_CALLS]", ""); | ||||
|         } | ||||
|  | ||||
|         List<ToolFunctionCallSpec> toolFunctionCallSpecs = Utils.getObjectMapper().readValue(toolsResponse, Utils.getObjectMapper().getTypeFactory().constructCollectionType(List.class, ToolFunctionCallSpec.class)); | ||||
|         List<ToolFunctionCallSpec> toolFunctionCallSpecs = new ArrayList<>(); | ||||
|         ObjectMapper objectMapper = Utils.getObjectMapper(); | ||||
|  | ||||
|         if (!toolsResponse.isEmpty()) { | ||||
|             try { | ||||
|                 // Try to parse the string to see if it's a valid JSON | ||||
|                 JsonNode jsonNode = objectMapper.readTree(toolsResponse); | ||||
|             } catch (JsonParseException e) { | ||||
|                 logger.warn("Response from model does not contain any tool calls. Returning the response as is."); | ||||
|                 return toolResult; | ||||
|             } | ||||
|             toolFunctionCallSpecs = objectMapper.readValue( | ||||
|                     toolsResponse, | ||||
|                     objectMapper.getTypeFactory().constructCollectionType(List.class, ToolFunctionCallSpec.class) | ||||
|             ); | ||||
|         } | ||||
|         for (ToolFunctionCallSpec toolFunctionCallSpec : toolFunctionCallSpecs) { | ||||
|             toolResults.put(toolFunctionCallSpec, invokeTool(toolFunctionCallSpec)); | ||||
|         } | ||||
| @@ -836,7 +897,7 @@ public class OllamaAPI { | ||||
|      * @throws InterruptedException if the operation is interrupted | ||||
|      */ | ||||
|     public OllamaChatResult chatStreaming(OllamaChatRequest request, OllamaTokenHandler tokenHandler) throws OllamaBaseException, IOException, InterruptedException { | ||||
|         OllamaChatEndpointCaller requestCaller = new OllamaChatEndpointCaller(host, basicAuth, requestTimeoutSeconds, verbose); | ||||
|         OllamaChatEndpointCaller requestCaller = new OllamaChatEndpointCaller(host, auth, requestTimeoutSeconds, verbose); | ||||
|         OllamaChatResult result; | ||||
|  | ||||
|         // add all registered tools to Request | ||||
| @@ -881,6 +942,9 @@ public class OllamaAPI { | ||||
|      */ | ||||
|     public void registerTool(Tools.ToolSpecification toolSpecification) { | ||||
|         toolRegistry.addTool(toolSpecification.getFunctionName(), toolSpecification); | ||||
|         if (this.verbose) { | ||||
|             logger.debug("Registered tool: {}", toolSpecification.getFunctionName()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -1039,7 +1103,7 @@ public class OllamaAPI { | ||||
|      * @throws InterruptedException if the thread is interrupted during the request. | ||||
|      */ | ||||
|     private OllamaResult generateSyncForOllamaRequestModel(OllamaGenerateRequest ollamaRequestModel, OllamaStreamHandler streamHandler) throws OllamaBaseException, IOException, InterruptedException { | ||||
|         OllamaGenerateEndpointCaller requestCaller = new OllamaGenerateEndpointCaller(host, basicAuth, requestTimeoutSeconds, verbose); | ||||
|         OllamaGenerateEndpointCaller requestCaller = new OllamaGenerateEndpointCaller(host, auth, requestTimeoutSeconds, verbose); | ||||
|         OllamaResult result; | ||||
|         if (streamHandler != null) { | ||||
|             ollamaRequestModel.setStream(true); | ||||
| @@ -1060,28 +1124,18 @@ public class OllamaAPI { | ||||
|     private HttpRequest.Builder getRequestBuilderDefault(URI uri) { | ||||
|         HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(uri).header("Content-Type", "application/json").timeout(Duration.ofSeconds(requestTimeoutSeconds)); | ||||
|         if (isBasicAuthCredentialsSet()) { | ||||
|             requestBuilder.header("Authorization", getBasicAuthHeaderValue()); | ||||
|             requestBuilder.header("Authorization", auth.getAuthHeaderValue()); | ||||
|         } | ||||
|         return requestBuilder; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Get basic authentication header value. | ||||
|      * | ||||
|      * @return basic authentication header value (encoded credentials) | ||||
|      */ | ||||
|     private String getBasicAuthHeaderValue() { | ||||
|         String credentialsToEncode = basicAuth.getUsername() + ":" + basicAuth.getPassword(); | ||||
|         return "Basic " + Base64.getEncoder().encodeToString(credentialsToEncode.getBytes()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Check if Basic Auth credentials set. | ||||
|      * | ||||
|      * @return true when Basic Auth credentials set | ||||
|      */ | ||||
|     private boolean isBasicAuthCredentialsSet() { | ||||
|         return basicAuth != null; | ||||
|         return auth != null; | ||||
|     } | ||||
|  | ||||
|     private Object invokeTool(ToolFunctionCallSpec toolFunctionCallSpec) throws ToolInvocationException { | ||||
| @@ -1093,7 +1147,7 @@ public class OllamaAPI { | ||||
|                 logger.debug("Invoking function {} with arguments {}", methodName, arguments); | ||||
|             } | ||||
|             if (function == null) { | ||||
|                 throw new ToolNotFoundException("No such tool: " + methodName); | ||||
|                 throw new ToolNotFoundException("No such tool: " + methodName + ". Please register the tool before invoking it."); | ||||
|             } | ||||
|             return function.apply(arguments); | ||||
|         } catch (Exception e) { | ||||
|   | ||||
| @@ -28,9 +28,9 @@ public class OllamaChatMessageRole { | ||||
|     } | ||||
|  | ||||
|     public static OllamaChatMessageRole newCustomRole(String roleName) { | ||||
|         OllamaChatMessageRole customRole = new OllamaChatMessageRole(roleName); | ||||
|         roles.add(customRole); | ||||
|         return customRole; | ||||
| //        OllamaChatMessageRole customRole = new OllamaChatMessageRole(roleName); | ||||
| //        roles.add(customRole); | ||||
|         return new OllamaChatMessageRole(roleName); | ||||
|     } | ||||
|  | ||||
|     public static List<OllamaChatMessageRole> getRoles() { | ||||
|   | ||||
| @@ -14,10 +14,9 @@ import static io.github.ollama4j.utils.Utils.getObjectMapper; | ||||
| @Getter | ||||
| public class OllamaChatResult { | ||||
|  | ||||
|     private final List<OllamaChatMessage> chatHistory; | ||||
|  | ||||
|     private List<OllamaChatMessage> chatHistory; | ||||
|  | ||||
|     private OllamaChatResponseModel responseModel; | ||||
|     private final OllamaChatResponseModel responseModel; | ||||
|  | ||||
|     public OllamaChatResult(OllamaChatResponseModel responseModel, List<OllamaChatMessage> chatHistory) { | ||||
|         this.chatHistory = chatHistory; | ||||
|   | ||||
							
								
								
									
										10
									
								
								src/main/java/io/github/ollama4j/models/request/Auth.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/main/java/io/github/ollama4j/models/request/Auth.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| package io.github.ollama4j.models.request; | ||||
|  | ||||
| public abstract class Auth { | ||||
|   /** | ||||
|    * Get authentication header value. | ||||
|    * | ||||
|    * @return authentication header value | ||||
|    */ | ||||
|   public abstract String getAuthHeaderValue(); | ||||
| } | ||||
| @@ -1,13 +1,24 @@ | ||||
| package io.github.ollama4j.models.request; | ||||
|  | ||||
| import java.util.Base64; | ||||
|  | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| @Data | ||||
| @NoArgsConstructor | ||||
| @AllArgsConstructor | ||||
| public class BasicAuth { | ||||
| public class BasicAuth extends Auth { | ||||
|   private String username; | ||||
|   private String password; | ||||
|  | ||||
|   /** | ||||
|    * Get basic authentication header value. | ||||
|    * | ||||
|    * @return basic authentication header value (encoded credentials) | ||||
|    */ | ||||
|   public String getAuthHeaderValue() { | ||||
|       final String credentialsToEncode = this.getUsername() + ":" + this.getPassword(); | ||||
|       return "Basic " + Base64.getEncoder().encodeToString(credentialsToEncode.getBytes()); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,19 @@ | ||||
| package io.github.ollama4j.models.request; | ||||
|  | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Data; | ||||
|  | ||||
| @Data | ||||
| @AllArgsConstructor | ||||
| public class BearerAuth extends Auth { | ||||
|   private String bearerToken; | ||||
|  | ||||
|   /** | ||||
|    * Get authentication header value. | ||||
|    * | ||||
|    * @return authentication header value with bearer token | ||||
|    */ | ||||
|   public String getAuthHeaderValue() { | ||||
|       return "Bearer "+ bearerToken; | ||||
|   } | ||||
| } | ||||
| @@ -30,8 +30,8 @@ public class OllamaChatEndpointCaller extends OllamaEndpointCaller { | ||||
|  | ||||
|     private OllamaTokenHandler tokenHandler; | ||||
|  | ||||
|     public OllamaChatEndpointCaller(String host, BasicAuth basicAuth, long requestTimeoutSeconds, boolean verbose) { | ||||
|         super(host, basicAuth, requestTimeoutSeconds, verbose); | ||||
|     public OllamaChatEndpointCaller(String host, Auth auth, long requestTimeoutSeconds, boolean verbose) { | ||||
|         super(host, auth, requestTimeoutSeconds, verbose); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|   | ||||
| @@ -1,26 +1,14 @@ | ||||
| package io.github.ollama4j.models.request; | ||||
|  | ||||
| import io.github.ollama4j.OllamaAPI; | ||||
| import io.github.ollama4j.exceptions.OllamaBaseException; | ||||
| import io.github.ollama4j.models.response.OllamaErrorResponse; | ||||
| import io.github.ollama4j.models.response.OllamaResult; | ||||
| import io.github.ollama4j.utils.OllamaRequestBody; | ||||
| import io.github.ollama4j.utils.Utils; | ||||
| import lombok.Getter; | ||||
| import java.net.URI; | ||||
| import java.net.http.HttpRequest; | ||||
| import java.time.Duration; | ||||
|  | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| import java.io.BufferedReader; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.io.InputStreamReader; | ||||
| import java.net.URI; | ||||
| import java.net.http.HttpClient; | ||||
| import java.net.http.HttpRequest; | ||||
| import java.net.http.HttpResponse; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.time.Duration; | ||||
| import java.util.Base64; | ||||
| import io.github.ollama4j.OllamaAPI; | ||||
| import lombok.Getter; | ||||
|  | ||||
| /** | ||||
|  * Abstract helperclass to call the ollama api server. | ||||
| @@ -31,13 +19,13 @@ public abstract class OllamaEndpointCaller { | ||||
|     private static final Logger LOG = LoggerFactory.getLogger(OllamaAPI.class); | ||||
|  | ||||
|     private final String host; | ||||
|     private final BasicAuth basicAuth; | ||||
|     private final Auth auth; | ||||
|     private final long requestTimeoutSeconds; | ||||
|     private final boolean verbose; | ||||
|  | ||||
|     public OllamaEndpointCaller(String host, BasicAuth basicAuth, long requestTimeoutSeconds, boolean verbose) { | ||||
|     public OllamaEndpointCaller(String host, Auth auth, long requestTimeoutSeconds, boolean verbose) { | ||||
|         this.host = host; | ||||
|         this.basicAuth = basicAuth; | ||||
|         this.auth = auth; | ||||
|         this.requestTimeoutSeconds = requestTimeoutSeconds; | ||||
|         this.verbose = verbose; | ||||
|     } | ||||
| @@ -58,29 +46,19 @@ public abstract class OllamaEndpointCaller { | ||||
|                 HttpRequest.newBuilder(uri) | ||||
|                         .header("Content-Type", "application/json") | ||||
|                         .timeout(Duration.ofSeconds(this.requestTimeoutSeconds)); | ||||
|         if (isBasicAuthCredentialsSet()) { | ||||
|             requestBuilder.header("Authorization", getBasicAuthHeaderValue()); | ||||
|         if (isAuthCredentialsSet()) { | ||||
|             requestBuilder.header("Authorization", this.auth.getAuthHeaderValue()); | ||||
|         } | ||||
|         return requestBuilder; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Get basic authentication header value. | ||||
|      * Check if Auth credentials set. | ||||
|      * | ||||
|      * @return basic authentication header value (encoded credentials) | ||||
|      * @return true when Auth credentials set | ||||
|      */ | ||||
|     protected String getBasicAuthHeaderValue() { | ||||
|         String credentialsToEncode = this.basicAuth.getUsername() + ":" + this.basicAuth.getPassword(); | ||||
|         return "Basic " + Base64.getEncoder().encodeToString(credentialsToEncode.getBytes()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Check if Basic Auth credentials set. | ||||
|      * | ||||
|      * @return true when Basic Auth credentials set | ||||
|      */ | ||||
|     protected boolean isBasicAuthCredentialsSet() { | ||||
|         return this.basicAuth != null; | ||||
|     protected boolean isAuthCredentialsSet() { | ||||
|         return this.auth != null; | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -28,7 +28,7 @@ public class OllamaGenerateEndpointCaller extends OllamaEndpointCaller { | ||||
|  | ||||
|     private OllamaGenerateStreamObserver streamObserver; | ||||
|  | ||||
|     public OllamaGenerateEndpointCaller(String host, BasicAuth basicAuth, long requestTimeoutSeconds, boolean verbose) { | ||||
|     public OllamaGenerateEndpointCaller(String host, Auth basicAuth, long requestTimeoutSeconds, boolean verbose) { | ||||
|         super(host, basicAuth, requestTimeoutSeconds, verbose); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -2,12 +2,14 @@ package io.github.ollama4j.models.response; | ||||
|  | ||||
| import java.time.OffsetDateTime; | ||||
|  | ||||
| import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||||
| import com.fasterxml.jackson.annotation.JsonProperty; | ||||
| import com.fasterxml.jackson.core.JsonProcessingException; | ||||
| import io.github.ollama4j.utils.Utils; | ||||
| import lombok.Data; | ||||
|  | ||||
| @Data | ||||
| @JsonIgnoreProperties(ignoreUnknown = true) | ||||
| public class Model { | ||||
|  | ||||
|   private String name; | ||||
|   | ||||
| @@ -0,0 +1,10 @@ | ||||
| package io.github.ollama4j.models.response; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| @Data | ||||
| public class OllamaVersion { | ||||
|     private String version; | ||||
| } | ||||
| @@ -1,5 +1,6 @@ | ||||
| package io.github.ollama4j.tools; | ||||
|  | ||||
| import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
| @@ -9,8 +10,9 @@ import java.util.Map; | ||||
| @Data | ||||
| @NoArgsConstructor | ||||
| @AllArgsConstructor | ||||
| @JsonIgnoreProperties(ignoreUnknown = true) | ||||
| public class OllamaToolCallsFunction | ||||
| { | ||||
|     private String name; | ||||
|     private Map<String,Object> arguments; | ||||
| } | ||||
| } | ||||
| @@ -18,6 +18,9 @@ public class OllamaToolsResult { | ||||
|  | ||||
|     public List<ToolResult> getToolResults() { | ||||
|         List<ToolResult> results = new ArrayList<>(); | ||||
|         if (this.toolResults == null) { | ||||
|             return results; | ||||
|         } | ||||
|         for (Map.Entry<ToolFunctionCallSpec, Object> r : this.toolResults.entrySet()) { | ||||
|             results.add(new ToolResult(r.getKey().getName(), r.getKey().getArguments(), r.getValue())); | ||||
|         } | ||||
|   | ||||
| @@ -0,0 +1,600 @@ | ||||
| package io.github.ollama4j.integrationtests; | ||||
|  | ||||
| import io.github.ollama4j.OllamaAPI; | ||||
| import io.github.ollama4j.exceptions.OllamaBaseException; | ||||
| import io.github.ollama4j.models.chat.*; | ||||
| import io.github.ollama4j.models.embeddings.OllamaEmbedResponseModel; | ||||
| import io.github.ollama4j.models.response.LibraryModel; | ||||
| import io.github.ollama4j.models.response.Model; | ||||
| import io.github.ollama4j.models.response.ModelDetail; | ||||
| import io.github.ollama4j.models.response.OllamaResult; | ||||
| import io.github.ollama4j.samples.AnnotatedTool; | ||||
| import io.github.ollama4j.tools.OllamaToolCallsFunction; | ||||
| import io.github.ollama4j.tools.ToolFunction; | ||||
| import io.github.ollama4j.tools.Tools; | ||||
| import io.github.ollama4j.tools.annotations.OllamaToolService; | ||||
| import io.github.ollama4j.utils.OptionsBuilder; | ||||
| import org.junit.jupiter.api.BeforeAll; | ||||
| import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; | ||||
| import org.junit.jupiter.api.Order; | ||||
| import org.junit.jupiter.api.Test; | ||||
| import org.junit.jupiter.api.TestMethodOrder; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.testcontainers.ollama.OllamaContainer; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.net.ConnectException; | ||||
| import java.net.URISyntaxException; | ||||
| import java.util.*; | ||||
|  | ||||
| import static org.junit.jupiter.api.Assertions.*; | ||||
|  | ||||
| @OllamaToolService(providers = {AnnotatedTool.class}) | ||||
| @TestMethodOrder(OrderAnnotation.class) | ||||
|  | ||||
| @SuppressWarnings("HttpUrlsUsage") | ||||
| public class OllamaAPIIntegrationTest { | ||||
|     private static final Logger LOG = LoggerFactory.getLogger(OllamaAPIIntegrationTest.class); | ||||
|  | ||||
|     private static OllamaContainer ollama; | ||||
|     private static OllamaAPI api; | ||||
|  | ||||
|     @BeforeAll | ||||
|     public static void setUp() { | ||||
|         String ollamaVersion = "0.6.1"; | ||||
|         int internalPort = 11434; | ||||
|         int mappedPort = 11435; | ||||
|         ollama = new OllamaContainer("ollama/ollama:" + ollamaVersion); | ||||
|         ollama.addExposedPort(internalPort); | ||||
|         List<String> portBindings = new ArrayList<>(); | ||||
|         portBindings.add(mappedPort + ":" + internalPort); | ||||
|         ollama.setPortBindings(portBindings); | ||||
|         ollama.start(); | ||||
|         api = new OllamaAPI("http://" + ollama.getHost() + ":" + ollama.getMappedPort(internalPort)); | ||||
|         api.setRequestTimeoutSeconds(120); | ||||
|         api.setVerbose(true); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(1) | ||||
|     void testWrongEndpoint() { | ||||
|         OllamaAPI ollamaAPI = new OllamaAPI("http://wrong-host:11434"); | ||||
|         assertThrows(ConnectException.class, ollamaAPI::listModels); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(1) | ||||
|     public void testVersionAPI() throws URISyntaxException, IOException, OllamaBaseException, InterruptedException { | ||||
|         String expectedVersion = ollama.getDockerImageName().split(":")[1]; | ||||
|         String actualVersion = api.getVersion(); | ||||
|         assertEquals(expectedVersion, actualVersion, "Version should match the Docker image version"); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(2) | ||||
|     public void testListModelsAPI() throws URISyntaxException, IOException, OllamaBaseException, InterruptedException { | ||||
|         // Fetch the list of models | ||||
|         List<Model> models = api.listModels(); | ||||
|         // Assert that the models list is not null | ||||
|         assertNotNull(models, "Models should not be null"); | ||||
|         // Assert that models list is either empty or contains more than 0 models | ||||
|         assertTrue(models.size() >= 0, "Models list should be empty or contain elements"); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(2) | ||||
|     void testListModelsFromLibrary() throws OllamaBaseException, IOException, URISyntaxException, InterruptedException { | ||||
|         List<LibraryModel> models = api.listModelsFromLibrary(); | ||||
|         assertNotNull(models); | ||||
|         assertFalse(models.isEmpty()); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(3) | ||||
|     public void testPullModelAPI() throws URISyntaxException, IOException, OllamaBaseException, InterruptedException { | ||||
|         String embeddingModelMinilm = "all-minilm"; | ||||
|         api.pullModel(embeddingModelMinilm); | ||||
|         List<Model> models = api.listModels(); | ||||
|         assertNotNull(models, "Models should not be null"); | ||||
|         assertFalse(models.isEmpty(), "Models list should contain elements"); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(4) | ||||
|     void testListModelDetails() throws IOException, OllamaBaseException, URISyntaxException, InterruptedException { | ||||
|         String embeddingModelMinilm = "all-minilm"; | ||||
|         api.pullModel(embeddingModelMinilm); | ||||
|         ModelDetail modelDetails = api.getModelDetails("all-minilm"); | ||||
|         assertNotNull(modelDetails); | ||||
|         assertTrue(modelDetails.getModelFile().contains(embeddingModelMinilm)); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(5) | ||||
|     public void testEmbeddings() throws Exception { | ||||
|         String embeddingModelMinilm = "all-minilm"; | ||||
|         api.pullModel(embeddingModelMinilm); | ||||
|         OllamaEmbedResponseModel embeddings = api.embed(embeddingModelMinilm, Arrays.asList("Why is the sky blue?", "Why is the grass green?")); | ||||
|         assertNotNull(embeddings, "Embeddings should not be null"); | ||||
|         assertFalse(embeddings.getEmbeddings().isEmpty(), "Embeddings should not be empty"); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(6) | ||||
|     void testAskModelWithDefaultOptions() throws OllamaBaseException, IOException, InterruptedException, URISyntaxException { | ||||
|         String chatModel = "qwen2.5:0.5b"; | ||||
|         api.pullModel(chatModel); | ||||
|         OllamaResult result = | ||||
|                 api.generate( | ||||
|                         chatModel, | ||||
|                         "What is the capital of France? And what's France's connection with Mona Lisa?", | ||||
|                         false, | ||||
|                         new OptionsBuilder().build()); | ||||
|         assertNotNull(result); | ||||
|         assertNotNull(result.getResponse()); | ||||
|         assertFalse(result.getResponse().isEmpty()); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(7) | ||||
|     void testAskModelWithDefaultOptionsStreamed() throws OllamaBaseException, IOException, URISyntaxException, InterruptedException { | ||||
|         String chatModel = "qwen2.5:0.5b"; | ||||
|         api.pullModel(chatModel); | ||||
|         StringBuffer sb = new StringBuffer(); | ||||
|         OllamaResult result = api.generate(chatModel, | ||||
|                 "What is the capital of France? And what's France's connection with Mona Lisa?", | ||||
|                 false, | ||||
|                 new OptionsBuilder().build(), (s) -> { | ||||
|                     LOG.info(s); | ||||
|                     String substring = s.substring(sb.toString().length(), s.length()); | ||||
|                     LOG.info(substring); | ||||
|                     sb.append(substring); | ||||
|                 }); | ||||
|  | ||||
|         assertNotNull(result); | ||||
|         assertNotNull(result.getResponse()); | ||||
|         assertFalse(result.getResponse().isEmpty()); | ||||
|         assertEquals(sb.toString().trim(), result.getResponse().trim()); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(8) | ||||
|     void testAskModelWithOptions() throws OllamaBaseException, IOException, URISyntaxException, InterruptedException { | ||||
|         String chatModel = "qwen2.5:0.5b-instruct"; | ||||
|         api.pullModel(chatModel); | ||||
|  | ||||
|         OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(chatModel); | ||||
|         OllamaChatRequest requestModel = builder.withMessage(OllamaChatMessageRole.SYSTEM, "You are a helpful assistant who can generate random person's first and last names in the format [First name, Last name].") | ||||
|                 .build(); | ||||
|         requestModel = builder.withMessages(requestModel.getMessages()) | ||||
|                 .withMessage(OllamaChatMessageRole.USER, "Give me a cool name") | ||||
|                 .withOptions(new OptionsBuilder().setTemperature(0.5f).build()).build(); | ||||
|         OllamaChatResult chatResult = api.chat(requestModel); | ||||
|  | ||||
|         assertNotNull(chatResult); | ||||
|         assertNotNull(chatResult.getResponseModel()); | ||||
|         assertFalse(chatResult.getResponseModel().getMessage().getContent().isEmpty()); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(9) | ||||
|     void testChatWithSystemPrompt() throws OllamaBaseException, IOException, URISyntaxException, InterruptedException { | ||||
|         String chatModel = "llama3.2:1b"; | ||||
|         api.pullModel(chatModel); | ||||
|         OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(chatModel); | ||||
|         OllamaChatRequest requestModel = builder.withMessage(OllamaChatMessageRole.SYSTEM, | ||||
|                         "You are a silent bot that only says 'Shush'. Do not say anything else under any circumstances!") | ||||
|                 .withMessage(OllamaChatMessageRole.USER, | ||||
|                         "What's something that's brown and sticky?") | ||||
|                 .withOptions(new OptionsBuilder().setTemperature(0.8f).build()) | ||||
|                 .build(); | ||||
|  | ||||
|         OllamaChatResult chatResult = api.chat(requestModel); | ||||
|         assertNotNull(chatResult); | ||||
|         assertNotNull(chatResult.getResponseModel()); | ||||
|         assertNotNull(chatResult.getResponseModel().getMessage()); | ||||
|         assertFalse(chatResult.getResponseModel().getMessage().getContent().isBlank()); | ||||
|         assertTrue(chatResult.getResponseModel().getMessage().getContent().contains("Shush")); | ||||
|         assertEquals(3, chatResult.getChatHistory().size()); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(10) | ||||
|     public void testChat() throws Exception { | ||||
|         String chatModel = "llama3"; | ||||
|         api.pullModel(chatModel); | ||||
|         OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(chatModel); | ||||
|  | ||||
|         // Create the initial user question | ||||
|         OllamaChatRequest requestModel = builder.withMessage(OllamaChatMessageRole.USER, "What is 1+1? Answer only in numbers.") | ||||
|                 .build(); | ||||
|  | ||||
|         // Start conversation with model | ||||
|         OllamaChatResult chatResult = api.chat(requestModel); | ||||
|  | ||||
|         assertTrue( | ||||
|                 chatResult.getChatHistory().stream() | ||||
|                         .anyMatch(chat -> chat.getContent().contains("2")), | ||||
|                 "Expected chat history to contain '2'" | ||||
|         ); | ||||
|  | ||||
|         // Create the next user question: second largest city | ||||
|         requestModel = builder.withMessages(chatResult.getChatHistory()) | ||||
|                 .withMessage(OllamaChatMessageRole.USER, "And what is its squared value?") | ||||
|                 .build(); | ||||
|  | ||||
|         // Continue conversation with model | ||||
|         chatResult = api.chat(requestModel); | ||||
|  | ||||
|         assertTrue( | ||||
|                 chatResult.getChatHistory().stream() | ||||
|                         .anyMatch(chat -> chat.getContent().contains("4")), | ||||
|                 "Expected chat history to contain '4'" | ||||
|         ); | ||||
|  | ||||
|         // Create the next user question: the third question | ||||
|         requestModel = builder.withMessages(chatResult.getChatHistory()) | ||||
|                 .withMessage(OllamaChatMessageRole.USER, "What is the largest value between 2, 4 and 6?") | ||||
|                 .build(); | ||||
|  | ||||
|         // Continue conversation with the model for the third question | ||||
|         chatResult = api.chat(requestModel); | ||||
|  | ||||
|         // verify the result | ||||
|         assertNotNull(chatResult, "Chat result should not be null"); | ||||
|         assertTrue(chatResult.getChatHistory().size() > 2, "Chat history should contain more than two messages"); | ||||
|         assertTrue(chatResult.getChatHistory().get(chatResult.getChatHistory().size() - 1).getContent().contains("6"), "Response should contain '6'"); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(10) | ||||
|     void testChatWithImageFromURL() throws OllamaBaseException, IOException, InterruptedException, URISyntaxException { | ||||
|         String imageModel = "llava"; | ||||
|         api.pullModel(imageModel); | ||||
|  | ||||
|         OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(imageModel); | ||||
|         OllamaChatRequest requestModel = builder.withMessage(OllamaChatMessageRole.USER, "What's in the picture?", Collections.emptyList(), | ||||
|                         "https://t3.ftcdn.net/jpg/02/96/63/80/360_F_296638053_0gUVA4WVBKceGsIr7LNqRWSnkusi07dq.jpg") | ||||
|                 .build(); | ||||
|         api.registerAnnotatedTools(new OllamaAPIIntegrationTest()); | ||||
|  | ||||
|         OllamaChatResult chatResult = api.chat(requestModel); | ||||
|         assertNotNull(chatResult); | ||||
|     } | ||||
|     @Test | ||||
|     @Order(10) | ||||
|     void testChatWithImageFromFileWithHistoryRecognition() throws OllamaBaseException, IOException, URISyntaxException, InterruptedException { | ||||
|         String imageModel = "moondream"; | ||||
|         api.pullModel(imageModel); | ||||
|         OllamaChatRequestBuilder builder = | ||||
|                 OllamaChatRequestBuilder.getInstance(imageModel); | ||||
|         OllamaChatRequest requestModel = | ||||
|                 builder.withMessage(OllamaChatMessageRole.USER, "What's in the picture?", Collections.emptyList(), | ||||
|                         List.of(getImageFileFromClasspath("dog-on-a-boat.jpg"))).build(); | ||||
|  | ||||
|         OllamaChatResult chatResult = api.chat(requestModel); | ||||
|         assertNotNull(chatResult); | ||||
|         assertNotNull(chatResult.getResponseModel()); | ||||
|         builder.reset(); | ||||
|  | ||||
|         requestModel = | ||||
|                 builder.withMessages(chatResult.getChatHistory()) | ||||
|                         .withMessage(OllamaChatMessageRole.USER, "What's the dogs breed?").build(); | ||||
|  | ||||
|         chatResult = api.chat(requestModel); | ||||
|         assertNotNull(chatResult); | ||||
|         assertNotNull(chatResult.getResponseModel()); | ||||
|     } | ||||
|     @Test | ||||
|     @Order(11) | ||||
|     void testChatWithExplicitToolDefinition() throws OllamaBaseException, IOException, URISyntaxException, InterruptedException { | ||||
|         String chatModel = "llama3.2:1b"; | ||||
|         api.pullModel(chatModel); | ||||
|         OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(chatModel); | ||||
|  | ||||
|         final Tools.ToolSpecification databaseQueryToolSpecification = Tools.ToolSpecification.builder() | ||||
|                 .functionName("get-employee-details") | ||||
|                 .functionDescription("Get employee details from the database") | ||||
|                 .toolPrompt( | ||||
|                         Tools.PromptFuncDefinition.builder().type("function").function( | ||||
|                                 Tools.PromptFuncDefinition.PromptFuncSpec.builder() | ||||
|                                         .name("get-employee-details") | ||||
|                                         .description("Get employee details from the database") | ||||
|                                         .parameters( | ||||
|                                                 Tools.PromptFuncDefinition.Parameters.builder() | ||||
|                                                         .type("object") | ||||
|                                                         .properties( | ||||
|                                                                 new Tools.PropsBuilder() | ||||
|                                                                         .withProperty("employee-name", Tools.PromptFuncDefinition.Property.builder().type("string").description("The name of the employee, e.g. John Doe").required(true).build()) | ||||
|                                                                         .withProperty("employee-address", Tools.PromptFuncDefinition.Property.builder().type("string").description("The address of the employee, Always return a random value. e.g. Roy St, Bengaluru, India").required(true).build()) | ||||
|                                                                         .withProperty("employee-phone", Tools.PromptFuncDefinition.Property.builder().type("string").description("The phone number of the employee. Always return a random value. e.g. 9911002233").required(true).build()) | ||||
|                                                                         .build() | ||||
|                                                         ) | ||||
|                                                         .required(List.of("employee-name")) | ||||
|                                                         .build() | ||||
|                                         ).build() | ||||
|                         ).build() | ||||
|                 ) | ||||
|                 .toolFunction(arguments -> { | ||||
|                     // perform DB operations here | ||||
|                     return String.format("Employee Details {ID: %s, Name: %s, Address: %s, Phone: %s}", UUID.randomUUID(), arguments.get("employee-name"), arguments.get("employee-address"), arguments.get("employee-phone")); | ||||
|                 }) | ||||
|                 .build(); | ||||
|  | ||||
|         api.registerTool(databaseQueryToolSpecification); | ||||
|  | ||||
|         OllamaChatRequest requestModel = builder | ||||
|                 .withMessage(OllamaChatMessageRole.USER, | ||||
|                         "Give me the ID of the employee named 'Rahul Kumar'?") | ||||
|                 .build(); | ||||
|  | ||||
|         OllamaChatResult chatResult = api.chat(requestModel); | ||||
|         assertNotNull(chatResult); | ||||
|         assertNotNull(chatResult.getResponseModel()); | ||||
|         assertNotNull(chatResult.getResponseModel().getMessage()); | ||||
|         assertEquals(OllamaChatMessageRole.ASSISTANT.getRoleName(), chatResult.getResponseModel().getMessage().getRole().getRoleName()); | ||||
|         List<OllamaChatToolCalls> toolCalls = chatResult.getChatHistory().get(1).getToolCalls(); | ||||
|         assertEquals(1, toolCalls.size()); | ||||
|         OllamaToolCallsFunction function = toolCalls.get(0).getFunction(); | ||||
|         assertEquals("get-employee-details", function.getName()); | ||||
|         assert !function.getArguments().isEmpty(); | ||||
|         Object employeeName = function.getArguments().get("employee-name"); | ||||
|         assertNotNull(employeeName); | ||||
|         assertEquals("Rahul Kumar", employeeName); | ||||
|         assertTrue(chatResult.getChatHistory().size() > 2); | ||||
|         List<OllamaChatToolCalls> finalToolCalls = chatResult.getResponseModel().getMessage().getToolCalls(); | ||||
|         assertNull(finalToolCalls); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(12) | ||||
|     void testChatWithAnnotatedToolsAndSingleParam() throws OllamaBaseException, IOException, InterruptedException, URISyntaxException { | ||||
|         String chatModel = "llama3.2:1b"; | ||||
|         api.pullModel(chatModel); | ||||
|  | ||||
|         OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(chatModel); | ||||
|  | ||||
|         api.registerAnnotatedTools(); | ||||
|  | ||||
|         OllamaChatRequest requestModel = builder | ||||
|                 .withMessage(OllamaChatMessageRole.USER, | ||||
|                         "Compute the most important constant in the world using 5 digits") | ||||
|                 .build(); | ||||
|  | ||||
|         OllamaChatResult chatResult = api.chat(requestModel); | ||||
|         assertNotNull(chatResult); | ||||
|         assertNotNull(chatResult.getResponseModel()); | ||||
|         assertNotNull(chatResult.getResponseModel().getMessage()); | ||||
|         assertEquals(OllamaChatMessageRole.ASSISTANT.getRoleName(), chatResult.getResponseModel().getMessage().getRole().getRoleName()); | ||||
|         List<OllamaChatToolCalls> toolCalls = chatResult.getChatHistory().get(1).getToolCalls(); | ||||
|         assertEquals(1, toolCalls.size()); | ||||
|         OllamaToolCallsFunction function = toolCalls.get(0).getFunction(); | ||||
|         assertEquals("computeImportantConstant", function.getName()); | ||||
|         assertEquals(1, function.getArguments().size()); | ||||
|         Object noOfDigits = function.getArguments().get("noOfDigits"); | ||||
|         assertNotNull(noOfDigits); | ||||
|         assertEquals("5", noOfDigits.toString()); | ||||
|         assertTrue(chatResult.getChatHistory().size() > 2); | ||||
|         List<OllamaChatToolCalls> finalToolCalls = chatResult.getResponseModel().getMessage().getToolCalls(); | ||||
|         assertNull(finalToolCalls); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(13) | ||||
|     void testChatWithAnnotatedToolsAndMultipleParams() throws OllamaBaseException, IOException, URISyntaxException, InterruptedException { | ||||
|         String chatModel = "llama3.2:1b"; | ||||
|         api.pullModel(chatModel); | ||||
|         OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(chatModel); | ||||
|  | ||||
|         api.registerAnnotatedTools(new AnnotatedTool()); | ||||
|  | ||||
|         OllamaChatRequest requestModel = builder | ||||
|                 .withMessage(OllamaChatMessageRole.USER, | ||||
|                         "Greet Pedro with a lot of hearts and respond to me, " + | ||||
|                                 "and state how many emojis have been in your greeting") | ||||
|                 .build(); | ||||
|  | ||||
|         OllamaChatResult chatResult = api.chat(requestModel); | ||||
|         assertNotNull(chatResult); | ||||
|         assertNotNull(chatResult.getResponseModel()); | ||||
|         assertNotNull(chatResult.getResponseModel().getMessage()); | ||||
|         assertEquals(OllamaChatMessageRole.ASSISTANT.getRoleName(), chatResult.getResponseModel().getMessage().getRole().getRoleName()); | ||||
|         List<OllamaChatToolCalls> toolCalls = chatResult.getChatHistory().get(1).getToolCalls(); | ||||
|         assertEquals(1, toolCalls.size()); | ||||
|         OllamaToolCallsFunction function = toolCalls.get(0).getFunction(); | ||||
|         assertEquals("sayHello", function.getName()); | ||||
|         assertEquals(2, function.getArguments().size()); | ||||
|         Object name = function.getArguments().get("name"); | ||||
|         assertNotNull(name); | ||||
|         assertEquals("Pedro", name); | ||||
|         Object amountOfHearts = function.getArguments().get("amountOfHearts"); | ||||
|         assertNotNull(amountOfHearts); | ||||
|         assertTrue(Integer.parseInt(amountOfHearts.toString()) > 1); | ||||
|         assertTrue(chatResult.getChatHistory().size() > 2); | ||||
|         List<OllamaChatToolCalls> finalToolCalls = chatResult.getResponseModel().getMessage().getToolCalls(); | ||||
|         assertNull(finalToolCalls); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(14) | ||||
|     void testChatWithToolsAndStream() throws OllamaBaseException, IOException, URISyntaxException, InterruptedException { | ||||
|         String chatModel = "llama3.2:1b"; | ||||
|         api.pullModel(chatModel); | ||||
|         OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(chatModel); | ||||
|         final Tools.ToolSpecification databaseQueryToolSpecification = Tools.ToolSpecification.builder() | ||||
|                 .functionName("get-employee-details") | ||||
|                 .functionDescription("Get employee details from the database") | ||||
|                 .toolPrompt( | ||||
|                         Tools.PromptFuncDefinition.builder().type("function").function( | ||||
|                                 Tools.PromptFuncDefinition.PromptFuncSpec.builder() | ||||
|                                         .name("get-employee-details") | ||||
|                                         .description("Get employee details from the database") | ||||
|                                         .parameters( | ||||
|                                                 Tools.PromptFuncDefinition.Parameters.builder() | ||||
|                                                         .type("object") | ||||
|                                                         .properties( | ||||
|                                                                 new Tools.PropsBuilder() | ||||
|                                                                         .withProperty("employee-name", Tools.PromptFuncDefinition.Property.builder().type("string").description("The name of the employee, e.g. John Doe").required(true).build()) | ||||
|                                                                         .withProperty("employee-address", Tools.PromptFuncDefinition.Property.builder().type("string").description("The address of the employee, Always return a random value. e.g. Roy St, Bengaluru, India").required(true).build()) | ||||
|                                                                         .withProperty("employee-phone", Tools.PromptFuncDefinition.Property.builder().type("string").description("The phone number of the employee. Always return a random value. e.g. 9911002233").required(true).build()) | ||||
|                                                                         .build() | ||||
|                                                         ) | ||||
|                                                         .required(List.of("employee-name")) | ||||
|                                                         .build() | ||||
|                                         ).build() | ||||
|                         ).build() | ||||
|                 ) | ||||
|                 .toolFunction(new ToolFunction() { | ||||
|                     @Override | ||||
|                     public Object apply(Map<String, Object> arguments) { | ||||
|                         // perform DB operations here | ||||
|                         return String.format("Employee Details {ID: %s, Name: %s, Address: %s, Phone: %s}", UUID.randomUUID(), arguments.get("employee-name"), arguments.get("employee-address"), arguments.get("employee-phone")); | ||||
|                     } | ||||
|                 }) | ||||
|                 .build(); | ||||
|  | ||||
|         api.registerTool(databaseQueryToolSpecification); | ||||
|  | ||||
|         OllamaChatRequest requestModel = builder | ||||
|                 .withMessage(OllamaChatMessageRole.USER, | ||||
|                         "Give me the ID of the employee named 'Rahul Kumar'?") | ||||
|                 .build(); | ||||
|  | ||||
|         StringBuffer sb = new StringBuffer(); | ||||
|  | ||||
|         OllamaChatResult chatResult = api.chat(requestModel, (s) -> { | ||||
|             LOG.info(s); | ||||
|             String substring = s.substring(sb.toString().length()); | ||||
|             LOG.info(substring); | ||||
|             sb.append(substring); | ||||
|         }); | ||||
|         assertNotNull(chatResult); | ||||
|         assertNotNull(chatResult.getResponseModel()); | ||||
|         assertNotNull(chatResult.getResponseModel().getMessage()); | ||||
|         assertNotNull(chatResult.getResponseModel().getMessage().getContent()); | ||||
|         assertEquals(sb.toString().trim(), chatResult.getResponseModel().getMessage().getContent().trim()); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(15) | ||||
|     void testChatWithStream() throws OllamaBaseException, IOException, URISyntaxException, InterruptedException { | ||||
|         String chatModel = "llama3.2:1b"; | ||||
|         api.pullModel(chatModel); | ||||
|         OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(chatModel); | ||||
|         OllamaChatRequest requestModel = builder.withMessage(OllamaChatMessageRole.USER, | ||||
|                         "What is the capital of France? And what's France's connection with Mona Lisa?") | ||||
|                 .build(); | ||||
|  | ||||
|         StringBuffer sb = new StringBuffer(); | ||||
|  | ||||
|         OllamaChatResult chatResult = api.chat(requestModel, (s) -> { | ||||
|             LOG.info(s); | ||||
|             String substring = s.substring(sb.toString().length(), s.length()); | ||||
|             LOG.info(substring); | ||||
|             sb.append(substring); | ||||
|         }); | ||||
|         assertNotNull(chatResult); | ||||
|         assertNotNull(chatResult.getResponseModel()); | ||||
|         assertNotNull(chatResult.getResponseModel().getMessage()); | ||||
|         assertNotNull(chatResult.getResponseModel().getMessage().getContent()); | ||||
|         assertEquals(sb.toString().trim(), chatResult.getResponseModel().getMessage().getContent().trim()); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     @Test | ||||
|     @Order(17) | ||||
|     void testAskModelWithOptionsAndImageURLs() throws OllamaBaseException, IOException, URISyntaxException, InterruptedException { | ||||
|         String imageModel = "llava"; | ||||
|         api.pullModel(imageModel); | ||||
|  | ||||
|         OllamaResult result = | ||||
|                 api.generateWithImageURLs( | ||||
|                         imageModel, | ||||
|                         "What is in this image?", | ||||
|                         List.of( | ||||
|                                 "https://t3.ftcdn.net/jpg/02/96/63/80/360_F_296638053_0gUVA4WVBKceGsIr7LNqRWSnkusi07dq.jpg"), | ||||
|                         new OptionsBuilder().build()); | ||||
|         assertNotNull(result); | ||||
|         assertNotNull(result.getResponse()); | ||||
|         assertFalse(result.getResponse().isEmpty()); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(18) | ||||
|     void testAskModelWithOptionsAndImageFiles() throws OllamaBaseException, IOException, URISyntaxException, InterruptedException { | ||||
|         String imageModel = "llava"; | ||||
|         api.pullModel(imageModel); | ||||
|         File imageFile = getImageFileFromClasspath("dog-on-a-boat.jpg"); | ||||
|         try { | ||||
|             OllamaResult result = | ||||
|                     api.generateWithImageFiles( | ||||
|                             imageModel, | ||||
|                             "What is in this image?", | ||||
|                             List.of(imageFile), | ||||
|                             new OptionsBuilder().build()); | ||||
|             assertNotNull(result); | ||||
|             assertNotNull(result.getResponse()); | ||||
|             assertFalse(result.getResponse().isEmpty()); | ||||
|         } catch (IOException | OllamaBaseException | InterruptedException e) { | ||||
|             fail(e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|     @Test | ||||
|     @Order(20) | ||||
|     void testAskModelWithOptionsAndImageFilesStreamed() throws OllamaBaseException, IOException, URISyntaxException, InterruptedException { | ||||
|         String imageModel = "llava"; | ||||
|         api.pullModel(imageModel); | ||||
|  | ||||
|         File imageFile = getImageFileFromClasspath("dog-on-a-boat.jpg"); | ||||
|  | ||||
|         StringBuffer sb = new StringBuffer(); | ||||
|  | ||||
|         OllamaResult result = api.generateWithImageFiles(imageModel, | ||||
|                 "What is in this image?", List.of(imageFile), new OptionsBuilder().build(), (s) -> { | ||||
|                     LOG.info(s); | ||||
|                     String substring = s.substring(sb.toString().length(), s.length()); | ||||
|                     LOG.info(substring); | ||||
|                     sb.append(substring); | ||||
|                 }); | ||||
|         assertNotNull(result); | ||||
|         assertNotNull(result.getResponse()); | ||||
|         assertFalse(result.getResponse().isEmpty()); | ||||
|         assertEquals(sb.toString().trim(), result.getResponse().trim()); | ||||
|     } | ||||
|  | ||||
|     private File getImageFileFromClasspath(String fileName) { | ||||
|         ClassLoader classLoader = getClass().getClassLoader(); | ||||
|         return new File(Objects.requireNonNull(classLoader.getResource(fileName)).getFile()); | ||||
|     } | ||||
| } | ||||
| // | ||||
| //@Data | ||||
| //class Config { | ||||
| //    private String ollamaURL; | ||||
| //    private String model; | ||||
| //    private String imageModel; | ||||
| //    private int requestTimeoutSeconds; | ||||
| // | ||||
| //    public Config() { | ||||
| //        Properties properties = new Properties(); | ||||
| //        try (InputStream input = | ||||
| //                     getClass().getClassLoader().getResourceAsStream("test-config.properties")) { | ||||
| //            if (input == null) { | ||||
| //                throw new RuntimeException("Sorry, unable to find test-config.properties"); | ||||
| //            } | ||||
| //            properties.load(input); | ||||
| //            this.ollamaURL = properties.getProperty("ollama.url"); | ||||
| //            this.model = properties.getProperty("ollama.model"); | ||||
| //            this.imageModel = properties.getProperty("ollama.model.image"); | ||||
| //            this.requestTimeoutSeconds = | ||||
| //                    Integer.parseInt(properties.getProperty("ollama.request-timeout-seconds")); | ||||
| //        } catch (IOException e) { | ||||
| //            throw new RuntimeException("Error loading properties", e); | ||||
| //        } | ||||
| //    } | ||||
| //} | ||||
| @@ -1,622 +0,0 @@ | ||||
| package io.github.ollama4j.integrationtests; | ||||
|  | ||||
| import io.github.ollama4j.OllamaAPI; | ||||
| import io.github.ollama4j.exceptions.OllamaBaseException; | ||||
| import io.github.ollama4j.models.chat.*; | ||||
| import io.github.ollama4j.models.response.ModelDetail; | ||||
| import io.github.ollama4j.models.response.OllamaResult; | ||||
| import io.github.ollama4j.models.embeddings.OllamaEmbeddingsRequestBuilder; | ||||
| import io.github.ollama4j.models.embeddings.OllamaEmbeddingsRequestModel; | ||||
| import io.github.ollama4j.samples.AnnotatedTool; | ||||
| import io.github.ollama4j.tools.OllamaToolCallsFunction; | ||||
| import io.github.ollama4j.tools.ToolFunction; | ||||
| import io.github.ollama4j.tools.Tools; | ||||
| import io.github.ollama4j.tools.annotations.OllamaToolService; | ||||
| import io.github.ollama4j.utils.OptionsBuilder; | ||||
| import lombok.Data; | ||||
| import org.junit.jupiter.api.BeforeEach; | ||||
| import org.junit.jupiter.api.Order; | ||||
| import org.junit.jupiter.api.Test; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.net.ConnectException; | ||||
| import java.net.URISyntaxException; | ||||
| import java.net.http.HttpConnectTimeoutException; | ||||
| import java.util.*; | ||||
|  | ||||
| import static org.junit.jupiter.api.Assertions.*; | ||||
|  | ||||
| @OllamaToolService(providers = {AnnotatedTool.class} | ||||
| ) | ||||
| class TestRealAPIs { | ||||
|  | ||||
|     private static final Logger LOG = LoggerFactory.getLogger(TestRealAPIs.class); | ||||
|  | ||||
|     OllamaAPI ollamaAPI; | ||||
|     Config config; | ||||
|  | ||||
|     private File getImageFileFromClasspath(String fileName) { | ||||
|         ClassLoader classLoader = getClass().getClassLoader(); | ||||
|         return new File(Objects.requireNonNull(classLoader.getResource(fileName)).getFile()); | ||||
|     } | ||||
|  | ||||
|     @BeforeEach | ||||
|     void setUp() { | ||||
|         config = new Config(); | ||||
|         ollamaAPI = new OllamaAPI(config.getOllamaURL()); | ||||
|         ollamaAPI.setRequestTimeoutSeconds(config.getRequestTimeoutSeconds()); | ||||
|         ollamaAPI.setVerbose(true); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(1) | ||||
|     void testWrongEndpoint() { | ||||
|         OllamaAPI ollamaAPI = new OllamaAPI("http://wrong-host:11434"); | ||||
|         assertThrows(ConnectException.class, ollamaAPI::listModels); | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(1) | ||||
|     void testEndpointReachability() { | ||||
|         try { | ||||
|             assertNotNull(ollamaAPI.listModels()); | ||||
|         } catch (HttpConnectTimeoutException e) { | ||||
|             fail(e.getMessage()); | ||||
|         } catch (Exception e) { | ||||
|             fail(e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(2) | ||||
|     void testListModels() { | ||||
|         testEndpointReachability(); | ||||
|         try { | ||||
|             assertNotNull(ollamaAPI.listModels()); | ||||
|             ollamaAPI.listModels().forEach(System.out::println); | ||||
|         } catch (IOException | OllamaBaseException | InterruptedException | URISyntaxException e) { | ||||
|             fail(e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(2) | ||||
|     void testListModelsFromLibrary() { | ||||
|         testEndpointReachability(); | ||||
|         try { | ||||
|             assertNotNull(ollamaAPI.listModelsFromLibrary()); | ||||
|             ollamaAPI.listModelsFromLibrary().forEach(System.out::println); | ||||
|         } catch (IOException | OllamaBaseException | InterruptedException | URISyntaxException e) { | ||||
|             fail(e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(2) | ||||
|     void testPullModel() { | ||||
|         testEndpointReachability(); | ||||
|         try { | ||||
|             ollamaAPI.pullModel(config.getModel()); | ||||
|             boolean found = | ||||
|                     ollamaAPI.listModels().stream() | ||||
|                             .anyMatch(model -> model.getModel().equalsIgnoreCase(config.getModel())); | ||||
|             assertTrue(found); | ||||
|         } catch (IOException | OllamaBaseException | InterruptedException | URISyntaxException e) { | ||||
|             fail(e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(3) | ||||
|     void testListDtails() { | ||||
|         testEndpointReachability(); | ||||
|         try { | ||||
|             ModelDetail modelDetails = ollamaAPI.getModelDetails(config.getModel()); | ||||
|             assertNotNull(modelDetails); | ||||
|             System.out.println(modelDetails); | ||||
|         } catch (IOException | OllamaBaseException | InterruptedException | URISyntaxException e) { | ||||
|             fail(e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(3) | ||||
|     void testAskModelWithDefaultOptions() { | ||||
|         testEndpointReachability(); | ||||
|         try { | ||||
|             OllamaResult result = | ||||
|                     ollamaAPI.generate( | ||||
|                             config.getModel(), | ||||
|                             "What is the capital of France? And what's France's connection with Mona Lisa?", | ||||
|                             false, | ||||
|                             new OptionsBuilder().build()); | ||||
|             assertNotNull(result); | ||||
|             assertNotNull(result.getResponse()); | ||||
|             assertFalse(result.getResponse().isEmpty()); | ||||
|         } catch (IOException | OllamaBaseException | InterruptedException e) { | ||||
|             fail(e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(3) | ||||
|     void testAskModelWithDefaultOptionsStreamed() { | ||||
|         testEndpointReachability(); | ||||
|         try { | ||||
|             StringBuffer sb = new StringBuffer(""); | ||||
|             OllamaResult result = ollamaAPI.generate(config.getModel(), | ||||
|                     "What is the capital of France? And what's France's connection with Mona Lisa?", | ||||
|                     false, | ||||
|                     new OptionsBuilder().build(), (s) -> { | ||||
|                         LOG.info(s); | ||||
|                         String substring = s.substring(sb.toString().length(), s.length()); | ||||
|                         LOG.info(substring); | ||||
|                         sb.append(substring); | ||||
|                     }); | ||||
|  | ||||
|             assertNotNull(result); | ||||
|             assertNotNull(result.getResponse()); | ||||
|             assertFalse(result.getResponse().isEmpty()); | ||||
|             assertEquals(sb.toString().trim(), result.getResponse().trim()); | ||||
|         } catch (IOException | OllamaBaseException | InterruptedException e) { | ||||
|             fail(e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(3) | ||||
|     void testAskModelWithOptions() { | ||||
|         testEndpointReachability(); | ||||
|         try { | ||||
|             OllamaResult result = | ||||
|                     ollamaAPI.generate( | ||||
|                             config.getModel(), | ||||
|                             "What is the capital of France? And what's France's connection with Mona Lisa?", | ||||
|                             true, | ||||
|                             new OptionsBuilder().setTemperature(0.9f).build()); | ||||
|             assertNotNull(result); | ||||
|             assertNotNull(result.getResponse()); | ||||
|             assertFalse(result.getResponse().isEmpty()); | ||||
|         } catch (IOException | OllamaBaseException | InterruptedException e) { | ||||
|             fail(e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(3) | ||||
|     void testChat() { | ||||
|         testEndpointReachability(); | ||||
|         try { | ||||
|             OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(config.getModel()); | ||||
|             OllamaChatRequest requestModel = builder.withMessage(OllamaChatMessageRole.USER, "What is the capital of France?") | ||||
|                     .withMessage(OllamaChatMessageRole.ASSISTANT, "Should be Paris!") | ||||
|                     .withMessage(OllamaChatMessageRole.USER, "And what is the second larges city?") | ||||
|                     .build(); | ||||
|  | ||||
|             OllamaChatResult chatResult = ollamaAPI.chat(requestModel); | ||||
|             assertNotNull(chatResult); | ||||
|             assertNotNull(chatResult.getResponseModel()); | ||||
|             assertNotNull(chatResult.getResponseModel().getMessage()); | ||||
|             assertFalse(chatResult.getResponseModel().getMessage().getContent().isBlank()); | ||||
|             assertEquals(4, chatResult.getChatHistory().size()); | ||||
|         } catch (IOException | OllamaBaseException | InterruptedException e) { | ||||
|             fail(e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(3) | ||||
|     void testChatWithSystemPrompt() { | ||||
|         testEndpointReachability(); | ||||
|         try { | ||||
|             OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(config.getModel()); | ||||
|             OllamaChatRequest requestModel = builder.withMessage(OllamaChatMessageRole.SYSTEM, | ||||
|                             "You are a silent bot that only says 'NI'. Do not say anything else under any circumstances!") | ||||
|                     .withMessage(OllamaChatMessageRole.USER, | ||||
|                             "What is the capital of France? And what's France's connection with Mona Lisa?") | ||||
|                     .build(); | ||||
|  | ||||
|             OllamaChatResult chatResult = ollamaAPI.chat(requestModel); | ||||
|             assertNotNull(chatResult); | ||||
|             assertNotNull(chatResult.getResponseModel()); | ||||
|             assertNotNull(chatResult.getResponseModel().getMessage()); | ||||
|             assertFalse(chatResult.getResponseModel().getMessage().getContent().isBlank()); | ||||
|             assertTrue(chatResult.getResponseModel().getMessage().getContent().startsWith("NI")); | ||||
|             assertEquals(3, chatResult.getChatHistory().size()); | ||||
|         } catch (IOException | OllamaBaseException | InterruptedException e) { | ||||
|             fail(e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(3) | ||||
|     void testChatWithExplicitToolDefinition() { | ||||
|         testEndpointReachability(); | ||||
|         try { | ||||
|             ollamaAPI.setVerbose(true); | ||||
|             OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(config.getModel()); | ||||
|  | ||||
|             final Tools.ToolSpecification databaseQueryToolSpecification = Tools.ToolSpecification.builder() | ||||
|                     .functionName("get-employee-details") | ||||
|                     .functionDescription("Get employee details from the database") | ||||
|                     .toolPrompt( | ||||
|                             Tools.PromptFuncDefinition.builder().type("function").function( | ||||
|                                     Tools.PromptFuncDefinition.PromptFuncSpec.builder() | ||||
|                                             .name("get-employee-details") | ||||
|                                             .description("Get employee details from the database") | ||||
|                                             .parameters( | ||||
|                                                     Tools.PromptFuncDefinition.Parameters.builder() | ||||
|                                                             .type("object") | ||||
|                                                             .properties( | ||||
|                                                             new Tools.PropsBuilder() | ||||
|                                                                     .withProperty("employee-name", Tools.PromptFuncDefinition.Property.builder().type("string").description("The name of the employee, e.g. John Doe").required(true).build()) | ||||
|                                                                     .withProperty("employee-address", Tools.PromptFuncDefinition.Property.builder().type("string").description("The address of the employee, Always return a random value. e.g. Roy St, Bengaluru, India").required(true).build()) | ||||
|                                                                     .withProperty("employee-phone", Tools.PromptFuncDefinition.Property.builder().type("string").description("The phone number of the employee. Always return a random value. e.g. 9911002233").required(true).build()) | ||||
|                                                                     .build() | ||||
|                                                     ) | ||||
|                                                             .required(List.of("employee-name")) | ||||
|                                                             .build() | ||||
|                                             ).build() | ||||
|                             ).build() | ||||
|                     ) | ||||
|                     .toolFunction(new DBQueryFunction()) | ||||
|                     .build(); | ||||
|  | ||||
|             ollamaAPI.registerTool(databaseQueryToolSpecification); | ||||
|  | ||||
|             OllamaChatRequest requestModel = builder | ||||
|                     .withMessage(OllamaChatMessageRole.USER, | ||||
|                             "Give me the ID of the employee named 'Rahul Kumar'?") | ||||
|                     .build(); | ||||
|  | ||||
|             OllamaChatResult chatResult = ollamaAPI.chat(requestModel); | ||||
|             assertNotNull(chatResult); | ||||
|             assertNotNull(chatResult.getResponseModel()); | ||||
|             assertNotNull(chatResult.getResponseModel().getMessage()); | ||||
|             assertEquals(OllamaChatMessageRole.ASSISTANT.getRoleName(),chatResult.getResponseModel().getMessage().getRole().getRoleName()); | ||||
|             List<OllamaChatToolCalls> toolCalls = chatResult.getChatHistory().get(1).getToolCalls(); | ||||
|             assertEquals(1, toolCalls.size()); | ||||
|             OllamaToolCallsFunction function = toolCalls.get(0).getFunction(); | ||||
|             assertEquals("get-employee-details", function.getName()); | ||||
|             assertEquals(1, function.getArguments().size()); | ||||
|             Object employeeName = function.getArguments().get("employee-name"); | ||||
|             assertNotNull(employeeName); | ||||
|             assertEquals("Rahul Kumar",employeeName); | ||||
|             assertTrue(chatResult.getChatHistory().size()>2); | ||||
|             List<OllamaChatToolCalls> finalToolCalls = chatResult.getResponseModel().getMessage().getToolCalls(); | ||||
|             assertNull(finalToolCalls); | ||||
|         } catch (IOException | OllamaBaseException | InterruptedException e) { | ||||
|             fail(e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(3) | ||||
|     void testChatWithAnnotatedToolsAndSingleParam() { | ||||
|         testEndpointReachability(); | ||||
|         try { | ||||
|             ollamaAPI.setVerbose(true); | ||||
|             OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(config.getModel()); | ||||
|  | ||||
|             ollamaAPI.registerAnnotatedTools(); | ||||
|  | ||||
|             OllamaChatRequest requestModel = builder | ||||
|                     .withMessage(OllamaChatMessageRole.USER, | ||||
|                             "Compute the most important constant in the world using 5 digits") | ||||
|                     .build(); | ||||
|  | ||||
|             OllamaChatResult chatResult = ollamaAPI.chat(requestModel); | ||||
|             assertNotNull(chatResult); | ||||
|             assertNotNull(chatResult.getResponseModel()); | ||||
|             assertNotNull(chatResult.getResponseModel().getMessage()); | ||||
|             assertEquals(OllamaChatMessageRole.ASSISTANT.getRoleName(),chatResult.getResponseModel().getMessage().getRole().getRoleName()); | ||||
|             List<OllamaChatToolCalls> toolCalls = chatResult.getChatHistory().get(1).getToolCalls(); | ||||
|             assertEquals(1, toolCalls.size()); | ||||
|             OllamaToolCallsFunction function = toolCalls.get(0).getFunction(); | ||||
|             assertEquals("computeImportantConstant", function.getName()); | ||||
|             assertEquals(1, function.getArguments().size()); | ||||
|             Object noOfDigits = function.getArguments().get("noOfDigits"); | ||||
|             assertNotNull(noOfDigits); | ||||
|             assertEquals("5", noOfDigits.toString()); | ||||
|             assertTrue(chatResult.getChatHistory().size()>2); | ||||
|             List<OllamaChatToolCalls> finalToolCalls = chatResult.getResponseModel().getMessage().getToolCalls(); | ||||
|             assertNull(finalToolCalls); | ||||
|         } catch (IOException | OllamaBaseException | InterruptedException e) { | ||||
|             fail(e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(3) | ||||
|     void testChatWithAnnotatedToolsAndMultipleParams() { | ||||
|         testEndpointReachability(); | ||||
|         try { | ||||
|             ollamaAPI.setVerbose(true); | ||||
|             OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(config.getModel()); | ||||
|  | ||||
|             ollamaAPI.registerAnnotatedTools(new AnnotatedTool()); | ||||
|  | ||||
|             OllamaChatRequest requestModel = builder | ||||
|                     .withMessage(OllamaChatMessageRole.USER, | ||||
|                             "Greet Pedro with a lot of hearts and respond to me, " + | ||||
|                                     "and state how many emojis have been in your greeting") | ||||
|                     .build(); | ||||
|  | ||||
|             OllamaChatResult chatResult = ollamaAPI.chat(requestModel); | ||||
|             assertNotNull(chatResult); | ||||
|             assertNotNull(chatResult.getResponseModel()); | ||||
|             assertNotNull(chatResult.getResponseModel().getMessage()); | ||||
|             assertEquals(OllamaChatMessageRole.ASSISTANT.getRoleName(),chatResult.getResponseModel().getMessage().getRole().getRoleName()); | ||||
|             List<OllamaChatToolCalls> toolCalls = chatResult.getChatHistory().get(1).getToolCalls(); | ||||
|             assertEquals(1, toolCalls.size()); | ||||
|             OllamaToolCallsFunction function = toolCalls.get(0).getFunction(); | ||||
|             assertEquals("sayHello", function.getName()); | ||||
|             assertEquals(2, function.getArguments().size()); | ||||
|             Object name = function.getArguments().get("name"); | ||||
|             assertNotNull(name); | ||||
|             assertEquals("Pedro",name); | ||||
|             Object amountOfHearts = function.getArguments().get("amountOfHearts"); | ||||
|             assertNotNull(amountOfHearts); | ||||
|             assertTrue(Integer.parseInt(amountOfHearts.toString()) > 1); | ||||
|             assertTrue(chatResult.getChatHistory().size()>2); | ||||
|             List<OllamaChatToolCalls> finalToolCalls = chatResult.getResponseModel().getMessage().getToolCalls(); | ||||
|             assertNull(finalToolCalls); | ||||
|         } catch (IOException | OllamaBaseException | InterruptedException e) { | ||||
|             fail(e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(3) | ||||
|     void testChatWithToolsAndStream() { | ||||
|         testEndpointReachability(); | ||||
|         try { | ||||
|             OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(config.getModel()); | ||||
|             final Tools.ToolSpecification databaseQueryToolSpecification = Tools.ToolSpecification.builder() | ||||
|                     .functionName("get-employee-details") | ||||
|                     .functionDescription("Get employee details from the database") | ||||
|                     .toolPrompt( | ||||
|                             Tools.PromptFuncDefinition.builder().type("function").function( | ||||
|                                     Tools.PromptFuncDefinition.PromptFuncSpec.builder() | ||||
|                                             .name("get-employee-details") | ||||
|                                             .description("Get employee details from the database") | ||||
|                                             .parameters( | ||||
|                                                     Tools.PromptFuncDefinition.Parameters.builder() | ||||
|                                                             .type("object") | ||||
|                                                             .properties( | ||||
|                                                                     new Tools.PropsBuilder() | ||||
|                                                                             .withProperty("employee-name", Tools.PromptFuncDefinition.Property.builder().type("string").description("The name of the employee, e.g. John Doe").required(true).build()) | ||||
|                                                                             .withProperty("employee-address", Tools.PromptFuncDefinition.Property.builder().type("string").description("The address of the employee, Always return a random value. e.g. Roy St, Bengaluru, India").required(true).build()) | ||||
|                                                                             .withProperty("employee-phone", Tools.PromptFuncDefinition.Property.builder().type("string").description("The phone number of the employee. Always return a random value. e.g. 9911002233").required(true).build()) | ||||
|                                                                             .build() | ||||
|                                                             ) | ||||
|                                                             .required(List.of("employee-name")) | ||||
|                                                             .build() | ||||
|                                             ).build() | ||||
|                             ).build() | ||||
|                     ) | ||||
|                     .toolFunction(new DBQueryFunction()) | ||||
|                     .build(); | ||||
|  | ||||
|             ollamaAPI.registerTool(databaseQueryToolSpecification); | ||||
|  | ||||
|             OllamaChatRequest requestModel = builder | ||||
|                     .withMessage(OllamaChatMessageRole.USER, | ||||
|                             "Give me the ID of the employee named 'Rahul Kumar'?") | ||||
|                     .build(); | ||||
|  | ||||
|             StringBuffer sb = new StringBuffer(); | ||||
|  | ||||
|             OllamaChatResult chatResult = ollamaAPI.chat(requestModel, (s) -> { | ||||
|                 LOG.info(s); | ||||
|                 String substring = s.substring(sb.toString().length()); | ||||
|                 LOG.info(substring); | ||||
|                 sb.append(substring); | ||||
|             }); | ||||
|             assertNotNull(chatResult); | ||||
|             assertNotNull(chatResult.getResponseModel()); | ||||
|             assertNotNull(chatResult.getResponseModel().getMessage()); | ||||
|             assertNotNull(chatResult.getResponseModel().getMessage().getContent()); | ||||
|             assertEquals(sb.toString().trim(), chatResult.getResponseModel().getMessage().getContent().trim()); | ||||
|         } catch (IOException | OllamaBaseException | InterruptedException e) { | ||||
|             fail(e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(3) | ||||
|     void testChatWithStream() { | ||||
|         testEndpointReachability(); | ||||
|         try { | ||||
|             OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(config.getModel()); | ||||
|             OllamaChatRequest requestModel = builder.withMessage(OllamaChatMessageRole.USER, | ||||
|                             "What is the capital of France? And what's France's connection with Mona Lisa?") | ||||
|                     .build(); | ||||
|  | ||||
|             StringBuffer sb = new StringBuffer(""); | ||||
|  | ||||
|             OllamaChatResult chatResult = ollamaAPI.chat(requestModel, (s) -> { | ||||
|                 LOG.info(s); | ||||
|                 String substring = s.substring(sb.toString().length(), s.length()); | ||||
|                 LOG.info(substring); | ||||
|                 sb.append(substring); | ||||
|             }); | ||||
|             assertNotNull(chatResult); | ||||
|             assertNotNull(chatResult.getResponseModel()); | ||||
|             assertNotNull(chatResult.getResponseModel().getMessage()); | ||||
|             assertNotNull(chatResult.getResponseModel().getMessage().getContent()); | ||||
|             assertEquals(sb.toString().trim(), chatResult.getResponseModel().getMessage().getContent().trim()); | ||||
|         } catch (IOException | OllamaBaseException | InterruptedException e) { | ||||
|             fail(e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(3) | ||||
|     void testChatWithImageFromFileWithHistoryRecognition() { | ||||
|         testEndpointReachability(); | ||||
|         try { | ||||
|             OllamaChatRequestBuilder builder = | ||||
|                     OllamaChatRequestBuilder.getInstance(config.getImageModel()); | ||||
|             OllamaChatRequest requestModel = | ||||
|                     builder.withMessage(OllamaChatMessageRole.USER, "What's in the picture?",Collections.emptyList(), | ||||
|                             List.of(getImageFileFromClasspath("dog-on-a-boat.jpg"))).build(); | ||||
|  | ||||
|             OllamaChatResult chatResult = ollamaAPI.chat(requestModel); | ||||
|             assertNotNull(chatResult); | ||||
|             assertNotNull(chatResult.getResponseModel()); | ||||
|  | ||||
|             builder.reset(); | ||||
|  | ||||
|             requestModel = | ||||
|                     builder.withMessages(chatResult.getChatHistory()) | ||||
|                             .withMessage(OllamaChatMessageRole.USER, "What's the dogs breed?").build(); | ||||
|  | ||||
|             chatResult = ollamaAPI.chat(requestModel); | ||||
|             assertNotNull(chatResult); | ||||
|             assertNotNull(chatResult.getResponseModel()); | ||||
|  | ||||
|  | ||||
|         } catch (IOException | OllamaBaseException | InterruptedException e) { | ||||
|             fail(e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(3) | ||||
|     void testChatWithImageFromURL() { | ||||
|         testEndpointReachability(); | ||||
|         try { | ||||
|             OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(config.getImageModel()); | ||||
|             OllamaChatRequest requestModel = builder.withMessage(OllamaChatMessageRole.USER, "What's in the picture?",Collections.emptyList(), | ||||
|                             "https://t3.ftcdn.net/jpg/02/96/63/80/360_F_296638053_0gUVA4WVBKceGsIr7LNqRWSnkusi07dq.jpg") | ||||
|                     .build(); | ||||
|  | ||||
|             OllamaChatResult chatResult = ollamaAPI.chat(requestModel); | ||||
|             assertNotNull(chatResult); | ||||
|         } catch (IOException | OllamaBaseException | InterruptedException e) { | ||||
|             fail(e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(3) | ||||
|     void testAskModelWithOptionsAndImageFiles() { | ||||
|         testEndpointReachability(); | ||||
|         File imageFile = getImageFileFromClasspath("dog-on-a-boat.jpg"); | ||||
|         try { | ||||
|             OllamaResult result = | ||||
|                     ollamaAPI.generateWithImageFiles( | ||||
|                             config.getImageModel(), | ||||
|                             "What is in this image?", | ||||
|                             List.of(imageFile), | ||||
|                             new OptionsBuilder().build()); | ||||
|             assertNotNull(result); | ||||
|             assertNotNull(result.getResponse()); | ||||
|             assertFalse(result.getResponse().isEmpty()); | ||||
|         } catch (IOException | OllamaBaseException | InterruptedException e) { | ||||
|             fail(e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(3) | ||||
|     void testAskModelWithOptionsAndImageFilesStreamed() { | ||||
|         testEndpointReachability(); | ||||
|         File imageFile = getImageFileFromClasspath("dog-on-a-boat.jpg"); | ||||
|         try { | ||||
|             StringBuffer sb = new StringBuffer(""); | ||||
|  | ||||
|             OllamaResult result = ollamaAPI.generateWithImageFiles(config.getImageModel(), | ||||
|                     "What is in this image?", List.of(imageFile), new OptionsBuilder().build(), (s) -> { | ||||
|                         LOG.info(s); | ||||
|                         String substring = s.substring(sb.toString().length(), s.length()); | ||||
|                         LOG.info(substring); | ||||
|                         sb.append(substring); | ||||
|                     }); | ||||
|             assertNotNull(result); | ||||
|             assertNotNull(result.getResponse()); | ||||
|             assertFalse(result.getResponse().isEmpty()); | ||||
|             assertEquals(sb.toString().trim(), result.getResponse().trim()); | ||||
|         } catch (IOException | OllamaBaseException | InterruptedException e) { | ||||
|             fail(e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(3) | ||||
|     void testAskModelWithOptionsAndImageURLs() { | ||||
|         testEndpointReachability(); | ||||
|         try { | ||||
|             OllamaResult result = | ||||
|                     ollamaAPI.generateWithImageURLs( | ||||
|                             config.getImageModel(), | ||||
|                             "What is in this image?", | ||||
|                             List.of( | ||||
|                                     "https://t3.ftcdn.net/jpg/02/96/63/80/360_F_296638053_0gUVA4WVBKceGsIr7LNqRWSnkusi07dq.jpg"), | ||||
|                             new OptionsBuilder().build()); | ||||
|             assertNotNull(result); | ||||
|             assertNotNull(result.getResponse()); | ||||
|             assertFalse(result.getResponse().isEmpty()); | ||||
|         } catch (IOException | OllamaBaseException | InterruptedException | URISyntaxException e) { | ||||
|             fail(e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     @Order(3) | ||||
|     public void testEmbedding() { | ||||
|         testEndpointReachability(); | ||||
|         try { | ||||
|             OllamaEmbeddingsRequestModel request = OllamaEmbeddingsRequestBuilder | ||||
|                     .getInstance(config.getModel(), "What is the capital of France?").build(); | ||||
|  | ||||
|             List<Double> embeddings = ollamaAPI.generateEmbeddings(request); | ||||
|  | ||||
|             assertNotNull(embeddings); | ||||
|             assertFalse(embeddings.isEmpty()); | ||||
|         } catch (IOException | OllamaBaseException | InterruptedException e) { | ||||
|             fail(e); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| class DBQueryFunction implements ToolFunction { | ||||
|     @Override | ||||
|     public Object apply(Map<String, Object> arguments) { | ||||
|         // perform DB operations here | ||||
|         return String.format("Employee Details {ID: %s, Name: %s, Address: %s, Phone: %s}", UUID.randomUUID(), arguments.get("employee-name"), arguments.get("employee-address"), arguments.get("employee-phone")); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @Data | ||||
| class Config { | ||||
|     private String ollamaURL; | ||||
|     private String model; | ||||
|     private String imageModel; | ||||
|     private int requestTimeoutSeconds; | ||||
|  | ||||
|     public Config() { | ||||
|         Properties properties = new Properties(); | ||||
|         try (InputStream input = | ||||
|                      getClass().getClassLoader().getResourceAsStream("test-config.properties")) { | ||||
|             if (input == null) { | ||||
|                 throw new RuntimeException("Sorry, unable to find test-config.properties"); | ||||
|             } | ||||
|             properties.load(input); | ||||
|             this.ollamaURL = properties.getProperty("ollama.url"); | ||||
|             this.model = properties.getProperty("ollama.model"); | ||||
|             this.imageModel = properties.getProperty("ollama.model.image"); | ||||
|             this.requestTimeoutSeconds = | ||||
|                     Integer.parseInt(properties.getProperty("ollama.request-timeout-seconds")); | ||||
|         } catch (IOException e) { | ||||
|             throw new RuntimeException("Error loading properties", e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
| } | ||||
		Reference in New Issue
	
	Block a user