Skip to content

Commit 4e405ab

Browse files
committed
clean: Adapt LLM URI syntax
1 parent 810c305 commit 4e405ab

8 files changed

+101
-32
lines changed

java/dev/enola/ai/langchain4j/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ java_library(
2929
deps = [
3030
"//java/dev/enola/common",
3131
"//java/dev/enola/common/context",
32+
"//java/dev/enola/common/io/iri",
3233
"//java/dev/enola/data",
3334
"//java/dev/enola/identity",
3435
"@enola_maven//:com_google_guava_guava",
@@ -46,6 +47,7 @@ junit_tests(
4647
),
4748
deps = [
4849
":langchain4j",
50+
"//java/dev/enola/common",
4951
"//java/dev/enola/common/context",
5052
"//java/dev/enola/common/context/testlib",
5153
"//java/dev/enola/data",

java/dev/enola/ai/langchain4j/ChatLanguageModelProvider.java

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
*/
1818
package dev.enola.ai.langchain4j;
1919

20+
import dev.enola.common.io.iri.URIs;
2021
import dev.enola.data.Provider;
2122
import dev.langchain4j.model.chat.ChatLanguageModel;
2223
import dev.langchain4j.model.ollama.OllamaChatModel;
@@ -32,22 +33,18 @@ public class ChatLanguageModelProvider implements Provider<URI, ChatLanguageMode
3233

3334
@Override
3435
public ChatLanguageModel get(URI uri) throws IllegalArgumentException, UncheckedIOException {
35-
if (!"llm".equals(uri.getScheme())) throw new IllegalArgumentException(uri.toString());
36-
var schemeSpecificPart = uri.getSchemeSpecificPart();
37-
38-
if (schemeSpecificPart.startsWith("fake:")) {
39-
var reply = schemeSpecificPart.substring("fake:".length());
36+
if ("mockllm".equalsIgnoreCase(uri.getScheme())) {
37+
var reply = uri.getSchemeSpecificPart();
4038
return new TestChatLanguageModel(reply);
4139
}
4240

43-
if (schemeSpecificPart.startsWith("ollama:")) {
44-
var tail = schemeSpecificPart.substring("ollama:".length());
45-
var split = tail.split(":");
46-
var baseURL = split[0] + ":" + split[1] + ":" + split[2];
47-
var model = split[3] + ":" + split[4];
41+
var queryMap = URIs.getQueryMap(uri);
42+
if ("ollama".equalsIgnoreCase(queryMap.get("type"))) {
43+
var baseURL = uri.getScheme() + "://" + uri.getAuthority();
44+
var model = queryMap.get("model");
4845
return OllamaChatModel.builder().baseUrl(baseURL).modelName(model).build();
4946
}
5047

51-
throw new IllegalArgumentException(schemeSpecificPart);
48+
throw new IllegalArgumentException(uri.toString());
5249
}
5350
}

java/dev/enola/ai/langchain4j/ChatLanguageModelProviderTest.java

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,12 @@
1919

2020
import static com.google.common.truth.Truth.assertThat;
2121

22+
import dev.enola.common.Net;
2223
import dev.enola.data.Provider;
2324
import dev.langchain4j.model.chat.ChatLanguageModel;
2425

2526
import org.junit.Test;
2627

27-
import java.io.IOException;
28-
import java.net.Socket;
2928
import java.net.URI;
3029

3130
public class ChatLanguageModelProviderTest {
@@ -46,21 +45,13 @@ public void bad() {
4645

4746
@Test
4847
public void fake() {
49-
check(provider.get(URI.create("llm:fake:Zurich")));
48+
check(provider.get(URI.create("mockllm:Zurich")));
5049
}
5150

5251
@Test
5352
public void ollama() {
54-
if (!portAvailable(11434)) return;
53+
if (!Net.portAvailable(11434)) return;
5554

56-
check(provider.get(URI.create("llm:ollama:http://localhost:11434:gemma3:1b")));
57-
}
58-
59-
private boolean portAvailable(int port) {
60-
try (var ignored = new Socket("localhost", port)) {
61-
return true;
62-
} catch (IOException e) {
63-
return false;
64-
}
55+
check(provider.get(URI.create("http://localhost:11434?type=ollama&model=gemma3:1b")));
6556
}
6657
}

java/dev/enola/cas/BUILD

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,12 @@ junit_tests(
5050
glob(["*TestAbstract.java"]),
5151
deps = [
5252
":cas",
53+
"//java/dev/enola/common",
5354
"//java/dev/enola/common/context/testlib",
55+
"//java/dev/enola/common/function",
5456
"//java/dev/enola/common/io",
5557
"@enola_maven//:com_github_ipfs_java_ipfs_http_client",
5658
"@enola_maven//:com_github_ipld_java_cid",
59+
"@enola_maven//:org_jspecify_jspecify",
5760
],
5861
)

java/dev/enola/cas/IPFSApiResourceTest.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,18 @@
1717
*/
1818
package dev.enola.cas;
1919

20+
import dev.enola.common.Net;
2021
import dev.enola.common.io.resource.ResourceProvider;
2122

2223
import io.ipfs.api.IPFS;
2324

25+
import org.jspecify.annotations.Nullable;
26+
2427
public class IPFSApiResourceTest extends IPFSResourceTestAbstract {
2528

2629
@Override
27-
protected ResourceProvider getResourceProvider() {
30+
protected @Nullable ResourceProvider getResourceProvider() {
31+
if (!Net.portAvailable(5001)) return null;
2832
var ipfs = new IPFS("/ip4/127.0.0.1/tcp/5001");
2933
return new IPFSApiResource.Provider(new IPFSBlobStore(ipfs));
3034
}

java/dev/enola/cas/IPFSBlobStoreTest.java

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,18 @@
2121

2222
import com.google.common.io.ByteSource;
2323

24+
import dev.enola.common.Net;
25+
import dev.enola.common.function.CheckedSupplier;
26+
2427
import io.ipfs.api.IPFS;
2528
import io.ipfs.cid.Cid;
2629

30+
import org.jspecify.annotations.Nullable;
31+
import org.junit.BeforeClass;
2732
import org.junit.Test;
2833

2934
import java.io.IOException;
35+
import java.net.SocketTimeoutException;
3036
import java.util.Random;
3137

3238
public class IPFSBlobStoreTest {
@@ -36,28 +42,37 @@ public class IPFSBlobStoreTest {
3642
String HELLO_CIDv1_RAW = "bafkreiefh74toyvanxn7oiwe5pu53vtnr5r53lvjp5jbypwmednhzf3aea";
3743
String HELLO_CID_IDENTITY = "bafyaafikcmeaeeqnnbswy3dpfqqho33snrsaugan";
3844

45+
static @Nullable IPFSBlobStore ipfs;
46+
47+
@BeforeClass
48+
public static void setUp() {
49+
if (Net.portAvailable(5001)) ipfs = new IPFSBlobStore(new IPFS("/ip4/127.0.0.1/tcp/5001"));
50+
}
51+
3952
@Test
4053
public void loadHelloCIDv0() throws IOException {
41-
var bytes = ipfs.load(Cid.decode(HELLO_CIDv0));
42-
assertThat(new String(bytes.read())).isEqualTo("hello, world\n");
54+
if (ipfs == null) return;
55+
var bytes = ignoreSocketTimeoutException(() -> ipfs.load(Cid.decode(HELLO_CIDv0)));
56+
if (bytes != null) assertThat(new String(bytes.read())).isEqualTo("hello, world\n");
4357
}
4458

45-
IPFSBlobStore ipfs = new IPFSBlobStore(new IPFS("/ip4/127.0.0.1/tcp/5001"));
46-
4759
@Test
4860
public void loadHelloCIDv1() throws IOException {
49-
var bytes = ipfs.load(Cid.decode(HELLO_CIDv1_RAW));
50-
assertThat(new String(bytes.read())).isEqualTo("hello, world\n");
61+
if (ipfs == null) return;
62+
var bytes = ignoreSocketTimeoutException(() -> ipfs.load(Cid.decode(HELLO_CIDv1_RAW)));
63+
if (bytes != null) assertThat(new String(bytes.read())).isEqualTo("hello, world\n");
5164
}
5265

5366
@Test
5467
public void loadHelloCIDv1Identity() throws IOException {
68+
if (ipfs == null) return;
5569
var bytes = ipfs.load(Cid.decode(HELLO_CID_IDENTITY));
5670
assertThat(new String(bytes.read())).isEqualTo("hello, world\n");
5771
}
5872

5973
@Test
6074
public void storeHello() throws IOException {
75+
if (ipfs == null) return;
6176
var cid = ipfs.store(ByteSource.wrap("hello, world\n".getBytes()));
6277
assertThat(cid.toString()).isEqualTo(HELLO_CIDv1_RAW);
6378
assertThat(cid.codec).isEqualTo(Cid.Codec.Raw);
@@ -66,6 +81,7 @@ public void storeHello() throws IOException {
6681

6782
@Test
6883
public void storeLoadRandom() throws IOException {
84+
if (ipfs == null) return;
6985
var bytes = generateRandomBytes(1024);
7086
var cid = ipfs.store(ByteSource.wrap(bytes));
7187
assertThat(cid.version).isEqualTo(1);
@@ -79,4 +95,15 @@ private static byte[] generateRandomBytes(int length) {
7995
random.nextBytes(bytes);
8096
return bytes;
8197
}
98+
99+
private @Nullable ByteSource ignoreSocketTimeoutException(
100+
CheckedSupplier<ByteSource, IOException> r) throws IOException {
101+
try {
102+
return r.get();
103+
} catch (RuntimeException e) {
104+
if (e.getCause() instanceof SocketTimeoutException) {
105+
return null;
106+
} else throw e;
107+
}
108+
}
82109
}

java/dev/enola/cas/IPFSResourceTestAbstract.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
import io.ipfs.cid.Cid.CidEncodingException;
3131

32+
import org.jspecify.annotations.Nullable;
3233
import org.junit.Rule;
3334
import org.junit.Test;
3435

@@ -37,18 +38,22 @@
3738

3839
public abstract class IPFSResourceTestAbstract {
3940

40-
abstract ResourceProvider getResourceProvider();
41+
abstract @Nullable ResourceProvider getResourceProvider();
4142

4243
public @Rule SingletonRule r1 = $(MediaTypeProviders.set());
4344

4445
@Test
4546
public void hello() throws IOException {
47+
if (getResourceProvider() == null) return;
4648
assertThat(bytesFromIPFS("ipfs://QmXV7pL1CB7A8Tzk7jP2XE9kRyk8HZd145KDptdxzmNLfu"))
4749
.isEqualTo("hello, world\n".getBytes(UTF_8));
4850
}
4951

5052
@Test
5153
public void vanGogh() throws IOException {
54+
var rp = getResourceProvider();
55+
if (rp == null) return;
56+
5257
var url =
5358
"ipfs://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/wiki/Vincent_van_Gogh.html";
5459
var r = getResourceProvider().get(url + "?mediaType=text/html;charset=UTF-8");
@@ -65,12 +70,18 @@ private byte[] bytesFromIPFS(String url) throws IOException {
6570

6671
@Test(expected = IllegalArgumentException.class)
6772
public void notIFPS() {
73+
var rp = getResourceProvider();
74+
if (rp == null) throw new IllegalArgumentException();
75+
6876
var url = "http://www.google.com";
6977
new IPFSGatewayResource(URI.create(url), null, null);
7078
}
7179

7280
@Test(expected = CidEncodingException.class)
7381
public void badCID() throws IOException {
82+
var rp = getResourceProvider();
83+
if (rp == null) throw new CidEncodingException("");
84+
7485
var url = "ipfs://bad";
7586
getResourceProvider().get(url).byteSource().read();
7687
}

java/dev/enola/common/Net.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* Copyright 2025 The Enola <https://enola.dev> Authors
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* https://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package dev.enola.common;
19+
20+
import java.io.IOException;
21+
import java.net.Socket;
22+
23+
public final class Net {
24+
private Net() {}
25+
26+
public static boolean portAvailable(int port) {
27+
// skipcq: JAVA-S1011 Non-SSL socket is OK for this purpose.
28+
try (var ignored = new Socket("localhost", port)) {
29+
return true;
30+
} catch (IOException e) {
31+
return false;
32+
}
33+
}
34+
}

0 commit comments

Comments
 (0)