Skip to content

Commit eb75dd3

Browse files
Merge pull request #1246 from atrauzzi/release-1.16-conversations-csharp
Conversations - tool calling for C#
2 parents 12aa7a9 + 807ecb4 commit eb75dd3

File tree

8 files changed

+351
-116
lines changed

8 files changed

+351
-116
lines changed

conversation/csharp/http/README.md

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,20 @@ Open a new terminal window and run the multi app run template:
2121
<!-- STEP
2222
name: Run multi app run template
2323
expected_stdout_lines:
24-
- '== APP - conversation == Input sent: What is dapr?'
24+
- '== APP - conversation == Conversation input sent: What is dapr?'
2525
- '== APP - conversation == Output response: What is dapr?'
26+
- '== 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: '
2632
expected_stderr_lines:
2733
output_match_mode: substring
2834
match_order: none
2935
background: true
3036
sleep: 15
31-
timeout_seconds: 15
37+
timeout_seconds: 30
3238
-->
3339

3440
```bash
@@ -37,14 +43,27 @@ dapr run -f .
3743

3844
The terminal console output should look similar to this, where:
3945

40-
- The app sends an input `What is dapr?` to the `echo` Component mock LLM.
46+
- The app first sends an input `What is dapr?` to the `echo` Component mock LLM.
4147
- The mock LLM echoes `What is dapr?`.
4248

4349
```text
44-
== APP - conversation == Input sent: What is dapr?
50+
== APP - conversation == Conversation input sent: What is dapr?
4551
== APP - conversation == Output response: What is dapr?
4652
```
4753

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
59+
== 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?
61+
== APP - conversation == Tool calls detected:
62+
== APP - conversation == Tool call: {"id":0,"function":{"name":"get_weather","arguments":"location,unit"}}
63+
== APP - conversation == Function name: get_weather
64+
== APP - conversation == Function arguments: location,unit
65+
```
66+
4867
<!-- END_STEP -->
4968

5069
Stop and clean up application processes.
@@ -78,8 +97,17 @@ The terminal console output should look similar to this, where:
7897

7998
- The app sends an input `What is dapr?` to the `echo` Component mock LLM.
8099
- 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.
81103

82104
```text
83-
== APP - conversation == Input sent: What is dapr?
105+
== APP - conversation == Conversation input sent: What is dapr?
84106
== APP - conversation == Output response: What is dapr?
107+
== 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?
109+
== APP - conversation == Tool calls detected:
110+
== APP - conversation == Tool call: {"id":0,"function":{"name":"get_weather","arguments":"location,unit"}}
111+
== APP - conversation == Function name: get_weather
112+
== APP - conversation == Function arguments: location,unit
85113
```

conversation/csharp/http/conversation/Program.cs

Lines changed: 160 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Copyright 2024 The Dapr Authors
44
you may not use this file except in compliance with the License.
55
You may obtain a copy of the License at
66
7-
http://www.apache.org/licenses/LICENSE-2.0
7+
http://www.apache.org/licenses/LICENSE-2.0
88
99
Unless required by applicable law or agreed to in writing, software
1010
distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,55 +13,167 @@ You may obtain a copy of the License at
1313
limitations under the License.
1414
*/
1515

16-
using System.Net.Http;
16+
using System.Net.Http.Json;
1717
using System.Text.Json;
18-
using System.Text;
1918

