Skip to content

Commit 2cd22ca

Browse files
authored
Merge pull request #111 from roryp/main
updated foundry local
2 parents ea43639 + 8bf732c commit 2cd22ca

6 files changed

Lines changed: 131 additions & 59 deletions

File tree

.devcontainer/devcontainer.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,17 @@
66
"installZsh": false,
77
"upgradePackages": false
88
},
9-
"ghcr.io/devcontainers/features/docker-in-docker:2": {},
9+
"ghcr.io/devcontainers/features/docker-in-docker:2": {
10+
"moby": false
11+
},
1012
"ghcr.io/devcontainers/features/azure-cli:1": {
1113
"installBicep": true,
1214
"version": "latest"
1315
},
1416
"ghcr.io/azure/azure-dev/azd:0": {
1517
"version": "stable"
1618
},
17-
"sshd": "latest"
19+
"ghcr.io/devcontainers/features/sshd:1": {}
1820
},
1921
"customizations": {
2022
"vscode": {

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ buildNumber.properties
4646
.project
4747
.classpath
4848

49+
# Node
50+
node_modules/
51+
package-lock.json
52+
4953
# OS
5054
.DS_Store
5155
.DS_Store?

04-PracticalSamples/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ This chapter showcases **sample projects** that demonstrate generative AI develo
2626

2727
### Foundry Local Spring Boot Demo
2828

29-
The **[Foundry Local Spring Boot Demo](foundrylocal/README.md)** demonstrates how to integrate with local AI models using the **OpenAI Java SDK**. It showcases connecting to the **Phi-3.5-mini** model running on Foundry Local, allowing you to run AI applications without relying on cloud services.
29+
The **[Foundry Local Spring Boot Demo](foundrylocal/README.md)** demonstrates how to integrate with local AI models using the **OpenAI Java SDK**. It showcases connecting to models running on Foundry Local (e.g., **Phi-4-mini**), with automatic model detection, allowing you to run AI applications without relying on cloud services.
3030

3131
### Pet Story Generator
3232

04-PracticalSamples/foundrylocal/README.md

Lines changed: 79 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,20 @@ Before starting this tutorial, make sure you have:
2727

2828
### **Install Foundry Local:**
2929

30+
> **Note:** Foundry Local CLI is available on **Windows** and **macOS** only. Linux is supported via the [Foundry Local SDKs](https://github.com/microsoft/Foundry-Local) (Python, JavaScript, C#, Rust).
31+
3032
```bash
3133
# Windows
3234
winget install Microsoft.FoundryLocal
3335

34-
# macOS (after installing)
35-
foundry model run phi-3.5-mini
36+
# macOS
37+
brew tap microsoft/foundrylocal
38+
brew install foundrylocal
39+
```
40+
41+
Verify the installation:
42+
```bash
43+
foundry --version
3644
```
3745

3846
## Project Overview
@@ -52,12 +60,13 @@ This project consists of four main components:
5260

5361
```properties
5462
foundry.local.base-url=http://localhost:5273/v1
55-
foundry.local.model=Phi-3.5-mini-instruct-cuda-gpu:1
63+
# foundry.local.model is auto-detected from Foundry Local. Set it here to override:
64+
# foundry.local.model=Phi-4-mini-instruct-cuda-gpu:5
5665
```
5766

5867
**What this does:**
59-
- **base-url**: Specifies where Foundry Local is running, including the `/v1` path for OpenAI API compatibility. **Note**: Foundry Local dynamically assigns a port, so check your actual port using `foundry service status`
60-
- **model**: Names the AI model to use for text generation, including the version number (e.g., `:1`). Use `foundry model list` to see available models with their exact IDs
68+
- **base-url**: Specifies where Foundry Local is running, including the `/v1` path for OpenAI API compatibility. The default port is `5273`. If the port differs, check it with `foundry service status`.
69+
- **model** (optional): Names the AI model to use for text generation. **By default, the application auto-detects the model** by querying the Foundry Local `/v1/models` endpoint at startup, so you don't need to set this. You can still set it explicitly to override auto-detection if needed.
6170

6271
**Key concept:** Spring Boot automatically loads these properties and makes them available to your application using the `@Value` annotation.
6372

@@ -117,19 +126,24 @@ public class FoundryLocalService {
117126
@Value("${foundry.local.base-url:http://localhost:5273/v1}")
118127
private String baseUrl;
119128

120-
@Value("${foundry.local.model:Phi-3.5-mini-instruct-cuda-gpu:1}")
121-
private String model;
129+
@Value("${foundry.local.model:}")
130+
private String model; // Auto-detected if empty
122131
```
123132

124133
**What this does:**
125134
- `@Service` tells Spring this class provides business logic
126135
- `@Value` injects configuration values from application.properties
127-
- The `:default-value` syntax provides fallback values if properties aren't set
136+
- The model defaults to empty, which triggers **auto-detection** from Foundry Local at startup. This means the app works with any model loaded in Foundry Local without manual configuration.
128137

129138
#### Client Initialization:
130139
```java
131140
@PostConstruct
132141
public void init() {
142+
// Auto-detect the model from Foundry Local if not explicitly configured
143+
if (model == null || model.isBlank()) {
144+
model = detectModel();
145+
}
146+
133147
this.openAIClient = OpenAIOkHttpClient.builder()
134148
.baseUrl(baseUrl) // Base URL already includes /v1 from configuration
135149
.apiKey("not-needed") // Local server doesn't need real API key
@@ -139,6 +153,7 @@ public void init() {
139153

140154
**What this does:**
141155
- `@PostConstruct` runs this method after Spring creates the service
156+
- If no model is configured, it queries Foundry Local's `/v1/models` endpoint and picks the first loaded model
142157
- Creates an OpenAI client that points to your local Foundry Local instance
143158
- The base URL from `application.properties` already includes `/v1` for OpenAI API compatibility
144159
- API key is set to "not-needed" because local development doesn't require authentication
@@ -216,52 +231,70 @@ Here's the complete flow when you run the application:
216231

217232
1. **Startup**: Spring Boot starts and reads `application.properties`
218233
2. **Service Creation**: Spring creates `FoundryLocalService` and injects configuration values
219-
3. **Client Setup**: `@PostConstruct` initializes the OpenAI client to connect to Foundry Local
220-
4. **Demo Execution**: `CommandLineRunner` executes after startup
221-
5. **AI Call**: The demo calls `foundryLocalService.chat()` with a test message
222-
6. **API Request**: Service builds and sends OpenAI-compatible request to Foundry Local
223-
7. **Response Processing**: Service extracts and returns the AI's response
224-
8. **Display**: Application prints the response and exits
234+
3. **Model Detection**: If no model is configured, the service queries Foundry Local's `/v1/models` endpoint and uses the first available model automatically
235+
4. **Client Setup**: `@PostConstruct` initializes the OpenAI client to connect to Foundry Local
236+
5. **Demo Execution**: `CommandLineRunner` executes after startup
237+
6. **AI Call**: The demo calls `foundryLocalService.chat()` with a test message
238+
7. **API Request**: Service builds and sends OpenAI-compatible request to Foundry Local
239+
8. **Response Processing**: Service extracts and returns the AI's response
240+
9. **Display**: Application prints the response and exits
225241

226242
## Setting Up Foundry Local
227243

228-
To set up Foundry Local, follow these steps:
229-
230244
1. **Install Foundry Local** using the instructions in the [Prerequisites](#prerequisites) section.
231245

232-
2. **Check the dynamically assigned port**. Foundry Local automatically assigns a port when it starts. Find your port with:
246+
2. **Start the service** (if not already running):
247+
```bash
248+
foundry service start
249+
```
250+
251+
3. **Check the service status** to confirm it is running and note the port:
233252
```bash
234253
foundry service status
235254
```
236-
237-
**Optional**: If you prefer to use a specific port (e.g., 5273), you can configure it manually:
255+
256+
4. **Download and run a model** (downloads on first run, cached for subsequent runs):
238257
```bash
239-
foundry service set --port 5273
258+
foundry model run phi-4-mini
240259
```
260+
This opens an interactive chat session. You can exit with `Ctrl+C`. The model stays loaded in the service.
261+
262+
> **Tip:** Run `foundry model list` to see all available models. Replace `phi-4-mini` with any alias from the catalog (e.g., `qwen2.5-0.5b` for a smaller/faster model).
241263

242-
3. **Download the AI model** you want to use, for example, `phi-3.5-mini`, with the following command:
264+
5. **Verify the model is loaded:**
243265
```bash
244-
foundry model run phi-3.5-mini
266+
foundry service ps
245267
```
246268

247-
4. **Configure the application.properties** file to match your Foundry Local settings:
248-
- Update the port in `base-url` (from step 2), ensuring it includes `/v1` at the end
249-
- Update the model name to include the version number (check with `foundry model list`)
250-
251-
Example:
269+
6. **Update `application.properties`** if needed:
270+
- The default `base-url` (`http://localhost:5273/v1`) matches the default CLI port. Update only if `foundry service status` shows a different port.
271+
- The model is **auto-detected** at startup — no configuration needed.
272+
252273
```properties
253274
foundry.local.base-url=http://localhost:5273/v1
254-
foundry.local.model=Phi-3.5-mini-instruct-cuda-gpu:1
275+
# Model is auto-detected. Uncomment below to override:
276+
# foundry.local.model=Phi-4-mini-instruct-cuda-gpu:5
255277
```
256278

257279
## Running the Application
258280

259-
### Step 1: Start Foundry Local
281+
### Step 1: Ensure a model is loaded in Foundry Local
282+
```bash
283+
foundry service ps
284+
```
285+
If no models are listed, load one:
260286
```bash
261-
foundry model run phi-3.5-mini
287+
foundry model run phi-4-mini
262288
```
263289

264290
### Step 2: Build and Run the Application
291+
In a separate terminal:
292+
```bash
293+
cd 04-PracticalSamples/foundrylocal
294+
mvn spring-boot:run
295+
```
296+
297+
Or build and run as a JAR:
265298
```bash
266299
mvn clean package
267300
java -jar target/foundry-local-spring-boot-0.0.1-SNAPSHOT.jar
@@ -274,11 +307,9 @@ java -jar target/foundry-local-spring-boot-0.0.1-SNAPSHOT.jar
274307
Calling Foundry Local service...
275308
Sending message: Hello! Can you tell me what you are and what model you're running?
276309
Response from Foundry Local:
277-
Hello! I'm Phi-3.5, a small language model created by Microsoft. I'm currently running
278-
as the Phi-3.5-mini-instruct model, which is designed to be helpful, harmless, and honest
279-
in my interactions. I can assist with a wide variety of tasks including answering
280-
questions, helping with analysis, creative writing, coding, and general conversation.
281-
Is there something specific you'd like help with today?
310+
Hello! I'm Phi, an AI developed by Microsoft. I can assist with a wide variety of
311+
tasks including answering questions, helping with analysis, creative writing, coding,
312+
and general conversation. How can I help you today?
282313
=========================
283314
```
284315

@@ -291,29 +322,26 @@ For more examples, see [Chapter 04: Practical samples](../README.md)
291322
### Common Issues
292323

293324
**"Connection refused" or "Service unavailable"**
294-
- Make sure Foundry Local is running: `foundry model list`
295-
- Check the actual port Foundry Local is using: `foundry service status`
296-
- Update your `application.properties` with the correct port, ensuring the URL ends with `/v1`
297-
- Alternatively, set a specific port if desired: `foundry service set --port 5273`
298-
- Try restarting Foundry Local: `foundry model run phi-3.5-mini`
299-
300-
**"Model not found" or "404 Not Found" errors**
301-
- Check available models with their exact IDs: `foundry model list`
302-
- Update the model name in `application.properties` to match exactly, including the version number (e.g., `Phi-3.5-mini-instruct-cuda-gpu:1`)
303-
- Ensure the `base-url` includes `/v1` at the end: `http://localhost:5273/v1`
304-
- Download the model if needed: `foundry model run phi-3.5-mini`
325+
- Check the service: `foundry service status`
326+
- Restart if needed: `foundry service restart`
327+
- Verify the port in `application.properties` matches `foundry service status` output
328+
- Ensure the URL ends with `/v1`: `http://localhost:5273/v1`
329+
330+
**"No model found" at startup**
331+
- The application auto-detects the model. Ensure at least one model is loaded: `foundry service ps`
332+
- If no models are loaded: `foundry model run phi-4-mini`
333+
- If you overrode the model name in `application.properties`, verify it matches `foundry model list`
305334

306335
**"400 Bad Request" errors**
307336
- Verify the base URL includes `/v1`: `http://localhost:5273/v1`
308-
- Check that the model ID matches exactly what's shown in `foundry model list`
309337
- Ensure you're using `maxCompletionTokens()` in your code (not the deprecated `maxTokens()`)
310338
311339
**Maven compilation errors**
312340
- Ensure Java 21 or higher: `java -version`
313341
- Clean and rebuild: `mvn clean compile`
314342
- Check internet connection for dependency downloads
315343
316-
**Application starts but no output**
317-
- Verify Foundry Local is responding: Check `http://localhost:5273/v1/models` or run `foundry service status`
318-
- Check application logs for specific error messages
319-
- Ensure the model is fully loaded and ready
344+
**Service connection problems**
345+
- If you see `Request to local service failed`, run: `foundry service restart`
346+
- Check loaded models: `foundry service ps`
347+
- View service logs: `foundry service diag`

04-PracticalSamples/foundrylocal/src/main/java/com/example/FoundryLocalService.java

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
package com.example;
22

3+
import com.fasterxml.jackson.databind.JsonNode;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
35
import com.openai.client.OpenAIClient;
46
import com.openai.client.okhttp.OpenAIOkHttpClient;
57
import com.openai.models.chat.completions.*;
68
import org.springframework.beans.factory.annotation.Value;
79
import org.springframework.stereotype.Service;
810

911
import jakarta.annotation.PostConstruct;
12+
import java.net.URI;
13+
import java.net.http.HttpClient;
14+
import java.net.http.HttpRequest;
15+
import java.net.http.HttpResponse;
1016

1117
/**
1218
* Service for connecting to locally running AI models via Foundry Local.
@@ -31,8 +37,8 @@ public class FoundryLocalService {
3137
@Value("${foundry.local.base-url:http://localhost:5273/v1}")
3238
private String baseUrl; // Where your local AI server is running
3339

34-
@Value("${foundry.local.model:Phi-3.5-mini-instruct-cuda-gpu:1}")
35-
private String model; // Which local AI model to use
40+
@Value("${foundry.local.model:}")
41+
private String model; // Which local AI model to use (auto-detected if empty)
3642

3743
// OpenAI client configured to talk to local server instead of OpenAI's servers
3844
private OpenAIClient openAIClient;
@@ -46,19 +52,51 @@ public class FoundryLocalService {
4652
*/
4753
@PostConstruct
4854
public void init() {
49-
// Create OpenAI client but point it to local server instead of OpenAI
50-
// This works because local AI servers often implement OpenAI-compatible APIs
55+
// Auto-detect the model from Foundry Local if not explicitly configured
56+
if (model == null || model.isBlank()) {
57+
model = detectModel();
58+
}
59+
5160
System.out.println("Initializing Foundry Local client:");
5261
System.out.println(" Base URL: " + baseUrl);
5362
System.out.println(" Model: " + model);
5463

64+
// Create OpenAI client but point it to local server instead of OpenAI
65+
// This works because local AI servers often implement OpenAI-compatible APIs
5566
this.openAIClient = OpenAIOkHttpClient.builder()
5667
.baseUrl(baseUrl) // Local server endpoint with /v1 path for OpenAI API compatibility
5768
.apiKey("not-needed") // Local servers usually don't need real API keys
5869
.build();
5970

6071
System.out.println("Client initialized successfully");
6172
}
73+
74+
/**
75+
* Query the Foundry Local /v1/models endpoint and return the first available model ID.
76+
* This makes the app work regardless of which model variant is loaded.
77+
*/
78+
private String detectModel() {
79+
try {
80+
HttpRequest request = HttpRequest.newBuilder()
81+
.uri(URI.create(baseUrl + "/models"))
82+
.GET()
83+
.build();
84+
HttpResponse<String> response = HttpClient.newHttpClient()
85+
.send(request, HttpResponse.BodyHandlers.ofString());
86+
JsonNode root = new ObjectMapper().readTree(response.body());
87+
JsonNode data = root.get("data");
88+
if (data != null && data.isArray() && !data.isEmpty()) {
89+
String detected = data.get(0).get("id").asText();
90+
System.out.println("Auto-detected model from Foundry Local: " + detected);
91+
return detected;
92+
}
93+
} catch (Exception e) {
94+
System.err.println("Could not auto-detect model from " + baseUrl + "/models: " + e.getMessage());
95+
}
96+
throw new RuntimeException(
97+
"No model found. Make sure Foundry Local is running at " + baseUrl +
98+
" with a model loaded, or set foundry.local.model in application.properties.");
99+
}
62100

63101
/**
64102
* Send a message to the local AI model and get a response.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
foundry.local.base-url=http://localhost:5273/v1
2-
foundry.local.model=Phi-3.5-mini-instruct-cuda-gpu:1
2+
# foundry.local.model is auto-detected from Foundry Local. Set it here to override:

0 commit comments

Comments
 (0)