Skip to content

Commit

Permalink
Add Content.functionResponses utility (#159)
Browse files Browse the repository at this point in the history
The alternative when replying to multiple functions in parallel is to
use the `Content` constructor and pass the string `'function'` to the
`role` parameter. Use a static method to obscure the role string for
consistency with the others.

Update sample with a demonstration of replying to all function calls.
  • Loading branch information
natebosch authored May 10, 2024
1 parent 49151a9 commit a9ebd46
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 27 deletions.
2 changes: 2 additions & 0 deletions pkgs/google_generative_ai/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
`'application/json'` to force the model to reply with JSON parseable output.
- Add `outputDimensionality` argument support for `embedContent` and
`batchEmbedContent`.
- Add `Content.functionResponses` utility to reply to multiple function calls in
parallel.
- **Breaking** The `Part` class is no longer `sealed`. Exhaustive switches over
a `Part` instance will need to add a wildcard case.

Expand Down
2 changes: 2 additions & 0 deletions pkgs/google_generative_ai/lib/src/content.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ final class Content {
static Content functionResponse(
String name, Map<String, Object?>? response) =>
Content('function', [FunctionResponse(name, response)]);
static Content functionResponses(Iterable<FunctionResponse> responses) =>
Content('function', responses.toList());
static Content system(String instructions) =>
Content('system', [TextPart(instructions)]);

Expand Down
60 changes: 33 additions & 27 deletions samples/dart/bin/function_calling.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,52 +23,53 @@ void main() async {
exit(1);
}
final model = GenerativeModel(
model: 'gemini-pro',
model: 'gemini-1.5-pro-latest',
apiKey: apiKey,
tools: [
Tool(functionDeclarations: [
FunctionDeclaration(
'fetchCurrentWeather',
'Returns the weather in a given location.',
Schema(SchemaType.object, properties: {
'location': Schema(SchemaType.string),
'unit': Schema(SchemaType.string,
enumValues: ['celcius', 'farenheit'])
'location':
Schema.string(description: 'A location name, like "London".'),
}, requiredProperties: [
'location'
]))
])
],
);

final prompt = 'What is the weather in Seattle?';
final prompt =
"I'm trying to decide whether to go to London or Zurich this weekend. "
'How hot are those cities? How about Singapore? Or maybe Tokyo. '
'I want to go somewhere not that cold but not too hot either. '
'Suggest a destination.';
final content = [Content.text(prompt)];
final response = await model.generateContent(content);
var response = await model.generateContent(content);

final functionCalls = response.functionCalls.toList();
if (functionCalls.isEmpty) {
print('No function calls.');
print(response.text);
} else if (functionCalls.length > 1) {
print('Too many function calls.');
print(response.text);
} else {
List<FunctionCall> functionCalls;
while ((functionCalls = response.functionCalls.toList()).isNotEmpty) {
var responses = <FunctionResponse>[
for (final functionCall in functionCalls)
_dispatchFunctionCall(functionCall)
];
content
..add(response.candidates.first.content)
..add(_dispatchFunctionCall(functionCalls.single));
final nextResponse = await model.generateContent(content);
print('Response: ${nextResponse.text}');
..add(Content.functionResponses(responses));
response = await model.generateContent(content);
}
print('Response: ${response.text}');
}

Content _dispatchFunctionCall(FunctionCall call) {
FunctionResponse _dispatchFunctionCall(FunctionCall call) {
final result = switch (call.name) {
'fetchCurrentWeather' => {
'weather': _fetchWeather(WeatherRequest._parse(call.args))
},
_ => throw UnimplementedError('Function not implemented: ${call.name}')
};
return Content.functionResponse(call.name, result);
return FunctionResponse(call.name, result);
}

class WeatherRequest {
Expand All @@ -80,14 +81,19 @@ class WeatherRequest {
};
final String location;
WeatherRequest(this.location);

@override
String toString() => {'location': location}.toString();
}

String _fetchWeather(WeatherRequest request) {
const weather = {
'Seattle': 'rainy',
'Chicago': 'windy',
'Sunnyvale': 'sunny'
};
final location = request.location;
return weather[location] ?? 'who knows?';
var _responseIndex = -1;
Map<String, Object?> _fetchWeather(WeatherRequest request) {
const responses = <Map<String, Object?>>[
{'condition': 'sunny', 'temp_c': -23.9},
{'condition': 'extreme rainstorm', 'temp_c': 13.9},
{'condition': 'cloudy', 'temp_c': 33.9},
{'condition': 'moderate', 'temp_c': 19.9},
];
_responseIndex = (_responseIndex + 1) % responses.length;
return responses[_responseIndex];
}

0 comments on commit a9ebd46

Please sign in to comment.