## Issue
#1506
## Change
Resolved version conflict:
```
[ERROR] Rule 0: org.apache.maven.enforcer.rules.dependency.DependencyConvergence failed with message:
[ERROR] Failed while enforcing releasability.
[ERROR]
[ERROR] Dependency convergence error for org.jetbrains.kotlin:kotlin-stdlib-jdk8:jar:1.9.10 paths to dependency are:
[ERROR] +-dev.langchain4j:langchain4j-ollama:jar:0.34.0-SNAPSHOT
[ERROR] +-com.squareup.okhttp3:okhttp:jar:4.12.0:compile
[ERROR] +-com.squareup.okio:okio:jar:3.6.0:compile
[ERROR] +-com.squareup.okio:okio-jvm:jar:3.6.0:compile
[ERROR] +-org.jetbrains.kotlin:kotlin-stdlib-jdk8:jar:1.9.10:compile
[ERROR] and
[ERROR] +-dev.langchain4j:langchain4j-ollama:jar:0.34.0-SNAPSHOT
[ERROR] +-com.squareup.okhttp3:okhttp:jar:4.12.0:compile
[ERROR] +-org.jetbrains.kotlin:kotlin-stdlib-jdk8:jar:1.8.21:compile
[ERROR]
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException
[ERROR]
[ERROR] After correcting the problems, you can resume the build with the command
[ERROR] mvn <args> -rf :langchain4j-ollama
```
... caused by 'okhttp' dependency and enabled Maven enforcer plugin in
the following modules:
- LangChain4j :: Integration :: Anthropic
- LangChain4j :: Integration :: ChatGLM
- LangChain4j :: Integration :: Chroma
- LangChain4j :: Integration :: CloudFlare Workers AI
- LangChain4j :: Integration :: Cohere
- LangChain4j :: Integration :: DashScope
- LangChain4j :: Integration :: Hugging Face
- LangChain4j :: Integration :: Jina
- LangChain4j :: Integration :: Judge0
- LangChain4j :: Integration :: MistralAI
- LangChain4j :: Integration :: Nomic
- LangChain4j :: Integration :: OVHcloud AI
- LangChain4j :: Integration :: Ollama
- LangChain4j :: Integration :: Qianfan
- LangChain4j :: Integration :: Vearch
- LangChain4j :: Integration :: Vespa
- LangChain4j :: Integration :: Zhipu AI
- LangChain4j :: Web Search Engine :: SearchApi
- LangChain4j :: Web Search Engine :: Tavily
## Note
Please note that [issue ](https://github.com/square/okhttp/issues/8288)
for this was already created in `httpok` repository but it will not be
fixed in 4.x. It's reportedly already tackled in version 5.x.
With that in mind I suggest we apply temporary changes proposed in this
PR. After upgrading to `httpok` 5.x we will be able to remove these.
## Tests
`mvn clean test` passed
## Change
Use awaitility in `EmbeddingStoreIT`
## General checklist
- [X] There are no breaking changes
- [X] I have added unit and integration tests for my change
- [x] I have manually run all the unit and integration tests in the
module I have added/changed, and they are all green
- [x] I have manually run all the unit and integration tests in the
[core]
## Issue
Closes#1465
## Change
According to
[retrofit](https://github.com/square/retrofit/blob/trunk/retrofit%2Fsrc%2Fmain%2Fjava%2Fretrofit2%2FRetrofit.java#L564)
base urls should always end with `/`.
Added new utility method to ensure that a provided base url always ends
with a `/` and checked existing API classes so that they all start
**without** a `/`.
### Tests
I have added unit test for the new utility method but testing the actual
invocation of the method in the different builder classes is harder. The
existing Ollama test case spins up a temporary web server and I don't
want to replicate this to al lmodules since I suspect build times will
increase a lot etc.
Thoughts?
## General checklist
- [X] There are no breaking changes
- [X] I have added unit and integration tests for my change
- [X] I have manually run all the unit and integration tests in the
module I have added/changed, and they are all green
- [X] I have manually run all the unit and integration tests in the
[core](https://github.com/langchain4j/langchain4j/tree/main/langchain4j-core)
and
[main](https://github.com/langchain4j/langchain4j/tree/main/langchain4j)
modules, and they are all green
<!-- Before adding documentation and example(s) (below), please wait
until the PR is reviewed and approved. -->
- [ ] I have added/updated the
[documentation](https://github.com/langchain4j/langchain4j/tree/main/docs/docs)
- [ ] I have added an example in the [examples
repo](https://github.com/langchain4j/langchain4j-examples) (only for
"big" features)
- [ ] I have added/updated [Spring Boot
starter(s)](https://github.com/langchain4j/langchain4j-spring) (if
applicable)
## Issue
Closes#1066
## Change
These are changes for each split package (each change was done in a
separate commit, so they can be reviewed in isolation):
- `dev.langchain4j.retriever` -> Moved `EmbeddingStoreRetriever` into
`langchain4j-core` module
- `dev.langchain4j.agent.tool` -> Moved `DefaultToolExecutor` and
`ToolExecutor` into `dev.langchain4j.service.tool` package
- `dev.langchain4j.classification` -> Moved `TextClassifier` into
`langchian4j` module
- `dev.langchain4j.chain` -> Moved `Chain` into `langchain4j` module
- `dev.langchain4j.model.embedding` -> [All in-process embedding models
should have unique package
name](https://github.com/langchain4j/langchain4j-embeddings/pull/33)
- `dev.langchain4j.model.output` -> Moved `OutputParser` and all it's
implementations into `dev.langchain4j.service.output` package of the
`langchain4j` module
More details can be found
[here](https://docs.google.com/spreadsheets/d/1U7f2MIfDgWA1tydPpzWpOGTHiBjBVZjsu0uZnXBT9qE/edit?usp=sharing).
## Breaking Changes
- All in-process ONNX model classes moved into their own unique
packages:
- `AllMiniLmL6V2EmbeddingModel` moved into
`dev.langchain4j.model.embedding.onnx.allminilml6v2`
- `AllMiniLmL6V2QuantizedEmbeddingModel` moved into
`dev.langchain4j.model.embedding.onnx.allminilml6v2q`
- `OnnxEmbeddingModel` moved into `dev.langchain4j.model.embedding.onnx`
package
- etc
- `ToolExecutor` and `DefaultToolExecutor` moved into
`dev.langchain4j.service.tool` package
- Moved `OutputParser` and all it's implementations into
`dev.langchain4j.service.output` package of the `langchain4j` module
- Moved `Chain` into `langchain4j` module
- Moved `TextClassifier` into `langchian4j` module
## General checklist
- [ ] There are no breaking changes
- [ ] I have added unit and integration tests for my change
- [X] I have manually run all the unit and integration tests in the
module I have added/changed, and they are all green
- [X] I have manually run all the unit and integration tests in the
[core](https://github.com/langchain4j/langchain4j/tree/main/langchain4j-core)
and
[main](https://github.com/langchain4j/langchain4j/tree/main/langchain4j)
modules, and they are all green
<!-- Before adding documentation and example(s) (below), please wait
until the PR is reviewed and approved. -->
- [ ] I have added/updated the
[documentation](https://github.com/langchain4j/langchain4j/tree/main/docs/docs)
- [ ] I have added an example in the [examples
repo](https://github.com/langchain4j/langchain4j-examples) (only for
"big" features)
- [ ] I have added/updated [Spring Boot
starter(s)](https://github.com/langchain4j/langchain4j-spring) (if
applicable)
## Issue
#1325
## Change
Update `VearchEmbeddingStoreIT`. The exception in the issue is because
the update of `EmbeddingStoreIT`. `EmbeddingStoreIT` add more metadatas
so `VearchEmbeddingStore` failed because of the mismatch of metadatas.
There are two major changes in `VearchEmbeddingStoreIT`
1. Because `Vearch` do not support long and double, so I manually
override `should_add_embedding_with_segment_with_metadata`.
2. Because `Vearch` do not allow `null` value, so every metadata should
contains a default value when embed. When the test create a Space with
metadatas, all `findRelevant` result will carry metadata even though
users don't embed content with metadata. So, tests like
`should_add_embedding_with_segment` which assert embedded will failed.
(because `Vearch` return embedded with default metadata, but this test
and assertion do not expect any metadata) . To solve this problem, I
check where the test from and create different Space before each test.
(see `@BeforeEach` in `VearchEmbeddingStoreIT`)
![image](https://github.com/langchain4j/langchain4j/assets/77151639/fa99307d-fa58-465d-8332-028180214b6f)
## General checklist
<!-- Please double-check the following points and mark them like this:
[X] -->
- [x] There are no breaking changes
- [x] I have added unit and integration tests for my change
- [x] I have manually run all the unit and integration tests in the
module I have added/changed, and they are all green
- [x] I have manually run all the unit and integration tests in the
[core](https://github.com/langchain4j/langchain4j/tree/main/langchain4j-core)
and
[main](https://github.com/langchain4j/langchain4j/tree/main/langchain4j)
modules, and they are all green
## Checklist for changing existing embedding store integration
<!-- Please double-check the following points and mark them like this:
[X] -->
- [x] I have manually verified that the
`{NameOfIntegration}EmbeddingStore` works correctly with the data
persisted using the latest released version of LangChain4j
* Update LocaStack image version
* Avoid custom LocalStackContainer configuration
* Fixed Vearch image version
* Use copyFileToContainer
Note: almost all Vearch IT works with version `3.4.x`.
`should_add_embedding_with_segment_with_metadata` fails with
```
status code: 400; body: {"error":{"root_cause":[{"type":"","reason":"param have error field [long_0]"}],"type":"","reason":"param have error field [long_0]"},"status":400}
```
## New EmbeddingStore (metadata) `Filter` API
Many embedding stores, such as
[Pinecone](https://docs.pinecone.io/docs/metadata-filtering) and
[Milvus](https://milvus.io/docs/boolean.md) support strict filtering
(think of an SQL "WHERE" clause) during similarity search.
So, if one has an embedding store with movies, for example, one could
search not only for the most semantically similar movies to the given
user query but also apply strict filtering by metadata fields like year,
genre, rating, etc. In this case, the similarity search will be
performed only on those movies that match the filter expression.
Since LangChain4j supports (and abstracts away) many embedding stores,
there needs to be an embedding-store-agnostic way for users to define
the filter expression.
This PR introduces a `Filter` interface, which can represent both simple
(e.g., `type = "documentation"`) and composite (e.g., `type in
("documentation", "tutorial") AND year > 2020`) filter expressions in an
embedding-store-agnostic manner.
`Filter` currently supports the following operations:
- Comparison:
- `IsEqualTo`
- `IsNotEqualTo`
- `IsGreaterThan`
- `IsGreaterThanOrEqualTo`
- `IsLessThan`
- `IsLessThanOrEqualTo`
- `IsIn`
- `IsNotIn`
- Logical:
- `And`
- `Not`
- `Or`
These operations are supported by most embedding stores and serve as a
good starting point. However, the list of operations will expand over
time to include other operations (e.g., `Contains`) supported by
embedding stores.
Currently, the DSL looks like this:
```java
Filter onlyDocs = metadataKey("type").isEqualTo("documentation");
Filter docsAndTutorialsAfter2020 = metadataKey("type").isIn("documentation", "tutorial").and(metadataKey("year").isGreaterThan(2020));
// or
Filter docsAndTutorialsAfter2020 = and(
metadataKey("type").isIn("documentation", "tutorial"),
metadataKey("year").isGreaterThan(2020)
);
```
## Filter expression as a `String`
Filter expression can also be specified as a `String`. This might be
necessary, for example, if the filter expression is generated
dynamically by the application or by the LLM (as in [self
querying](https://python.langchain.com/docs/modules/data_connection/retrievers/self_query/)).
This PR introduces a `FilterParser` interface with a simple `Filter
parse(String)` API, allowing for future support of multiple syntaxes (if
this will be required).
For the out-of-the-box filter syntax, ANSI SQL's `WHERE` clause is
proposed as a suitable candidate for several reasons:
- SQL is well-known among Java developers
- There is extensive tooling available for SQL (e.g., parsers)
- LLMs are pretty good at generating valid SQL, as there are tons of SQL
queries on the internet, which are included in the LLM training
datasets. There are also specialized LLMs that are trained for
text-to-SQL task, such as [SQLCoder](https://huggingface.co/defog).
The downside is that SQL's `WHERE` clause might not support all
operations and data types that could be supported in the future by
various embedding stores. In such case, we could extend it to a superset
of ANSI SQL `WHERE` syntax and/or provide an option to express filters
in the native syntax of the store.
An out-of-the-box implementation of the SQL `FilterParser` is provided
as a `SqlFilterParser` in a separate module
`langchain4j-embedding-store-filter-parser-sql`, using
[JSqlParser](https://github.com/JSQLParser/JSqlParser) under the hood.
`SqlFilterParser` can parse SQL "SELECT" (or just "WHERE" clause)
statement into a `Filter` object:
- `SELECT * FROM fake_table WHERE userId = '123-456'` ->
`metadataKey("userId").isEqualTo("123-456")`
- `userId = '123-456'` -> `metadataKey("userId").isEqualTo("123-456")`
It can also resolve `CURDATE()` and
`CURRENT_DATE`/`CURRENT_TIME`/`CURRENT_TIMESTAMP`:
`SELECT * FROM fake_table WHERE year = EXTRACT(YEAR FROM CURRENT_DATE`
-> `metadataKey("year").isEqualTo(LocalDate.now().getYear())`
## Changes in `Metadata` API
Until now, `Metadata` supported only `String` values. This PR expands
the list of supported value types to `Integer`, `Long`, `Float` and
`Double`. In the future, more types may be added (if needed).
The method `String get(String key)` will be deprecated later in favor
of:
- `String getString(String key)`
- `Integer getInteger(String key)`
- `Long getLong(String key)`
- etc
New overloaded `put(key, value)` methods are introduced to support more
value types:
- `put(String key, int value)`
- `put(String key, long value)`
- etc
## Changes in `EmbeddingStore` API
New method `search` is added that will become the main entry point for
search in the future. All `findRelevant` methods will be deprecated
later.
New `search` method accepts `EmbeddingSearchRequest` and returns
`EmbeddingSearchResult`.
`EmbeddingSearchRequest` contains all search criteria (e.g.
`maxResults`, `minScore`), including new `Filter`.
`EmbeddingSearchResult` contains a list of `EmbeddingMatch`.
```java
EmbeddingSearchResult search(EmbeddingSearchRequest request);
```
## Changes in `EmbeddingStoreContentRetriever` API
`EmbeddingStoreContentRetriever` can now be configured with a static
`filter` as well as dynamic `dynamicMaxResults`, `dynamicMinScore` and
`dynamicFilter` in the builder:
```java
ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
...
.maxResults(3)
// or
.dynamicMaxResults(query -> 3) // You can define maxResults dynamically. The value could, for example, depend on the query or the user associated with the query.
...
.minScore(0.3)
// or
.dynamicMinScore(query -> 0.3)
...
.filter(metadataKey("userId").isEqualTo("123-456")) // Assuming your TextSegments contain Metadata with key "userId"
// or
.dynamicFilter(query -> metadataKey("userId").isEqualTo(query.metadata().chatMemoryId().toString()))
...
.build();
```
So now you can define `maxResults`, `minScore` and `filter` both
statically and dynamically (they can depend on the query, user, etc.).
These values will be propagated to the underlying `EmbeddingStore`.
##
["Self-querying"](https://python.langchain.com/docs/modules/data_connection/retrievers/self_query/)
This PR also introduces `LanguageModelSqlFilterBuilder` in
`langchain4j-embedding-store-filter-parser-sql` module which can be used
with `EmbeddingStoreContentRetriever`'s `dynamicFilter` to automatically
build a `Filter` object from the `Query` using language model and
`SqlFilterParser`.
For example:
```java
TextSegment groundhogDay = TextSegment.from("Groundhog Day", new Metadata().put("genre", "comedy").put("year", 1993));
TextSegment forrestGump = TextSegment.from("Forrest Gump", new Metadata().put("genre", "drama").put("year", 1994));
TextSegment dieHard = TextSegment.from("Die Hard", new Metadata().put("genre", "action").put("year", 1998));
// describe metadata keys as if they were columns in the SQL table
TableDefinition tableDefinition = TableDefinition.builder()
.name("movies")
.addColumn("genre", "VARCHAR", "one of [comedy, drama, action]")
.addColumn("year", "INT")
.build();
LanguageModelSqlFilterBuilder sqlFilterBuilder = new LanguageModelSqlFilterBuilder(model, tableDefinition);
ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
.dynamicFilter(sqlFilterBuilder::build)
.build();
String answer = assistant.answer("Recommend me a good drama from 90s"); // Forrest Gump
```
## Which embedding store integrations will support `Filter`?
In the long run, all (provided the embedding store itself supports it).
In the first iteration, I aim to add support to just a few:
- `InMemoryEmbeddingStore`
- Elasticsearch
- Milvus
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
## Summary by CodeRabbit
- **New Features**
- Introduced filters for checking key's value existence in a collection
for improved data handling.
- **Enhancements**
- Updated `InMemoryEmbeddingStoreTest` to extend a different class for
improved testing coverage and added a new test method.
- **Refactor**
- Made minor formatting adjustments in the assertion block for better
readability.
- **Documentation**
- Updated class hierarchy information for clarity.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
support #425 . Due to my local environment problem (`vearch` docker
container start failed in Apple M1), I do the integration test in remote
`vearch` (I start up `vearch` container in remote host using docker),
and it works fine. (But I don't check using `Testcontainers` to start
up)
Two more things need discussion and your opinion:
1. There is a translation between `RelevantScore` and `CosineSimilarity`
in `findRelevant` method, I don't know if that's correct, because
`vearch` do not support cosine similarity, so I use inner product
instead (same as cosine similarity if vector is normalized). Should we
normalize vector before adding it to the embedding store?
2. There are many contraints in creating `vearch` space (retrieval types
have different parameters). Should we check it or just let users to
check themselves? (see [Create
Space](https://vearch.readthedocs.io/en/latest/use_op/op_space.html#create-space)).
Currently I implement it by using many inner static class (see
`RetrievalParam` and `RetrievalType`, in `SpaceEngine` it will do some
constraint check.)