Skip to content

Commit 920c9e9

Browse files
Merge pull request #1283 from sicoyle/feat-convo-api-new-fields
feat: use ollama to show new conversation api fields
2 parents 66c00ae + 7c8cc4d commit 920c9e9

File tree

25 files changed

+589
-413
lines changed

25 files changed

+589
-413
lines changed

.github/workflows/validate_csharp_quickstarts.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,15 @@ jobs:
8484
export GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}
8585
dapr init --runtime-version=${{ env.DAPR_RUNTIME_VERSION }}
8686
dapr --version
87+
- name: Cache Ollama models
88+
uses: actions/cache@v4
89+
with:
90+
path: ~/.ollama
91+
key: ${{ runner.os }}-ollama-llama3.2
92+
- name: Setup Ollama
93+
uses: ai-action/setup-ollama@v2
94+
- name: Pull Ollama model
95+
run: ollama pull llama3.2:latest
8796
- name: Install utilities dependencies
8897
run: |
8998
echo "PATH=$PATH:$HOME/.local/bin" >> $GITHUB_ENV

.github/workflows/validate_go_quickstarts.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,15 @@ jobs:
8282
export GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}
8383
dapr init --runtime-version=${{ env.DAPR_RUNTIME_VERSION }}
8484
dapr --version
85+
- name: Cache Ollama models
86+
uses: actions/cache@v4
87+
with:
88+
path: ~/.ollama
89+
key: ${{ runner.os }}-ollama-llama3.2
90+
- name: Setup Ollama
91+
uses: ai-action/setup-ollama@v2
92+
- name: Pull Ollama model
93+
run: ollama pull llama3.2:latest
8594
- name: Install utilities dependencies
8695
run: |
8796
echo "PATH=$PATH:$HOME/.local/bin" >> $GITHUB_ENV

.github/workflows/validate_java_quickstarts.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,15 @@ jobs:
8383
export GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}
8484
dapr init --runtime-version=${{ env.DAPR_RUNTIME_VERSION }}
8585
dapr --version
86+
- name: Cache Ollama models
87+
uses: actions/cache@v4
88+
with:
89+
path: ~/.ollama
90+
key: ${{ runner.os }}-ollama-llama3.2
91+
- name: Setup Ollama
92+
uses: ai-action/setup-ollama@v2
93+
- name: Pull Ollama model
94+
run: ollama pull llama3.2:latest
8695
- name: Install utilities dependencies
8796
run: |
8897
echo "PATH=$PATH:$HOME/.local/bin" >> $GITHUB_ENV

.github/workflows/validate_javascript_quickstarts.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,15 @@ jobs:
7878
export GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}
7979
dapr init --runtime-version=${{ env.DAPR_RUNTIME_VERSION }}
8080
dapr --version
81+
- name: Cache Ollama models
82+
uses: actions/cache@v4
83+
with:
84+
path: ~/.ollama
85+
key: ${{ runner.os }}-ollama-llama3.2
86+
- name: Setup Ollama
87+
uses: ai-action/setup-ollama@v2
88+
- name: Pull Ollama model
89+
run: ollama pull llama3.2:latest
8190
- name: Install utilities dependencies
8291
run: |
8392
echo "PATH=$PATH:$HOME/.local/bin" >> $GITHUB_ENV

.github/workflows/validate_python_quickstarts.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,15 @@ jobs:
8686
export GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}
8787
dapr init --runtime-version=${{ env.DAPR_RUNTIME_VERSION }}
8888
dapr --version
89+
- name: Cache Ollama models
90+
uses: actions/cache@v4
91+
with:
92+
path: ~/.ollama
93+
key: ${{ runner.os }}-ollama-llama3.2
94+
- name: Setup Ollama
95+
uses: ai-action/setup-ollama@v2
96+
- name: Pull Ollama model
97+
run: ollama pull llama3.2:latest
8998
- name: Install utilities dependencies
9099
run: |
91100
echo "PATH=$PATH:$HOME/.local/bin" >> $GITHUB_ENV
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
apiVersion: dapr.io/v1alpha1
2+
kind: Component
3+
metadata:
4+
name: ollama
5+
spec:
6+
type: conversation.ollama
7+
version: v1
8+
metadata:
9+
- name: model
10+
value: llama3.2:latest

conversation/csharp/http/README.md

Lines changed: 21 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -14,27 +14,24 @@ This quickstart includes one app:
1414

