Skip to content

Commit

Permalink
add an experimental gemini call to the backend
Browse files Browse the repository at this point in the history
  • Loading branch information
devoncarew committed Jun 3, 2024
1 parent e93bae9 commit b5e0f5f
Show file tree
Hide file tree
Showing 10 changed files with 109 additions and 6 deletions.
2 changes: 2 additions & 0 deletions pkgs/dart_services/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
ARG PROJECT_ID
ARG FLUTTER_CHANNEL
ARG BUILD_SHA
ARG GOOGLE_API_KEY

FROM gcr.io/$PROJECT_ID/flutter:$FLUTTER_CHANNEL

Expand All @@ -16,6 +17,7 @@ RUN dart compile exe bin/server.dart -o bin/server
RUN dart run grinder build-project-templates

ENV BUILD_SHA=$BUILD_SHA
ENV GOOGLE_API_KEY=$GOOGLE_API_KEY

EXPOSE 8080
CMD ["/app/bin/server"]
6 changes: 0 additions & 6 deletions pkgs/dart_services/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,6 @@ To rebuild the shelf router, run:
dart run build_runner build --delete-conflicting-outputs
```

And to update the shared code from dartpad_shared, run:

```
dart tool/grind.dart copy-shared-source
```

### Modifying supported packages

Package dependencies are pinned using the `pub_dependencies_<CHANNEL>.yaml`
Expand Down
1 change: 1 addition & 0 deletions pkgs/dart_services/analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ linter:
rules:
- prefer_final_in_for_each
- prefer_final_locals
- sort_pub_dependencies
58 changes: 58 additions & 0 deletions pkgs/dart_services/lib/src/common_server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import 'package:dartpad_shared/model.dart' as api;
import 'package:logging/logging.dart';
import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart';
import 'package:google_generative_ai/google_generative_ai.dart' as google_ai;
import 'package:http/http.dart' as http;

import 'analysis.dart';
import 'caching.dart';
Expand Down Expand Up @@ -200,6 +202,54 @@ class CommonServerApi {
return ok(version().toJson());
}

static String? googleApiKey = Platform.environment['GOOGLE_API_KEY'];
http.Client? geminiHttpClient;

@Route.post('$apiPrefix/_gemini')
Future<Response> gemini(Request request, String apiVersion) async {
if (apiVersion != api3) return unhandledVersion(apiVersion);

// Read the api key from env variables (populated on the server).
final apiKey = googleApiKey;
if (apiKey == null) {
return Response.internalServerError(
body: 'gemini key not configured on server');
}

// Only allow the call from known clients / endpoints.
const firebaseHostAddress = '199.36.158.100';
const localHostAddress = '127.0.0.1';

final clientAddress = request.shelfClientAddress ?? '';
if (clientAddress != firebaseHostAddress &&
clientAddress != localHostAddress) {
return Response.badRequest(
body: 'gemini calls only allowed from the DartPad frontend');
}

final sourceRequest =
api.SourceRequest.fromJson(await request.readAsJson());

geminiHttpClient ??= http.Client();

final model = google_ai.GenerativeModel(
model: 'models/gemini-1.5-flash-latest',
apiKey: apiKey,
httpClient: geminiHttpClient,
);

final result = await serialize(() async {
// call gemini
final result = await model.generateContent([
google_ai.Content.text(sourceRequest.source),
]);

return api.GeminiResponse(response: result.text!);
});

return ok(result.toJson());
}

Response ok(Map<String, dynamic> json) {
return Response.ok(
_jsonEncoder.convert(json),
Expand Down Expand Up @@ -327,3 +377,11 @@ String _formatMessage(

return message;
}

extension on Request {
/// Return the IP address of the request client.
String? get shelfClientAddress =>
(context['shelf.io.connection_info'] as HttpConnectionInfo?)
?.remoteAddress
.address;
}
5 changes: 5 additions & 0 deletions pkgs/dart_services/lib/src/common_server.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pkgs/dart_services/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ dependencies:
bazel_worker: ^1.1.1
dartpad_shared: any
encrypt: ^5.0.3
google_generative_ai: ^0.4.0
http: ^1.2.1
json_annotation: any
logging: ^1.2.0
Expand Down
17 changes: 17 additions & 0 deletions pkgs/dartpad_shared/lib/model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,23 @@ class VersionResponse {
Map<String, dynamic> toJson() => _$VersionResponseToJson(this);
}

@JsonSerializable()
class GeminiResponse {
final String response;

GeminiResponse({
required this.response,
});

factory GeminiResponse.fromJson(Map<String, dynamic> json) =>
_$GeminiResponseFromJson(json);

Map<String, dynamic> toJson() => _$GeminiResponseToJson(this);

@override
String toString() => 'GeminiResponse[response=$response]';
}

@JsonSerializable()
class PackageInfo {
final String name;
Expand Down
10 changes: 10 additions & 0 deletions pkgs/dartpad_shared/lib/model.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions pkgs/dartpad_shared/lib/services.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ class ServicesClient {
Future<CompileDDCResponse> compileDDC(CompileRequest request) =>
_requestPost('compileDDC', request.toJson(), CompileDDCResponse.fromJson);

/// Note: this API is experimental and could change or be removed at any time.
@experimental
Future<GeminiResponse> gemini(SourceRequest request) =>
_requestPost('_gemini', request.toJson(), GeminiResponse.fromJson);

void dispose() => client.close();

Future<T> _requestGet<T>(
Expand Down Expand Up @@ -92,3 +97,9 @@ class ApiRequestError implements Exception {
@override
String toString() => '$message: $body';
}

class _Experimental {
const _Experimental();
}

const _Experimental experimental = _Experimental();
4 changes: 4 additions & 0 deletions pkgs/dartpad_ui/lib/model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,10 @@ class AppServices {
return await services.document(request);
}

Future<GeminiResponse> gemini(SourceRequest request) async {
return await services.gemini(request);
}

Future<CompileResponse> compile(CompileRequest request) async {
try {
appModel.compilingBusy.value = true;
Expand Down

0 comments on commit b5e0f5f

Please sign in to comment.