Skip to content

Commit 845749b

Browse files
Merge pull request #1208 from sicoyle/feat-http-tool-calling
feat(conversation): add python http tool calling quickstart
2 parents 1387c6d + a25e7fc commit 845749b

File tree

3 files changed

+159
-20
lines changed

3 files changed

+159
-20
lines changed

conversation/python/http/README.md

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Visit [this](https://docs.dapr.io/developing-applications/building-blocks/conver
88
99
This quickstart includes one app:
1010

11-
- `app.py`, responsible for sending an input to the underlying LLM and retrieving an output.
11+
- `app.py`, responsible for sending an input to the underlying LLM and retrieving an output. It includes a secondary conversation request to showcase tool calling to the underlying LLM.
1212

1313
## Run the app with the template file
1414

@@ -35,8 +35,11 @@ cd ..
3535
<!-- STEP
3636
name: Run multi app run template
3737
expected_stdout_lines:
38-
- '== APP - conversation == INFO:root:Input sent: What is dapr?'
39-
- '== APP - conversation == INFO:root:Output response: What is dapr?'
38+
- '== APP - conversation == Conversation input sent: What is dapr?'
39+
- '== APP - conversation == Output response: What is dapr?'
40+
- '== APP - conversation == Tool calling input sent: What is the weather like in San Francisco in celsius?'
41+
- '== APP - conversation == Output message: What is the weather like in San Francisco in celsius?'
42+
- '== APP - conversation == No tool calls in response'
4043
expected_stderr_lines:
4144
output_match_mode: substring
4245
match_order: none
@@ -51,14 +54,27 @@ dapr run -f .
5154

5255
The terminal console output should look similar to this, where:
5356

54-
- The app sends an input `What is dapr?` to the `echo` Component mock LLM.
57+
- The app first sends an input `What is dapr?` to the `echo` Component mock LLM.
5558
- The mock LLM echoes `What is dapr?`.
59+
- The app then sends a weather request to the component with tools available to the LLM.
60+
- The LLM will either respond back with a tool call for the user, or an ask for more information.
5661

5762
```text
5863
== APP - conversation == Input sent: What is dapr?
5964
== APP - conversation == Output response: What is dapr?
6065
```
6166

67+
- The app then sends an input `What is the weather like in San Francisco in celsius?` to the `echo` Component mock LLM.
68+
- The mock LLM echoes `What is the weather like in San Francisco in celsius?` and calls the `get_weather` tool.
69+
- Since we are using the `echo` Component mock LLM, the tool call is not executed and the LLM returns `No tool calls in response`.
70+
71+
```text
72+
== APP == Tool calling input sent: What is the weather like in San Francisco in celsius?
73+
== APP == Output message: What is the weather like in San Francisco in celsius?
74+
== APP == No tool calls in response
75+
```
76+
```
77+
6278
<!-- END_STEP -->
6379
6480
2. Stop and clean up application processes.
@@ -91,9 +107,17 @@ pip3 install -r requirements.txt
91107
dapr run --app-id conversation --resources-path ../../../components -- python3 app.py
92108
```
93109

94-
You should see the output:
110+
The terminal console output should look similar to this, where:
111+
112+
- The app first sends an input `What is dapr?` to the `echo` Component mock LLM.
113+
- The mock LLM echoes `What is dapr?`.
114+
- The app then sends an input `What is the weather like in San Francisco in celsius?` to the `echo` Component mock LLM.
115+
- The mock LLM echoes `What is the weather like in San Francisco in celsius?`
95116

96-
```bash
97-
== APP == INFO:root:Input sent: What is dapr?
98-
== APP == INFO:root:Output response: What is dapr?
99-
```
117+
```text
118+
== APP - conversation == Conversation input sent: What is dapr?
119+
== APP - conversation == Output response: What is dapr?
120+
== APP - conversation == Tool calling input sent: What is the weather like in San Francisco in celsius?
121+
== APP - conversation == Output message: What is the weather like in San Francisco in celsius?
122+
== APP - conversation == No tool calls in response
123+
```

conversation/python/http/conversation/app.py

Lines changed: 125 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,29 +14,144 @@
1414
import requests
1515
import os
1616

17-
logging.basicConfig(level=logging.INFO)
17+
# Configure logging to only show the message without level/logger prefix
18+
logging.basicConfig(
19+
level=logging.INFO,
20+
format='%(message)s'
21+
)
1822

1923
base_url = os.getenv('BASE_URL', 'http://localhost') + ':' + os.getenv(
2024
'DAPR_HTTP_PORT', '3500')
2125

2226
CONVERSATION_COMPONENT_NAME = 'echo'
2327

2428
input = {
25-
'inputs': [{'content':'What is dapr?'}],
26-
'parameters': {},
27-
'metadata': {}
28-
}
29+
'name': 'anthropic',
30+
'inputs': [{
31+
'messages': [{
32+
'of_user': {
33+
'content': [{
34+
'text': 'What is dapr?'
35+
}]
36+
}
37+
}]
38+
}],
39+
'parameters': {},
40+
'metadata': {}
41+
}
2942

