From 85cedc1de392e597c62915cadc5c1952f4a59494 Mon Sep 17 00:00:00 2001 From: Samantha Coyle Date: Tue, 3 Feb 2026 11:42:19 -0600 Subject: [PATCH 01/17] feat: update py + go http convo fields Signed-off-by: Samantha Coyle --- .../go/http/conversation/conversation.go | 67 +++++++++++++++---- conversation/python/http/conversation/app.py | 40 ++++++++++- 2 files changed, 91 insertions(+), 16 deletions(-) diff --git a/conversation/go/http/conversation/conversation.go b/conversation/go/http/conversation/conversation.go index 0ec19e3fa..bf130f194 100644 --- a/conversation/go/http/conversation/conversation.go +++ b/conversation/go/http/conversation/conversation.go @@ -42,6 +42,7 @@ func main() { } var inputBody = `{ + "name": "echo", "inputs": [{ "messages": [{ "ofUser": { @@ -52,8 +53,22 @@ func main() { }] }], "parameters": {}, - "metadata": {} - }` + "metadata": {}, + "response_format": { + "type": "json_schema", + "json_schema": { + "name": "response", + "strict": true, + "schema": { + "type": "object", + "properties": { + "answer": {"type": "string"} + } + } + } + }, + "prompt_cache_retention": "24h" + }` reqURL := daprHost + ":" + daprHttpPort + "/v1.0-alpha2/conversation/" + conversationComponentName + "/converse" @@ -79,20 +94,33 @@ func main() { log.Fatal(err) } - // Unmarshal the response - var data map[string]any + var data struct { + Outputs []struct { + Choices []struct { + Message struct { + Content string `json:"content"` + } `json:"message"` + } `json:"choices"` + Model string `json:"model"` + Usage any `json:"usage"` + Result string `json:"result"` + } `json:"outputs"` + } if err := json.Unmarshal(bodyBytes, &data); err != nil { log.Fatal(err) } - // Navigate the new response structure: outputs[0].choices[0].message.content - outputs := data["outputs"].([]any) - output := outputs[0].(map[string]any) - choices := output["choices"].([]any) - choice := choices[0].(map[string]any) - message := choice["message"].(map[string]any) - result := message["content"].(string) - + if len(data.Outputs) == 0 { + log.Fatal("no outputs in response") + } + out := data.Outputs[0] + result := out.Result + if len(out.Choices) > 0 { + result = out.Choices[0].Message.Content + } + if out.Model != "" { + fmt.Println("Model:", out.Model) + } fmt.Println("Output response:", result) // Tool calling example @@ -159,8 +187,21 @@ func main() { } // Parse tool calling response - outputs2 := data2["outputs"].([]any) + outputs2, ok := data2["outputs"].([]any) + if !ok || len(outputs2) == 0 { + fmt.Println("No outputs in tool calling response") + return + } output2 := outputs2[0].(map[string]any) + + if model, ok := output2["model"].(string); ok && model != "" { + fmt.Println("Model:", model) + } + if usage, ok := output2["usage"].(map[string]any); ok { + fmt.Printf("Usage: prompt_tokens=%v completion_tokens=%v total_tokens=%v\n", + usage["prompt_tokens"], usage["completion_tokens"], usage["total_tokens"]) + } + choices2 := output2["choices"].([]any) choice2 := choices2[0].(map[string]any) message2 := choice2["message"].(map[string]any) diff --git a/conversation/python/http/conversation/app.py b/conversation/python/http/conversation/app.py index c76f527a3..16fdb53c3 100644 --- a/conversation/python/http/conversation/app.py +++ b/conversation/python/http/conversation/app.py @@ -36,7 +36,16 @@ }] }], 'parameters': {}, - 'metadata': {} + 'metadata': {}, + 'response_format': { + 'type': 'json_schema', + 'json_schema': { + 'name': 'response', + 'strict': True, + 'schema': {'type': 'object', 'properties': {'answer': {'type': 'string'}}} + } + }, + 'prompt_cache_retention': '24h' } # Send input to conversation endpoint @@ -51,8 +60,18 @@ data = result.json() try: if 'outputs' in data and len(data['outputs']) > 0: - output = data["outputs"][0]["choices"][0]["message"]["content"] - logging.info('Output response: ' + output) + out = data['outputs'][0] + if out.get('model'): + logging.info('Model: ' + out['model']) + if out.get('usage'): + u = out['usage'] + logging.info('Usage: prompt_tokens=%s completion_tokens=%s total_tokens=%s', + u.get('prompt_tokens'), u.get('completion_tokens'), u.get('total_tokens')) + if 'choices' in out and len(out['choices']) > 0: + output = out["choices"][0]["message"]["content"] + logging.info('Output response: ' + output) + else: + logging.error('No choices in output') else: logging.error('No outputs found in response') logging.error('Response data: ' + str(data)) @@ -89,6 +108,15 @@ 'api_key': 'test-key', 'version': '1.0' }, + 'response_format': { + 'type': 'json_schema', + 'json_schema': { + 'name': 'response', + 'strict': True, + 'schema': {'type': 'object', 'properties': {'answer': {'type': 'string'}}} + } + }, + 'prompt_cache_retention': '24h', 'scrubPii': False, 'temperature': 0.7, 'tools': [{ @@ -127,6 +155,12 @@ data = tool_call_result.json() if 'outputs' in data and len(data['outputs']) > 0: output = data['outputs'][0] + if output.get('model'): + logging.info('Model: %s', output['model']) + if output.get('usage'): + u = output['usage'] + logging.info('Usage: prompt_tokens=%s completion_tokens=%s total_tokens=%s', + u.get('prompt_tokens'), u.get('completion_tokens'), u.get('total_tokens')) if 'choices' in output and len(output['choices']) > 0: choice = output['choices'][0] if 'message' in choice: From fb99ef22fa2eefd4a1d532c6b152d53e6a48a6ca Mon Sep 17 00:00:00 2001 From: Samantha Coyle Date: Mon, 23 Feb 2026 12:55:04 -0600 Subject: [PATCH 02/17] fix: more updates to make build happy Signed-off-by: Samantha Coyle --- .../workflows/validate_go_quickstarts.yaml | 9 +++ .../validate_python_quickstarts.yaml | 9 +++ conversation/components/ollama.yaml | 10 +++ conversation/go/http/README.md | 40 ++++------ .../go/http/conversation/conversation.go | 26 +++---- conversation/go/sdk/README.md | 22 +++--- .../go/sdk/conversation/conversation.go | 73 ++++++++++++------- conversation/go/sdk/conversation/go.mod | 29 ++++---- conversation/go/sdk/conversation/go.sum | 70 +++++++++--------- conversation/python/http/README.md | 66 +++++++---------- conversation/python/http/conversation/app.py | 25 +++---- conversation/python/sdk/README.md | 28 +++---- conversation/python/sdk/conversation/app.py | 2 +- .../python/sdk/conversation/tool_calling.py | 2 +- 14 files changed, 216 insertions(+), 195 deletions(-) create mode 100644 conversation/components/ollama.yaml diff --git a/.github/workflows/validate_go_quickstarts.yaml b/.github/workflows/validate_go_quickstarts.yaml index 7db1ca45f..ee6d96c6a 100644 --- a/.github/workflows/validate_go_quickstarts.yaml +++ b/.github/workflows/validate_go_quickstarts.yaml @@ -82,6 +82,15 @@ jobs: export GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} dapr init --runtime-version=${{ env.DAPR_RUNTIME_VERSION }} dapr --version + - name: Cache Ollama models + uses: actions/cache@v4 + with: + path: ~/.ollama + key: ${{ runner.os }}-ollama-llama3.2 + - name: Setup Ollama + uses: ai-action/setup-ollama@v2 + - name: Pull Ollama model + run: ollama pull llama3.2:latest - name: Install utilities dependencies run: | echo "PATH=$PATH:$HOME/.local/bin" >> $GITHUB_ENV diff --git a/.github/workflows/validate_python_quickstarts.yaml b/.github/workflows/validate_python_quickstarts.yaml index 66c9b7e44..1a000f3bb 100644 --- a/.github/workflows/validate_python_quickstarts.yaml +++ b/.github/workflows/validate_python_quickstarts.yaml @@ -86,6 +86,15 @@ jobs: export GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} dapr init --runtime-version=${{ env.DAPR_RUNTIME_VERSION }} dapr --version + - name: Cache Ollama models + uses: actions/cache@v4 + with: + path: ~/.ollama + key: ${{ runner.os }}-ollama-llama3.2 + - name: Setup Ollama + uses: ai-action/setup-ollama@v2 + - name: Pull Ollama model + run: ollama pull llama3.2:latest - name: Install utilities dependencies run: | echo "PATH=$PATH:$HOME/.local/bin" >> $GITHUB_ENV diff --git a/conversation/components/ollama.yaml b/conversation/components/ollama.yaml new file mode 100644 index 000000000..80c0df1f7 --- /dev/null +++ b/conversation/components/ollama.yaml @@ -0,0 +1,10 @@ +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: ollama +spec: + type: conversation.ollama + version: v1 + metadata: + - name: model + value: llama3.2:latest diff --git a/conversation/go/http/README.md b/conversation/go/http/README.md index 50de3c07a..692d5b16e 100644 --- a/conversation/go/http/README.md +++ b/conversation/go/http/README.md @@ -14,7 +14,7 @@ This quickstart includes one app: This section shows how to run the application using the [multi-app run template files](https://docs.dapr.io/developing-applications/local-development/multi-app-dapr-run/multi-app-overview/) with `dapr run -f .`. -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/). +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/). Open a new terminal window and run the multi app run template: @@ -22,16 +22,17 @@ Open a new terminal window and run the multi app run template: name: Run multi app run template expected_stdout_lines: - '== APP - conversation == Input sent: What is dapr?' - - '== APP - conversation == Output response: What is dapr?' + - '== APP - conversation == Usage:' + - '== APP - conversation == Output response:' - '== APP - conversation == Tool calling input sent: What is the weather like in San Francisco in celsius?' - - '== APP - conversation == Output message: What is the weather like in San Francisco in celsius?' - '== APP - conversation == Tool calls detected:' + - '== APP - conversation == Tool call: map[function:map[arguments:' expected_stderr_lines: output_match_mode: substring match_order: none background: false sleep: 15 -timeout_seconds: 30 +timeout_seconds: 60 --> ```bash @@ -40,25 +41,17 @@ dapr run -f . The terminal console output should look similar to this, where: -- The app first sends an input `What is dapr?` to the `echo` Component mock LLM. -- The mock LLM echoes `What is dapr?`. +- The app first sends an input `What is dapr?` to the Ollama LLM component with a structured JSON response format. +- The LLM returns a JSON object with an `answer` field describing Dapr. - The app then sends a weather request to the component with tools available to the LLM. -- The LLM will either respond back with a tool call for the user, or an ask for more information. +- The LLM responds with a tool call to `get_weather`. ```text == APP - conversation == Input sent: What is dapr? -== APP - conversation == Output response: What is dapr? -``` - -- The app then sends an input `What is the weather like in San Francisco in celsius?` to the `echo` Component mock LLM. -- The mock LLM echoes `What is the weather like in San Francisco in celsius?` and calls the `get_weather` tool. -- The echo Component returns the tool call information. - -```text +== APP - conversation == Output response: { "answer": "Dapr is an open-source, cross-platform microservices framework..." } == APP - conversation == Tool calling input sent: What is the weather like in San Francisco in celsius? -== APP - conversation == Output message: What is the weather like in San Francisco in celsius? == APP - conversation == Tool calls detected: -== APP - conversation == Tool call: map[function:map[arguments:unit,location name:get_weather] id:0] +== APP - conversation == Tool call: map[function:map[arguments:... name:get_weather] id:call_xxxx] ``` @@ -79,16 +72,15 @@ dapr run --app-id conversation --resources-path ../../../components -- go run co The terminal console output should look similar to this, where: -- The app first sends an input `What is dapr?` to the `echo` Component mock LLM. -- The mock LLM echoes `What is dapr?`. -- The app then sends an input `What is the weather like in San Francisco in celsius?` to the `echo` Component mock LLM. -- The mock LLM echoes `What is the weather like in San Francisco in celsius?` +- The app first sends an input `What is dapr?` to the Ollama LLM component with a structured JSON response format. +- The LLM returns a JSON object with an `answer` field describing Dapr. +- The app then sends a weather request with tools available to the LLM. +- The LLM responds with a tool call to `get_weather`. ```text == APP - conversation == Input sent: What is dapr? -== APP - conversation == Output response: What is dapr? +== APP - conversation == Output response: { "answer": "Dapr is an open-source, cross-platform microservices framework..." } == APP - conversation == Tool calling input sent: What is the weather like in San Francisco in celsius? -== APP - conversation == Output message: What is the weather like in San Francisco in celsius? == APP - conversation == Tool calls detected: -== APP - conversation == Tool call: map[function:map[arguments:unit,location name:get_weather] id:0] +== APP - conversation == Tool call: map[function:map[arguments:... name:get_weather] id:call_xxxx] ``` \ No newline at end of file diff --git a/conversation/go/http/conversation/conversation.go b/conversation/go/http/conversation/conversation.go index bf130f194..1a3efa421 100644 --- a/conversation/go/http/conversation/conversation.go +++ b/conversation/go/http/conversation/conversation.go @@ -25,7 +25,7 @@ import ( "time" ) -const conversationComponentName = "echo" +const conversationComponentName = "ollama" func main() { daprHost := os.Getenv("DAPR_HOST") @@ -38,11 +38,11 @@ func main() { } client := http.Client{ - Timeout: 15 * time.Second, + Timeout: 60 * time.Second, } var inputBody = `{ - "name": "echo", + "name": "ollama", "inputs": [{ "messages": [{ "ofUser": { @@ -55,19 +55,13 @@ func main() { "parameters": {}, "metadata": {}, "response_format": { - "type": "json_schema", - "json_schema": { - "name": "response", - "strict": true, - "schema": { - "type": "object", - "properties": { - "answer": {"type": "string"} - } - } - } + "type": "object", + "properties": { + "answer": {"type": "string"} + }, + "required": ["answer"] }, - "prompt_cache_retention": "24h" + "prompt_cache_retention": "86400s" }` reqURL := daprHost + ":" + daprHttpPort + "/v1.0-alpha2/conversation/" + conversationComponentName + "/converse" @@ -79,7 +73,7 @@ func main() { req.Header.Set("Content-Type", "application/json") - // Send a request to the echo LLM component + // Send a request to the Ollama LLM component res, err := client.Do(req) if err != nil { log.Fatal(err) diff --git a/conversation/go/sdk/README.md b/conversation/go/sdk/README.md index e4bdc9b5a..5ab0abdab 100644 --- a/conversation/go/sdk/README.md +++ b/conversation/go/sdk/README.md @@ -14,7 +14,7 @@ This quickstart includes one app: This section shows how to run the application using the [multi-app run template files](https://docs.dapr.io/developing-applications/local-development/multi-app-dapr-run/multi-app-overview/) with `dapr run -f .`. -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/). +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/). Open a new terminal window and run the multi app run template: @@ -22,16 +22,17 @@ Open a new terminal window and run the multi app run template: name: Run multi app run template expected_stdout_lines: - '== APP - conversation-sdk == Input sent: What is dapr?' - - '== APP - conversation-sdk == Output response: What is dapr?' + - '== APP - conversation-sdk == Usage:' + - '== APP - conversation-sdk == Output response:' - '== APP - conversation-sdk == Tool calling input sent: What is the weather like in San Francisco in celsius?' - - '== APP - conversation-sdk == Tool Call: Name: getWeather - Arguments: ' - - '== APP - conversation-sdk == Tool Call Output: The weather in San Francisco is 25 degrees Celsius' + - '== APP - conversation-sdk == Tool Call: Name: getWeather' + - '== APP - conversation-sdk == Tool Call Output:' expected_stderr_lines: output_match_mode: substring match_order: none background: false sleep: 15 -timeout_seconds: 30 +timeout_seconds: 60 --> ```bash @@ -40,15 +41,16 @@ dapr run -f . The terminal console output should look similar to this, where: -- The app sends an input `What is dapr?` to the `echo` Component mock LLM. -- The mock LLM echoes `What is dapr?`. +- The app sends an input `What is dapr?` to the Ollama LLM component with a structured JSON response format. +- The LLM returns a JSON object with an `answer` field describing Dapr. +- The app sends a weather question with a `getWeather` tool available; the LLM calls the tool and the app executes it. ```text == APP - conversation-sdk == Input sent: What is dapr? -== APP - conversation-sdk == Output response: What is dapr? +== APP - conversation-sdk == Output response: { "answer": "Dapr is an open-source, cross-platform microservices framework..." } == APP - conversation-sdk == Tool calling input sent: What is the weather like in San Francisco in celsius? -== APP - conversation-sdk == Tool Call: Name: getWeather, Arguments: location,unit -== APP - conversation-sdk == Tool Call Output: The weather in San Francisco is 25 degrees Celsius +== APP - conversation-sdk == Tool Call: Name: getWeather - Arguments: {"location":"San Francisco, CA","unit":"celsius"} +== APP - conversation-sdk == Tool Call Output: The weather in San Francisco, CA is 25 degrees celsius ``` diff --git a/conversation/go/sdk/conversation/conversation.go b/conversation/go/sdk/conversation/conversation.go index 41eb2b92e..b422ede4f 100644 --- a/conversation/go/sdk/conversation/conversation.go +++ b/conversation/go/sdk/conversation/conversation.go @@ -19,24 +19,16 @@ import ( "encoding/json" "fmt" "log" - "strings" + "time" "github.com/invopop/jsonschema" "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/structpb" dapr "github.com/dapr/go-sdk/client" ) -// createMapOfArgsForEcho is a helper function to deal with the issue with the echo component not returning args as a map but in csv format -func createMapOfArgsForEcho(s string) ([]byte, error) { - m := map[string]any{} - for _, p := range strings.Split(s, ",") { - m[p] = p - } - return json.Marshal(m) -} - // getWeatherInLocation is an example function to use as a tool call func getWeatherInLocation(request GetDegreesWeatherRequest, defaultValues GetDegreesWeatherRequest) string { location := request.Location @@ -106,11 +98,25 @@ func main() { } inputMsg := "What is dapr?" - conversationComponent := "echo" + conversationComponent := "ollama" + + // Optional: structured outputs and prompt cache retention + responseFormat, err := structpb.NewStruct(map[string]any{ + "type": "object", + "properties": map[string]any{ + "answer": map[string]any{"type": "string"}, + }, + "required": []any{"answer"}, + }) + if err != nil { + log.Fatalf("failed to build response_format: %v", err) + } request := dapr.ConversationRequestAlpha2{ - Name: conversationComponent, - Inputs: []*dapr.ConversationInputAlpha2{createUserMessageInput(inputMsg)}, + Name: conversationComponent, + Inputs: []*dapr.ConversationInputAlpha2{createUserMessageInput(inputMsg)}, + ResponseFormat: responseFormat, + PromptCacheRetention: durationpb.New(24 * time.Hour), } fmt.Println("Input sent:", inputMsg) @@ -120,18 +126,29 @@ func main() { log.Fatalf("err: %v", err) } - fmt.Println("Output response:", resp.Outputs[0].Choices[0].Message.Content) + firstOut := resp.Outputs[0] + if firstOut.Model != nil && *firstOut.Model != "" { + fmt.Println("Model:", *firstOut.Model) + } + if firstOut.Usage != nil { + fmt.Printf("Usage: prompt_tokens=%d completion_tokens=%d total_tokens=%d\n", + firstOut.Usage.PromptTokens, firstOut.Usage.CompletionTokens, firstOut.Usage.TotalTokens) + } + fmt.Println("Output response:", firstOut.Choices[0].Message.Content) tool, err := GenerateFunctionTool[GetDegreesWeatherRequest]("getWeather", "get weather from a location in the given unit") if err != nil { log.Fatalf("err: %v", err) } - weatherMessage := "Tool calling input sent: What is the weather like in San Francisco in celsius?'" + weatherMessage := "What is the weather like in San Francisco in celsius?" + fmt.Println("Tool calling input sent:", weatherMessage) requestWithTool := dapr.ConversationRequestAlpha2{ - Name: conversationComponent, - Inputs: []*dapr.ConversationInputAlpha2{createUserMessageInput(weatherMessage)}, - Tools: []*dapr.ConversationToolsAlpha2{tool}, + Name: conversationComponent, + Inputs: []*dapr.ConversationInputAlpha2{createUserMessageInput(weatherMessage)}, + Tools: []*dapr.ConversationToolsAlpha2{tool}, + ResponseFormat: responseFormat, + PromptCacheRetention: durationpb.New(24 * time.Hour), } resp, err = client.ConverseAlpha2(context.Background(), requestWithTool) @@ -139,19 +156,21 @@ func main() { log.Fatalf("err: %v", err) } - fmt.Println(resp.Outputs[0].Choices[0].Message.Content) - for _, toolCalls := range resp.Outputs[0].Choices[0].Message.ToolCalls { + toolOut := resp.Outputs[0] + if toolOut.Model != nil && *toolOut.Model != "" { + fmt.Println("Model:", *toolOut.Model) + } + if toolOut.Usage != nil { + fmt.Printf("Usage: prompt_tokens=%d completion_tokens=%d total_tokens=%d\n", + toolOut.Usage.PromptTokens, toolOut.Usage.CompletionTokens, toolOut.Usage.TotalTokens) + } + + fmt.Println(toolOut.Choices[0].Message.Content) + for _, toolCalls := range toolOut.Choices[0].Message.ToolCalls { fmt.Printf("Tool Call: Name: %s - Arguments: %v\n", toolCalls.ToolTypes.Name, toolCalls.ToolTypes.Arguments) // parse the arguments and execute tool args := []byte(toolCalls.ToolTypes.Arguments) - if conversationComponent == "echo" { - // The echo component does not return a compliant tool calling response in json format but rather returns a csv - args, err = createMapOfArgsForEcho(toolCalls.ToolTypes.Arguments) - if err != nil { - log.Fatalf("err: %v", err) - } - } // find the tool (only one in this case) and execute for _, toolInfo := range requestWithTool.Tools { diff --git a/conversation/go/sdk/conversation/go.mod b/conversation/go/sdk/conversation/go.mod index 3b63da02e..5f62fa339 100644 --- a/conversation/go/sdk/conversation/go.mod +++ b/conversation/go/sdk/conversation/go.mod @@ -1,32 +1,33 @@ module conversation -go 1.24.6 +go 1.24.12 require ( - github.com/dapr/go-sdk v1.13.0 + github.com/dapr/go-sdk v1.14.0-rc.1.0.20260204102546-4111304b5cbd github.com/invopop/jsonschema v0.13.0 - google.golang.org/protobuf v1.36.6 + google.golang.org/protobuf v1.36.9 ) require ( github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/buger/jsonparser v1.1.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect - github.com/dapr/dapr v1.16.0 // indirect - github.com/dapr/durabletask-go v0.10.0 // indirect - github.com/dapr/kit v0.16.1 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/dapr/dapr v1.17.0-rc.1.0.20260129135036-c169c5be5eeb // indirect + github.com/dapr/durabletask-go v0.11.0 // indirect + github.com/dapr/kit v0.16.2-0.20251124175541-3ac186dff64d // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.36.0 // indirect - go.opentelemetry.io/otel/metric v1.36.0 // indirect - go.opentelemetry.io/otel/trace v1.36.0 // indirect - golang.org/x/net v0.41.0 // indirect - golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.26.0 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/otel v1.39.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect google.golang.org/grpc v1.73.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/conversation/go/sdk/conversation/go.sum b/conversation/go/sdk/conversation/go.sum index 310f31707..926089137 100644 --- a/conversation/go/sdk/conversation/go.sum +++ b/conversation/go/sdk/conversation/go.sum @@ -4,19 +4,21 @@ github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMU github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/dapr/dapr v1.16.0 h1:la2WLZM8Myr2Pq3cyrFjHKWDSPYLzGZCs3p502TwBjI= -github.com/dapr/dapr v1.16.0/go.mod h1:ln/mxvNOeqklaDmic4ppsxmnjl2D/oZGKaJy24IwaEY= -github.com/dapr/durabletask-go v0.10.0 h1:vfIivPl4JYd55xZTslDwhA6p6F8ipcNxBtMaupxArr8= -github.com/dapr/durabletask-go v0.10.0/go.mod h1:0Ts4rXp74JyG19gDWPcwNo5V6NBZzhARzHF5XynmA7Q= -github.com/dapr/go-sdk v1.13.0 h1:Qw2BmUonClQ9yK/rrEEaFL1PyDgq616RrvYj0CT67Lk= -github.com/dapr/go-sdk v1.13.0/go.mod h1:RsffVNZitDApmQqoS68tNKGMXDZUjTviAbKZupJSzts= -github.com/dapr/kit v0.16.1 h1:MqLAhHVg8trPy2WJChMZFU7ToeondvxcNHYVvMDiVf4= -github.com/dapr/kit v0.16.1/go.mod h1:40ZWs5P6xfYf7O59XgwqZkIyDldTIXlhTQhGop8QoSM= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/dapr/dapr v1.17.0-rc.1.0.20260129135036-c169c5be5eeb h1:BuQun1gZ21I2Lbb73mDEmcr8W2OB9fKEZQJ3FQ+ynKw= +github.com/dapr/dapr v1.17.0-rc.1.0.20260129135036-c169c5be5eeb/go.mod h1:52/4RClzENU7q3PYiBN/ihNp6DH09x6ZtJA60yP4y/M= +github.com/dapr/durabletask-go v0.11.0 h1:e9Ns/3a2b6JDKGuvksvx6gCHn7rd+nwZZyAXbg5Ley4= +github.com/dapr/durabletask-go v0.11.0/go.mod h1:0Ts4rXp74JyG19gDWPcwNo5V6NBZzhARzHF5XynmA7Q= +github.com/dapr/go-sdk v1.14.0-rc.1.0.20260204102546-4111304b5cbd h1:RW06usrcNw33Jly5zIRZ42Nfl9NHNHX5QbGJsJHbXDQ= +github.com/dapr/go-sdk v1.14.0-rc.1.0.20260204102546-4111304b5cbd/go.mod h1:7JJAciT6JxcS6+0w/+m8KROoCdLyowlLO9XjAjJFJQE= +github.com/dapr/kit v0.16.2-0.20251124175541-3ac186dff64d h1:csljij9d1IO6u9nqbg+TuSRmTZ+OXT8G49yh6zie1yI= +github.com/dapr/kit v0.16.2-0.20251124175541-3ac186dff64d/go.mod h1:40ZWs5P6xfYf7O59XgwqZkIyDldTIXlhTQhGop8QoSM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= @@ -36,36 +38,36 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= -go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= -go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= -go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= -go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= -go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= -go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= -go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= -go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= -golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= -golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= +go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= +go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= +go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE= google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/conversation/python/http/README.md b/conversation/python/http/README.md index b6af77aed..b62402dd3 100644 --- a/conversation/python/http/README.md +++ b/conversation/python/http/README.md @@ -14,7 +14,7 @@ This quickstart includes one app: This section shows how to run the application using the [multi-app run template files](https://docs.dapr.io/developing-applications/local-development/multi-app-dapr-run/multi-app-overview/) with `dapr run -f .`. -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/). +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/). 1. Install dependencies: @@ -66,50 +66,40 @@ This example uses the default LLM component (Echo) which simply returns the inpu name: Run multi app run template expected_stdout_lines: - '== APP - conversation == Conversation input sent: What is dapr?' - - '== APP - conversation == Output response: What is dapr?' + - '== APP - conversation == Usage:' + - '== APP - conversation == Output response:' - '== APP - conversation == Tool calling input sent: What is the weather like in San Francisco in celsius?' - - '== APP - conversation == Output message: What is the weather like in San Francisco in celsius?' - '== APP - conversation == Tool calls detected:' - - "== APP - conversation == Tool call: {'id': '0', 'function': {'name': 'get_weather', 'arguments': 'location,unit'}}" + - "== APP - conversation == Tool call: {'id': 'call_" - '== APP - conversation == Function name: get_weather' - - '== APP - conversation == Function arguments: location,unit' expected_stderr_lines: output_match_mode: substring match_order: none background: true - sleep: 15 - timeout_seconds: 30 + sleep: 30 + timeout_seconds: 60 --> ```bash source conversation/.venv/bin/activate dapr run -f . ``` - + The terminal console output should look similar to this, where: - - - The app first sends an input `What is dapr?` to the `echo` Component mock LLM. - - The mock LLM echoes `What is dapr?`. + + - The app first sends an input `What is dapr?` to the Ollama LLM component with a structured JSON response format. + - The LLM returns a JSON object with an `answer` field describing Dapr. - The app then sends a weather request to the component with tools available to the LLM. - - The LLM will either respond back with a tool call for the user, or an ask for more information. - - ```text - == APP - conversation == Input sent: What is dapr? - == APP - conversation == Output response: What is dapr? - ``` - - - The app then sends an input `What is the weather like in San Francisco in celsius?` to the `echo` Component mock LLM. - - The mock LLM echoes `What is the weather like in San Francisco in celsius?` and calls the `get_weather` tool. - - Since we are using the `echo` Component mock LLM, the tool call is not executed and the LLM returns but we return a simulated `tool call` response. - The response is mocked to showcase the tool call response and what was sent as the tool definition, like the function name and arguments. - + - The LLM responds with a tool call to `get_weather`. + ```text - == APP == Tool calling input sent: What is the weather like in San Francisco in celsius? - == APP == Output message: What is the weather like in San Francisco in celsius? + == APP - conversation == Conversation input sent: What is dapr? + == APP - conversation == Output response: { "answer": "Dapr is an open-source, cross-platform microservices framework..." } + == APP - conversation == Tool calling input sent: What is the weather like in San Francisco in celsius? == APP - conversation == Tool calls detected: - == APP - conversation == Tool call: {'id': '0', 'function': {'name': 'get_weather', 'arguments': 'location,unit'}} + == APP - conversation == Tool call: {'id': 'call_xxxx', 'function': {'name': 'get_weather', 'arguments': '...'}} == APP - conversation == Function name: get_weather - == APP - conversation == Function arguments: location,unit + == APP - conversation == Function arguments: ... ``` @@ -172,22 +162,18 @@ This example uses the default LLM component (Echo) which simply returns the inpu ``` The terminal console output should look similar to this, where: - - - The app first sends an input `What is dapr?` to the `echo` Component mock LLM. - - The mock LLM echoes `What is dapr?`. - - The app then sends an input `What is the weather like in San Francisco in celsius?` to the `echo` Component mock LLM. - - The mock LLM echoes `What is the weather like in San Francisco in celsius?` - - The mock also "calls" the `get_weather` tool. - - Since we are using the `echo` Component mock LLM, the tool call is not executed and the LLM returns but we return a simulated `tool call` response. - The response is mocked to showcase the tool call response and what was sent as the tool definition, like the function name and arguments. - + + - The app first sends an input `What is dapr?` to the Ollama LLM component with a structured JSON response format. + - The LLM returns a JSON object with an `answer` field describing Dapr. + - The app then sends a weather request with tools available to the LLM. + - The LLM responds with a tool call to `get_weather`. + ```text == APP - conversation == Conversation input sent: What is dapr? - == APP - conversation == Output response: What is dapr? + == APP - conversation == Output response: { "answer": "Dapr is an open-source, cross-platform microservices framework..." } == APP - conversation == Tool calling input sent: What is the weather like in San Francisco in celsius? - == APP - conversation == Output message: What is the weather like in San Francisco in celsius? == APP - conversation == Tool calls detected: - == APP - conversation == Tool call: {'id': '0', 'function': {'name': 'get_weather', 'arguments': 'location,unit'}} + == APP - conversation == Tool call: {'id': 'call_xxxx', 'function': {'name': 'get_weather', 'arguments': '...'}} == APP - conversation == Function name: get_weather - == APP - conversation == Function arguments: location,unit + == APP - conversation == Function arguments: ... ``` \ No newline at end of file diff --git a/conversation/python/http/conversation/app.py b/conversation/python/http/conversation/app.py index 16fdb53c3..3d60c1846 100644 --- a/conversation/python/http/conversation/app.py +++ b/conversation/python/http/conversation/app.py @@ -23,7 +23,7 @@ base_url = os.getenv('BASE_URL', 'http://localhost') + ':' + os.getenv( 'DAPR_HTTP_PORT', '3500') -CONVERSATION_COMPONENT_NAME = 'echo' +CONVERSATION_COMPONENT_NAME = 'ollama' input = { 'inputs': [{ @@ -38,14 +38,13 @@ 'parameters': {}, 'metadata': {}, 'response_format': { - 'type': 'json_schema', - 'json_schema': { - 'name': 'response', - 'strict': True, - 'schema': {'type': 'object', 'properties': {'answer': {'type': 'string'}}} - } + 'type': 'object', + 'properties': { + 'answer': {'type': 'string'} + }, + 'required': ['answer'] }, - 'prompt_cache_retention': '24h' + 'prompt_cache_retention': '86400s' } # Send input to conversation endpoint @@ -109,14 +108,12 @@ 'version': '1.0' }, 'response_format': { - 'type': 'json_schema', - 'json_schema': { - 'name': 'response', - 'strict': True, - 'schema': {'type': 'object', 'properties': {'answer': {'type': 'string'}}} + 'type': 'object', + 'properties': { + 'answer': {'type': 'string'} } }, - 'prompt_cache_retention': '24h', + 'prompt_cache_retention': '86400s', 'scrubPii': False, 'temperature': 0.7, 'tools': [{ diff --git a/conversation/python/sdk/README.md b/conversation/python/sdk/README.md index 03985bd8d..1ac048f99 100644 --- a/conversation/python/sdk/README.md +++ b/conversation/python/sdk/README.md @@ -70,25 +70,25 @@ For more LLM options, see the [supported Conversation components](https://docs.d name: Run multi app run template expected_stdout_lines: - '== APP - conversation-sdk == Input sent: What is dapr?' - - '== APP - conversation-sdk == Output response: What is dapr?' + - '== APP - conversation-sdk == Output response:' expected_stderr_lines: output_match_mode: substring match_order: none background: true - sleep: 15 - timeout_seconds: 30 + sleep: 30 + timeout_seconds: 60 --> - + ```bash source conversation/.venv/bin/activate - dapr run -f . + dapr run -f . ``` - + Expected output: - + ```text == APP - conversation-sdk == Input sent: What is dapr? - == APP - conversation-sdk == Output response: What is dapr? + == APP - conversation-sdk == Output response: Dapr is an open-source, cross-platform microservices framework... ``` @@ -112,15 +112,15 @@ For more LLM options, see the [supported Conversation components](https://docs.d name: Run multi app run template expected_stdout_lines: - "== APP - conversation-tool-calling == Input sent: calculate square root of 15" - - "== APP - conversation-tool-calling == Output response: ConversationResultAlpha2Choices(finish_reason='tool_calls', index=0, message=ConversationResultAlpha2Message(content='calculate square root of 15', tool_calls=[ConversationToolCalls(id='0', function=ConversationToolCallsOfFunction(name='calculate', arguments='expression'))]))" + - "== APP - conversation-tool-calling == Output response: ConversationResultAlpha2Choices(finish_reason='tool_calls'" - "== APP - conversation-tool-calling == Input sent: get weather in San Francisco in celsius" - - "== APP - conversation-tool-calling == Output response: ConversationResultAlpha2Choices(finish_reason='tool_calls', index=0, message=ConversationResultAlpha2Message(content='get weather in San Francisco in celsius', tool_calls=[ConversationToolCalls(id='0', function=ConversationToolCallsOfFunction(name='get_weather', arguments='location,unit'))]))" + - "== APP - conversation-tool-calling == Output response: ConversationResultAlpha2Choices(finish_reason='tool_calls'" expected_stderr_lines: output_match_mode: substring match_order: none background: true - sleep: 15 - timeout_seconds: 30 + sleep: 30 + timeout_seconds: 60 --> ```bash @@ -132,9 +132,9 @@ For more LLM options, see the [supported Conversation components](https://docs.d ```text == APP - conversation-tool-calling == Input sent: calculate square root of 15 - == APP - conversation-tool-calling == Output response: ConversationResultAlpha2Choices(finish_reason='tool_calls', index=0, message=ConversationResultAlpha2Message(content='calculate square root of 15', tool_calls=[ConversationToolCalls(id='0', function=ConversationToolCallsOfFunction(name='calculate', arguments='expression'))])) + == APP - conversation-tool-calling == Output response: ConversationResultAlpha2Choices(finish_reason='tool_calls', index=0, message=ConversationResultAlpha2Message(content='calculate square root of 15', tool_calls=[ConversationToolCalls(id='call_...', function=ConversationToolCallsOfFunction(name='calculate', arguments='{"expression": "sqrt(15)"}'))])) == APP - conversation-tool-calling == Input sent: get weather in San Francisco in celsius - == APP - conversation-tool-calling == Output response: ConversationResultAlpha2Choices(finish_reason='tool_calls', index=0, message=ConversationResultAlpha2Message(content='get weather in San Francisco in celsius', tool_calls=[ConversationToolCalls(id='0', function=ConversationToolCallsOfFunction(name='get_weather', arguments='location,unit'))])) + == APP - conversation-tool-calling == Output response: ConversationResultAlpha2Choices(finish_reason='tool_calls', index=0, message=ConversationResultAlpha2Message(content='get weather in San Francisco in celsius', tool_calls=[ConversationToolCalls(id='call_...', function=ConversationToolCallsOfFunction(name='get_weather', arguments='{"location": "San Francisco", "unit": "celsius"}'))])) ``` diff --git a/conversation/python/sdk/conversation/app.py b/conversation/python/sdk/conversation/app.py index db898a9f1..5360049ec 100644 --- a/conversation/python/sdk/conversation/app.py +++ b/conversation/python/sdk/conversation/app.py @@ -15,7 +15,7 @@ with DaprClient() as d: text_input = "What is dapr?" - provider_component = "echo" + provider_component = "ollama" inputs = [ ConversationInputAlpha2(messages=[ConversationMessage(of_user=ConversationMessageOfUser(content=[ConversationMessageContent(text=text_input)]))], diff --git a/conversation/python/sdk/conversation/tool_calling.py b/conversation/python/sdk/conversation/tool_calling.py index 3f460b8a1..2052b6899 100644 --- a/conversation/python/sdk/conversation/tool_calling.py +++ b/conversation/python/sdk/conversation/tool_calling.py @@ -14,7 +14,7 @@ from dapr.clients.grpc import conversation with DaprClient() as d: - provider_component = "echo" + provider_component = "ollama" # ------------------------------------------------------------ # Creating Tool Function definition using lower level API and hand-crafted JSON schema From f3fdcdf3fb7330325fbf649614a90598f953a034 Mon Sep 17 00:00:00 2001 From: Samantha Coyle Date: Mon, 23 Feb 2026 13:33:57 -0600 Subject: [PATCH 03/17] fix: go mod tidy Signed-off-by: Samantha Coyle --- conversation/go/sdk/conversation/go.sum | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/conversation/go/sdk/conversation/go.sum b/conversation/go/sdk/conversation/go.sum index 494794c8b..f05080ee4 100644 --- a/conversation/go/sdk/conversation/go.sum +++ b/conversation/go/sdk/conversation/go.sum @@ -15,8 +15,8 @@ github.com/dapr/kit v0.16.2-0.20251124175541-3ac186dff64d/go.mod h1:40ZWs5P6xfYf github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= @@ -36,10 +36,10 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= From 88ac19e7f5bbdc5017b031f77c5474740d9e1743 Mon Sep 17 00:00:00 2001 From: Samantha Coyle Date: Mon, 23 Feb 2026 14:46:14 -0600 Subject: [PATCH 04/17] feat: add csharp, java, js Signed-off-by: Samantha Coyle --- conversation/csharp/http/README.md | 52 +++++++--------- .../csharp/http/conversation/Program.cs | 61 +++++++++++-------- conversation/csharp/sdk/README.md | 49 ++++++--------- .../csharp/sdk/conversation/Program.cs | 2 +- .../go/http/conversation/conversation.go | 2 +- conversation/java/http/README.md | 37 +++++------ .../com/service/ConversationApplication.java | 60 +++++++++--------- conversation/java/sdk/README.md | 31 +++++----- .../main/java/com/service/Conversation.java | 5 +- conversation/javascript/http/README.md | 50 +++++++-------- .../javascript/http/conversation/index.js | 46 +++++++++++--- conversation/python/http/conversation/app.py | 2 +- 12 files changed, 198 insertions(+), 199 deletions(-) diff --git a/conversation/csharp/http/README.md b/conversation/csharp/http/README.md index 48a4b6e37..8cb24a204 100644 --- a/conversation/csharp/http/README.md +++ b/conversation/csharp/http/README.md @@ -14,7 +14,7 @@ This quickstart includes one app: 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 .`. -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/). +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/). Open a new terminal window and run the multi app run template: @@ -22,19 +22,19 @@ Open a new terminal window and run the multi app run template: name: Run multi app run template expected_stdout_lines: - '== APP - conversation == Conversation input sent: What is dapr?' - - '== APP - conversation == Output response: What is dapr?' + - '== APP - conversation == Usage:' + - '== APP - conversation == Output response:' - '== APP - conversation == Tool calling input sent: What is the weather like in San Francisco in celsius?' - - '== APP - conversation == Output message: What is the weather like in San Francisco in celsius?' - '== APP - conversation == Tool calls detected:' - - "== APP - conversation == Tool call: {\"id\":0,\"function\":{\"name\":\"get_weather\",\"arguments\":" + - '== APP - conversation == Tool call: {"id":0,"function":{"name":"get_weather","arguments":' - '== APP - conversation == Function name: get_weather' - - '== APP - conversation == Function arguments: ' + - '== APP - conversation == Function arguments:' expected_stderr_lines: output_match_mode: substring match_order: none -background: true -sleep: 15 -timeout_seconds: 30 +background: false +sleep: 30 +timeout_seconds: 120 --> ```bash @@ -43,25 +43,19 @@ dapr run -f . The terminal console output should look similar to this, where: -- The app first sends an input `What is dapr?` to the `echo` Component mock LLM. -- The mock LLM echoes `What is dapr?`. +- The app first sends an input `What is dapr?` to the Ollama LLM component with a structured JSON response format. +- The LLM returns a JSON object with an `answer` field describing Dapr. +- The app then sends a weather request with a `get_weather` tool available; the LLM calls the tool. ```text == APP - conversation == Conversation input sent: What is dapr? -== APP - conversation == Output response: What is dapr? -``` - -- The app then sends an input `What is the weather like in San Francisco in celsius?` to the `echo` Component mock LLM. -- The mock LLM echoes `What is the weather like in San Francisco in celsius?` and calls the `get_weather` tool. -- The echo Component calls the `get_weather` tool and returns the requested weather information. - -```text +== APP - conversation == Usage: prompt_tokens=30 completion_tokens=64 total_tokens=94 +== APP - conversation == Output response: { "answer": "Dapr is an open-source, cross-platform microservices framework..." } == APP - conversation == Tool calling input sent: What is the weather like in San Francisco in celsius? -== APP - conversation == Output message: What is the weather like in San Francisco in celsius? == APP - conversation == Tool calls detected: -== APP - conversation == Tool call: {"id":0,"function":{"name":"get_weather","arguments":"location,unit"}} +== APP - conversation == Tool call: {"id":0,"function":{"name":"get_weather","arguments":"{\"location\":\"San Francisco, CA\",\"unit\":\"celsius\"}"}} == APP - conversation == Function name: get_weather -== APP - conversation == Function arguments: location,unit +== APP - conversation == Function arguments: {"location":"San Francisco, CA","unit":"celsius"} ``` @@ -95,19 +89,17 @@ dapr run --app-id conversation --resources-path ../../../components/ -- dotnet r The terminal console output should look similar to this, where: -- The app sends an input `What is dapr?` to the `echo` Component mock LLM. -- The mock LLM echoes `What is dapr?`. -- The app then sends an input `What is the weather like in San Francisco in celsius?` to the `echo` Component mock LLM. -- The mock LLM echoes `What is the weather like in San Francisco in celsius?` and calls the `get_weather` tool. -- The echo Component calls the `get_weather` tool and returns the requested weather information. +- The app sends an input `What is dapr?` to the Ollama LLM component with a structured JSON response format. +- The LLM returns a JSON object with an `answer` field describing Dapr. +- The app then sends a weather request with a `get_weather` tool available; the LLM calls the tool. ```text == APP - conversation == Conversation input sent: What is dapr? -== APP - conversation == Output response: What is dapr? +== APP - conversation == Usage: prompt_tokens=30 completion_tokens=64 total_tokens=94 +== APP - conversation == Output response: { "answer": "Dapr is an open-source, cross-platform microservices framework..." } == APP - conversation == Tool calling input sent: What is the weather like in San Francisco in celsius? -== APP - conversation == Output message: What is the weather like in San Francisco in celsius? == APP - conversation == Tool calls detected: -== APP - conversation == Tool call: {"id":0,"function":{"name":"get_weather","arguments":"location,unit"}} +== APP - conversation == Tool call: {"id":0,"function":{"name":"get_weather","arguments":"{\"location\":\"San Francisco, CA\",\"unit\":\"celsius\"}"}} == APP - conversation == Function name: get_weather -== APP - conversation == Function arguments: location,unit +== APP - conversation == Function arguments: {"location":"San Francisco, CA","unit":"celsius"} ``` diff --git a/conversation/csharp/http/conversation/Program.cs b/conversation/csharp/http/conversation/Program.cs index 46640eabc..95ab91e41 100644 --- a/conversation/csharp/http/conversation/Program.cs +++ b/conversation/csharp/http/conversation/Program.cs @@ -1,4 +1,4 @@ -/* +/* Copyright 2024 The Dapr Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,9 +14,9 @@ limitations under the License. */ using System.Net.Http.Json; +using System.Text.Encodings.Web; using System.Text.Json; -// const string conversationComponentName = "echo"; const string conversationText = "What is dapr?"; const string toolCallInput = "What is the weather like in San Francisco in celsius?"; @@ -40,17 +40,28 @@ limitations under the License. }] }], "parameters": {}, - "metadata": {} + "metadata": {}, + "response_format": { + "type": "object", + "properties": {"answer": {"type": "string"}}, + "required": ["answer"] + }, + "prompt_cache_retention": "86400s" } """); -var conversationResponse = await httpClient.PostAsJsonAsync("http://localhost:3500/v1.0-alpha2/conversation/echo/converse", conversationRequestBody); +var conversationResponse = await httpClient.PostAsJsonAsync("http://localhost:3500/v1.0-alpha2/conversation/ollama/converse", conversationRequestBody); var conversationResult = await conversationResponse.Content.ReadFromJsonAsync(); -var conversationContent = conversationResult - .GetProperty("outputs") - .EnumerateArray() - .First() +var firstOutput = conversationResult.GetProperty("outputs").EnumerateArray().First(); + +Console.WriteLine($"Conversation input sent: {conversationText}"); +if (firstOutput.TryGetProperty("model", out var modelElement) && modelElement.GetString() is { Length: > 0 } model) + Console.WriteLine($"Model: {model}"); +if (firstOutput.TryGetProperty("usage", out var usageElement)) + Console.WriteLine($"Usage: prompt_tokens={usageElement.GetProperty("promptTokens").GetString()} completion_tokens={usageElement.GetProperty("completionTokens").GetString()} total_tokens={usageElement.GetProperty("totalTokens").GetString()}"); + +var conversationContent = firstOutput .GetProperty("choices") .EnumerateArray() .First() @@ -58,7 +69,6 @@ limitations under the License. .GetProperty("content") .GetString(); -Console.WriteLine($"Conversation input sent: {conversationText}"); Console.WriteLine($"Output response: {conversationContent}"); // @@ -82,20 +92,14 @@ limitations under the License. "scrubPii": false } ], - "parameters": { - "max_tokens": { - "@type": "type.googleapis.com/google.protobuf.Int64Value", - "value": "100" - }, - "model": { - "@type": "type.googleapis.com/google.protobuf.StringValue", - "value": "claude-3-5-sonnet-20240620" - } - }, - "metadata": { - "api_key": "test-key", - "version": "1.0" + "parameters": {}, + "metadata": {}, + "response_format": { + "type": "object", + "properties": {"answer": {"type": "string"}}, + "required": ["answer"] }, + "prompt_cache_retention": "86400s", "scrubPii": false, "temperature": 0.7, "tools": [ @@ -130,7 +134,7 @@ limitations under the License. } """); -var toolCallingResponse = await httpClient.PostAsJsonAsync("http://localhost:3500/v1.0-alpha2/conversation/echo/converse", toolCallRequestBody); +var toolCallingResponse = await httpClient.PostAsJsonAsync("http://localhost:3500/v1.0-alpha2/conversation/ollama/converse", toolCallRequestBody); var toolCallingResult = await toolCallingResponse.Content.ReadFromJsonAsync(); var messageElement = toolCallingResult @@ -142,8 +146,8 @@ limitations under the License. .First() .GetProperty("message"); -var toolCallingContent = messageElement.TryGetProperty("content", out var contentElement) - ? contentElement.GetString() +var toolCallingContent = messageElement.TryGetProperty("content", out var contentElement) + ? contentElement.GetString() : null; var functionCalled = messageElement @@ -163,7 +167,7 @@ limitations under the License. name = functionName, arguments = functionArguments, }, -}); +}, s_jsonOptions); Console.WriteLine($"Tool calling input sent: {toolCallInput}"); Console.WriteLine($"Output message: {toolCallingContent}"); @@ -171,3 +175,8 @@ limitations under the License. Console.WriteLine($"Tool call: {toolCallJson}"); Console.WriteLine($"Function name: {functionName}"); Console.WriteLine($"Function arguments: {functionArguments}"); + +static partial class Program +{ + static readonly JsonSerializerOptions s_jsonOptions = new() { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }; +} diff --git a/conversation/csharp/sdk/README.md b/conversation/csharp/sdk/README.md index 70a1634b5..4444b4436 100644 --- a/conversation/csharp/sdk/README.md +++ b/conversation/csharp/sdk/README.md @@ -14,7 +14,7 @@ This quickstart includes one app: 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 .`. -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/). +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/). Open a new terminal window and run the multi app run template: @@ -22,19 +22,18 @@ Open a new terminal window and run the multi app run template: name: Run multi app run template expected_stdout_lines: - '== APP - conversation == Conversation input sent: What is dapr?' - - '== APP - conversation == Output response: What is dapr?' + - '== APP - conversation == Output response:' - '== APP - conversation == Tool calling input sent: What is the weather like in San Francisco in celsius?' - - '== APP - conversation == Output message: What is the weather like in San Francisco in celsius?' - '== APP - conversation == Tool calls detected:' - - "== APP - conversation == Tool call: {\"id\":0,\"function\":{\"name\":\"get_weather\",\"arguments\":" + - '== APP - conversation == Tool call: {"id":0,"function":{"name":"get_weather","arguments":' - '== APP - conversation == Function name: get_weather' - - '== APP - conversation == Function arguments: ' + - '== APP - conversation == Function arguments:' expected_stderr_lines: output_match_mode: substring match_order: none -background: true -sleep: 15 -timeout_seconds: 30 +background: false +sleep: 30 +timeout_seconds: 120 --> ```bash @@ -43,25 +42,18 @@ dapr run -f . The terminal console output should look similar to this, where: -- The app sends an input `What is dapr?` to the `echo` Component mock LLM. -- The mock LLM echoes `What is dapr?`. +- The app sends an input `What is dapr?` to the Ollama LLM component. +- The LLM returns a response describing Dapr. +- The app then sends a weather request with a `get_weather` tool available; the LLM calls the tool. ```text == APP - conversation == Conversation input sent: What is dapr? -== APP - conversation == Output response: What is dapr? -``` - -- The app then sends an input `What is the weather like in San Francisco in celsius?` to the `echo` Component mock LLM. -- The mock LLM echoes `What is the weather like in San Francisco in celsius?` and calls the `get_weather` tool. -- The echo Component calls the `get_weather` tool and returns the requested weather information. - -```text +== APP - conversation == Output response: Dapr is an open-source, cross-platform microservices framework... == APP - conversation == Tool calling input sent: What is the weather like in San Francisco in celsius? -== APP - conversation == Output message: What is the weather like in San Francisco in celsius? == APP - conversation == Tool calls detected: -== APP - conversation == Tool call: {"id":0,"function":{"name":"get_weather","arguments":"location,unit"}} +== APP - conversation == Tool call: {"id":0,"function":{"name":"get_weather","arguments":"{\"location\":\"San Francisco, CA\",\"unit\":\"celsius\"}"}} == APP - conversation == Function name: get_weather -== APP - conversation == Function arguments: location,unit +== APP - conversation == Function arguments: {"location":"San Francisco, CA","unit":"celsius"} ``` @@ -95,19 +87,16 @@ dapr run --app-id conversation --resources-path ../../../components/ -- dotnet r The terminal console output should look similar to this, where: -- The app sends an input `What is dapr?` to the `echo` Component mock LLM. -- The mock LLM echoes `What is dapr?`. -- The app then sends an input `What is the weather like in San Francisco in celsius?` to the `echo` Component mock LLM. -- The mock LLM echoes `What is the weather like in San Francisco in celsius?` and calls the `get_weather` tool. -- The echo Component calls the `get_weather` tool and returns the requested weather information. +- The app sends an input `What is dapr?` to the Ollama LLM component. +- The LLM returns a response describing Dapr. +- The app then sends a weather request with a `get_weather` tool available; the LLM calls the tool. ```text == APP - conversation == Conversation input sent: What is dapr? -== APP - conversation == Output response: What is dapr? +== APP - conversation == Output response: Dapr is an open-source, cross-platform microservices framework... == APP - conversation == Tool calling input sent: What is the weather like in San Francisco in celsius? -== APP - conversation == Output message: What is the weather like in San Francisco in celsius? == APP - conversation == Tool calls detected: -== APP - conversation == Tool call: {"id":0,"function":{"name":"get_weather","arguments":"location,unit"}} +== APP - conversation == Tool call: {"id":0,"function":{"name":"get_weather","arguments":"{\"location\":\"San Francisco, CA\",\"unit\":\"celsius\"}"}} == APP - conversation == Function name: get_weather -== APP - conversation == Function arguments: location,unit +== APP - conversation == Function arguments: {"location":"San Francisco, CA","unit":"celsius"} ``` diff --git a/conversation/csharp/sdk/conversation/Program.cs b/conversation/csharp/sdk/conversation/Program.cs index 8cdee2e49..218f149fb 100644 --- a/conversation/csharp/sdk/conversation/Program.cs +++ b/conversation/csharp/sdk/conversation/Program.cs @@ -20,7 +20,7 @@ limitations under the License. using Dapr.AI.Conversation.Extensions; using Dapr.AI.Conversation.Tools; -const string conversationComponentName = "echo"; +const string conversationComponentName = "ollama"; const string conversationText = "What is dapr?"; const string toolCallInput = "What is the weather like in San Francisco in celsius?"; diff --git a/conversation/go/http/conversation/conversation.go b/conversation/go/http/conversation/conversation.go index 1a3efa421..31c6f9449 100644 --- a/conversation/go/http/conversation/conversation.go +++ b/conversation/go/http/conversation/conversation.go @@ -193,7 +193,7 @@ func main() { } if usage, ok := output2["usage"].(map[string]any); ok { fmt.Printf("Usage: prompt_tokens=%v completion_tokens=%v total_tokens=%v\n", - usage["prompt_tokens"], usage["completion_tokens"], usage["total_tokens"]) + usage["promptTokens"], usage["completionTokens"], usage["totalTokens"]) } choices2 := output2["choices"].([]any) diff --git a/conversation/java/http/README.md b/conversation/java/http/README.md index 3024d6c5d..a1fc33d88 100644 --- a/conversation/java/http/README.md +++ b/conversation/java/http/README.md @@ -38,9 +38,9 @@ Tool calling follows [OpenAI's function calling format](https://developers.opena ## Run the app with the template file -Run the application using the [multi-app run template files](https://docs.dapr.io/developing-applications/local-development/multi-app-dapr-run/multi-app-overview/) and the Dapr CLI with dapr run -f .. +Run the application using the [multi-app run template files](https://docs.dapr.io/developing-applications/local-development/multi-app-dapr-run/multi-app-overview/) and the Dapr CLI with `dapr run -f .`. -This example uses the default LLM Component provided by Dapr which simply echoes the input provided, for testing purposes. Here are other [supported Conversation components](https://docs.dapr.io/reference/components-reference/supported-conversation/). +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/). ### Build and run the Java application @@ -65,25 +65,20 @@ name: Run multi app run template expected_stdout_lines: - '== APP - conversation == === Basic Conversation Example ===' - '== APP - conversation == Input sent: What is dapr?' - - '== APP - conversation == Output response: What is dapr?' - - '== APP - conversation ==' + - '== APP - conversation == Usage:' + - '== APP - conversation == Output response:' - '== APP - conversation == === Tool Calling Example ===' - '== APP - conversation == Input sent: What is the weather like in San Francisco?' - '== APP - conversation == Tools defined: get_weather (location, unit)' - '== APP - conversation == LLM requested tool calls:' - - '== APP - conversation == Tool ID: 0' - '== APP - conversation == Function: get_weather' - - '== APP - conversation == Arguments: location,unit' - '== APP - conversation == Tool Result: {"temperature": 65, "unit": "fahrenheit", "description": "Sunny"}' - - '== APP - conversation == ' - - '== APP - conversation == Note: The echo component echoes input for testing purposes.' - - '== APP - conversation == For actual tool calling, configure a real LLM component like OpenAI.' expected_stderr_lines: output_match_mode: substring match_order: none background: true -sleep: 15 -timeout_seconds: 30 +sleep: 30 +timeout_seconds: 120 --> ```bash @@ -95,19 +90,17 @@ The terminal console output should look similar to this: ```text == APP - conversation == === Basic Conversation Example === == APP - conversation == Input sent: What is dapr? -== APP - conversation == Output response: What is dapr? +== APP - conversation == Usage: prompt_tokens=30 completion_tokens=64 total_tokens=94 +== APP - conversation == Output response: { "answer": "Dapr is an open-source, cross-platform microservices framework..." } == APP - conversation == == APP - conversation == === Tool Calling Example === == APP - conversation == Input sent: What is the weather like in San Francisco? == APP - conversation == Tools defined: get_weather (location, unit) == APP - conversation == LLM requested tool calls: -== APP - conversation == Tool ID: 0 +== APP - conversation == Tool ID: call_xxxx == APP - conversation == Function: get_weather -== APP - conversation == Arguments: location,unit +== APP - conversation == Arguments: {"location":"San Francisco, CA","unit":"celsius"} == APP - conversation == Tool Result: {"temperature": 65, "unit": "fahrenheit", "description": "Sunny"} -== APP - conversation == -== APP - conversation == Note: The echo component echoes input for testing purposes. -== APP - conversation == For actual tool calling, configure a real LLM component like OpenAI. ``` @@ -145,19 +138,17 @@ You should see the output: ```bash == APP - conversation == === Basic Conversation Example === == APP - conversation == Input sent: What is dapr? -== APP - conversation == Output response: What is dapr? +== APP - conversation == Usage: prompt_tokens=30 completion_tokens=64 total_tokens=94 +== APP - conversation == Output response: { "answer": "Dapr is an open-source, cross-platform microservices framework..." } == APP - conversation == == APP - conversation == === Tool Calling Example === == APP - conversation == Input sent: What is the weather like in San Francisco? == APP - conversation == Tools defined: get_weather (location, unit) == APP - conversation == LLM requested tool calls: -== APP - conversation == Tool ID: 0 +== APP - conversation == Tool ID: call_xxxx == APP - conversation == Function: get_weather -== APP - conversation == Arguments: location,unit +== APP - conversation == Arguments: {"location":"San Francisco, CA","unit":"celsius"} == APP - conversation == Tool Result: {"temperature": 65, "unit": "fahrenheit", "description": "Sunny"} -== APP - conversation == -== APP - conversation == Note: The echo component echoes input for testing purposes. -== APP - conversation == For actual tool calling, configure a real LLM component like OpenAI. ``` 3. Stop the application: diff --git a/conversation/java/http/conversation/src/main/java/com/service/ConversationApplication.java b/conversation/java/http/conversation/src/main/java/com/service/ConversationApplication.java index c733077c1..49c393859 100644 --- a/conversation/java/http/conversation/src/main/java/com/service/ConversationApplication.java +++ b/conversation/java/http/conversation/src/main/java/com/service/ConversationApplication.java @@ -23,10 +23,10 @@ import java.time.Duration; public class ConversationApplication { - private static final String CONVERSATION_COMPONENT_NAME = "echo"; + private static final String CONVERSATION_COMPONENT_NAME = "ollama"; private static final String DAPR_HOST = System.getenv().getOrDefault("DAPR_HOST", "http://localhost"); private static final String DAPR_HTTP_PORT = System.getenv().getOrDefault("DAPR_HTTP_PORT", "3500"); - + private static final HttpClient httpClient = HttpClient.newBuilder() .version(HttpClient.Version.HTTP_2) .build(); @@ -35,7 +35,6 @@ public class ConversationApplication { public static void main(String[] args) throws Exception { String baseUrl = DAPR_HOST + ":" + DAPR_HTTP_PORT; - String conversationUrl = baseUrl + "/v1.0-alpha2/conversation/" + CONVERSATION_COMPONENT_NAME + "/converse"; // Example 1: Basic conversation System.out.println("=== Basic Conversation Example ==="); @@ -54,7 +53,6 @@ public static void main(String[] args) throws Exception { private static void basicConversation(String baseUrl) throws Exception { String conversationUrl = baseUrl + "/v1.0-alpha2/conversation/" + CONVERSATION_COMPONENT_NAME + "/converse"; - // Create the input request body using the alpha2 API format String inputBody = """ { "inputs": [ @@ -73,7 +71,13 @@ private static void basicConversation(String baseUrl) throws Exception { } ], "parameters": {}, - "metadata": {} + "metadata": {}, + "response_format": { + "type": "object", + "properties": {"answer": {"type": "string"}}, + "required": ["answer"] + }, + "prompt_cache_retention": "86400s" } """; @@ -87,18 +91,25 @@ private static void basicConversation(String baseUrl) throws Exception { System.out.println("Input sent: What is dapr?"); - // Parse the response JsonNode responseJson = objectMapper.readTree(response.body()); - - // Extract the result from outputs array (alpha2 format) JsonNode outputs = responseJson.get("outputs"); if (outputs != null && outputs.isArray() && outputs.size() > 0) { - JsonNode choices = outputs.get(0).get("choices"); + JsonNode output = outputs.get(0); + if (output.has("model") && !output.get("model").asText().isEmpty()) { + System.out.println("Model: " + output.get("model").asText()); + } + if (output.has("usage")) { + JsonNode usage = output.get("usage"); + System.out.printf("Usage: prompt_tokens=%s completion_tokens=%s total_tokens=%s%n", + usage.path("promptTokens").asText(), + usage.path("completionTokens").asText(), + usage.path("totalTokens").asText()); + } + JsonNode choices = output.get("choices"); if (choices != null && choices.isArray() && choices.size() > 0) { JsonNode message = choices.get(0).get("message"); if (message != null) { - String content = message.get("content").asText(); - System.out.println("Output response: " + content); + System.out.println("Output response: " + message.get("content").asText()); } } } @@ -107,13 +118,10 @@ private static void basicConversation(String baseUrl) throws Exception { /** * Conversation example with tool calling. * This demonstrates how to define tools and handle tool call responses. - * Note: The echo component echoes input for testing. For actual tool calling, - * use a real LLM component like OpenAI. */ private static void conversationWithToolCalling(String baseUrl) throws Exception { String conversationUrl = baseUrl + "/v1.0-alpha2/conversation/" + CONVERSATION_COMPONENT_NAME + "/converse"; - // Create input with tool definitions String inputBody = """ { "inputs": [ @@ -133,6 +141,12 @@ private static void conversationWithToolCalling(String baseUrl) throws Exception ], "parameters": {}, "metadata": {}, + "response_format": { + "type": "object", + "properties": {"answer": {"type": "string"}}, + "required": ["answer"] + }, + "prompt_cache_retention": "86400s", "temperature": 0.7, "tools": [ { @@ -172,10 +186,7 @@ private static void conversationWithToolCalling(String baseUrl) throws Exception System.out.println("Input sent: What is the weather like in San Francisco?"); System.out.println("Tools defined: get_weather (location, unit)"); - // Parse the response JsonNode responseJson = objectMapper.readTree(response.body()); - - // Check for tool calls in the response JsonNode outputs = responseJson.get("outputs"); if (outputs != null && outputs.isArray() && outputs.size() > 0) { JsonNode choices = outputs.get(0).get("choices"); @@ -185,7 +196,6 @@ private static void conversationWithToolCalling(String baseUrl) throws Exception JsonNode message = choice.get("message"); if ("tool_calls".equals(finishReason) && message != null && message.has("toolCalls")) { - // Handle tool calls System.out.println("LLM requested tool calls:"); JsonNode toolCalls = message.get("toolCalls"); for (JsonNode toolCall : toolCalls) { @@ -193,28 +203,19 @@ private static void conversationWithToolCalling(String baseUrl) throws Exception JsonNode function = toolCall.get("function"); String functionName = function.get("name").asText(); String arguments = function.get("arguments").asText(); - + System.out.println(" Tool ID: " + toolId); System.out.println(" Function: " + functionName); System.out.println(" Arguments: " + arguments); - // Execute the tool (simulated) String toolResult = executeWeatherTool(functionName, arguments); System.out.println(" Tool Result: " + toolResult); - - // In a real application, you would send the tool result back - // to the LLM using an ofTool message to continue the conversation } } else if (message != null && message.has("content")) { - // Direct response without tool calls - String content = message.get("content").asText(); - System.out.println("Output response: " + content); + System.out.println("Output response: " + message.get("content").asText()); } } } - - System.out.println("\nNote: The echo component echoes input for testing purposes."); - System.out.println("For actual tool calling, configure a real LLM component like OpenAI."); } /** @@ -223,7 +224,6 @@ private static void conversationWithToolCalling(String baseUrl) throws Exception */ private static String executeWeatherTool(String functionName, String arguments) { if ("get_weather".equals(functionName)) { - // Parse arguments and return simulated weather data return "{\"temperature\": 65, \"unit\": \"fahrenheit\", \"description\": \"Sunny\"}"; } return "{\"error\": \"Unknown function\"}"; diff --git a/conversation/java/sdk/README.md b/conversation/java/sdk/README.md index 54996a867..a09cc8944 100644 --- a/conversation/java/sdk/README.md +++ b/conversation/java/sdk/README.md @@ -12,7 +12,7 @@ This quickstart includes one app: 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 .`. -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/). +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/). Open a new terminal window and run the multi app run template: @@ -37,20 +37,17 @@ name: Run multi app run template expected_stdout_lines: - '== APP - conversation == === Simple Conversation ===' - '== APP - conversation == Conversation input sent: What is dapr?' - - '== APP - conversation == Output response: What is dapr?' + - '== APP - conversation == Output response:' - '== APP - conversation == === Tool Calling ===' - '== APP - conversation == Tool calling input sent: What is the weather like in San Francisco in celsius?' - - '== APP - conversation == Output message: What is the weather like in San Francisco in celsius?' - '== APP - conversation == Tool calls detected:' - - '== APP - conversation == Tool call: {"id": "0", "function": {"name": "get_weather", "arguments": location,unit}}' - '== APP - conversation == Function name: get_weather' - - '== APP - conversation == Function arguments: location,unit' expected_stderr_lines: output_match_mode: substring match_order: none background: true -sleep: 15 -timeout_seconds: 15 +sleep: 30 +timeout_seconds: 120 --> ```bash @@ -59,23 +56,23 @@ dapr run -f . The terminal console output should look similar to this, where: -- The app sends a simple conversation input `What is dapr?` to the `echo` Component mock LLM. -- The mock LLM echoes back the response. +- The app sends a simple conversation input `What is dapr?` to the Ollama LLM component with a structured JSON response format. +- The LLM returns a JSON object with an `answer` field describing Dapr. - The app then demonstrates tool calling by sending `What is the weather like in San Francisco in celsius?`. - The response includes detected tool calls with function name and arguments. ```text == APP - conversation == === Simple Conversation === == APP - conversation == Conversation input sent: What is dapr? -== APP - conversation == Output response: What is dapr? +== APP - conversation == Output response: {"answer":"Dapr is an open-source, cross-platform microservices framework..."} +== APP - conversation == Conversation model : llama3.2:latest == APP - conversation == === Tool Calling === == APP - conversation == Tool calling input sent: What is the weather like in San Francisco in celsius? -== APP - conversation == Output message: What is the weather like in San Francisco in celsius? == APP - conversation == Tool calls detected: -== APP - conversation == Tool call: {"id": "0", "function": {"name": "get_weather", "arguments": location,unit}} +== APP - conversation == Tool call: {"id": "call_xxxx", "function": {"name": "get_weather", "arguments": {"location":"San Francisco, CA","unit":"celsius"}}} == APP - conversation == Function name: get_weather -== APP - conversation == Function arguments: location,unit +== APP - conversation == Function arguments: {"location":"San Francisco, CA","unit":"celsius"} ``` @@ -113,13 +110,13 @@ The terminal console output should look similar to this: ```text === Simple Conversation === Conversation input sent: What is dapr? -Output response: What is dapr? +Output response: {"answer":"Dapr is an open-source, cross-platform microservices framework..."} +Conversation model : llama3.2:latest === Tool Calling === Tool calling input sent: What is the weather like in San Francisco in celsius? -Output message: What is the weather like in San Francisco in celsius? Tool calls detected: -Tool call: {"id": "0", "function": {"name": "get_weather", "arguments": location,unit}} +Tool call: {"id": "call_xxxx", "function": {"name": "get_weather", "arguments": {"location":"San Francisco, CA","unit":"celsius"}}} Function name: get_weather -Function arguments: location,unit +Function arguments: {"location":"San Francisco, CA","unit":"celsius"} ``` diff --git a/conversation/java/sdk/conversation/src/main/java/com/service/Conversation.java b/conversation/java/sdk/conversation/src/main/java/com/service/Conversation.java index eeb8f6a85..68a7181ac 100644 --- a/conversation/java/sdk/conversation/src/main/java/com/service/Conversation.java +++ b/conversation/java/sdk/conversation/src/main/java/com/service/Conversation.java @@ -21,7 +21,7 @@ public class Conversation { - private static final String CONVERSATION_COMPONENT_NAME = "echo"; + private static final String CONVERSATION_COMPONENT_NAME = "ollama"; private static final String CONVERSATION_TEXT = "What is dapr?"; private static final String TOOL_CALL_INPUT = "What is the weather like in San Francisco in celsius?"; @@ -33,7 +33,8 @@ public class Conversation { "answer": { "type": "string", "description": "The answer to the user's question" - }, + } + }, "required": ["answer"], "additionalProperties": false } diff --git a/conversation/javascript/http/README.md b/conversation/javascript/http/README.md index b44f3af43..223b0e9f8 100644 --- a/conversation/javascript/http/README.md +++ b/conversation/javascript/http/README.md @@ -14,7 +14,7 @@ This quickstart includes one app: This section shows how to run the application using the [multi-app run template files](https://docs.dapr.io/developing-applications/local-development/multi-app-dapr-run/multi-app-overview/) with `dapr run -f .`. -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/). +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/). 1. Install dependencies: @@ -35,16 +35,16 @@ npm install name: Run multi app run template expected_stdout_lines: - '== APP - conversation == Conversation input sent: What is dapr?' - - '== APP - conversation == Output response: What is dapr?' + - '== APP - conversation == Usage:' + - '== APP - conversation == Output response:' - '== APP - conversation == Tool calling input sent: What is the weather like in San Francisco in celsius?' - - '== APP - conversation == Output message: What is the weather like in San Francisco in celsius?' - - '== APP - conversation == Tool calls detected: [{"id":"0","function":{"name":"get_weather","arguments":' + - '== APP - conversation == Tool calls detected:' expected_stderr_lines: output_match_mode: substring match_order: none -background: true -sleep: 15 -timeout_seconds: 30 +background: false +sleep: 30 +timeout_seconds: 120 --> ```bash @@ -53,22 +53,16 @@ dapr run -f . The terminal console output should look similar to this, where: -- The app first sends an input `What is dapr?` to the `echo` Component mock LLM. -- The mock LLM echoes `What is dapr?`. +- The app first sends an input `What is dapr?` to the Ollama LLM component with a structured JSON response format. +- The LLM returns a JSON object with an `answer` field describing Dapr. +- The app then sends a weather request with a `get_weather` tool available; the LLM calls the tool. ```text -== APP == Conversation input sent: What is dapr? -== APP == Output response: What is dapr? -``` - -- The app then sends an input `What is the weather like in San Francisco in celsius?` to the `echo` Component mock LLM. -- The mock LLM echoes `What is the weather like in San Francisco in celsius?` and calls the `get_weather` tool. -- The echo Component calls the `get_weather` tool and returns the requested weather information. - -```text -== APP == Tool calling input sent: What is the weather like in San Francisco in celsius? -== APP == Output message: What is the weather like in San Francisco in celsius? -== APP == Tool calls detected: [{"id":"0","function":{"name":"get_weather","arguments":"location,unit"}}] +== APP - conversation == Conversation input sent: What is dapr? +== APP - conversation == Usage: prompt_tokens=30 completion_tokens=64 total_tokens=94 +== APP - conversation == Output response: { "answer": "Dapr is an open-source, cross-platform microservices framework..." } +== APP - conversation == Tool calling input sent: What is the weather like in San Francisco in celsius? +== APP - conversation == Tool calls detected: [{"id":"call_xxxx","function":{"name":"get_weather","arguments":"{\"location\":\"San Francisco, CA\",\"unit\":\"celsius\"}"}}] ``` @@ -103,16 +97,14 @@ dapr run --app-id conversation --resources-path ../../../components/ -- npm run The terminal console output should look similar to this, where: -- The app first sends an input `What is dapr?` to the `echo` Component mock LLM. -- The mock LLM echoes `What is dapr?`. -- The app then sends an input `What is the weather like in San Francisco in celsius?` to the `echo` Component mock LLM. -- The mock LLM echoes `What is the weather like in San Francisco in celsius?` -- The echo Component returns the information from the `get_weather` tool call. +- The app first sends an input `What is dapr?` to the Ollama LLM component with a structured JSON response format. +- The LLM returns a JSON object with an `answer` field describing Dapr. +- The app then sends a weather request with a `get_weather` tool available; the LLM calls the tool. ```text == APP - conversation == Conversation input sent: What is dapr? -== APP - conversation == Output response: What is dapr? +== APP - conversation == Usage: prompt_tokens=30 completion_tokens=64 total_tokens=94 +== APP - conversation == Output response: { "answer": "Dapr is an open-source, cross-platform microservices framework..." } == APP - conversation == Tool calling input sent: What is the weather like in San Francisco in celsius? -== APP - conversation == Output message: What is the weather like in San Francisco in celsius? -== APP == Tool calls detected: [{"id":"0","function":{"name":"get_weather","arguments":"location,unit"}}] +== APP - conversation == Tool calls detected: [{"id":"call_xxxx","function":{"name":"get_weather","arguments":"{\"location\":\"San Francisco, CA\",\"unit\":\"celsius\"}"}}] ``` diff --git a/conversation/javascript/http/conversation/index.js b/conversation/javascript/http/conversation/index.js index 0bac81826..ffb044f9e 100644 --- a/conversation/javascript/http/conversation/index.js +++ b/conversation/javascript/http/conversation/index.js @@ -1,4 +1,4 @@ -const conversationComponentName = "echo"; +const conversationComponentName = "ollama"; async function main() { const daprHost = process.env.DAPR_HOST || "http://localhost"; @@ -26,6 +26,12 @@ async function main() { ], parameters: {}, metadata: {}, + response_format: { + type: "object", + properties: { answer: { type: "string" } }, + required: ["answer"], + }, + prompt_cache_retention: "86400s", }; const response = await fetch(reqURL, { method: "POST", @@ -38,7 +44,17 @@ async function main() { console.log("Conversation input sent: What is dapr?"); const data = await response.json(); - const result = data.outputs[0].choices[0].message.content; + const output = data.outputs[0]; + if (output.model) { + console.log("Model:", output.model); + } + if (output.usage) { + const u = output.usage; + console.log( + `Usage: prompt_tokens=${u.promptTokens} completion_tokens=${u.completionTokens} total_tokens=${u.totalTokens}` + ); + } + const result = output.choices[0].message.content; console.log("Output response:", result); } catch (error) { console.error("Error:", error.message); @@ -64,10 +80,13 @@ async function main() { scrubPii: false, }, ], - metadata: { - api_key: "test-key", - version: "1.0", + metadata: {}, + response_format: { + type: "object", + properties: { answer: { type: "string" } }, + required: ["answer"], }, + prompt_cache_retention: "86400s", scrubPii: false, temperature: 0.7, tools: [ @@ -108,14 +127,23 @@ async function main() { ); const data = await response.json(); + const output = data?.outputs?.[0]; + if (output?.usage) { + const u = output.usage; + console.log( + `Usage: prompt_tokens=${u.promptTokens} completion_tokens=${u.completionTokens} total_tokens=${u.totalTokens}` + ); + } - const result = data?.outputs?.[0]?.choices?.[0]?.message?.content; - console.log("Output message:", result); + const result = output?.choices?.[0]?.message?.content; + if (result) { + console.log("Output message:", result); + } - if (data?.outputs?.[0]?.choices?.[0]?.message?.toolCalls) { + if (output?.choices?.[0]?.message?.toolCalls) { console.log( "Tool calls detected:", - JSON.stringify(data.outputs[0].choices[0].message?.toolCalls) + JSON.stringify(output.choices[0].message?.toolCalls) ); } else { console.log("No tool calls in response"); diff --git a/conversation/python/http/conversation/app.py b/conversation/python/http/conversation/app.py index 3d60c1846..08c307d42 100644 --- a/conversation/python/http/conversation/app.py +++ b/conversation/python/http/conversation/app.py @@ -65,7 +65,7 @@ if out.get('usage'): u = out['usage'] logging.info('Usage: prompt_tokens=%s completion_tokens=%s total_tokens=%s', - u.get('prompt_tokens'), u.get('completion_tokens'), u.get('total_tokens')) + u.get('promptTokens'), u.get('completionTokens'), u.get('totalTokens')) if 'choices' in out and len(out['choices']) > 0: output = out["choices"][0]["message"]["content"] logging.info('Output response: ' + output) From da760fcc6330259f5fc644b165615c829016ebb4 Mon Sep 17 00:00:00 2001 From: Samantha Coyle Date: Mon, 23 Feb 2026 15:15:17 -0600 Subject: [PATCH 05/17] fix( build): updates for build to pass Signed-off-by: Samantha Coyle --- .../csharp/http/conversation/Program.cs | 33 ++++++- .../com/service/ConversationApplication.java | 98 +++++++++++-------- .../javascript/http/conversation/index.js | 58 +++++++++-- conversation/python/sdk/conversation/app.py | 19 +++- 4 files changed, 150 insertions(+), 58 deletions(-) diff --git a/conversation/csharp/http/conversation/Program.cs b/conversation/csharp/http/conversation/Program.cs index 95ab91e41..7512d7781 100644 --- a/conversation/csharp/http/conversation/Program.cs +++ b/conversation/csharp/http/conversation/Program.cs @@ -51,9 +51,21 @@ limitations under the License. """); var conversationResponse = await httpClient.PostAsJsonAsync("http://localhost:3500/v1.0-alpha2/conversation/ollama/converse", conversationRequestBody); -var conversationResult = await conversationResponse.Content.ReadFromJsonAsync(); +conversationResponse.EnsureSuccessStatusCode(); +var responseText = await conversationResponse.Content.ReadAsStringAsync(); +var conversationResult = JsonSerializer.Deserialize(responseText); -var firstOutput = conversationResult.GetProperty("outputs").EnumerateArray().First(); +if (conversationResult.ValueKind == JsonValueKind.Null || conversationResult.ValueKind == JsonValueKind.Undefined) +{ + throw new InvalidOperationException($"Failed to parse response as JSON. Response: {responseText}"); +} + +if (!conversationResult.TryGetProperty("outputs", out var outputsElement)) +{ + throw new InvalidOperationException($"Response does not contain 'outputs' property. Response: {responseText}"); +} + +var firstOutput = outputsElement.EnumerateArray().First(); Console.WriteLine($"Conversation input sent: {conversationText}"); if (firstOutput.TryGetProperty("model", out var modelElement) && modelElement.GetString() is { Length: > 0 } model) @@ -135,10 +147,21 @@ limitations under the License. """); var toolCallingResponse = await httpClient.PostAsJsonAsync("http://localhost:3500/v1.0-alpha2/conversation/ollama/converse", toolCallRequestBody); -var toolCallingResult = await toolCallingResponse.Content.ReadFromJsonAsync(); +toolCallingResponse.EnsureSuccessStatusCode(); +var toolCallingResponseText = await toolCallingResponse.Content.ReadAsStringAsync(); +var toolCallingResult = JsonSerializer.Deserialize(toolCallingResponseText); + +if (toolCallingResult.ValueKind == JsonValueKind.Null || toolCallingResult.ValueKind == JsonValueKind.Undefined) +{ + throw new InvalidOperationException($"Failed to parse response as JSON. Response: {toolCallingResponseText}"); +} + +if (!toolCallingResult.TryGetProperty("outputs", out var toolCallingOutputsElement)) +{ + throw new InvalidOperationException($"Response does not contain 'outputs' property. Response: {toolCallingResponseText}"); +} -var messageElement = toolCallingResult - .GetProperty("outputs") +var messageElement = toolCallingOutputsElement .EnumerateArray() .First() .GetProperty("choices") diff --git a/conversation/java/http/conversation/src/main/java/com/service/ConversationApplication.java b/conversation/java/http/conversation/src/main/java/com/service/ConversationApplication.java index 49c393859..bd46c76d8 100644 --- a/conversation/java/http/conversation/src/main/java/com/service/ConversationApplication.java +++ b/conversation/java/http/conversation/src/main/java/com/service/ConversationApplication.java @@ -91,26 +91,32 @@ private static void basicConversation(String baseUrl) throws Exception { System.out.println("Input sent: What is dapr?"); + if (response.statusCode() < 200 || response.statusCode() >= 300) { + throw new RuntimeException("API request failed with status " + response.statusCode() + ": " + response.body()); + } + JsonNode responseJson = objectMapper.readTree(response.body()); JsonNode outputs = responseJson.get("outputs"); - if (outputs != null && outputs.isArray() && outputs.size() > 0) { - JsonNode output = outputs.get(0); - if (output.has("model") && !output.get("model").asText().isEmpty()) { - System.out.println("Model: " + output.get("model").asText()); - } - if (output.has("usage")) { - JsonNode usage = output.get("usage"); - System.out.printf("Usage: prompt_tokens=%s completion_tokens=%s total_tokens=%s%n", - usage.path("promptTokens").asText(), - usage.path("completionTokens").asText(), - usage.path("totalTokens").asText()); - } - JsonNode choices = output.get("choices"); - if (choices != null && choices.isArray() && choices.size() > 0) { - JsonNode message = choices.get(0).get("message"); - if (message != null) { - System.out.println("Output response: " + message.get("content").asText()); - } + if (outputs == null || !outputs.isArray() || outputs.size() == 0) { + throw new RuntimeException("Response does not contain 'outputs' array. Response: " + response.body()); + } + + JsonNode output = outputs.get(0); + if (output.has("model") && !output.get("model").asText().isEmpty()) { + System.out.println("Model: " + output.get("model").asText()); + } + if (output.has("usage")) { + JsonNode usage = output.get("usage"); + System.out.printf("Usage: prompt_tokens=%s completion_tokens=%s total_tokens=%s%n", + usage.path("promptTokens").asText(), + usage.path("completionTokens").asText(), + usage.path("totalTokens").asText()); + } + JsonNode choices = output.get("choices"); + if (choices != null && choices.isArray() && choices.size() > 0) { + JsonNode message = choices.get(0).get("message"); + if (message != null) { + System.out.println("Output response: " + message.get("content").asText()); } } } @@ -186,34 +192,40 @@ private static void conversationWithToolCalling(String baseUrl) throws Exception System.out.println("Input sent: What is the weather like in San Francisco?"); System.out.println("Tools defined: get_weather (location, unit)"); + if (response.statusCode() < 200 || response.statusCode() >= 300) { + throw new RuntimeException("API request failed with status " + response.statusCode() + ": " + response.body()); + } + JsonNode responseJson = objectMapper.readTree(response.body()); JsonNode outputs = responseJson.get("outputs"); - if (outputs != null && outputs.isArray() && outputs.size() > 0) { - JsonNode choices = outputs.get(0).get("choices"); - if (choices != null && choices.isArray() && choices.size() > 0) { - JsonNode choice = choices.get(0); - String finishReason = choice.has("finishReason") ? choice.get("finishReason").asText() : ""; - JsonNode message = choice.get("message"); - - if ("tool_calls".equals(finishReason) && message != null && message.has("toolCalls")) { - System.out.println("LLM requested tool calls:"); - JsonNode toolCalls = message.get("toolCalls"); - for (JsonNode toolCall : toolCalls) { - String toolId = toolCall.get("id").asText(); - JsonNode function = toolCall.get("function"); - String functionName = function.get("name").asText(); - String arguments = function.get("arguments").asText(); - - System.out.println(" Tool ID: " + toolId); - System.out.println(" Function: " + functionName); - System.out.println(" Arguments: " + arguments); - - String toolResult = executeWeatherTool(functionName, arguments); - System.out.println(" Tool Result: " + toolResult); - } - } else if (message != null && message.has("content")) { - System.out.println("Output response: " + message.get("content").asText()); + if (outputs == null || !outputs.isArray() || outputs.size() == 0) { + throw new RuntimeException("Response does not contain 'outputs' array. Response: " + response.body()); + } + + JsonNode choices = outputs.get(0).get("choices"); + if (choices != null && choices.isArray() && choices.size() > 0) { + JsonNode choice = choices.get(0); + String finishReason = choice.has("finishReason") ? choice.get("finishReason").asText() : ""; + JsonNode message = choice.get("message"); + + if ("tool_calls".equals(finishReason) && message != null && message.has("toolCalls")) { + System.out.println("LLM requested tool calls:"); + JsonNode toolCalls = message.get("toolCalls"); + for (JsonNode toolCall : toolCalls) { + String toolId = toolCall.get("id").asText(); + JsonNode function = toolCall.get("function"); + String functionName = function.get("name").asText(); + String arguments = function.get("arguments").asText(); + + System.out.println(" Tool ID: " + toolId); + System.out.println(" Function: " + functionName); + System.out.println(" Arguments: " + arguments); + + String toolResult = executeWeatherTool(functionName, arguments); + System.out.println(" Tool Result: " + toolResult); } + } else if (message != null && message.has("content")) { + System.out.println("Output response: " + message.get("content").asText()); } } } diff --git a/conversation/javascript/http/conversation/index.js b/conversation/javascript/http/conversation/index.js index ffb044f9e..8fe4f9f38 100644 --- a/conversation/javascript/http/conversation/index.js +++ b/conversation/javascript/http/conversation/index.js @@ -43,7 +43,17 @@ async function main() { console.log("Conversation input sent: What is dapr?"); + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`API request failed with status ${response.status}: ${errorText}`); + } + const data = await response.json(); + + if (!data || !data.outputs || !Array.isArray(data.outputs) || data.outputs.length === 0) { + throw new Error(`Response does not contain 'outputs' array. Response: ${JSON.stringify(data)}`); + } + const output = data.outputs[0]; if (output.model) { console.log("Model:", output.model); @@ -54,7 +64,16 @@ async function main() { `Usage: prompt_tokens=${u.promptTokens} completion_tokens=${u.completionTokens} total_tokens=${u.totalTokens}` ); } - const result = output.choices[0].message.content; + if (!output.choices || !Array.isArray(output.choices) || output.choices.length === 0) { + throw new Error(`Output does not contain 'choices' array. Output: ${JSON.stringify(output)}`); + } + + const choice = output.choices[0]; + if (!choice || !choice.message || !choice.message.content) { + throw new Error(`Choice does not contain 'message.content'. Choice: ${JSON.stringify(choice)}`); + } + + const result = choice.message.content; console.log("Output response:", result); } catch (error) { console.error("Error:", error.message); @@ -126,8 +145,18 @@ async function main() { "Tool calling input sent: What is the weather like in San Francisco in celsius?" ); + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`API request failed with status ${response.status}: ${errorText}`); + } + const data = await response.json(); - const output = data?.outputs?.[0]; + + if (!data || !data.outputs || !Array.isArray(data.outputs) || data.outputs.length === 0) { + throw new Error(`Response does not contain 'outputs' array. Response: ${JSON.stringify(data)}`); + } + + const output = data.outputs[0]; if (output?.usage) { const u = output.usage; console.log( @@ -135,16 +164,29 @@ async function main() { ); } - const result = output?.choices?.[0]?.message?.content; + if (!output.choices || !Array.isArray(output.choices) || output.choices.length === 0) { + throw new Error(`Output does not contain 'choices' array. Output: ${JSON.stringify(output)}`); + } + + const choice = output.choices[0]; + if (!choice || !choice.message) { + throw new Error(`Choice does not contain 'message'. Choice: ${JSON.stringify(choice)}`); + } + + const message = choice.message; + const result = message.content; if (result) { console.log("Output message:", result); } - if (output?.choices?.[0]?.message?.toolCalls) { - console.log( - "Tool calls detected:", - JSON.stringify(output.choices[0].message?.toolCalls) - ); + if (message.toolCalls) { + console.log("Tool calls detected:"); + for (const toolCall of message.toolCalls) { + const functionName = toolCall.function?.name; + const functionArgs = toolCall.function?.arguments; + console.log(` Function: ${functionName}`); + console.log(` Arguments: ${functionArgs}`); + } } else { console.log("No tool calls in response"); } diff --git a/conversation/python/sdk/conversation/app.py b/conversation/python/sdk/conversation/app.py index 5360049ec..e3b368439 100644 --- a/conversation/python/sdk/conversation/app.py +++ b/conversation/python/sdk/conversation/app.py @@ -25,6 +25,21 @@ print(f'Input sent: {text_input}') response = d.converse_alpha2(name=provider_component, inputs=inputs, temperature=0.7, context_id='chat-123') - + + if not response or not hasattr(response, 'outputs') or not response.outputs: + raise ValueError(f"Response does not contain 'outputs'. Response: {response}") + for output in response.outputs: - print(f'Output response: {output.choices[0].message.content}') + if not output or not hasattr(output, 'choices') or not output.choices or len(output.choices) == 0: + raise ValueError(f"Output does not contain 'choices' array. Output: {output}") + + choice = output.choices[0] + if not choice or not hasattr(choice, 'message') or not choice.message: + raise ValueError(f"Choice does not contain 'message'. Choice: {choice}") + + message = choice.message + content = getattr(message, 'content', None) + if content is None: + raise ValueError(f"Message does not contain 'content'. Message: {message}") + + print(f'Output response: {content}') From 73e6357b37288dae827c444fd7fdb19f7d8d34cc Mon Sep 17 00:00:00 2001 From: Samantha Coyle Date: Mon, 23 Feb 2026 15:31:48 -0600 Subject: [PATCH 06/17] fix(build): enable ollama in ci Signed-off-by: Samantha Coyle --- .github/workflows/validate_csharp_quickstarts.yaml | 9 +++++++++ .github/workflows/validate_java_quickstarts.yaml | 9 +++++++++ .github/workflows/validate_javascript_quickstarts.yaml | 9 +++++++++ 3 files changed, 27 insertions(+) diff --git a/.github/workflows/validate_csharp_quickstarts.yaml b/.github/workflows/validate_csharp_quickstarts.yaml index f7386c4dc..ba65997d4 100644 --- a/.github/workflows/validate_csharp_quickstarts.yaml +++ b/.github/workflows/validate_csharp_quickstarts.yaml @@ -84,6 +84,15 @@ jobs: export GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} dapr init --runtime-version=${{ env.DAPR_RUNTIME_VERSION }} dapr --version + - name: Cache Ollama models + uses: actions/cache@v4 + with: + path: ~/.ollama + key: ${{ runner.os }}-ollama-llama3.2 + - name: Setup Ollama + uses: ai-action/setup-ollama@v2 + - name: Pull Ollama model + run: ollama pull llama3.2:latest - name: Install utilities dependencies run: | echo "PATH=$PATH:$HOME/.local/bin" >> $GITHUB_ENV diff --git a/.github/workflows/validate_java_quickstarts.yaml b/.github/workflows/validate_java_quickstarts.yaml index d970d8cc7..5bb903b5c 100644 --- a/.github/workflows/validate_java_quickstarts.yaml +++ b/.github/workflows/validate_java_quickstarts.yaml @@ -83,6 +83,15 @@ jobs: export GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} dapr init --runtime-version=${{ env.DAPR_RUNTIME_VERSION }} dapr --version + - name: Cache Ollama models + uses: actions/cache@v4 + with: + path: ~/.ollama + key: ${{ runner.os }}-ollama-llama3.2 + - name: Setup Ollama + uses: ai-action/setup-ollama@v2 + - name: Pull Ollama model + run: ollama pull llama3.2:latest - name: Install utilities dependencies run: | echo "PATH=$PATH:$HOME/.local/bin" >> $GITHUB_ENV diff --git a/.github/workflows/validate_javascript_quickstarts.yaml b/.github/workflows/validate_javascript_quickstarts.yaml index fe9236622..5cfc4af7b 100644 --- a/.github/workflows/validate_javascript_quickstarts.yaml +++ b/.github/workflows/validate_javascript_quickstarts.yaml @@ -78,6 +78,15 @@ jobs: export GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} dapr init --runtime-version=${{ env.DAPR_RUNTIME_VERSION }} dapr --version + - name: Cache Ollama models + uses: actions/cache@v4 + with: + path: ~/.ollama + key: ${{ runner.os }}-ollama-llama3.2 + - name: Setup Ollama + uses: ai-action/setup-ollama@v2 + - name: Pull Ollama model + run: ollama pull llama3.2:latest - name: Install utilities dependencies run: | echo "PATH=$PATH:$HOME/.local/bin" >> $GITHUB_ENV From 6249f84f2e876f52e16aff917a09e74d8f1eac1a Mon Sep 17 00:00:00 2001 From: Samantha Coyle Date: Mon, 23 Feb 2026 15:33:56 -0600 Subject: [PATCH 07/17] style: make output substring Signed-off-by: Samantha Coyle --- pub_sub/csharp/http/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pub_sub/csharp/http/README.md b/pub_sub/csharp/http/README.md index 155084255..1af95239e 100644 --- a/pub_sub/csharp/http/README.md +++ b/pub_sub/csharp/http/README.md @@ -25,8 +25,8 @@ name: Run multi app run template expected_stdout_lines: - 'Started Dapr with app id "order-processor-http"' - 'Started Dapr with app id "checkout-http"' - - '== APP - checkout-http == Published data: Order { OrderId = 2 }' - - '== APP - order-processor-http == Subscriber received : 2' + - 'Published data: Order { OrderId = 2 }' + - 'Subscriber received : Order { OrderId = 2 }' expected_stderr_lines: output_match_mode: substring match_order: none From 0193a69ee5a1d28b78ab7f391b0bdf1963c80109 Mon Sep 17 00:00:00 2001 From: Samantha Coyle Date: Mon, 23 Feb 2026 15:44:40 -0600 Subject: [PATCH 08/17] style: fix csharp output Signed-off-by: Samantha Coyle --- pub_sub/csharp/http/README.md | 4 ++-- pub_sub/csharp/http/order-processor/Program.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pub_sub/csharp/http/README.md b/pub_sub/csharp/http/README.md index 1af95239e..c89703440 100644 --- a/pub_sub/csharp/http/README.md +++ b/pub_sub/csharp/http/README.md @@ -43,9 +43,9 @@ The terminal console output should look similar to this: ```text == APP - checkout-http == Published data: Order { OrderId = 1 } -== APP - order-processor-http == Subscriber received : 1 +== APP - order-processor-http == Subscriber received : Order { OrderId = 1 } == APP - checkout-http == Published data: Order { OrderId = 2 } -== APP - order-processor-http == Subscriber received : 2 +== APP - order-processor-http == Subscriber received : Order { OrderId = 2 } == APP - checkout-http == Published data: Order { OrderId = 3 } == APP - order-processor-http == Subscriber received : 3 == APP - checkout-http == Published data: Order { OrderId = 4 } diff --git a/pub_sub/csharp/http/order-processor/Program.cs b/pub_sub/csharp/http/order-processor/Program.cs index 039f2f650..456be91ef 100644 --- a/pub_sub/csharp/http/order-processor/Program.cs +++ b/pub_sub/csharp/http/order-processor/Program.cs @@ -15,7 +15,7 @@ // Dapr subscription in /dapr/subscribe sets up this route app.MapPost("/orders", (DaprData requestData) => { - Console.WriteLine("Subscriber received : " + requestData.Data.OrderId); + Console.WriteLine("Subscriber received : " + requestData.Data); return Results.Ok(requestData.Data); }); From b3f62c17b5002581afedb1e34aadeb6e0c271b99 Mon Sep 17 00:00:00 2001 From: Samantha Coyle Date: Mon, 23 Feb 2026 16:17:22 -0600 Subject: [PATCH 09/17] fix: update the python sdk for build faliure Signed-off-by: Samantha Coyle --- conversation/python/sdk/conversation/app.py | 69 +++++++++++++-------- 1 file changed, 43 insertions(+), 26 deletions(-) diff --git a/conversation/python/sdk/conversation/app.py b/conversation/python/sdk/conversation/app.py index e3b368439..3c14ce3a7 100644 --- a/conversation/python/sdk/conversation/app.py +++ b/conversation/python/sdk/conversation/app.py @@ -10,36 +10,53 @@ # See the License for the specific language governing permissions and # limitations under the License. # ------------------------------------------------------------ +import sys from dapr.clients import DaprClient from dapr.clients.grpc.conversation import ConversationInputAlpha2, ConversationMessage, ConversationMessageContent, ConversationMessageOfUser -with DaprClient() as d: - text_input = "What is dapr?" - provider_component = "ollama" +try: + with DaprClient() as d: + text_input = "What is dapr?" + provider_component = "ollama" - inputs = [ - ConversationInputAlpha2(messages=[ConversationMessage(of_user=ConversationMessageOfUser(content=[ConversationMessageContent(text=text_input)]))], - scrub_pii=True), - ] + inputs = [ + ConversationInputAlpha2(messages=[ConversationMessage(of_user=ConversationMessageOfUser(content=[ConversationMessageContent(text=text_input)]))], + scrub_pii=True), + ] - print(f'Input sent: {text_input}') + print(f'Input sent: {text_input}', flush=True) - response = d.converse_alpha2(name=provider_component, inputs=inputs, temperature=0.7, context_id='chat-123') - - if not response or not hasattr(response, 'outputs') or not response.outputs: - raise ValueError(f"Response does not contain 'outputs'. Response: {response}") - - for output in response.outputs: - if not output or not hasattr(output, 'choices') or not output.choices or len(output.choices) == 0: - raise ValueError(f"Output does not contain 'choices' array. Output: {output}") + response = d.converse_alpha2(name=provider_component, inputs=inputs, temperature=0.7, context_id='chat-123') - choice = output.choices[0] - if not choice or not hasattr(choice, 'message') or not choice.message: - raise ValueError(f"Choice does not contain 'message'. Choice: {choice}") - - message = choice.message - content = getattr(message, 'content', None) - if content is None: - raise ValueError(f"Message does not contain 'content'. Message: {message}") - - print(f'Output response: {content}') + if response and hasattr(response, 'outputs') and response.outputs: + for output in response.outputs: + if hasattr(output, 'model') and output.model: + print(f'Model: {output.model}', flush=True) + + if hasattr(output, 'usage') and output.usage: + usage = output.usage + prompt_tokens = getattr(usage, 'prompt_tokens', None) or getattr(usage, 'promptTokens', None) + completion_tokens = getattr(usage, 'completion_tokens', None) or getattr(usage, 'completionTokens', None) + total_tokens = getattr(usage, 'total_tokens', None) or getattr(usage, 'totalTokens', None) + print(f'Usage: prompt_tokens={prompt_tokens} completion_tokens={completion_tokens} total_tokens={total_tokens}', flush=True) + + if output and hasattr(output, 'choices') and output.choices and len(output.choices) > 0: + choice = output.choices[0] + if choice and hasattr(choice, 'message') and choice.message: + message = choice.message + content = getattr(message, 'content', None) + if content: + print(f'Output response: {content}', flush=True) + else: + print(f'Output response: {message}', flush=True) + else: + print(f'Output response: {choice}', flush=True) + else: + print('No choices in output', flush=True) + print(f'Output response: {output}', flush=True) + else: + print('No outputs found in response', flush=True) + print(f'Response data: {response}', flush=True) +except Exception as e: + print(f'Error: {e}', file=sys.stderr, flush=True) + sys.exit(1) From 07ccfa77b3a5c59a03387a2c3dc236eab1018b02 Mon Sep 17 00:00:00 2001 From: Samantha Coyle Date: Mon, 23 Feb 2026 16:48:31 -0600 Subject: [PATCH 10/17] style: update outputs Signed-off-by: Samantha Coyle --- pub_sub/python/sdk/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pub_sub/python/sdk/README.md b/pub_sub/python/sdk/README.md index db98ab869..6f92855fe 100644 --- a/pub_sub/python/sdk/README.md +++ b/pub_sub/python/sdk/README.md @@ -45,8 +45,8 @@ cd .. - + ```bash source conversation/.venv/bin/activate - dapr run -f . + dapr run -f . ``` - + Expected output: - + ```text == APP - conversation-sdk == Input sent: What is dapr? - == APP - conversation-sdk == Output response: Dapr is an open-source, cross-platform microservices framework... + == APP - conversation-sdk == Output response: What is dapr? ``` @@ -112,15 +112,15 @@ For more LLM options, see the [supported Conversation components](https://docs.d name: Run multi app run template expected_stdout_lines: - "== APP - conversation-tool-calling == Input sent: calculate square root of 15" - - "== APP - conversation-tool-calling == Output response: ConversationResultAlpha2Choices(finish_reason='tool_calls'" + - "== APP - conversation-tool-calling == Output response: ConversationResultAlpha2Choices(finish_reason='tool_calls', index=0, message=ConversationResultAlpha2Message(content='calculate square root of 15', tool_calls=[ConversationToolCalls(id='0', function=ConversationToolCallsOfFunction(name='calculate', arguments='expression'))]))" - "== APP - conversation-tool-calling == Input sent: get weather in San Francisco in celsius" - - "== APP - conversation-tool-calling == Output response: ConversationResultAlpha2Choices(finish_reason='tool_calls'" + - "== APP - conversation-tool-calling == Output response: ConversationResultAlpha2Choices(finish_reason='tool_calls', index=0, message=ConversationResultAlpha2Message(content='get weather in San Francisco in celsius', tool_calls=[ConversationToolCalls(id='0', function=ConversationToolCallsOfFunction(name='get_weather', arguments='location,unit'))]))" expected_stderr_lines: output_match_mode: substring match_order: none background: true - sleep: 30 - timeout_seconds: 60 + sleep: 15 + timeout_seconds: 30 --> ```bash @@ -132,9 +132,9 @@ For more LLM options, see the [supported Conversation components](https://docs.d ```text == APP - conversation-tool-calling == Input sent: calculate square root of 15 - == APP - conversation-tool-calling == Output response: ConversationResultAlpha2Choices(finish_reason='tool_calls', index=0, message=ConversationResultAlpha2Message(content='calculate square root of 15', tool_calls=[ConversationToolCalls(id='call_...', function=ConversationToolCallsOfFunction(name='calculate', arguments='{"expression": "sqrt(15)"}'))])) + == APP - conversation-tool-calling == Output response: ConversationResultAlpha2Choices(finish_reason='tool_calls', index=0, message=ConversationResultAlpha2Message(content='calculate square root of 15', tool_calls=[ConversationToolCalls(id='0', function=ConversationToolCallsOfFunction(name='calculate', arguments='expression'))])) == APP - conversation-tool-calling == Input sent: get weather in San Francisco in celsius - == APP - conversation-tool-calling == Output response: ConversationResultAlpha2Choices(finish_reason='tool_calls', index=0, message=ConversationResultAlpha2Message(content='get weather in San Francisco in celsius', tool_calls=[ConversationToolCalls(id='call_...', function=ConversationToolCallsOfFunction(name='get_weather', arguments='{"location": "San Francisco", "unit": "celsius"}'))])) + == APP - conversation-tool-calling == Output response: ConversationResultAlpha2Choices(finish_reason='tool_calls', index=0, message=ConversationResultAlpha2Message(content='get weather in San Francisco in celsius', tool_calls=[ConversationToolCalls(id='0', function=ConversationToolCallsOfFunction(name='get_weather', arguments='location,unit'))])) ``` diff --git a/conversation/python/sdk/conversation/app.py b/conversation/python/sdk/conversation/app.py index 47c42498b..db898a9f1 100644 --- a/conversation/python/sdk/conversation/app.py +++ b/conversation/python/sdk/conversation/app.py @@ -10,53 +10,21 @@ # See the License for the specific language governing permissions and # limitations under the License. # ------------------------------------------------------------ -import sys from dapr.clients import DaprClient from dapr.clients.grpc.conversation import ConversationInputAlpha2, ConversationMessage, ConversationMessageContent, ConversationMessageOfUser -try: - with DaprClient() as d: - text_input = "What is dapr?" - provider_component = "ollama" +with DaprClient() as d: + text_input = "What is dapr?" + provider_component = "echo" - inputs = [ - ConversationInputAlpha2(messages=[ConversationMessage(of_user=ConversationMessageOfUser(content=[ConversationMessageContent(text=text_input)]))], - scrub_pii=True), - ] + inputs = [ + ConversationInputAlpha2(messages=[ConversationMessage(of_user=ConversationMessageOfUser(content=[ConversationMessageContent(text=text_input)]))], + scrub_pii=True), + ] - print(f'Input sent: {text_input}', flush=True) + print(f'Input sent: {text_input}') - response = d.converse_alpha2(name=provider_component, inputs=inputs, temperature=0.7) - - if response and hasattr(response, 'outputs') and response.outputs: - for output in response.outputs: - if hasattr(output, 'model') and output.model: - print(f'Model: {output.model}', flush=True) - - if hasattr(output, 'usage') and output.usage: - usage = output.usage - prompt_tokens = getattr(usage, 'prompt_tokens', None) or getattr(usage, 'promptTokens', None) - completion_tokens = getattr(usage, 'completion_tokens', None) or getattr(usage, 'completionTokens', None) - total_tokens = getattr(usage, 'total_tokens', None) or getattr(usage, 'totalTokens', None) - print(f'Usage: prompt_tokens={prompt_tokens} completion_tokens={completion_tokens} total_tokens={total_tokens}', flush=True) - - if output and hasattr(output, 'choices') and output.choices and len(output.choices) > 0: - choice = output.choices[0] - if choice and hasattr(choice, 'message') and choice.message: - message = choice.message - content = getattr(message, 'content', None) - if content: - print(f'Output response: {content}', flush=True) - else: - print(f'Output response: {message}', flush=True) - else: - print(f'Output response: {choice}', flush=True) - else: - print('No choices in output', flush=True) - print(f'Output response: {output}', flush=True) - else: - print('No outputs found in response', flush=True) - print(f'Response data: {response}', flush=True) -except Exception as e: - print(f'Error: {e}', file=sys.stderr, flush=True) - sys.exit(1) + response = d.converse_alpha2(name=provider_component, inputs=inputs, temperature=0.7, context_id='chat-123') + + for output in response.outputs: + print(f'Output response: {output.choices[0].message.content}') diff --git a/conversation/python/sdk/conversation/tool_calling.py b/conversation/python/sdk/conversation/tool_calling.py index 2052b6899..3f460b8a1 100644 --- a/conversation/python/sdk/conversation/tool_calling.py +++ b/conversation/python/sdk/conversation/tool_calling.py @@ -14,7 +14,7 @@ from dapr.clients.grpc import conversation with DaprClient() as d: - provider_component = "ollama" + provider_component = "echo" # ------------------------------------------------------------ # Creating Tool Function definition using lower level API and hand-crafted JSON schema