20-
class Program
21-
{
22-
private const string ConversationComponentName = "echo";
19+
// const string conversationComponentName = "echo";
20+
const string conversationText = "What is dapr?";
21+
const string toolCallInput = "What is the weather like in San Francisco in celsius?";
22+
23+
//
24+
// Setup
25+
26+
var httpClient = new HttpClient();
2327

24-
static async Task Main(string[] args)
28+
//
29+
// Simple Conversation
30+
31+
var conversationRequestBody = JsonSerializer.Deserialize<Dictionary<string, object?>>("""
2532
{
26-
var daprHost = Environment.GetEnvironmentVariable("DAPR_HOST") ?? "http://localhost";
27-
var daprHttpPort = Environment.GetEnvironmentVariable("DAPR_HTTP_PORT") ?? "3500";
28-
29-
var client = new HttpClient
30-
{
31-
Timeout = TimeSpan.FromSeconds(15)
32-
};
33-
34-
var inputBody = new
35-
{
36-
name = "echo",
37-
inputs = new[] { new { content = "What is dapr?" } },
38-
parameters = new { },
39-
metadata = new { }
40-
};
41-
42-
var daprUrl = $"{daprHost}:{daprHttpPort}/v1.0-alpha1/conversation/{ConversationComponentName}/converse";
43-
44-
try
45-
{
46-
var content = new StringContent(JsonSerializer.Serialize(inputBody), Encoding.UTF8, "application/json");
47-
48-
// Send a request to the echo mock LLM component
49-
var response = await client.PostAsync(daprUrl, content);
50-
response.EnsureSuccessStatusCode();
51-
52-
Console.WriteLine("Input sent: " + inputBody.inputs[0].content);
53-
54-
var responseBody = await response.Content.ReadAsStringAsync();
55-
56-
// Parse the response
57-
var data = JsonSerializer.Deserialize<Dictionary<string, List<Dictionary<string, string>>>>(responseBody);
58-
var result = data?["outputs"]?[0]?["result"];
59-
60-
Console.WriteLine("Output response: " + result);
61-
}
62-
catch (Exception ex)
63-
{
64-
Console.WriteLine("Error: " + ex.Message);
65-
}
33+
"name": "echo",
34+
"inputs": [{
35+
"messages": [{
36+
"of_user": {
37+
"content": [{
38+
"text": "What is dapr?"
39+
}]
40+
}
41+
}]
42+
}],
43+
"parameters": {},
44+
"metadata": {}
6645
}
67-
}
46+
""");
47+
48+
var conversationResponse = await httpClient.PostAsJsonAsync("http://localhost:3500/v1.0-alpha2/conversation/echo/converse", conversationRequestBody);
49+
var conversationResult = await conversationResponse.Content.ReadFromJsonAsync<JsonElement>();
50+
51+
var conversationContent = conversationResult
52+
.GetProperty("outputs")
53+
.EnumerateArray()
54+
.First()
55+
.GetProperty("choices")
56+
.EnumerateArray()
57+
.First()
58+
.GetProperty("message")
59+
.GetProperty("content")
60+
.GetString();
61+
62+
Console.WriteLine($"Conversation input sent: {conversationText}");
63+
Console.WriteLine($"Output response: {conversationContent}");
64+
65+
//
66+
// Tool Calling
67+
68+
var toolCallRequestBody = JsonSerializer.Deserialize<Dictionary<string, object?>>("""
69+
{
70+
"name": "demo",
71+
"inputs": [
72+
{
73+
"messages": [
74+
{
75+
"of_user": {
76+
"content": [
77+
{
78+
"text": "What is the weather like in San Francisco in celsius?"
79+
}
80+
]
81+
}
82+
}
83+
],
84+
"scrubPII": false
85+
}
86+
],
87+
"parameters": {
88+
"max_tokens": {
89+
"@type": "type.googleapis.com/google.protobuf.Int64Value",
90+
"value": "100"
91+
},
92+
"model": {
93+
"@type": "type.googleapis.com/google.protobuf.StringValue",
94+
"value": "claude-3-5-sonnet-20240620"
95+
}
96+
},
97+
"metadata": {
98+
"api_key": "test-key",
99+
"version": "1.0"
100+
},
101+
"scrubPii": false,
102+
"temperature": 0.7,
103+
"tools": [
104+
{
105+
"function": {
106+
"name": "get_weather",
107+
"description": "Get the current weather for a location",
108+
"parameters": {
109+
"type": "object",
110+
"properties": {
111+
"location": {
112+
"type": "string",
113+
"description": "The city and state, e.g. San Francisco, CA"
114+
},
115+
"unit": {
116+
"type": "string",
117+
"enum": [
118+
"celsius",
119+
"fahrenheit"
120+
],
121+
"description": "The temperature unit to use"
122+
}
123+
},
124+
"required": [
125+
"location"
126+
]
127+
}
128+
}
129+
}
130+
],
131+
"toolChoice": "auto"
132+
}
133+
""");
134+
135+
var toolCallingResponse = await httpClient.PostAsJsonAsync("http://localhost:3500/v1.0-alpha2/conversation/echo/converse", toolCallRequestBody);
136+
var toolCallingResult = await toolCallingResponse.Content.ReadFromJsonAsync<JsonElement>();
137+
138+
var toolCallingContent = toolCallingResult
139+
.GetProperty("outputs")
140+
.EnumerateArray()
141+
.First()
142+
.GetProperty("choices")
143+
.EnumerateArray()
144+
.First()
145+
.GetProperty("message")
146+
.GetProperty("content");
147+
148+
var functionCalled = toolCallingResult
149+
.GetProperty("outputs")
150+
.EnumerateArray()
151+
.First()
152+
.GetProperty("choices")
153+
.EnumerateArray()
154+
.First()
155+
.GetProperty("message")
156+
.GetProperty("toolCalls")
157+
.EnumerateArray()
158+
.First()
159+
.GetProperty("function");
160+
161+
var functionName = functionCalled.GetProperty("name").GetString();
162+
var functionArguments = functionCalled.GetProperty("arguments").GetString();
163+
164+
var toolCallJson = JsonSerializer.Serialize(new
165+
{
166+
id = 0,
167+
function = new
168+
{
169+
name = functionName,
170+
arguments = functionArguments,
171+
},
172+
});
173+
174+
Console.WriteLine($"Tool calling input sent: {toolCallInput}");
175+
Console.WriteLine($"Output message: {toolCallingContent}");
176+
Console.WriteLine("Tool calls detected:");
177+
Console.WriteLine($"Tool call: {toolCallJson}");
178+
Console.WriteLine($"Function name: {functionName}");
179+
Console.WriteLine($"Function arguments: {functionArguments}");

conversation/csharp/http/conversation/Program.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
<Nullable>enable</Nullable>
88
</PropertyGroup>
99

10-
<ItemGroup>
11-
<PackageReference Include="System.Text.Json" Version="9.0.1" />
10+
<ItemGroup>
11+
<PackageReference Include="System.Text.Json" Version="9.0.1" />
1212
</ItemGroup>
1313

1414
</Project>

conversation/csharp/http/dapr.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ apps:
55
- appDirPath: ./conversation/
66
appID: conversation
77
daprHTTPPort: 3500
8-
command: ["dotnet", "run"]
8+
command: ["dotnet", "run"]

conversation/csharp/sdk/README.md

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,20 @@ Open a new terminal window and run the multi app run template:
2121
<!-- STEP
2222
name: Run multi app run template
2323
expected_stdout_lines:
24-
- '== APP - conversation == Input sent: What is dapr?'
24+
- '== APP - conversation == Conversation input sent: What is dapr?'
2525
- '== APP - conversation == Output response: What is dapr?'
26+
- '== 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: '
2632
expected_stderr_lines:
2733
output_match_mode: substring
2834
match_order: none
2935
background: true
3036
sleep: 15
31-
timeout_seconds: 15
37+
timeout_seconds: 30
3238
-->
3339

3440
```bash
@@ -41,10 +47,23 @@ The terminal console output should look similar to this, where:
4147
- The mock LLM echoes `What is dapr?`.
4248

4349
```text
44-
== APP - conversation == Input sent: What is dapr?
50+
== APP - conversation == Conversation input sent: What is dapr?
4551
== APP - conversation == Output response: What is dapr?
4652
```
4753

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
59+
== 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?
61+
== APP - conversation == Tool calls detected:
62+
== APP - conversation == Tool call: {"id":0,"function":{"name":"get_weather","arguments":"location,unit"}}
63+
== APP - conversation == Function name: get_weather
64+
== APP - conversation == Function arguments: location,unit
65+
```
66+
4867
<!-- END_STEP -->
4968

5069
Stop and clean up application processes.
@@ -78,8 +97,17 @@ The terminal console output should look similar to this, where:
7897

7998
- The app sends an input `What is dapr?` to the `echo` Component mock LLM.
8099
- 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.
81103

82104
```text
83-
== APP - conversation == Input sent: What is dapr?
105+
== APP - conversation == Conversation input sent: What is dapr?
84106
== APP - conversation == Output response: What is dapr?
107+
== 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?
109+
== APP - conversation == Tool calls detected:
110+
== APP - conversation == Tool call: {"id":0,"function":{"name":"get_weather","arguments":"location,unit"}}
111+
== APP - conversation == Function name: get_weather
112+
== APP - conversation == Function arguments: location,unit
85113
```

0 commit comments

Comments
 (0)