1515
This section shows how to run the application using the [multi-app run template file](https://docs.dapr.io/developing-applications/local-development/multi-app-dapr-run/multi-app-overview/) and Dapr CLI with `dapr run -f .`.
1616

17-
This example uses the default LLM component (Echo) which simply returns the input for testing purposes. You can switch to the OpenAI component by adding your API token in the provided OpenAI component file and changing the component name from `echo` to `openai`. For other available integrations, see the other [supported conversation components](https://docs.dapr.io/reference/components-reference/supported-conversation/).
17+
This example uses the Ollama LLM component for local inference. You can switch to the OpenAI component by adding your API token in the provided OpenAI component file and changing the component name from `ollama` to `openai`. For other available integrations, see the other [supported conversation components](https://docs.dapr.io/reference/components-reference/supported-conversation/).
1818

1919
Open a new terminal window and run the multi app run template:
2020

2121
<!-- STEP
2222
name: Run multi app run template
2323
expected_stdout_lines:
2424
- '== APP - conversation == Conversation input sent: What is dapr?'
25-
- '== APP - conversation == Output response: What is dapr?'
25+
- '== APP - conversation == Usage:'
26+
- '== APP - conversation == Output response:'
2627
- '== APP - conversation == Tool calling input sent: What is the weather like in San Francisco in celsius?'
27-
- '== APP - conversation == Output message: What is the weather like in San Francisco in celsius?'
28-
- '== APP - conversation == Tool calls detected:'
29-
- "== APP - conversation == Tool call: {\"id\":0,\"function\":{\"name\":\"get_weather\",\"arguments\":"
30-
- '== APP - conversation == Function name: get_weather'
31-
- '== APP - conversation == Function arguments: '
28+
- '== APP - conversation == Tool calls'
3229
expected_stderr_lines:
3330
output_match_mode: substring
3431
match_order: none
35-
background: true
36-
sleep: 15
37-
timeout_seconds: 30
32+
background: false
33+
sleep: 30
34+
timeout_seconds: 120
3835
-->
3936

4037
```bash
@@ -43,25 +40,19 @@ dapr run -f .
4340

4441
The terminal console output should look similar to this, where:
4542

46-
- The app first sends an input `What is dapr?` to the `echo` Component mock LLM.
47-
- The mock LLM echoes `What is dapr?`.
43+
- The app first sends an input `What is dapr?` to the Ollama LLM component with a structured JSON response format.
44+
- The LLM returns a JSON object with an `answer` field describing Dapr.
45+
- The app then sends a weather request with a `get_weather` tool available; the LLM calls the tool.
4846

4947
```text
5048
== APP - conversation == Conversation input sent: What is dapr?
51-
== APP - conversation == Output response: What is dapr?
52-
```
53-
54-
- The app then sends an input `What is the weather like in San Francisco in celsius?` to the `echo` Component mock LLM.
55-
- The mock LLM echoes `What is the weather like in San Francisco in celsius?` and calls the `get_weather` tool.
56-
- The echo Component calls the `get_weather` tool and returns the requested weather information.
57-
58-
```text
49+
== APP - conversation == Usage: prompt_tokens=30 completion_tokens=64 total_tokens=94
50+
== APP - conversation == Output response: { "answer": "Dapr is an open-source, cross-platform microservices framework..." }
5951
== APP - conversation == Tool calling input sent: What is the weather like in San Francisco in celsius?
60-
== APP - conversation == Output message: What is the weather like in San Francisco in celsius?
6152
== APP - conversation == Tool calls detected:
62-
== APP - conversation == Tool call: {"id":0,"function":{"name":"get_weather","arguments":"location,unit"}}
53+
== APP - conversation == Tool call: {"id":0,"function":{"name":"get_weather","arguments":"{\"location\":\"San Francisco, CA\",\"unit\":\"celsius\"}"}}
6354
== APP - conversation == Function name: get_weather
64-
== APP - conversation == Function arguments: location,unit
55+
== APP - conversation == Function arguments: {"location":"San Francisco, CA","unit":"celsius"}
6556
```
6657

6758
<!-- END_STEP -->
@@ -95,19 +86,17 @@ dapr run --app-id conversation --resources-path ../../../components/ -- dotnet r
9586

9687
The terminal console output should look similar to this, where:
9788

98-
- The app sends an input `What is dapr?` to the `echo` Component mock LLM.
99-
- The mock LLM echoes `What is dapr?`.
100-
- The app then sends an input `What is the weather like in San Francisco in celsius?` to the `echo` Component mock LLM.
101-
- The mock LLM echoes `What is the weather like in San Francisco in celsius?` and calls the `get_weather` tool.
102-
- The echo Component calls the `get_weather` tool and returns the requested weather information.
89+
- The app sends an input `What is dapr?` to the Ollama LLM component with a structured JSON response format.
90+
- The LLM returns a JSON object with an `answer` field describing Dapr.
91+
- The app then sends a weather request with a `get_weather` tool available; the LLM calls the tool.
10392

10493
```text
10594
== APP - conversation == Conversation input sent: What is dapr?
106-
== APP - conversation == Output response: What is dapr?
95+
== APP - conversation == Usage: prompt_tokens=30 completion_tokens=64 total_tokens=94
96+
== APP - conversation == Output response: { "answer": "Dapr is an open-source, cross-platform microservices framework..." }
10797
== APP - conversation == Tool calling input sent: What is the weather like in San Francisco in celsius?
108-
== APP - conversation == Output message: What is the weather like in San Francisco in celsius?
10998
== APP - conversation == Tool calls detected:
110-
== APP - conversation == Tool call: {"id":0,"function":{"name":"get_weather","arguments":"location,unit"}}
99+
== APP - conversation == Tool call: {"id":0,"function":{"name":"get_weather","arguments":"{\"location\":\"San Francisco, CA\",\"unit\":\"celsius\"}"}}
111100
== APP - conversation == Function name: get_weather
112-
== APP - conversation == Function arguments: location,unit
101+
== APP - conversation == Function arguments: {"location":"San Francisco, CA","unit":"celsius"}
113102
```

conversation/csharp/http/conversation/Program.cs

Lines changed: 86 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*
1+
/*
22
Copyright 2024 The Dapr Authors
33
Licensed under the Apache License, Version 2.0 (the "License");
44
you may not use this file except in compliance with the License.
@@ -14,9 +14,9 @@ limitations under the License.
1414
*/
1515

1616
using System.Net.Http.Json;
17+
using System.Text.Encodings.Web;
1718
using System.Text.Json;
1819

19-
// const string conversationComponentName = "echo";
2020
const string conversationText = "What is dapr?";
2121
const string toolCallInput = "What is the weather like in San Francisco in celsius?";
2222

@@ -40,25 +40,47 @@ limitations under the License.
4040
}]
4141
}],
4242
"parameters": {},
43-
"metadata": {}
43+
"metadata": {},
44+
"response_format": {
45+
"type": "object",
46+
"properties": {"answer": {"type": "string"}},
47+
"required": ["answer"]
48+
},
49+
"prompt_cache_retention": "86400s"
4450
}
4551
""");
4652

47-
var conversationResponse = await httpClient.PostAsJsonAsync("http://localhost:3500/v1.0-alpha2/conversation/echo/converse", conversationRequestBody);
48-
var conversationResult = await conversationResponse.Content.ReadFromJsonAsync<JsonElement>();
53+
var conversationResponse = await httpClient.PostAsJsonAsync("http://localhost:3500/v1.0-alpha2/conversation/ollama/converse", conversationRequestBody);
54+
conversationResponse.EnsureSuccessStatusCode();
55+
var responseText = await conversationResponse.Content.ReadAsStringAsync();
56+
var conversationResult = JsonSerializer.Deserialize<JsonElement>(responseText);
4957

50-
var conversationContent = conversationResult
51-
.GetProperty("outputs")
52-
.EnumerateArray()
53-
.First()
58+
if (conversationResult.ValueKind == JsonValueKind.Null || conversationResult.ValueKind == JsonValueKind.Undefined)
59+
{
60+
throw new InvalidOperationException($"Failed to parse response as JSON. Response: {responseText}");
61+
}
62+
63+
if (!conversationResult.TryGetProperty("outputs", out var outputsElement))
64+
{
65+
throw new InvalidOperationException($"Response does not contain 'outputs' property. Response: {responseText}");
66+
}
67+
68+
var firstOutput = outputsElement.EnumerateArray().First();
69+
70+
Console.WriteLine($"Conversation input sent: {conversationText}");
71+
if (firstOutput.TryGetProperty("model", out var modelElement) && modelElement.GetString() is { Length: > 0 } model)
72+
Console.WriteLine($"Model: {model}");
73+
if (firstOutput.TryGetProperty("usage", out var usageElement))
74+
Console.WriteLine($"Usage: prompt_tokens={usageElement.GetProperty("promptTokens").GetString()} completion_tokens={usageElement.GetProperty("completionTokens").GetString()} total_tokens={usageElement.GetProperty("totalTokens").GetString()}");
75+
76+
var conversationContent = firstOutput
5477
.GetProperty("choices")
5578
.EnumerateArray()
5679
.First()
5780
.GetProperty("message")
5881
.GetProperty("content")
5982
.GetString();
6083

61-
Console.WriteLine($"Conversation input sent: {conversationText}");
6284
Console.WriteLine($"Output response: {conversationContent}");
6385

6486
//
@@ -82,20 +104,8 @@ limitations under the License.
82104
"scrubPii": false
83105
}
84106
],
85-
"parameters": {
86-
"max_tokens": {
87-
"@type": "type.googleapis.com/google.protobuf.Int64Value",
88-
"value": "100"
89-
},
90-
"model": {
91-
"@type": "type.googleapis.com/google.protobuf.StringValue",
92-
"value": "claude-3-5-sonnet-20240620"
93-
}
94-
},
95-
"metadata": {
96-
"api_key": "test-key",
97-
"version": "1.0"
98-
},
107+
"parameters": {},
108+
"metadata": {},
99109
"scrubPii": false,
100110
"temperature": 0.7,
101111
"tools": [
@@ -126,48 +136,71 @@ limitations under the License.
126136
}
127137
}
128138
],
129-
"toolChoice": "auto"
139+
"toolChoice": "required"
130140
}
131141
""");
132142

133-
var toolCallingResponse = await httpClient.PostAsJsonAsync("http://localhost:3500/v1.0-alpha2/conversation/echo/converse", toolCallRequestBody);
134-
var toolCallingResult = await toolCallingResponse.Content.ReadFromJsonAsync<JsonElement>();
143+
var toolCallingResponse = await httpClient.PostAsJsonAsync("http://localhost:3500/v1.0-alpha2/conversation/ollama/converse", toolCallRequestBody);
144+
toolCallingResponse.EnsureSuccessStatusCode();
145+
var toolCallingResponseText = await toolCallingResponse.Content.ReadAsStringAsync();
146+
var toolCallingResult = JsonSerializer.Deserialize<JsonElement>(toolCallingResponseText);
135147

136-
var messageElement = toolCallingResult
137-
.GetProperty("outputs")
148+
if (toolCallingResult.ValueKind == JsonValueKind.Null || toolCallingResult.ValueKind == JsonValueKind.Undefined)
149+
{
150+
throw new InvalidOperationException($"Failed to parse response as JSON. Response: {toolCallingResponseText}");
151+
}
152+
153+
if (!toolCallingResult.TryGetProperty("outputs", out var toolCallingOutputsElement))
154+
{
155+
throw new InvalidOperationException($"Response does not contain 'outputs' property. Response: {toolCallingResponseText}");
156+
}
157+
158+
var messageElement = toolCallingOutputsElement
138159
.EnumerateArray()
139160
.First()
140161
.GetProperty("choices")
141162
.EnumerateArray()
142163
.First()
143164
.GetProperty("message");
144165

145-
var toolCallingContent = messageElement.TryGetProperty("content", out var contentElement)
146-
? contentElement.GetString()
166+
var toolCallingContent = messageElement.TryGetProperty("content", out var contentElement)
167+
? contentElement.GetString()
147168
: null;
148169

149-
var functionCalled = messageElement
150-
.GetProperty("toolCalls")
151-
.EnumerateArray()
152-
.First()
153-
.GetProperty("function");
154-
155-
var functionName = functionCalled.GetProperty("name").GetString();
156-
var functionArguments = functionCalled.GetProperty("arguments").GetString();
170+
Console.WriteLine($"Tool calling input sent: {toolCallInput}");
171+
Console.WriteLine($"Output message: {toolCallingContent}");
157172

158-
var toolCallJson = JsonSerializer.Serialize(new
173+
if (messageElement.TryGetProperty("toolCalls", out var toolCallsElement) && toolCallsElement.GetArrayLength() > 0)
159174
{
160-
id = 0,
161-
function = new
162-
{
163-
name = functionName,
164-
arguments = functionArguments,
165-
},
166-
});
175+
var functionCalled = toolCallsElement
176+
.EnumerateArray()
177+
.First()
178+
.GetProperty("function");
179+
180+
var functionName = functionCalled.GetProperty("name").GetString();
181+
var functionArguments = functionCalled.GetProperty("arguments").GetString();
182+
183+
var toolCallJson = JsonSerializer.Serialize(new
184+
{
185+
id = 0,
186+
function = new
187+
{
188+
name = functionName,
189+
arguments = functionArguments,
190+
},
191+
}, s_jsonOptions);
192+
193+
Console.WriteLine("Tool calls detected:");
194+
Console.WriteLine($"Tool call: {toolCallJson}");
195+
Console.WriteLine($"Function name: {functionName}");
196+
Console.WriteLine($"Function arguments: {functionArguments}");
197+
}
198+
else
199+
{
200+
Console.WriteLine("Tool calls not found");
201+
}
167202

168-
Console.WriteLine($"Tool calling input sent: {toolCallInput}");
169-
Console.WriteLine($"Output message: {toolCallingContent}");
170-
Console.WriteLine("Tool calls detected:");
171-
Console.WriteLine($"Tool call: {toolCallJson}");
172-
Console.WriteLine($"Function name: {functionName}");
173-
Console.WriteLine($"Function arguments: {functionArguments}");
203+
static partial class Program
204+
{
205+
static readonly JsonSerializerOptions s_jsonOptions = new() { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
206+
}

0 commit comments

Comments
 (0)