3043
# Send input to conversation endpoint
3144
result = requests.post(
32-
url='%s/v1.0-alpha1/conversation/%s/converse' % (base_url, CONVERSATION_COMPONENT_NAME),
33-
json=input
45+
url='%s/v1.0-alpha2/conversation/%s/converse' % (base_url, CONVERSATION_COMPONENT_NAME),
46+
json=input
3447
)
3548

36-
logging.info('Input sent: What is dapr?')
49+
logging.info('Conversation input sent: What is dapr?')
3750

3851
# Parse conversation output
3952
data = result.json()
40-
output = data["outputs"][0]["result"]
53+
try:
54+
if 'outputs' in data and len(data['outputs']) > 0:
55+
output = data["outputs"][0]["choices"][0]["message"]["content"]
56+
logging.info('Output response: ' + output)
57+
else:
58+
logging.error('No outputs found in response')
59+
logging.error('Response data: ' + str(data))
60+
61+
except (KeyError, IndexError) as e:
62+
logging.error(f'Error parsing response: {e}')
63+
if 'outputs' in data:
64+
logging.info(f'Available outputs: {data["outputs"]}')
65+
else:
66+
logging.info(f'No outputs found in response')
67+
68+
tool_call_input = {
69+
'name': 'anthropic',
70+
'inputs': [{
71+
'messages': [{
72+
'of_user': {
73+
'content': [{
74+
'text': 'What is the weather like in San Francisco in celsius?'
75+
}]
76+
}
77+
}],
78+
'scrubPII': False
79+
}],
80+
'parameters': {
81+
'max_tokens': {
82+
'@type': 'type.googleapis.com/google.protobuf.Int64Value',
83+
'value': '100'
84+
},
85+
'model': {
86+
'@type': 'type.googleapis.com/google.protobuf.StringValue',
87+
'value': 'claude-3-5-sonnet-20240620'
88+
}
89+
},
90+
'metadata': {
91+
'api_key': 'test-key',
92+
'version': '1.0'
93+
},
94+
'scrubPii': False,
95+
'temperature': 0.7,
96+
'tools': [{
97+
'function': {
98+
'name': 'get_weather',
99+
'description': 'Get the current weather for a location',
100+
'parameters': {
101+
'type': 'object',
102+
'properties': {
103+
'location': {
104+
'type': 'string',
105+
'description': 'The city and state, e.g. San Francisco, CA'
106+
},
107+
'unit': {
108+
'type': 'string',
109+
'enum': ['celsius', 'fahrenheit'],
110+
'description': 'The temperature unit to use'
111+
}
112+
},
113+
'required': ['location']
114+
}
115+
}
116+
}],
117+
'toolChoice': 'auto'
118+
}
41119

42-
logging.info('Output response: ' + output)
120+
# Send input to conversation endpoint
121+
tool_call_result = requests.post(
122+
url='%s/v1.0-alpha2/conversation/%s/converse' % (base_url, CONVERSATION_COMPONENT_NAME),
123+
json=tool_call_input
124+
)
125+
126+
logging.info('Tool calling input sent: What is the weather like in San Francisco in celsius?')
127+
128+
# Parse conversation output
129+
data = tool_call_result.json()
130+
if 'outputs' in data and len(data['outputs']) > 0:
131+
output = data['outputs'][0]
132+
if 'choices' in output and len(output['choices']) > 0:
133+
choice = output['choices'][0]
134+
if 'message' in choice:
135+
message = choice['message']
136+
137+
if 'content' in message and message['content']:
138+
logging.info('Output message: ' + message['content'])
139+
140+
if 'toolCalls' in message and message['toolCalls']:
141+
logging.info('Tool calls detected:')
142+
for tool_call in message['toolCalls']:
143+
logging.info('Tool call: ' + str(tool_call))
144+
145+
if 'function' in tool_call:
146+
func_call = tool_call['function']
147+
logging.info(f"Function name: {func_call.get('name', 'unknown')}")
148+
logging.info(f"Function arguments: {func_call.get('arguments', 'none')}")
149+
else:
150+
logging.info('No tool calls in response')
151+
else:
152+
logging.error('No message in choice')
153+
else:
154+
logging.error('No choices in output')
155+
else:
156+
logging.error('No outputs in response')
157+
logging.error('Response data: ' + str(data))
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
requests
1+
requests>=2.25.0

0 commit comments

Comments
 (0)