Skip to content

Commit d48de92

Browse files
authored
Add example how to use function calling with completions streaming (#80)
1 parent a90a10c commit d48de92

File tree

2 files changed

+238
-0
lines changed

2 files changed

+238
-0
lines changed
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
#!/usr/bin/env python3
2+
3+
from __future__ import annotations
4+
5+
import asyncio
6+
7+
from yandex_cloud_ml_sdk import AsyncYCloudML
8+
9+
10+
def calculator(expression: str) -> str:
11+
print(f'calculator got {expression=}')
12+
return "-5"
13+
14+
15+
def weather(location: str, date: str) -> str:
16+
print(f"weather func got {location=} and {date=}")
17+
return "-10 celsius"
18+
19+
20+
def process_tool_calls(tool_calls) -> dict[str, list[dict]]:
21+
"""
22+
This function is an example how you could organize
23+
dispatching of function calls in general case
24+
"""
25+
26+
function_map = {
27+
'calculator': calculator,
28+
'Weather': weather
29+
}
30+
31+
result = []
32+
for tool_call in tool_calls:
33+
# only functions are available at the moment
34+
assert tool_call.function
35+
36+
function = function_map[tool_call.function.name]
37+
38+
answer = function(**tool_call.function.arguments) # type: ignore[operator]
39+
40+
result.append({'name': tool_call.function.name, 'content': answer})
41+
42+
return {'tool_results': result}
43+
44+
45+
def create_tools(sdk: AsyncYCloudML):
46+
# it is imported inside only because yandex-cloud-ml-sdk does not require pydantic by default
47+
# pylint: disable=import-outside-toplevel
48+
from pydantic import BaseModel, Field
49+
50+
calculator_tool = sdk.tools.function(
51+
name="calculator",
52+
description="A simple calculator that performs basic arithmetic operations.",
53+
# parameters could contain valid jsonschema with function parameters types and description
54+
parameters={
55+
"type": "object",
56+
"properties": {
57+
"expression": {
58+
"type": "string",
59+
"description": "The mathematical expression to evaluate (e.g., '2 + 3 * 4').",
60+
}
61+
},
62+
"required": ["expression"],
63+
}
64+
)
65+
66+
class Weather(BaseModel):
67+
"""Getting the weather in specified location for specified date"""
68+
69+
# It is important to describe all the arguments with a natural language
70+
location: str = Field(description="Name of the place for fetching weatcher at")
71+
date: str = Field(description="Date which a user interested in")
72+
73+
weather_tool = sdk.tools.function(Weather)
74+
75+
return [calculator_tool, weather_tool]
76+
77+
78+
async def main() -> None:
79+
sdk = AsyncYCloudML(folder_id='b1ghsjum2v37c2un8h64')
80+
sdk.setup_default_logging()
81+
82+
model = sdk.models.completions('yandexgpt')
83+
84+
# tools must be bound to a model object via .configure method and would be used in all
85+
# model calls from this model object.
86+
model = model.configure(tools=create_tools(sdk), temperature=0)
87+
88+
for question in ["How much it would be 7@8?", "What is the weather like in Paris at 12 of March?"]:
89+
# it is required to carefully maintain context for passing tool_results back to the model after function call
90+
messages: list = [
91+
{"role": "system", "text": "Please use English language for answer"},
92+
question
93+
]
94+
95+
done = False
96+
result = None
97+
while not done:
98+
done = True
99+
async for event in model.run_stream(messages):
100+
print(
101+
'Stream event - '
102+
f'status={event.status.name!r}, text={event.text!r}, tool_calls={event.tool_calls}'
103+
)
104+
105+
if event.tool_calls:
106+
tool_results = process_tool_calls(event.tool_calls)
107+
108+
# We need to enrich message history with tool_call record, tool_results record
109+
messages.append(event)
110+
messages.append(tool_results)
111+
# and launch another run_stream with a new message history
112+
done = False
113+
114+
result = event
115+
116+
assert result
117+
print(f"Model answer for {question=}:", result.text)
118+
119+
if __name__ == '__main__':
120+
asyncio.run(main())
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
#!/usr/bin/env python3
2+
3+
from __future__ import annotations
4+
5+
from yandex_cloud_ml_sdk import YCloudML
6+
7+
8+
def calculator(expression: str) -> str:
9+
print(f'calculator got {expression=}')
10+
return "-5"
11+
12+
13+
def weather(location: str, date: str) -> str:
14+
print(f"weather func got {location=} and {date=}")
15+
return "-10 celsius"
16+
17+
18+
def process_tool_calls(tool_calls) -> dict[str, list[dict]]:
19+
"""
20+
This function is an example how you could organize
21+
dispatching of function calls in general case
22+
"""
23+
24+
function_map = {
25+
'calculator': calculator,
26+
'Weather': weather
27+
}
28+
29+
result = []
30+
for tool_call in tool_calls:
31+
# only functions are available at the moment
32+
assert tool_call.function
33+
34+
function = function_map[tool_call.function.name]
35+
36+
answer = function(**tool_call.function.arguments) # type: ignore[operator]
37+
38+
result.append({'name': tool_call.function.name, 'content': answer})
39+
40+
return {'tool_results': result}
41+
42+
43+
def create_tools(sdk: YCloudML):
44+
# it is imported inside only because yandex-cloud-ml-sdk does not require pydantic by default
45+
# pylint: disable=import-outside-toplevel
46+
from pydantic import BaseModel, Field
47+
48+
calculator_tool = sdk.tools.function(
49+
name="calculator",
50+
description="A simple calculator that performs basic arithmetic operations.",
51+
# parameters could contain valid jsonschema with function parameters types and description
52+
parameters={
53+
"type": "object",
54+
"properties": {
55+
"expression": {
56+
"type": "string",
57+
"description": "The mathematical expression to evaluate (e.g., '2 + 3 * 4').",
58+
}
59+
},
60+
"required": ["expression"],
61+
}
62+
)
63+
64+
class Weather(BaseModel):
65+
"""Getting the weather in specified location for specified date"""
66+
67+
# It is important to describe all the arguments with a natural language
68+
location: str = Field(description="Name of the place for fetching weatcher at")
69+
date: str = Field(description="Date which a user interested in")
70+
71+
weather_tool = sdk.tools.function(Weather)
72+
73+
return [calculator_tool, weather_tool]
74+
75+
76+
def main() -> None:
77+
sdk = YCloudML(folder_id='b1ghsjum2v37c2un8h64')
78+
sdk.setup_default_logging()
79+
80+
model = sdk.models.completions('yandexgpt')
81+
82+
# tools must be bound to a model object via .configure method and would be used in all
83+
# model calls from this model object.
84+
model = model.configure(tools=create_tools(sdk), temperature=0)
85+
86+
for question in ["How much it would be 7@8?", "What is the weather like in Paris at 12 of March?"]:
87+
# it is required to carefully maintain context for passing tool_results back to the model after function call
88+
messages: list = [
89+
{"role": "system", "text": "Please use English language for answer"},
90+
question
91+
]
92+
93+
done = False
94+
result = None
95+
while not done:
96+
done = True
97+
for event in model.run_stream(messages):
98+
print(
99+
'Stream event - '
100+
f'status={event.status.name!r}, text={event.text!r}, tool_calls={event.tool_calls}'
101+
)
102+
103+
if event.tool_calls:
104+
tool_results = process_tool_calls(event.tool_calls)
105+
106+
# We need to enrich message history with tool_call record, tool_results record
107+
messages.append(event)
108+
messages.append(tool_results)
109+
# and launch another run_stream with a new message history
110+
done = False
111+
112+
result = event
113+
114+
assert result
115+
print(f"Model answer for {question=}:", result.text)
116+
117+
if __name__ == '__main__':
118+
main()

0 commit comments

Comments
 (0)