Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,16 @@ readme's instructions.
=== Examples

// examples: START
Number of Examples: 69 (0 deprecated)
Number of Examples: 70 (0 deprecated)

[width="100%",cols="4,2,4",options="header"]
|===
| Example | Category | Description

| link:ai-agent/README.adoc[Ai Agent] (ai-agent) | AI | An example showing how to work with Camel Spring AI for chat, tools, and vector store

| link:milvus/readme.adoc[Milvus] (milvus) | AI | An example showing vector similarity search on European cities using Camel OpenAI and Milvus

| link:aot-basic/readme.adoc[Aot Basic] (aot-basic) | AOT | Example on how to leverage Spring Boot AOT in Camel Spring Boot

| link:endpointdsl/readme.adoc[Endpointdsl] (endpointdsl) | Beginner | Using type-safe Endpoint DSL
Expand Down
24 changes: 24 additions & 0 deletions milvus/input/cities.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
Rome
Paris
London
Madrid
Berlin
Prague
Vienna
Amsterdam
Lisbon
Barcelona
Dublin
Brussels
Warsaw
Istanbul
Budapest
Copenhagen
Stockholm
Oslo
Helsinki
Athens
Zurich
Milan
Sofia

125 changes: 125 additions & 0 deletions milvus/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--

Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.camel.springboot.example</groupId>
<artifactId>examples</artifactId>
<version>4.19.0-SNAPSHOT</version>
</parent>

<artifactId>camel-example-spring-boot-milvus</artifactId>
<name>Camel SB Examples :: Milvus</name>
<description>An example showing vector similarity search on European cities using Camel OpenAI and Milvus</description>

<properties>
<category>AI</category>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>

<dependencyManagement>
<dependencies>
<!-- Spring Boot BOM -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Camel BOM -->
<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-spring-boot-bom</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

<!-- Camel -->
<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-milvus-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-openai-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-file-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-yaml-dsl-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-jackson-starter</artifactId>
</dependency>

<!-- test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-test-spring-junit6</artifactId>
<version>${camel-version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot-version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
94 changes: 94 additions & 0 deletions milvus/readme.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
== Camel Example Spring Boot Milvus Vector Search

This example shows how to use Apache Camel with Milvus as a vector database to perform
similarity search on European cities based on geographic and demographic features.

OpenAI (via Ollama) is used to generate city data (country, latitude, longitude,
population in millions) which is stored as a 3-dimensional feature vector in Milvus.
A similarity search then finds cities with the most similar characteristics.

The pipeline works end-to-end:

1. **Insert**: LLM returns raw city data (`Rome: Italy;40.41;12.51;2.81`) → `VectorUtils` normalizes to 0-1 range → stored in Milvus as 3D vector
2. **Search**: LLM returns raw Rome coordinates → normalized → Milvus L2 similarity search → finds Rome, Milan, Zurich as the 3 closest cities
3. **Explain**: The results are sent back to the LLM which provides a natural language explanation with approximate distances

=== Prerequisites

1. A running Milvus instance (using https://camel.apache.org/manual/camel-jbang.html[Camel JBang]):

----
camel infra run milvus
----

2. A running Ollama instance with the required model:

----
ollama serve
ollama pull granite4:3b
----

=== How to run

You can run this example using:

----
mvn spring-boot:run
----

=== What happens

When the application starts, it executes the following pipeline:

1. **Create Collection** - Creates a `cities` collection in Milvus with four fields: `id` (Int64 primary key), `city` (VarChar), `country` (VarChar), and `features` (3-dimensional FloatVector). The `features` vector encodes each city as `[latitude, longitude, population_millions]`, normalized to 0-1 range. An IVF_FLAT index with L2 distance metric is created on the vector field.
2. **Insert Cities** - Reads city names from `input/cities.txt`. For each city, it asks the LLM via `openai:chat-completion` to provide the country, latitude, longitude, and population (in millions). The response is parsed using Camel Simple expressions and normalized to a float vector via `VectorUtils`, then inserted into Milvus.
3. **Similarity Search** - Searches for the 3 cities most similar to Rome based on L2 distance of their feature vectors. The search vector is also generated by the LLM.
4. **Explain** - The search results are sent back to the LLM which provides a natural language explanation with approximate distances.

=== Expected output

----
Creating Milvus collection: cities
Collection created successfully
Index created successfully
Processing cities from: cities.txt
City data for Rome: Italy;41.90;12.50;2.87
Inserted Rome into Milvus
City data for Paris: France;48.86;2.35;2.16
Inserted Paris into Milvus
...
All cities indexed. Starting similarity search...
Searching for three closest cities to: Rome
Search vector for Rome: 41.90;12.50;2.87
Closest cities to Rome: [{rank=1, city=Rome, country=Italy}, {rank=2, city=Milan, country=Italy}, {rank=3, city=Zurich, country=Switzerland}].
Answer: Rome: 0 km, Milan: 200 km, Zurich: 400 km
----

NOTE: The exact similarity results may vary depending on the LLM responses.

=== Configuration

Edit `src/main/resources/application.properties` to configure:

* `camel.component.milvus.host` - Milvus server host (default: localhost)
* `camel.component.milvus.port` - Milvus gRPC port (default: 19530)
* `camel.component.openai.base-url` - OpenAI-compatible API base URL (default: Ollama at http://localhost:11434/v1)
* `camel.component.openai.model` - Chat completion model (default: granite4:3b)

To use OpenAI instead of Ollama, change the base URL and set your API key:

----
camel.component.openai.base-url=https://api.openai.com/v1
camel.component.openai.api-key=${OPENAI_API_KEY}
camel.component.openai.model=gpt-4o-mini
----

=== Help and contributions

If you hit any problem using Camel or have some feedback, then please
https://camel.apache.org/support.html[let us know].

We also love contributors, so
https://camel.apache.org/contributing.html[get involved] :-)

The Camel riders!
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.example.springboot.milvus;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.example.springboot.milvus.bean;

import java.util.ArrayList;
import java.util.List;

import org.apache.camel.Exchange;
import org.apache.camel.Processor;

public class VectorUtils implements Processor {

private String normalizationRanges;
private float[][] ranges;

@Override
public void process(Exchange exchange) throws Exception {
// Converts a semicolon-separated string of numeric values into a normalized List feature vector.
String[] parts = exchange.getIn().getBody(String.class).trim().split(";");
List<Float> vector = new ArrayList<>(parts.length);
for (int i = 0; i < parts.length; i++) {
float val = Float.parseFloat(parts[i].trim());
if (ranges != null && i < ranges.length) {
val = (val - ranges[i][0]) / (ranges[i][1] - ranges[i][0]);
val = Math.max(0.0f, Math.min(1.0f, val));
}
vector.add(val);
}
exchange.getIn().setBody(vector);
}

public String getNormalizationRanges() {
return normalizationRanges;
}

/**
* Each dimension is normalized to a 0-1 range using configurable min/max ranges, so all features contribute equally to distance calculations in Milvus.
*/
public void setNormalizationRanges(String normalizationRanges) {
this.normalizationRanges = normalizationRanges;
if (normalizationRanges != null && !normalizationRanges.isBlank()) {
String[] pairs = normalizationRanges.split(",");
this.ranges = new float[pairs.length][2];
for (int i = 0; i < pairs.length; i++) {
String[] minMax = pairs[i].trim().split(":");
this.ranges[i][0] = Float.parseFloat(minMax[0].trim());
this.ranges[i][1] = Float.parseFloat(minMax[1].trim());
}
} else {
this.ranges = null;
}
}
}
13 changes: 13 additions & 0 deletions milvus/src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
camel.main.name=MilvusCitiesExample
camel.main.run-controller=true

input.dir=input

# Milvus
camel.component.milvus.host=localhost
camel.component.milvus.port=19530

# OpenAI component (configured for Ollama)
camel.component.openai.base-url=http://localhost:11434/v1
camel.component.openai.api-key=ollama
camel.component.openai.model=granite4:3b
Loading