From 9b31c107fd824ecdd369273df1bd3e73a234b92d Mon Sep 17 00:00:00 2001 From: Pavel Jbanov Date: Fri, 31 May 2024 14:12:58 -0400 Subject: [PATCH 1/6] Scaffolding a json streaming sample --- js/testapps/flow-simple-ai/src/index.ts | 41 +++- samples/js-angular/.gitignore | 1 + samples/js-angular/README.md | 17 ++ samples/js-angular/genkit-app/.editorconfig | 16 ++ samples/js-angular/genkit-app/.gitignore | 42 ++++ samples/js-angular/genkit-app/README.md | 27 +++ samples/js-angular/genkit-app/angular.json | 104 ++++++++++ samples/js-angular/genkit-app/package.json | 40 ++++ .../js-angular/genkit-app/public/favicon.ico | Bin 0 -> 15086 bytes .../genkit-app/src/app/app.component.html | 20 ++ .../genkit-app/src/app/app.component.scss | 51 +++++ .../genkit-app/src/app/app.component.spec.ts | 29 +++ .../genkit-app/src/app/app.component.ts | 45 +++++ .../genkit-app/src/app/app.config.ts | 9 + .../genkit-app/src/app/app.routes.ts | 15 ++ .../src/app/home/home.component.html | 11 ++ .../src/app/home/home.component.scss | 3 + .../src/app/home/home.component.spec.ts | 23 +++ .../genkit-app/src/app/home/home.component.ts | 11 ++ .../streaming-json.component.html | 20 ++ .../streaming-json.component.scss | 7 + .../streaming-json.component.spec.ts | 23 +++ .../streaming-json.component.ts | 44 +++++ samples/js-angular/genkit-app/src/index.html | 15 ++ samples/js-angular/genkit-app/src/main.ts | 6 + samples/js-angular/genkit-app/src/styles.scss | 40 ++++ .../js-angular/genkit-app/src/utils/flow.ts | 127 ++++++++++++ .../js-angular/genkit-app/tsconfig.app.json | 14 ++ samples/js-angular/genkit-app/tsconfig.json | 32 +++ .../js-angular/genkit-app/tsconfig.spec.json | 14 ++ samples/js-angular/package.json | 22 +++ samples/js-angular/server/package.json | 28 +++ samples/js-angular/server/src/index.ts | 185 ++++++++++++++++++ samples/js-angular/server/tsconfig.json | 16 ++ 34 files changed, 1097 insertions(+), 1 deletion(-) create mode 100644 samples/js-angular/.gitignore create mode 100644 samples/js-angular/README.md create mode 100644 samples/js-angular/genkit-app/.editorconfig create mode 100644 samples/js-angular/genkit-app/.gitignore create mode 100644 samples/js-angular/genkit-app/README.md create mode 100644 samples/js-angular/genkit-app/angular.json create mode 100644 samples/js-angular/genkit-app/package.json create mode 100644 samples/js-angular/genkit-app/public/favicon.ico create mode 100644 samples/js-angular/genkit-app/src/app/app.component.html create mode 100644 samples/js-angular/genkit-app/src/app/app.component.scss create mode 100644 samples/js-angular/genkit-app/src/app/app.component.spec.ts create mode 100644 samples/js-angular/genkit-app/src/app/app.component.ts create mode 100644 samples/js-angular/genkit-app/src/app/app.config.ts create mode 100644 samples/js-angular/genkit-app/src/app/app.routes.ts create mode 100644 samples/js-angular/genkit-app/src/app/home/home.component.html create mode 100644 samples/js-angular/genkit-app/src/app/home/home.component.scss create mode 100644 samples/js-angular/genkit-app/src/app/home/home.component.spec.ts create mode 100644 samples/js-angular/genkit-app/src/app/home/home.component.ts create mode 100644 samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.html create mode 100644 samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.scss create mode 100644 samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.spec.ts create mode 100644 samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.ts create mode 100644 samples/js-angular/genkit-app/src/index.html create mode 100644 samples/js-angular/genkit-app/src/main.ts create mode 100644 samples/js-angular/genkit-app/src/styles.scss create mode 100644 samples/js-angular/genkit-app/src/utils/flow.ts create mode 100644 samples/js-angular/genkit-app/tsconfig.app.json create mode 100644 samples/js-angular/genkit-app/tsconfig.json create mode 100644 samples/js-angular/genkit-app/tsconfig.spec.json create mode 100644 samples/js-angular/package.json create mode 100644 samples/js-angular/server/package.json create mode 100644 samples/js-angular/server/src/index.ts create mode 100644 samples/js-angular/server/tsconfig.json diff --git a/js/testapps/flow-simple-ai/src/index.ts b/js/testapps/flow-simple-ai/src/index.ts index 71f0bcb2f8..7b3f709cd8 100644 --- a/js/testapps/flow-simple-ai/src/index.ts +++ b/js/testapps/flow-simple-ai/src/index.ts @@ -22,7 +22,7 @@ import { defineFirestoreRetriever, firebase } from '@genkit-ai/firebase'; import { defineFlow, run } from '@genkit-ai/flow'; import { googleCloud } from '@genkit-ai/google-cloud'; import { googleAI, geminiPro as googleGeminiPro } from '@genkit-ai/googleai'; -import { geminiPro, textEmbeddingGecko, vertexAI } from '@genkit-ai/vertexai'; +import { gemini15ProPreview, geminiPro, textEmbeddingGecko, vertexAI } from '@genkit-ai/vertexai'; import { AlwaysOnSampler } from '@opentelemetry/sdk-trace-base'; import { initializeApp } from 'firebase-admin/app'; import { getFirestore } from 'firebase-admin/firestore'; @@ -331,3 +331,42 @@ export const dotpromptContext = defineFlow( return result.output() as any; } ); + + + +const jokeSubjectGenerator = defineTool( + { + name: 'jokeSubjectGenerator', + description: 'can be called to generate a subject for a joke', + }, + async () => { + return 'banana'; + } +); + +export const toolCaller = defineFlow( + { + name: 'toolCaller', + outputSchema: z.string(), + }, + async (_, streamingCallback) => { + if (!streamingCallback) { + throw new Error('this flow only works in streaming mode'); + } + + const { response, stream } = await generateStream({ + model: gemini15ProPreview, + config: { + temperature: 1, + }, + tools: [jokeSubjectGenerator], + prompt: `tell me a joke`, + }); + + for await (const chunk of stream()) { + streamingCallback(chunk); + } + + return (await response()).text(); + } +); \ No newline at end of file diff --git a/samples/js-angular/.gitignore b/samples/js-angular/.gitignore new file mode 100644 index 0000000000..7951405f85 --- /dev/null +++ b/samples/js-angular/.gitignore @@ -0,0 +1 @@ +lib \ No newline at end of file diff --git a/samples/js-angular/README.md b/samples/js-angular/README.md new file mode 100644 index 0000000000..1231ce451c --- /dev/null +++ b/samples/js-angular/README.md @@ -0,0 +1,17 @@ +# Angular and Genkit streaming sample + +This is a simple UI for streaming RPG character generator. + +To build: + +```bash +npm run build +``` + +Run: + +```bash +npm run start -- --project YOUR_PROJECT_ID +``` + +Point your browser to http://127.0.0.1:5000 diff --git a/samples/js-angular/genkit-app/.editorconfig b/samples/js-angular/genkit-app/.editorconfig new file mode 100644 index 0000000000..59d9a3a3e7 --- /dev/null +++ b/samples/js-angular/genkit-app/.editorconfig @@ -0,0 +1,16 @@ +# Editor configuration, see https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.ts] +quote_type = single + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/samples/js-angular/genkit-app/.gitignore b/samples/js-angular/genkit-app/.gitignore new file mode 100644 index 0000000000..cc7b141350 --- /dev/null +++ b/samples/js-angular/genkit-app/.gitignore @@ -0,0 +1,42 @@ +# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. + +# Compiled output +/dist +/tmp +/out-tsc +/bazel-out + +# Node +/node_modules +npm-debug.log +yarn-error.log + +# IDEs and editors +.idea/ +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# Visual Studio Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history/* + +# Miscellaneous +/.angular/cache +.sass-cache/ +/connect.lock +/coverage +/libpeerconnection.log +testem.log +/typings + +# System files +.DS_Store +Thumbs.db diff --git a/samples/js-angular/genkit-app/README.md b/samples/js-angular/genkit-app/README.md new file mode 100644 index 0000000000..0aeb2095bb --- /dev/null +++ b/samples/js-angular/genkit-app/README.md @@ -0,0 +1,27 @@ +# GenkitApp + +This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 18.0.2. + +## Development server + +Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. + +## Code scaffolding + +Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. + +## Build + +Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. + +## Running unit tests + +Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). + +## Running end-to-end tests + +Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. + +## Further help + +To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page. diff --git a/samples/js-angular/genkit-app/angular.json b/samples/js-angular/genkit-app/angular.json new file mode 100644 index 0000000000..0c5548c247 --- /dev/null +++ b/samples/js-angular/genkit-app/angular.json @@ -0,0 +1,104 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "genkit-app": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" + } + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:application", + "options": { + "outputPath": "dist/genkit-app", + "index": "src/index.html", + "browser": "src/main.ts", + "polyfills": [ + "zone.js" + ], + "tsConfig": "tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + { + "glob": "**/*", + "input": "public" + } + ], + "styles": [ + "@angular/material/prebuilt-themes/azure-blue.css", + "src/styles.scss" + ], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kB", + "maximumError": "1MB" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kB", + "maximumError": "4kB" + } + ], + "outputHashing": "all" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "buildTarget": "genkit-app:build:production" + }, + "development": { + "buildTarget": "genkit-app:build:development" + } + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n" + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "polyfills": [ + "zone.js", + "zone.js/testing" + ], + "tsConfig": "tsconfig.spec.json", + "inlineStyleLanguage": "scss", + "assets": [ + { + "glob": "**/*", + "input": "public" + } + ], + "styles": [ + "@angular/material/prebuilt-themes/azure-blue.css", + "src/styles.scss" + ], + "scripts": [] + } + } + } + } + } +} diff --git a/samples/js-angular/genkit-app/package.json b/samples/js-angular/genkit-app/package.json new file mode 100644 index 0000000000..c2882f7ae1 --- /dev/null +++ b/samples/js-angular/genkit-app/package.json @@ -0,0 +1,40 @@ +{ + "name": "genkit-app", + "version": "0.0.0", + "scripts": { + "ng": "ng", + "start": "ng serve", + "build": "ng build", + "watch": "ng build --watch --configuration development", + "test": "ng test" + }, + "private": true, + "dependencies": { + "@angular/animations": "^18.0.0", + "@angular/cdk": "^18.0.1", + "@angular/common": "^18.0.0", + "@angular/compiler": "^18.0.0", + "@angular/core": "^18.0.0", + "@angular/forms": "^18.0.0", + "@angular/material": "^18.0.1", + "@angular/platform-browser": "^18.0.0", + "@angular/platform-browser-dynamic": "^18.0.0", + "@angular/router": "^18.0.0", + "rxjs": "~7.8.0", + "tslib": "^2.3.0", + "zone.js": "~0.14.3" + }, + "devDependencies": { + "@angular-devkit/build-angular": "^18.0.2", + "@angular/cli": "^18.0.2", + "@angular/compiler-cli": "^18.0.0", + "@types/jasmine": "~5.1.0", + "jasmine-core": "~5.1.0", + "karma": "~6.4.0", + "karma-chrome-launcher": "~3.2.0", + "karma-coverage": "~2.2.0", + "karma-jasmine": "~5.1.0", + "karma-jasmine-html-reporter": "~2.1.0", + "typescript": "~5.4.2" + } +} \ No newline at end of file diff --git a/samples/js-angular/genkit-app/public/favicon.ico b/samples/js-angular/genkit-app/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..57614f9c967596fad0a3989bec2b1deff33034f6 GIT binary patch literal 15086 zcmd^G33O9Omi+`8$@{|M-I6TH3wzF-p5CV8o}7f~KxR60LK+ApEFB<$bcciv%@SmA zV{n>g85YMFFeU*Uvl=i4v)C*qgnb;$GQ=3XTe9{Y%c`mO%su)noNCCQ*@t1WXn|B(hQ7i~ zrUK8|pUkD6#lNo!bt$6)jR!&C?`P5G(`e((P($RaLeq+o0Vd~f11;qB05kdbAOm?r zXv~GYr_sibQO9NGTCdT;+G(!{4Xs@4fPak8#L8PjgJwcs-Mm#nR_Z0s&u?nDX5^~@ z+A6?}g0|=4e_LoE69pPFO`yCD@BCjgKpzMH0O4Xs{Ahc?K3HC5;l=f zg>}alhBXX&);z$E-wai+9TTRtBX-bWYY@cl$@YN#gMd~tM_5lj6W%8ah4;uZ;jP@Q zVbuel1rPA?2@x9Y+u?e`l{Z4ngfG5q5BLH5QsEu4GVpt{KIp1?U)=3+KQ;%7ec8l* zdV=zZgN5>O3G(3L2fqj3;oBbZZw$Ij@`Juz@?+yy#OPw)>#wsTewVgTK9BGt5AbZ&?K&B3GVF&yu?@(Xj3fR3n+ZP0%+wo)D9_xp>Z$`A4 zfV>}NWjO#3lqumR0`gvnffd9Ka}JJMuHS&|55-*mCD#8e^anA<+sFZVaJe7{=p*oX zE_Uv?1>e~ga=seYzh{9P+n5<+7&9}&(kwqSaz;1aD|YM3HBiy<))4~QJSIryyqp| z8nGc(8>3(_nEI4n)n7j(&d4idW1tVLjZ7QbNLXg;LB ziHsS5pXHEjGJZb59KcvS~wv;uZR-+4qEqow`;JCfB*+b^UL^3!?;-^F%yt=VjU|v z39SSqKcRu_NVvz!zJzL0CceJaS6%!(eMshPv_0U5G`~!a#I$qI5Ic(>IONej@aH=f z)($TAT#1I{iCS4f{D2+ApS=$3E7}5=+y(rA9mM#;Cky%b*Gi0KfFA`ofKTzu`AV-9 znW|y@19rrZ*!N2AvDi<_ZeR3O2R{#dh1#3-d%$k${Rx42h+i&GZo5!C^dSL34*AKp z27mTd>k>?V&X;Nl%GZ(>0s`1UN~Hfyj>KPjtnc|)xM@{H_B9rNr~LuH`Gr5_am&Ep zTjZA8hljNj5H1Ipm-uD9rC}U{-vR!eay5&6x6FkfupdpT*84MVwGpdd(}ib)zZ3Ky z7C$pnjc82(W_y_F{PhYj?o!@3__UUvpX)v69aBSzYj3 zdi}YQkKs^SyXyFG2LTRz9{(w}y~!`{EuAaUr6G1M{*%c+kP1olW9z23dSH!G4_HSK zzae-DF$OGR{ofP*!$a(r^5Go>I3SObVI6FLY)N@o<*gl0&kLo-OT{Tl*7nCz>Iq=? zcigIDHtj|H;6sR?or8Wd_a4996GI*CXGU}o;D9`^FM!AT1pBY~?|4h^61BY#_yIfO zKO?E0 zJ{Pc`9rVEI&$xxXu`<5E)&+m(7zX^v0rqofLs&bnQT(1baQkAr^kEsk)15vlzAZ-l z@OO9RF<+IiJ*O@HE256gCt!bF=NM*vh|WVWmjVawcNoksRTMvR03H{p@cjwKh(CL4 z7_PB(dM=kO)!s4fW!1p0f93YN@?ZSG` z$B!JaAJCtW$B97}HNO9(x-t30&E}Mo1UPi@Av%uHj~?T|!4JLwV;KCx8xO#b9IlUW zI6+{a@Wj|<2Y=U;a@vXbxqZNngH8^}LleE_4*0&O7#3iGxfJ%Id>+sb;7{L=aIic8 z|EW|{{S)J-wr@;3PmlxRXU8!e2gm_%s|ReH!reFcY8%$Hl4M5>;6^UDUUae?kOy#h zk~6Ee_@ZAn48Bab__^bNmQ~+k=02jz)e0d9Z3>G?RGG!65?d1>9}7iG17?P*=GUV-#SbLRw)Hu{zx*azHxWkGNTWl@HeWjA?39Ia|sCi{e;!^`1Oec zb>Z|b65OM*;eC=ZLSy?_fg$&^2xI>qSLA2G*$nA3GEnp3$N-)46`|36m*sc#4%C|h zBN<2U;7k>&G_wL4=Ve5z`ubVD&*Hxi)r@{4RCDw7U_D`lbC(9&pG5C*z#W>8>HU)h z!h3g?2UL&sS!oY5$3?VlA0Me9W5e~V;2jds*fz^updz#AJ%G8w2V}AEE?E^=MK%Xt z__Bx1cr7+DQmuHmzn*|hh%~eEc9@m05@clWfpEFcr+06%0&dZJH&@8^&@*$qR@}o3 z@Tuuh2FsLz^zH+dN&T&?0G3I?MpmYJ;GP$J!EzjeM#YLJ!W$}MVNb0^HfOA>5Fe~UNn%Zk(PT@~9}1dt)1UQ zU*B5K?Dl#G74qmg|2>^>0WtLX#Jz{lO4NT`NYB*(L#D|5IpXr9v&7a@YsGp3vLR7L zHYGHZg7{ie6n~2p$6Yz>=^cEg7tEgk-1YRl%-s7^cbqFb(U7&Dp78+&ut5!Tn(hER z|Gp4Ed@CnOPeAe|N>U(dB;SZ?NU^AzoD^UAH_vamp6Ws}{|mSq`^+VP1g~2B{%N-!mWz<`)G)>V-<`9`L4?3dM%Qh6<@kba+m`JS{Ya@9Fq*m6$$ zA1%Ogc~VRH33|S9l%CNb4zM%k^EIpqY}@h{w(aBcJ9c05oiZx#SK9t->5lSI`=&l~ z+-Ic)a{FbBhXV$Xt!WRd`R#Jk-$+_Z52rS>?Vpt2IK<84|E-SBEoIw>cs=a{BlQ7O z-?{Fy_M&84&9|KM5wt~)*!~i~E=(6m8(uCO)I=)M?)&sRbzH$9Rovzd?ZEY}GqX+~ zFbEbLz`BZ49=2Yh-|<`waK-_4!7`ro@zlC|r&I4fc4oyb+m=|c8)8%tZ-z5FwhzDt zL5kB@u53`d@%nHl0Sp)Dw`(QU&>vujEn?GPEXUW!Wi<+4e%BORl&BIH+SwRcbS}X@ z01Pk|vA%OdJKAs17zSXtO55k!;%m9>1eW9LnyAX4uj7@${O6cfii`49qTNItzny5J zH&Gj`e}o}?xjQ}r?LrI%FjUd@xflT3|7LA|ka%Q3i}a8gVm<`HIWoJGH=$EGClX^C0lysQJ>UO(q&;`T#8txuoQ_{l^kEV9CAdXuU1Ghg8 zN_6hHFuy&1x24q5-(Z7;!poYdt*`UTdrQOIQ!2O7_+AHV2hgXaEz7)>$LEdG z<8vE^Tw$|YwZHZDPM!SNOAWG$?J)MdmEk{U!!$M#fp7*Wo}jJ$Q(=8>R`Ats?e|VU?Zt7Cdh%AdnfyN3MBWw{ z$OnREvPf7%z6`#2##_7id|H%Y{vV^vWXb?5d5?a_y&t3@p9t$ncHj-NBdo&X{wrfJ zamN)VMYROYh_SvjJ=Xd!Ga?PY_$;*L=SxFte!4O6%0HEh%iZ4=gvns7IWIyJHa|hT z2;1+e)`TvbNb3-0z&DD_)Jomsg-7p_Uh`wjGnU1urmv1_oVqRg#=C?e?!7DgtqojU zWoAB($&53;TsXu^@2;8M`#z{=rPy?JqgYM0CDf4v@z=ZD|ItJ&8%_7A#K?S{wjxgd z?xA6JdJojrWpB7fr2p_MSsU4(R7=XGS0+Eg#xR=j>`H@R9{XjwBmqAiOxOL` zt?XK-iTEOWV}f>Pz3H-s*>W z4~8C&Xq25UQ^xH6H9kY_RM1$ch+%YLF72AA7^b{~VNTG}Tj#qZltz5Q=qxR`&oIlW Nr__JTFzvMr^FKp4S3v*( literal 0 HcmV?d00001 diff --git a/samples/js-angular/genkit-app/src/app/app.component.html b/samples/js-angular/genkit-app/src/app/app.component.html new file mode 100644 index 0000000000..1c1efeedd4 --- /dev/null +++ b/samples/js-angular/genkit-app/src/app/app.component.html @@ -0,0 +1,20 @@ + diff --git a/samples/js-angular/genkit-app/src/app/app.component.scss b/samples/js-angular/genkit-app/src/app/app.component.scss new file mode 100644 index 0000000000..214e7d6146 --- /dev/null +++ b/samples/js-angular/genkit-app/src/app/app.component.scss @@ -0,0 +1,51 @@ +app-bar { + border-bottom: 1px solid var(--divider-color); + grid-area: header; +} + +article { + grid-area: content; +} + +.wrapper { + display: grid; + grid-template: + 'header' auto + 'content' 1fr + / 1fr; + height: 100vh; +} + +.home-link { + align-items: center; + color: var(--mat-app-color); + display: flex; + gap: 8px; + + img { + height: 22px; + padding-left: 4px; + } +} + +.mat-toolbar { + gap: 4px; + color: #005cbb; + background: #d7e3ff; +} + +nav { + --mdc-secondary-navigation-tab-container-height: 64px; + --mat-tab-header-divider-height: 0; + margin-left: 32px; +} + +.preview-badge { + margin-left: 8px; + + mat-icon { + font-size: 18px; + height: 18px; + width: 18px; + } +} \ No newline at end of file diff --git a/samples/js-angular/genkit-app/src/app/app.component.spec.ts b/samples/js-angular/genkit-app/src/app/app.component.spec.ts new file mode 100644 index 0000000000..796f77a2a1 --- /dev/null +++ b/samples/js-angular/genkit-app/src/app/app.component.spec.ts @@ -0,0 +1,29 @@ +import { TestBed } from '@angular/core/testing'; +import { AppComponent } from './app.component'; + +describe('AppComponent', () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [AppComponent], + }).compileComponents(); + }); + + it('should create the app', () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.componentInstance; + expect(app).toBeTruthy(); + }); + + it(`should have the 'genkit-app' title`, () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.componentInstance; + expect(app.title).toEqual('genkit-app'); + }); + + it('should render title', () => { + const fixture = TestBed.createComponent(AppComponent); + fixture.detectChanges(); + const compiled = fixture.nativeElement as HTMLElement; + expect(compiled.querySelector('h1')?.textContent).toContain('Hello, genkit-app'); + }); +}); diff --git a/samples/js-angular/genkit-app/src/app/app.component.ts b/samples/js-angular/genkit-app/src/app/app.component.ts new file mode 100644 index 0000000000..39d4f4ea0e --- /dev/null +++ b/samples/js-angular/genkit-app/src/app/app.component.ts @@ -0,0 +1,45 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { CommonModule } from '@angular/common'; +import { Component } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { MatTabNavPanel, MatTabsModule } from '@angular/material/tabs'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router'; + +@Component({ + selector: 'app-root', + standalone: true, + imports: [ + CommonModule, + MatToolbarModule, + RouterOutlet, + MatIconModule, + MatTabNavPanel, + MatButtonModule, + MatTabsModule, + MatToolbarModule, + MatTooltipModule, + RouterLink, + RouterLinkActive, + ], + templateUrl: './app.component.html', + styleUrl: './app.component.scss', +}) +export class AppComponent {} diff --git a/samples/js-angular/genkit-app/src/app/app.config.ts b/samples/js-angular/genkit-app/src/app/app.config.ts new file mode 100644 index 0000000000..96116fdc72 --- /dev/null +++ b/samples/js-angular/genkit-app/src/app/app.config.ts @@ -0,0 +1,9 @@ +import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; +import { provideRouter } from '@angular/router'; + +import { routes } from './app.routes'; +import { provideAnimationsAsync } from '@angular/platform-browser/animations/async'; + +export const appConfig: ApplicationConfig = { + providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideAnimationsAsync()] +}; diff --git a/samples/js-angular/genkit-app/src/app/app.routes.ts b/samples/js-angular/genkit-app/src/app/app.routes.ts new file mode 100644 index 0000000000..a134d8e446 --- /dev/null +++ b/samples/js-angular/genkit-app/src/app/app.routes.ts @@ -0,0 +1,15 @@ +import { Routes } from '@angular/router'; +import { StreamingJSONComponent } from './samples/streaming-json/streaming-json.component'; +import { HomeComponent } from './home/home.component'; + +export const routes: Routes = [ + { + path: 'home', + component: HomeComponent, + }, + { + path: 'samples/streaming-json', + component: StreamingJSONComponent, + }, + { path: '**', redirectTo: '/home' }, +]; diff --git a/samples/js-angular/genkit-app/src/app/home/home.component.html b/samples/js-angular/genkit-app/src/app/home/home.component.html new file mode 100644 index 0000000000..d018fabeca --- /dev/null +++ b/samples/js-angular/genkit-app/src/app/home/home.component.html @@ -0,0 +1,11 @@ +
+

Samples

+ + +
diff --git a/samples/js-angular/genkit-app/src/app/home/home.component.scss b/samples/js-angular/genkit-app/src/app/home/home.component.scss new file mode 100644 index 0000000000..9912614217 --- /dev/null +++ b/samples/js-angular/genkit-app/src/app/home/home.component.scss @@ -0,0 +1,3 @@ +.wrapper { + padding: 20px; +} \ No newline at end of file diff --git a/samples/js-angular/genkit-app/src/app/home/home.component.spec.ts b/samples/js-angular/genkit-app/src/app/home/home.component.spec.ts new file mode 100644 index 0000000000..60c47c4115 --- /dev/null +++ b/samples/js-angular/genkit-app/src/app/home/home.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { HomeComponent } from './home.component'; + +describe('HomeComponent', () => { + let component: HomeComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [HomeComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(HomeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/samples/js-angular/genkit-app/src/app/home/home.component.ts b/samples/js-angular/genkit-app/src/app/home/home.component.ts new file mode 100644 index 0000000000..6c1570665c --- /dev/null +++ b/samples/js-angular/genkit-app/src/app/home/home.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; +import { RouterLink, RouterLinkActive } from '@angular/router'; + +@Component({ + selector: 'app-home', + standalone: true, + imports: [RouterLink, RouterLinkActive], + templateUrl: './home.component.html', + styleUrl: './home.component.scss', +}) +export class HomeComponent {} diff --git a/samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.html b/samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.html new file mode 100644 index 0000000000..ac3a01af40 --- /dev/null +++ b/samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.html @@ -0,0 +1,20 @@ +
+

Stream JSON from LLM

+ This is a Game Character Generator.
+ How many game chatacters do you need? + + + +
Loading...
+
+ {{ error }} +
+
+
+ {{ character.name }} +
    +
  • {{ ability }}
  • +
+
+
+
diff --git a/samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.scss b/samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.scss new file mode 100644 index 0000000000..978f5970f2 --- /dev/null +++ b/samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.scss @@ -0,0 +1,7 @@ +.wrapper { + padding: 20px; +} + +.characters { + margin-top: 20px; +} \ No newline at end of file diff --git a/samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.spec.ts b/samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.spec.ts new file mode 100644 index 0000000000..dde9d94128 --- /dev/null +++ b/samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { StreamingJSONComponent } from './streaming-json.component'; + +describe('StreamingJSONComponent', () => { + let component: StreamingJSONComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [StreamingJSONComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(StreamingJSONComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.ts b/samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.ts new file mode 100644 index 0000000000..0ab46c53e9 --- /dev/null +++ b/samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.ts @@ -0,0 +1,44 @@ +import { Component } from '@angular/core'; +import { streamFlow } from '../../../utils/flow'; +import { FormsModule } from '@angular/forms'; +import { CommonModule } from '@angular/common'; + +const url = 'http://127.0.0.1:3400/streamCharacters'; + +@Component({ + selector: 'app-streaming-json', + standalone: true, + imports: [FormsModule, CommonModule], + templateUrl: './streaming-json.component.html', + styleUrl: './streaming-json.component.scss' +}) +export class StreamingJSONComponent { + count: string = '3'; + characters: any = undefined; + error?: string = undefined; + loading: boolean = false; + + async callFlow() { + this.characters = undefined; + this.error = undefined; + this.loading = true; + try { + const response = streamFlow({ + url, + payload: parseInt(this.count), + }); + for await (const chunk of response.stream()) { + this.characters = chunk; + } + console.log('streamConsumer done', await response.output()); + this.loading = false; + } catch (e) { + this.loading = false; + if ((e as any).cause) { + this.error = `${(e as any).cause}`; + } else { + this.error = `${e}`; + } + } + } +} diff --git a/samples/js-angular/genkit-app/src/index.html b/samples/js-angular/genkit-app/src/index.html new file mode 100644 index 0000000000..f6b16719a7 --- /dev/null +++ b/samples/js-angular/genkit-app/src/index.html @@ -0,0 +1,15 @@ + + + + + GenkitApp + + + + + + + + + + diff --git a/samples/js-angular/genkit-app/src/main.ts b/samples/js-angular/genkit-app/src/main.ts new file mode 100644 index 0000000000..35b00f3463 --- /dev/null +++ b/samples/js-angular/genkit-app/src/main.ts @@ -0,0 +1,6 @@ +import { bootstrapApplication } from '@angular/platform-browser'; +import { appConfig } from './app/app.config'; +import { AppComponent } from './app/app.component'; + +bootstrapApplication(AppComponent, appConfig) + .catch((err) => console.error(err)); diff --git a/samples/js-angular/genkit-app/src/styles.scss b/samples/js-angular/genkit-app/src/styles.scss new file mode 100644 index 0000000000..c5e09da0d7 --- /dev/null +++ b/samples/js-angular/genkit-app/src/styles.scss @@ -0,0 +1,40 @@ +/* You can add global styles to this file, and also import other style files */ + +:root { + --header-height: 65px; + --container-border-radius: 20px; + --input-border-radius: 8px; +} + +html, +body { + height: 100%; +} + +body { + background-color: var(--app-background); + color: var(--mat-app-text-color); + margin: 0; +} + +hr { + border-bottom: 1px solid var(--divider-color); + border-width: 0 0 1px; + margin: 12px 0; +} + +a { + color: var(--link-color); + text-decoration: none; +} + +pre { + margin: 0; + white-space: pre-wrap; +} + + +// Helper for filling available space in flex layouts +.flex-spacer { + flex: 1; +} \ No newline at end of file diff --git a/samples/js-angular/genkit-app/src/utils/flow.ts b/samples/js-angular/genkit-app/src/utils/flow.ts new file mode 100644 index 0000000000..0e2ca92eac --- /dev/null +++ b/samples/js-angular/genkit-app/src/utils/flow.ts @@ -0,0 +1,127 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const __flowStreamDelimiter = '\n'; + +export function streamFlow({ + url, + payload, + headers, +}: { + url: string; + payload: any; + headers?: Record; +}) { + let chunkStreamController: ReadableStreamDefaultController | undefined = + undefined; + const chunkStream = new ReadableStream({ + start(controller) { + chunkStreamController = controller; + }, + pull() {}, + cancel() {}, + }); + + const operationPromise = __flowRunEnvelope({ + url, + payload, + streamingCallback: (c) => { + chunkStreamController?.enqueue(c); + }, + headers, + }); + operationPromise.then((o) => { + chunkStreamController?.close(); + return o; + }); + + return { + output() { + return operationPromise.then((op) => { + if (!op.done) { + throw new Error(`flow ${op.name} did not finish execution`); + } + if (op.result?.error) { + throw new Error(op.name, op.result?.error + op.result?.stacktrace); + } + return op.result?.response; + }); + }, + async *stream() { + const reader = chunkStream.getReader(); + while (true) { + const chunk = await reader.read(); + if (chunk.value) { + yield chunk.value; + } + if (chunk.done) { + break; + } + } + return await operationPromise; + }, + }; +} + +async function __flowRunEnvelope({ + url, + payload, + streamingCallback, + headers, +}: { + url: string; + payload: any; + streamingCallback: (chunk: any) => void; + headers?: Record; +}) { + let response; + response = await fetch(url + '?stream=true', { + method: 'POST', + body: JSON.stringify({ + data: payload, + }), + headers: { + 'Content-Type': 'application/json', + ...headers, + }, + }); + if (!response.body) { + throw new Error('Response body is empty'); + } + var reader = response.body.getReader(); + var decoder = new TextDecoder(); + + let buffer = ''; + while (true) { + const result = await reader.read(); + const decodedValue = decoder.decode(result.value); + if (decodedValue) { + buffer += decodedValue; + } + // If buffer includes the delimiter that means we are still recieving chunks. + while (buffer.includes(__flowStreamDelimiter)) { + streamingCallback( + JSON.parse(buffer.substring(0, buffer.indexOf(__flowStreamDelimiter))) + ); + buffer = buffer.substring( + buffer.indexOf(__flowStreamDelimiter) + __flowStreamDelimiter.length + ); + } + if (result.done) { + return JSON.parse(buffer); + } + } +} diff --git a/samples/js-angular/genkit-app/tsconfig.app.json b/samples/js-angular/genkit-app/tsconfig.app.json new file mode 100644 index 0000000000..374cc9d294 --- /dev/null +++ b/samples/js-angular/genkit-app/tsconfig.app.json @@ -0,0 +1,14 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [] + }, + "files": [ + "src/main.ts" + ], + "include": [ + "src/**/*.d.ts" + ] +} diff --git a/samples/js-angular/genkit-app/tsconfig.json b/samples/js-angular/genkit-app/tsconfig.json new file mode 100644 index 0000000000..56b644d6a6 --- /dev/null +++ b/samples/js-angular/genkit-app/tsconfig.json @@ -0,0 +1,32 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "compileOnSave": false, + "compilerOptions": { + "outDir": "./dist/out-tsc", + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "esModuleInterop": true, + "sourceMap": true, + "declaration": false, + "experimentalDecorators": true, + "moduleResolution": "bundler", + "importHelpers": true, + "target": "ES2022", + "module": "ES2022", + "useDefineForClassFields": false, + "lib": [ + "ES2022", + "dom" + ] + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + } +} diff --git a/samples/js-angular/genkit-app/tsconfig.spec.json b/samples/js-angular/genkit-app/tsconfig.spec.json new file mode 100644 index 0000000000..be7e9da76f --- /dev/null +++ b/samples/js-angular/genkit-app/tsconfig.spec.json @@ -0,0 +1,14 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/spec", + "types": [ + "jasmine" + ] + }, + "include": [ + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} diff --git a/samples/js-angular/package.json b/samples/js-angular/package.json new file mode 100644 index 0000000000..3a6fc3a807 --- /dev/null +++ b/samples/js-angular/package.json @@ -0,0 +1,22 @@ +{ + "scripts": { + "start": "concurrently npm:start:server npm:start:ng", + "start:server": "cd server && genkit start", + "start:ng": "cd genkit-app && npm run start", + "build": "tsc", + "build:watch": "tsc --watch", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "name": "js-angular", + "version": "1.0.0", + "description": "This is a simple UI for streaming RPG character generator.", + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "genkit": "^0.5.2", + "@angular/cli": "^18.0.2", + "concurrently": "^8.2.2", + "typescript": "^5.4.5" + } +} diff --git a/samples/js-angular/server/package.json b/samples/js-angular/server/package.json new file mode 100644 index 0000000000..855136c3d9 --- /dev/null +++ b/samples/js-angular/server/package.json @@ -0,0 +1,28 @@ +{ + "main": "lib/index.js", + "scripts": { + "start": "genkit start", + "build": "tsc", + "build:watch": "tsc --watch", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "name": "js-angular", + "version": "1.0.0", + "description": "This is a simple UI for streaming RPG character generator.", + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@genkit-ai/ai": "^0.5.2", + "@genkit-ai/core": "^0.5.2", + "@genkit-ai/dotprompt": "^0.5.2", + "@genkit-ai/flow": "^0.5.2", + "@genkit-ai/vertexai": "^0.5.2", + "express": "^4.19.2", + "partial-json": "^0.1.7", + "zod": "^3.23.8" + }, + "devDependencies": { + "typescript": "^5.4.5" + } +} diff --git a/samples/js-angular/server/src/index.ts b/samples/js-angular/server/src/index.ts new file mode 100644 index 0000000000..a3404d1fa1 --- /dev/null +++ b/samples/js-angular/server/src/index.ts @@ -0,0 +1,185 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + GenerateOptions, + GenerateResponse, + ToolAction, + defineTool, + generate, + generateStream, +} from '@genkit-ai/ai'; +import { MessageData } from '@genkit-ai/ai/model'; +import { StreamingCallback, configureGenkit } from '@genkit-ai/core'; +import { defineFlow, startFlowsServer } from '@genkit-ai/flow'; +import { gemini15ProPreview, vertexAI } from '@genkit-ai/vertexai'; +import { Allow, parse } from 'partial-json'; +import * as z from 'zod'; + +configureGenkit({ + plugins: [vertexAI()], + logLevel: 'error', + enableTracingAndMetrics: true, +}); + +const GameCharactersSchema = z.object({ + characters: z + .array( + z + .object({ + name: z.string().describe('Name of a character'), + abilities: z + .array(z.string()) + .describe('Various abilities (strength, magic, archery, etc.)'), + }) + .describe('Game character') + ) + .describe('Characters'), +}); + +export const streamCharacters = defineFlow( + { + name: 'streamCharacters', + inputSchema: z.number(), + outputSchema: z.string(), + streamSchema: GameCharactersSchema, + }, + async (count, streamingCallback) => { + if (!streamingCallback) { + throw new Error('this flow only works in streaming mode'); + } + + const { response, stream } = await generateStream({ + model: gemini15ProPreview, + output: { + schema: GameCharactersSchema, + }, + config: { + temperature: 1, + }, + prompt: `Respond as JSON only. Generate ${count} different RPG game characters.`, + }); + + let buffer = ''; + for await (const chunk of stream()) { + buffer += chunk.content[0].text!; + if (buffer.length > 10) { + streamingCallback(parse(maybeStripMarkdown(buffer), Allow.ALL)); + } + } + + return (await response()).text(); + } +); + +const jokeSubjectGenerator = defineTool( + { + name: 'jokeSubjectGenerator', + description: 'this tool can be called to generate a subject for a joke', + inputSchema: z.object({ dummy: z.string() }), + outputSchema: z.object({ subject: z.string() }), + }, + async () => { + return { subject: 'banana' }; + } +); + +export const toolCaller = defineFlow( + { + name: 'toolCaller', + outputSchema: z.string(), + }, + async (_, streamingCallback) => { + let history: MessageData[] = [ + { + role: 'system', + content: [ + { + text: 'use the available tools if you need to, for example as joke subject', + }, + ], + }, + ]; + + const tools: Record = { + jokeSubjectGenerator, + }; + + let prompt: GenerateOptions['prompt'] = `tell me a joke`; + let iteration = 0; + while (true) { + const response: GenerateResponse = await generate({ + model: gemini15ProPreview, + tools: Object.values(tools), + prompt, + returnToolRequests: true, + history, + streamingCallback: wrapModelStream( + `model call ${iteration}`, + streamingCallback + ), + }); + history = response.toHistory(); + if (response.toolRequests().length > 0) { + const toolRequest = response.toolRequests()[0].toolRequest; + if (!tools[toolRequest.name]) { + throw new Error(`unknown tool toolRequest.name`); + } + const tool = tools[toolRequest.name]; + if (streamingCallback) { + streamingCallback({ label: `tool ${toolRequest.name}`, toolRequest }); + } + const toolResponse = await tool(toolRequest.input); + if (streamingCallback) { + streamingCallback({ + label: `tool ${toolRequest.name}`, + toolRequest, + toolResponse, + }); + } + prompt = { + toolResponse: { + name: toolRequest.name, + ref: toolRequest.ref, + output: toolResponse, + }, + }; + } else { + return response.text(); + } + iteration++; + } + } +); + +function wrapModelStream( + label: string, + streamingCallback: StreamingCallback | undefined +): StreamingCallback | undefined { + if (!streamingCallback) return undefined; + return (data: any) => streamingCallback({ label, llmChunk: data }); +} + +const markdownRegex = /^\s*(```json)?((.|\n)*?)(```)?\s*$/i; +function maybeStripMarkdown(withMarkdown: string) { + const mdMatch = markdownRegex.exec(withMarkdown); + if (!mdMatch) { + return withMarkdown; + } + return mdMatch[2]; +} + +startFlowsServer(); diff --git a/samples/js-angular/server/tsconfig.json b/samples/js-angular/server/tsconfig.json new file mode 100644 index 0000000000..0932e7dc66 --- /dev/null +++ b/samples/js-angular/server/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compileOnSave": true, + "include": [ + "src" + ], + "compilerOptions": { + "module": "commonjs", + "noImplicitReturns": true, + "outDir": "lib", + "sourceMap": true, + "strict": true, + "target": "es2017", + "skipLibCheck": true, + "esModuleInterop": true + } +} \ No newline at end of file From 36704ca40fc1b74bfeebabc55ef41d0fc8755bf6 Mon Sep 17 00:00:00 2001 From: Pavel Jbanov Date: Sat, 1 Jun 2024 18:57:29 -0400 Subject: [PATCH 2/6] more samples --- .../genkit-app/src/app/app.routes.ts | 5 + .../src/app/home/home.component.html | 7 + .../streaming-json.component.ts | 3 +- .../tool-streaming.component.html | 28 +++ .../tool-streaming.component.scss | 3 + .../tool-streaming.component.spec.ts | 23 +++ .../tool-streaming.component.ts | 68 +++++++ .../js-angular/genkit-app/src/utils/flow.ts | 4 +- samples/js-angular/server/src/index.ts | 167 +----------------- .../js-angular/server/src/jsonStreaming.ts | 65 +++++++ .../js-angular/server/src/toolStreaming.ts | 101 +++++++++++ 11 files changed, 310 insertions(+), 164 deletions(-) create mode 100644 samples/js-angular/genkit-app/src/app/samples/tool-streaming/tool-streaming.component.html create mode 100644 samples/js-angular/genkit-app/src/app/samples/tool-streaming/tool-streaming.component.scss create mode 100644 samples/js-angular/genkit-app/src/app/samples/tool-streaming/tool-streaming.component.spec.ts create mode 100644 samples/js-angular/genkit-app/src/app/samples/tool-streaming/tool-streaming.component.ts create mode 100644 samples/js-angular/server/src/jsonStreaming.ts create mode 100644 samples/js-angular/server/src/toolStreaming.ts diff --git a/samples/js-angular/genkit-app/src/app/app.routes.ts b/samples/js-angular/genkit-app/src/app/app.routes.ts index a134d8e446..04f20d4b42 100644 --- a/samples/js-angular/genkit-app/src/app/app.routes.ts +++ b/samples/js-angular/genkit-app/src/app/app.routes.ts @@ -1,6 +1,7 @@ import { Routes } from '@angular/router'; import { StreamingJSONComponent } from './samples/streaming-json/streaming-json.component'; import { HomeComponent } from './home/home.component'; +import { ToolStreamingComponent } from './samples/tool-streaming/tool-streaming.component'; export const routes: Routes = [ { @@ -11,5 +12,9 @@ export const routes: Routes = [ path: 'samples/streaming-json', component: StreamingJSONComponent, }, + { + path: 'samples/tool-streaming', + component: ToolStreamingComponent, + }, { path: '**', redirectTo: '/home' }, ]; diff --git a/samples/js-angular/genkit-app/src/app/home/home.component.html b/samples/js-angular/genkit-app/src/app/home/home.component.html index d018fabeca..5dfc04324f 100644 --- a/samples/js-angular/genkit-app/src/app/home/home.component.html +++ b/samples/js-angular/genkit-app/src/app/home/home.component.html @@ -8,4 +8,11 @@

Samples

+ diff --git a/samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.ts b/samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.ts index 0ab46c53e9..ddd7076ac8 100644 --- a/samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.ts +++ b/samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.ts @@ -2,13 +2,14 @@ import { Component } from '@angular/core'; import { streamFlow } from '../../../utils/flow'; import { FormsModule } from '@angular/forms'; import { CommonModule } from '@angular/common'; +import { MatButtonModule } from '@angular/material/button'; const url = 'http://127.0.0.1:3400/streamCharacters'; @Component({ selector: 'app-streaming-json', standalone: true, - imports: [FormsModule, CommonModule], + imports: [FormsModule, CommonModule, MatButtonModule], templateUrl: './streaming-json.component.html', styleUrl: './streaming-json.component.scss' }) diff --git a/samples/js-angular/genkit-app/src/app/samples/tool-streaming/tool-streaming.component.html b/samples/js-angular/genkit-app/src/app/samples/tool-streaming/tool-streaming.component.html new file mode 100644 index 0000000000..293a0e8877 --- /dev/null +++ b/samples/js-angular/genkit-app/src/app/samples/tool-streaming/tool-streaming.component.html @@ -0,0 +1,28 @@ +

Tool streaming

+ + + + + +
+
+ + + {{out.label}} + + +
{{out.text}}
+
+ Tool call requested:
{{out.llmChunk.content[0].toolRequest.name}}
+
+
+ Calling tool {{out.toolRequest.name}}... + +
+
+
{{out.toolResponse | json}}
+
+
+
+
+
diff --git a/samples/js-angular/genkit-app/src/app/samples/tool-streaming/tool-streaming.component.scss b/samples/js-angular/genkit-app/src/app/samples/tool-streaming/tool-streaming.component.scss new file mode 100644 index 0000000000..d411f129a2 --- /dev/null +++ b/samples/js-angular/genkit-app/src/app/samples/tool-streaming/tool-streaming.component.scss @@ -0,0 +1,3 @@ +.step { + margin-bottom: 16px; +} \ No newline at end of file diff --git a/samples/js-angular/genkit-app/src/app/samples/tool-streaming/tool-streaming.component.spec.ts b/samples/js-angular/genkit-app/src/app/samples/tool-streaming/tool-streaming.component.spec.ts new file mode 100644 index 0000000000..40bc1bed1d --- /dev/null +++ b/samples/js-angular/genkit-app/src/app/samples/tool-streaming/tool-streaming.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ToolStreamingComponent } from './tool-streaming.component'; + +describe('ToolStreamingComponent', () => { + let component: ToolStreamingComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ToolStreamingComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ToolStreamingComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/samples/js-angular/genkit-app/src/app/samples/tool-streaming/tool-streaming.component.ts b/samples/js-angular/genkit-app/src/app/samples/tool-streaming/tool-streaming.component.ts new file mode 100644 index 0000000000..ff775bfbfd --- /dev/null +++ b/samples/js-angular/genkit-app/src/app/samples/tool-streaming/tool-streaming.component.ts @@ -0,0 +1,68 @@ +import { CommonModule } from '@angular/common'; +import { Component } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { streamFlow } from '../../../utils/flow'; +import { MatExpansionModule } from '@angular/material/expansion'; +import { MatCardModule } from '@angular/material/card'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; + +const url = 'http://127.0.0.1:3400/streamToolCalling'; + +@Component({ + selector: 'app-tool-streaming', + standalone: true, + imports: [CommonModule, MatButtonModule, MatExpansionModule, MatCardModule, MatProgressBarModule], + templateUrl: './tool-streaming.component.html', + styleUrl: './tool-streaming.component.scss', +}) +export class ToolStreamingComponent { + error?: string = undefined; + loading: boolean = false; + steps: Record = {} + output: any[] = []; + + async callFlow() { + this.error = undefined; + this.loading = true; + this.output = []; + this.steps = {} + try { + const response = streamFlow({ + url, + }); + for await (const chunk of response.stream()) { + console.log(chunk) + if (chunk.label) { + if (!this.steps[chunk.label]) { + if (chunk?.label?.startsWith('model call')) { + if (chunk?.llmChunk?.content[0]?.text) { + chunk.text = chunk.llmChunk.content[0].text; + } + } + this.steps[chunk.label] = chunk; + this.output.push(chunk); + } else { + const existing = this.steps[chunk.label]; + if (chunk?.label?.startsWith('model call')) { + if (chunk?.llmChunk?.content[0]?.text) { + existing.text += chunk.llmChunk.content[0].text; + } + } + if (chunk?.label?.startsWith('tool') && chunk.toolResponse) { + existing.toolResponse = chunk.toolResponse; + } + } + } + } + console.log('streamConsumer done', await response.output()); + this.loading = false; + } catch (e) { + this.loading = false; + if ((e as any).cause) { + this.error = `${(e as any).cause}`; + } else { + this.error = `${e}`; + } + } + } +} diff --git a/samples/js-angular/genkit-app/src/utils/flow.ts b/samples/js-angular/genkit-app/src/utils/flow.ts index 0e2ca92eac..e2eaa2ec05 100644 --- a/samples/js-angular/genkit-app/src/utils/flow.ts +++ b/samples/js-angular/genkit-app/src/utils/flow.ts @@ -22,7 +22,7 @@ export function streamFlow({ headers, }: { url: string; - payload: any; + payload?: any; headers?: Record; }) { let chunkStreamController: ReadableStreamDefaultController | undefined = @@ -83,7 +83,7 @@ async function __flowRunEnvelope({ headers, }: { url: string; - payload: any; + payload?: any; streamingCallback: (chunk: any) => void; headers?: Record; }) { diff --git a/samples/js-angular/server/src/index.ts b/samples/js-angular/server/src/index.ts index a3404d1fa1..19972739a4 100644 --- a/samples/js-angular/server/src/index.ts +++ b/samples/js-angular/server/src/index.ts @@ -14,172 +14,17 @@ * limitations under the License. */ -import { - GenerateOptions, - GenerateResponse, - ToolAction, - defineTool, - generate, - generateStream, -} from '@genkit-ai/ai'; -import { MessageData } from '@genkit-ai/ai/model'; -import { StreamingCallback, configureGenkit } from '@genkit-ai/core'; -import { defineFlow, startFlowsServer } from '@genkit-ai/flow'; -import { gemini15ProPreview, vertexAI } from '@genkit-ai/vertexai'; -import { Allow, parse } from 'partial-json'; -import * as z from 'zod'; +import { configureGenkit } from '@genkit-ai/core'; +import { startFlowsServer } from '@genkit-ai/flow'; +import { vertexAI } from '@genkit-ai/vertexai'; configureGenkit({ plugins: [vertexAI()], - logLevel: 'error', + logLevel: 'debug', enableTracingAndMetrics: true, }); -const GameCharactersSchema = z.object({ - characters: z - .array( - z - .object({ - name: z.string().describe('Name of a character'), - abilities: z - .array(z.string()) - .describe('Various abilities (strength, magic, archery, etc.)'), - }) - .describe('Game character') - ) - .describe('Characters'), -}); - -export const streamCharacters = defineFlow( - { - name: 'streamCharacters', - inputSchema: z.number(), - outputSchema: z.string(), - streamSchema: GameCharactersSchema, - }, - async (count, streamingCallback) => { - if (!streamingCallback) { - throw new Error('this flow only works in streaming mode'); - } - - const { response, stream } = await generateStream({ - model: gemini15ProPreview, - output: { - schema: GameCharactersSchema, - }, - config: { - temperature: 1, - }, - prompt: `Respond as JSON only. Generate ${count} different RPG game characters.`, - }); - - let buffer = ''; - for await (const chunk of stream()) { - buffer += chunk.content[0].text!; - if (buffer.length > 10) { - streamingCallback(parse(maybeStripMarkdown(buffer), Allow.ALL)); - } - } - - return (await response()).text(); - } -); - -const jokeSubjectGenerator = defineTool( - { - name: 'jokeSubjectGenerator', - description: 'this tool can be called to generate a subject for a joke', - inputSchema: z.object({ dummy: z.string() }), - outputSchema: z.object({ subject: z.string() }), - }, - async () => { - return { subject: 'banana' }; - } -); - -export const toolCaller = defineFlow( - { - name: 'toolCaller', - outputSchema: z.string(), - }, - async (_, streamingCallback) => { - let history: MessageData[] = [ - { - role: 'system', - content: [ - { - text: 'use the available tools if you need to, for example as joke subject', - }, - ], - }, - ]; - - const tools: Record = { - jokeSubjectGenerator, - }; - - let prompt: GenerateOptions['prompt'] = `tell me a joke`; - let iteration = 0; - while (true) { - const response: GenerateResponse = await generate({ - model: gemini15ProPreview, - tools: Object.values(tools), - prompt, - returnToolRequests: true, - history, - streamingCallback: wrapModelStream( - `model call ${iteration}`, - streamingCallback - ), - }); - history = response.toHistory(); - if (response.toolRequests().length > 0) { - const toolRequest = response.toolRequests()[0].toolRequest; - if (!tools[toolRequest.name]) { - throw new Error(`unknown tool toolRequest.name`); - } - const tool = tools[toolRequest.name]; - if (streamingCallback) { - streamingCallback({ label: `tool ${toolRequest.name}`, toolRequest }); - } - const toolResponse = await tool(toolRequest.input); - if (streamingCallback) { - streamingCallback({ - label: `tool ${toolRequest.name}`, - toolRequest, - toolResponse, - }); - } - prompt = { - toolResponse: { - name: toolRequest.name, - ref: toolRequest.ref, - output: toolResponse, - }, - }; - } else { - return response.text(); - } - iteration++; - } - } -); - -function wrapModelStream( - label: string, - streamingCallback: StreamingCallback | undefined -): StreamingCallback | undefined { - if (!streamingCallback) return undefined; - return (data: any) => streamingCallback({ label, llmChunk: data }); -} - -const markdownRegex = /^\s*(```json)?((.|\n)*?)(```)?\s*$/i; -function maybeStripMarkdown(withMarkdown: string) { - const mdMatch = markdownRegex.exec(withMarkdown); - if (!mdMatch) { - return withMarkdown; - } - return mdMatch[2]; -} +export * from "./jsonStreaming.js"; +export * from "./toolStreaming.js"; startFlowsServer(); diff --git a/samples/js-angular/server/src/jsonStreaming.ts b/samples/js-angular/server/src/jsonStreaming.ts new file mode 100644 index 0000000000..f7c5678a48 --- /dev/null +++ b/samples/js-angular/server/src/jsonStreaming.ts @@ -0,0 +1,65 @@ +import { generateStream } from "@genkit-ai/ai"; +import { defineFlow } from "@genkit-ai/flow"; +import { gemini15ProPreview } from "@genkit-ai/vertexai"; +import { Allow, parse } from 'partial-json'; +import { z } from "zod"; + +const GameCharactersSchema = z.object({ + characters: z + .array( + z + .object({ + name: z.string().describe('Name of a character'), + abilities: z + .array(z.string()) + .describe('Various abilities (strength, magic, archery, etc.)'), + }) + .describe('Game character') + ) + .describe('Characters'), +}); + +export const streamCharacters = defineFlow( + { + name: 'streamCharacters', + inputSchema: z.number(), + outputSchema: z.string(), + streamSchema: GameCharactersSchema, + }, + async (count, streamingCallback) => { + if (!streamingCallback) { + throw new Error('this flow only works in streaming mode'); + } + + const { response, stream } = await generateStream({ + model: gemini15ProPreview, + output: { + schema: GameCharactersSchema, + }, + config: { + temperature: 1, + }, + prompt: `Respond as JSON only. Generate ${count} different RPG game characters.`, + }); + + let buffer = ''; + for await (const chunk of stream()) { + buffer += chunk.content[0].text!; + if (buffer.length > 10) { + streamingCallback(parse(maybeStripMarkdown(buffer), Allow.ALL)); + } + } + + return (await response()).text(); + } +); + + +const markdownRegex = /^\s*(```json)?((.|\n)*?)(```)?\s*$/i; +function maybeStripMarkdown(withMarkdown: string) { + const mdMatch = markdownRegex.exec(withMarkdown); + if (!mdMatch) { + return withMarkdown; + } + return mdMatch[2]; +} \ No newline at end of file diff --git a/samples/js-angular/server/src/toolStreaming.ts b/samples/js-angular/server/src/toolStreaming.ts new file mode 100644 index 0000000000..a938e52aaf --- /dev/null +++ b/samples/js-angular/server/src/toolStreaming.ts @@ -0,0 +1,101 @@ +import { + GenerateOptions, + GenerateResponse, + ToolAction, + defineTool, + generate +} from '@genkit-ai/ai'; +import { MessageData } from '@genkit-ai/ai/model'; +import { StreamingCallback } from '@genkit-ai/core'; +import { defineFlow } from '@genkit-ai/flow'; +import { gemini15ProPreview } from '@genkit-ai/vertexai'; +import * as z from 'zod'; + +const jokeSubjectGenerator = defineTool( + { + name: 'jokeSubjectGenerator', + description: 'this tool can be called to generate a subject for a joke', + inputSchema: z.object({ dummy: z.string() }), + outputSchema: z.object({ subject: z.string() }), + }, + async () => { + await new Promise((r) => setTimeout(r, 2000)); + return { subject: 'banana' }; + } +); + +export const streamToolCalling = defineFlow( + { + name: 'streamToolCalling', + outputSchema: z.string(), + }, + async (_, streamingCallback) => { + let history: MessageData[] = [ + { + role: 'system', + content: [ + { + text: 'use the available tools if you need to, for example as joke subject', + }, + ], + }, + ]; + + const tools: Record = { + jokeSubjectGenerator, + }; + + let prompt: GenerateOptions['prompt'] = `tell me a joke`; + let iteration = 0; + while (true) { + const response: GenerateResponse = await generate({ + model: gemini15ProPreview, + tools: Object.values(tools), + prompt, + returnToolRequests: true, + history, + streamingCallback: wrapModelStream( + `model call ${iteration}`, + streamingCallback + ), + }); + history = response.toHistory(); + if (response.toolRequests().length > 0) { + const toolRequest = response.toolRequests()[0].toolRequest; + if (!tools[toolRequest.name]) { + throw new Error(`unknown tool toolRequest.name`); + } + const tool = tools[toolRequest.name]; + if (streamingCallback) { + streamingCallback({ label: `tool ${toolRequest.name}`, toolRequest }); + } + const toolResponse = await tool(toolRequest.input); + if (streamingCallback) { + streamingCallback({ + label: `tool ${toolRequest.name}`, + toolRequest, + toolResponse, + }); + } + prompt = { + toolResponse: { + name: toolRequest.name, + ref: toolRequest.ref, + output: toolResponse, + }, + }; + } else { + return response.text(); + } + iteration++; + } + } +); + +function wrapModelStream( + label: string, + streamingCallback: StreamingCallback | undefined +): StreamingCallback | undefined { + if (!streamingCallback) return undefined; + return (data: any) => streamingCallback({ label, llmChunk: data }); +} From b44f78693fc7f8f3bc6859279202c1cabbc44064 Mon Sep 17 00:00:00 2001 From: Pavel Jbanov Date: Tue, 11 Jun 2024 20:52:33 -0400 Subject: [PATCH 3/6] chatbot --- .../genkit-app/src/app/app.routes.ts | 5 + .../src/app/home/home.component.html | 7 + .../samples/chatbot/chatbot.component.html | 54 +++++++ .../samples/chatbot/chatbot.component.scss | 39 +++++ .../samples/chatbot/chatbot.component.spec.ts | 23 +++ .../app/samples/chatbot/chatbot.component.ts | 135 ++++++++++++++++++ .../js-angular/genkit-app/src/utils/flow.ts | 23 +++ samples/js-angular/server/src/chatbot.ts | 123 ++++++++++++++++ samples/js-angular/server/src/index.ts | 1 + 9 files changed, 410 insertions(+) create mode 100644 samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.html create mode 100644 samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.scss create mode 100644 samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.spec.ts create mode 100644 samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.ts create mode 100644 samples/js-angular/server/src/chatbot.ts diff --git a/samples/js-angular/genkit-app/src/app/app.routes.ts b/samples/js-angular/genkit-app/src/app/app.routes.ts index 04f20d4b42..40b29894c8 100644 --- a/samples/js-angular/genkit-app/src/app/app.routes.ts +++ b/samples/js-angular/genkit-app/src/app/app.routes.ts @@ -2,6 +2,7 @@ import { Routes } from '@angular/router'; import { StreamingJSONComponent } from './samples/streaming-json/streaming-json.component'; import { HomeComponent } from './home/home.component'; import { ToolStreamingComponent } from './samples/tool-streaming/tool-streaming.component'; +import { ChatbotComponent } from './samples/chatbot/chatbot.component'; export const routes: Routes = [ { @@ -16,5 +17,9 @@ export const routes: Routes = [ path: 'samples/tool-streaming', component: ToolStreamingComponent, }, + { + path: 'samples/chatbot', + component: ChatbotComponent, + }, { path: '**', redirectTo: '/home' }, ]; diff --git a/samples/js-angular/genkit-app/src/app/home/home.component.html b/samples/js-angular/genkit-app/src/app/home/home.component.html index 5dfc04324f..1cee8b29ba 100644 --- a/samples/js-angular/genkit-app/src/app/home/home.component.html +++ b/samples/js-angular/genkit-app/src/app/home/home.component.html @@ -15,4 +15,11 @@

Samples

+ diff --git a/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.html b/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.html new file mode 100644 index 0000000000..e72d96e1c3 --- /dev/null +++ b/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.html @@ -0,0 +1,54 @@ +
+

Chat with Gemini

+ +
+
+ {{ entry.text }} +
+
+ {{ entry.text }} +
+
+
+

+ sunny_snowing + {{ getWeatherLocation(entry.toolRequest) }} 27°C +

+
Warn sunny day with a mix of sun and snow.
+
+
+ + Choose a date + + MM/DD/YYYY + + + + + + + + +
+
+ Oops... unknown tool {{ entry.toolRequest.name }} +
+
+
+
+
+ +
+ + Chat input + + + + +
+
diff --git a/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.scss b/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.scss new file mode 100644 index 0000000000..e11ae92c40 --- /dev/null +++ b/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.scss @@ -0,0 +1,39 @@ +.wrapper { + padding: 20px; + width: 800px; + margin-left: auto; + margin-right: auto; +} + +.user-bubble { + background-color: #eeddee; + border: 1px solid #ccc; + border-radius: 10px; + margin-bottom: 20px; + margin-left: auto; + margin-right: 0; + min-width: 300px; + padding: 20px; + white-space: pre-wrap; + width: 80%; +} + +.model-bubble { + background-color: #ddddee; + border: 1px solid #ccc; + border-radius: 10px; + margin-bottom: 20px; + min-width: 300px; + padding: 20px; + width: 80%; + + .text { + white-space: pre-wrap; + } +} + +.input-field { + min-width: 400px; + vertical-align: top; + width: 730px; +} diff --git a/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.spec.ts b/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.spec.ts new file mode 100644 index 0000000000..23b6d56897 --- /dev/null +++ b/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ChatbotComponent } from './chatbot.component'; + +describe('ChatbotComponent', () => { + let component: ChatbotComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ChatbotComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ChatbotComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.ts b/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.ts new file mode 100644 index 0000000000..77ca1b9376 --- /dev/null +++ b/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.ts @@ -0,0 +1,135 @@ +import { CommonModule } from '@angular/common'; +import { Component } from '@angular/core'; +import { + FormControl, + FormsModule, + ReactiveFormsModule, + Validators, +} from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { streamFlow } from '../../../utils/flow'; +import { MatDatepickerInputEvent, MatDatepickerModule } from '@angular/material/datepicker'; +import { provideNativeDateAdapter } from '@angular/material/core'; + +const url = 'http://127.0.0.1:3400/chatbotFlow'; + + +interface ToolResponse { + name: string; + ref: string; + output?: unknown; +} + +interface InputSchema { + role: 'user'; + text?: string; + toolResponse?: ToolResponse; +} + +interface ToolRequest { + name: string; + ref: string; + input?: unknown; +} +interface OutputSchema { + role: 'model'; + text?: string; + toolRequest?: ToolRequest; +} + +@Component({ + selector: 'app-chatbot', + standalone: true, + providers: [provideNativeDateAdapter()], + imports: [ + CommonModule, + FormsModule, + MatFormFieldModule, + MatInputModule, + ReactiveFormsModule, + MatButtonModule, + MatIconModule, + MatProgressBarModule, + MatDatepickerModule, + ], + templateUrl: './chatbot.component.html', + styleUrl: './chatbot.component.scss', +}) +export class ChatbotComponent { + history: (InputSchema | OutputSchema)[] = []; + error?: string; + input?: string; + loading = false; + id = Date.now() + '' + Math.floor(Math.random() * 1000000000); + + chatFormControl = new FormControl('', [Validators.required]); + + ask(input?: string) { + const text = this.chatFormControl.value!.trim(); + if (!text) return; + this.history.push({ role: 'user', text: text }); + this.chatFormControl.setValue(''); + this.chatFormControl.disable(); + this.callFlow({role: 'user', text}); + this.loading = true; + } + + async callFlow(input: InputSchema) { + this.error = undefined; + this.loading = true; + try { + const response = await streamFlow({ + url, + payload: { + ...input, + id: this.id, + }, + }); + + let textBlock: OutputSchema | undefined = undefined; + for await (const chunk of response.stream()) { + if (chunk.text) { + if (!textBlock) { + textBlock = { role: 'model', text: chunk.text!}; + this.history.push(textBlock); + } else { + textBlock.text += chunk.text!; + } + } + if (chunk.toolRequest) { + this.history.push({ + role: 'model', + toolRequest: chunk.toolRequest, + }); + } + } + + this.loading = false; + this.chatFormControl.enable(); + } catch (e) { + this.loading = false; + this.chatFormControl.enable(); + if ((e as any).cause) { + this.error = `${(e as any).cause}`; + } else { + this.error = `${e}`; + } + } + } + + getWeatherLocation(toolRequest: ToolRequest) { + return (toolRequest.input as any).location; + } + + datePicked(toolRequest: ToolRequest, event: MatDatepickerInputEvent) { + this.callFlow({role: 'user', toolResponse: { + name: toolRequest.name, + ref: toolRequest.ref, + output: `${event.value}`, + }}); + } +} diff --git a/samples/js-angular/genkit-app/src/utils/flow.ts b/samples/js-angular/genkit-app/src/utils/flow.ts index e2eaa2ec05..3aabbaa23e 100644 --- a/samples/js-angular/genkit-app/src/utils/flow.ts +++ b/samples/js-angular/genkit-app/src/utils/flow.ts @@ -16,6 +16,29 @@ const __flowStreamDelimiter = '\n'; +export async function runFlow({ + url, + payload, + headers, +}: { + url: string; + payload?: any; + headers?: Record; +}) { + const response = await fetch(url, { + method: 'POST', + body: JSON.stringify({ + data: payload, + }), + headers: { + 'Content-Type': 'application/json', + ...headers, + }, + }); + const wrappedDesult = await response.json(); + return wrappedDesult.result; +} + export function streamFlow({ url, payload, diff --git a/samples/js-angular/server/src/chatbot.ts b/samples/js-angular/server/src/chatbot.ts new file mode 100644 index 0000000000..6f94e9ad50 --- /dev/null +++ b/samples/js-angular/server/src/chatbot.ts @@ -0,0 +1,123 @@ +import { defineTool, generate } from '@genkit-ai/ai'; +import { MessageData } from '@genkit-ai/ai/model'; +import { defineFlow } from '@genkit-ai/flow'; +import { gemini15ProPreview } from '@genkit-ai/vertexai'; +import { z } from 'zod'; + +const chatHistory: Record = {}; + +const InputSchema = z.object({ + id: z.string(), + text: z.string().optional(), + toolResponse: z + .object({ + name: z.string(), + ref: z.string().optional(), + output: z.unknown().optional(), + }) + .optional(), +}); + +const OutputSchema = z.object({ + text: z.string().optional(), + toolRequest: z + .object({ + name: z.string(), + ref: z.string().optional(), + input: z.unknown().optional(), + }) + .optional(), +}); + +const weatherTool = defineTool( + { + name: 'weatherTool', + description: 'use this tool to display weather', + inputSchema: z.object({ + date: z.string().describe('date (use datePicker tool if user did not specify)'), + location: z.string().describe('location (ZIP, city, etc.)', + )}), + outputSchema: z.string().optional(), + }, + async () => undefined +); + +const datePicker = defineTool( + { + name: 'datePicker', + description: 'user can use this UI tool to enter a date (prefer this over asking the user to enter the date manually)', + inputSchema: z.object({ignore: z.string().describe('ignore this (set to undefined)').optional()}), + outputSchema: z.string().optional(), + }, + async () => undefined +); + +export const chatbotFlow = defineFlow( + { + name: 'chatbotFlow', + inputSchema: InputSchema, + outputSchema: OutputSchema, + streamSchema: OutputSchema + }, + async (input, streamingCallback) => { + let prompt = input.text + ? input.text + : input.toolResponse + ? { toolResponse: input.toolResponse } + : undefined; + if (!prompt) { + throw 'prompt missing'; + } + const history = await loadChatState(input.id); + let toolCallSent = false; + const resp = await generate({ + prompt, + history, + model: gemini15ProPreview, + tools: [weatherTool, datePicker], + returnToolRequests: true, + streamingCallback: (chunk) => { + if (!streamingCallback) return; + if (!toolCallSent) { + if (chunk.toolRequests().length > 0) { + toolCallSent = true; + streamingCallback({ + toolRequest: chunk.toolRequests()[0].toolRequest + }); + } else if (chunk.text()) { + streamingCallback({ + text: chunk.text() + }); + } + } + }, + }); + await saveChatState(input.id, resp.toHistory()); + return resp.text() + ? { text: resp.text() } + : { toolRequest: resp.toolRequests()[0].toolRequest }; + } +); + +async function loadChatState(id: string): Promise { + if (!chatHistory[id]) { + return [ + { + role: 'system', + content: [ + { + text: + 'You are a helpful, chatty agent. There are tools/functions at your disposal, ' + + 'feel free to call them. If you think a tool/function can help but you do ' + + 'not have sufficient context feel free to ask clarifying questions.', + }, + ], + }, + ]; + } + return chatHistory[id]; +} + +async function saveChatState(id: string, history: MessageData[]) { + chatHistory[id] = history; +} diff --git a/samples/js-angular/server/src/index.ts b/samples/js-angular/server/src/index.ts index 19972739a4..7d09533b54 100644 --- a/samples/js-angular/server/src/index.ts +++ b/samples/js-angular/server/src/index.ts @@ -26,5 +26,6 @@ configureGenkit({ export * from "./jsonStreaming.js"; export * from "./toolStreaming.js"; +export * from "./chatbot.js"; startFlowsServer(); From 92139f74e3ed45cbe633631c8de62e2eeb42845a Mon Sep 17 00:00:00 2001 From: Pavel Jbanov Date: Thu, 13 Jun 2024 13:12:05 -0400 Subject: [PATCH 4/6] agent --- .../samples/chatbot/chatbot.component.html | 2 +- .../app/samples/chatbot/chatbot.component.ts | 53 ++++--- samples/js-angular/server/src/agent.ts | 93 ++++++++++++ samples/js-angular/server/src/chatbot.ts | 135 +++++------------- 4 files changed, 160 insertions(+), 123 deletions(-) create mode 100644 samples/js-angular/server/src/agent.ts diff --git a/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.html b/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.html index e72d96e1c3..8798340fa1 100644 --- a/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.html +++ b/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.html @@ -1,5 +1,5 @@
-

Chat with Gemini

+

Chat with Agent Smith

diff --git a/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.ts b/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.ts index 77ca1b9376..d68f43c5a3 100644 --- a/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.ts +++ b/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.ts @@ -7,17 +7,19 @@ import { Validators, } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; +import { provideNativeDateAdapter } from '@angular/material/core'; +import { + MatDatepickerInputEvent, + MatDatepickerModule, +} from '@angular/material/datepicker'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { MatProgressBarModule } from '@angular/material/progress-bar'; import { streamFlow } from '../../../utils/flow'; -import { MatDatepickerInputEvent, MatDatepickerModule } from '@angular/material/datepicker'; -import { provideNativeDateAdapter } from '@angular/material/core'; const url = 'http://127.0.0.1:3400/chatbotFlow'; - interface ToolResponse { name: string; ref: string; @@ -74,7 +76,7 @@ export class ChatbotComponent { this.history.push({ role: 'user', text: text }); this.chatFormControl.setValue(''); this.chatFormControl.disable(); - this.callFlow({role: 'user', text}); + this.callFlow({ role: 'user', text }); this.loading = true; } @@ -85,26 +87,28 @@ export class ChatbotComponent { const response = await streamFlow({ url, payload: { - ...input, - id: this.id, + prompt: input, + conversationId: this.id, }, }); let textBlock: OutputSchema | undefined = undefined; for await (const chunk of response.stream()) { - if (chunk.text) { - if (!textBlock) { - textBlock = { role: 'model', text: chunk.text!}; - this.history.push(textBlock); - } else { - textBlock.text += chunk.text!; + for (const content of chunk.content) { + if (content.text) { + if (!textBlock) { + textBlock = { role: 'model', text: content.text! }; + this.history.push(textBlock); + } else { + textBlock.text += content.text!; + } + } + if (content.toolRequest) { + this.history.push({ + role: 'model', + toolRequest: content.toolRequest, + }); } - } - if (chunk.toolRequest) { - this.history.push({ - role: 'model', - toolRequest: chunk.toolRequest, - }); } } @@ -126,10 +130,13 @@ export class ChatbotComponent { } datePicked(toolRequest: ToolRequest, event: MatDatepickerInputEvent) { - this.callFlow({role: 'user', toolResponse: { - name: toolRequest.name, - ref: toolRequest.ref, - output: `${event.value}`, - }}); + this.callFlow({ + role: 'user', + toolResponse: { + name: toolRequest.name, + ref: toolRequest.ref, + output: `${event.value}`, + }, + }); } } diff --git a/samples/js-angular/server/src/agent.ts b/samples/js-angular/server/src/agent.ts new file mode 100644 index 0000000000..b8e16b8914 --- /dev/null +++ b/samples/js-angular/server/src/agent.ts @@ -0,0 +1,93 @@ +import { GenerateResponse, generate } from '@genkit-ai/ai'; +import { + GenerateResponseSchema, + MessageData, + ModelArgument, + PartSchema, +} from '@genkit-ai/ai/model'; +import { ToolArgument } from '@genkit-ai/ai/tool'; +import { defineFlow, run } from '@genkit-ai/flow'; +import { z } from 'zod'; + +export interface HistoryStore { + load(id: string): Promise; + save(id: string, history: MessageData[]): Promise; +} + +export const AgentInput = z.object({ + conversationId: z.string(), + prompt: z.union([z.string(), PartSchema, z.array(PartSchema)]), + config: z.record(z.string(), z.any()).optional(), +}); + +type AgentFn = ( + request: z.infer, + history: MessageData[] | undefined +) => Promise>; + +export function defineAgent( + { + name, + tools, + model, + historyStore, + systemPrompt, + returnToolRequests, + }: { + name: string; + systemPrompt?: string; + tools?: ToolArgument[]; + model: ModelArgument; + historyStore?: HistoryStore; + returnToolRequests?: boolean; + }, + customFn?: AgentFn +) { + return defineFlow( + { name, inputSchema: AgentInput, outputSchema: GenerateResponseSchema }, + async (request, streamingCallback) => { + const history = await run( + 'retrieve-history', + request.conversationId, + async () => { + let history = request.conversationId + ? await historyStore?.load(request.conversationId) + : undefined; + if (!history && systemPrompt) { + history = [ + { + role: 'system', + content: [ + { + text: systemPrompt, + }, + ], + }, + ]; + } + return history; + } + ); + const resp = customFn + ? await customFn(request, history) + : await generate({ + prompt: request.prompt, + history, + model, + tools, + returnToolRequests, + streamingCallback, + }); + await run( + 'save-history', + { conversationId: request.conversationId, history: resp.toHistory() }, + async () => { + request.conversationId + ? await historyStore?.save(request.conversationId, resp.toHistory()) + : undefined; + } + ); + return resp.toJSON(); + } + ); +} diff --git a/samples/js-angular/server/src/chatbot.ts b/samples/js-angular/server/src/chatbot.ts index 6f94e9ad50..bf6e282497 100644 --- a/samples/js-angular/server/src/chatbot.ts +++ b/samples/js-angular/server/src/chatbot.ts @@ -1,42 +1,19 @@ -import { defineTool, generate } from '@genkit-ai/ai'; +import { defineTool } from '@genkit-ai/ai'; import { MessageData } from '@genkit-ai/ai/model'; -import { defineFlow } from '@genkit-ai/flow'; -import { gemini15ProPreview } from '@genkit-ai/vertexai'; +import { gemini15FlashPreview } from '@genkit-ai/vertexai'; import { z } from 'zod'; - -const chatHistory: Record = {}; - -const InputSchema = z.object({ - id: z.string(), - text: z.string().optional(), - toolResponse: z - .object({ - name: z.string(), - ref: z.string().optional(), - output: z.unknown().optional(), - }) - .optional(), -}); - -const OutputSchema = z.object({ - text: z.string().optional(), - toolRequest: z - .object({ - name: z.string(), - ref: z.string().optional(), - input: z.unknown().optional(), - }) - .optional(), -}); +import { HistoryStore, defineAgent } from './agent'; const weatherTool = defineTool( { name: 'weatherTool', description: 'use this tool to display weather', inputSchema: z.object({ - date: z.string().describe('date (use datePicker tool if user did not specify)'), - location: z.string().describe('location (ZIP, city, etc.)', - )}), + date: z + .string() + .describe('date (use datePicker tool if user did not specify)'), + location: z.string().describe('location (ZIP, city, etc.)'), + }), outputSchema: z.string().optional(), }, async () => undefined @@ -45,79 +22,39 @@ const weatherTool = defineTool( const datePicker = defineTool( { name: 'datePicker', - description: 'user can use this UI tool to enter a date (prefer this over asking the user to enter the date manually)', - inputSchema: z.object({ignore: z.string().describe('ignore this (set to undefined)').optional()}), + description: + 'user can use this UI tool to enter a date (prefer this over asking the user to enter the date manually)', + inputSchema: z.object({ + ignore: z.string().describe('ignore this (set to undefined)').optional(), + }), outputSchema: z.string().optional(), }, async () => undefined ); -export const chatbotFlow = defineFlow( - { - name: 'chatbotFlow', - inputSchema: InputSchema, - outputSchema: OutputSchema, - streamSchema: OutputSchema - }, - async (input, streamingCallback) => { - let prompt = input.text - ? input.text - : input.toolResponse - ? { toolResponse: input.toolResponse } - : undefined; - if (!prompt) { - throw 'prompt missing'; - } - const history = await loadChatState(input.id); - let toolCallSent = false; - const resp = await generate({ - prompt, - history, - model: gemini15ProPreview, - tools: [weatherTool, datePicker], - returnToolRequests: true, - streamingCallback: (chunk) => { - if (!streamingCallback) return; - if (!toolCallSent) { - if (chunk.toolRequests().length > 0) { - toolCallSent = true; - streamingCallback({ - toolRequest: chunk.toolRequests()[0].toolRequest - }); - } else if (chunk.text()) { - streamingCallback({ - text: chunk.text() - }); - } - } - }, - }); - await saveChatState(input.id, resp.toHistory()); - return resp.text() - ? { text: resp.text() } - : { toolRequest: resp.toolRequests()[0].toolRequest }; - } -); +export const chatbotFlow = defineAgent({ + name: 'chatbotFlow', + model: gemini15FlashPreview, + tools: [weatherTool, datePicker], + returnToolRequests: true, + systemPrompt: + 'You are a helpful agent. You have the personality of Agent Smith from Matrix. ' + + 'There are tools/functions at your disposal, ' + + 'feel free to call them. If you think a tool/function can help but you do ' + + 'not have sufficient context make sure to ask clarifying questions.', + historyStore: inMemoryStore(), +}); -async function loadChatState(id: string): Promise { - if (!chatHistory[id]) { - return [ - { - role: 'system', - content: [ - { - text: - 'You are a helpful, chatty agent. There are tools/functions at your disposal, ' + - 'feel free to call them. If you think a tool/function can help but you do ' + - 'not have sufficient context feel free to ask clarifying questions.', - }, - ], - }, - ]; - } - return chatHistory[id]; -} -async function saveChatState(id: string, history: MessageData[]) { - chatHistory[id] = history; +const chatHistory: Record = {}; + +function inMemoryStore(): HistoryStore { + return { + async load(id: string): Promise { + return chatHistory[id]; + }, + async save(id: string, history: MessageData[]) { + chatHistory[id] = history; + }, + }; } From d6f4dcab74dd8467bc28611615c1e7e8d68da629 Mon Sep 17 00:00:00 2001 From: Pavel Jbanov Date: Tue, 18 Jun 2024 11:03:40 -0400 Subject: [PATCH 5/6] cleanup --- samples/js-angular/README.md | 13 ++- .../genkit-app/src/app/app.routes.ts | 5 - .../src/app/home/home.component.html | 7 -- .../tool-streaming.component.html | 28 ----- .../tool-streaming.component.scss | 3 - .../tool-streaming.component.spec.ts | 23 ---- .../tool-streaming.component.ts | 68 ------------ samples/js-angular/server/src/index.ts | 1 - .../js-angular/server/src/toolStreaming.ts | 101 ------------------ 9 files changed, 10 insertions(+), 239 deletions(-) delete mode 100644 samples/js-angular/genkit-app/src/app/samples/tool-streaming/tool-streaming.component.html delete mode 100644 samples/js-angular/genkit-app/src/app/samples/tool-streaming/tool-streaming.component.scss delete mode 100644 samples/js-angular/genkit-app/src/app/samples/tool-streaming/tool-streaming.component.spec.ts delete mode 100644 samples/js-angular/genkit-app/src/app/samples/tool-streaming/tool-streaming.component.ts delete mode 100644 samples/js-angular/server/src/toolStreaming.ts diff --git a/samples/js-angular/README.md b/samples/js-angular/README.md index 1231ce451c..4e02b6f321 100644 --- a/samples/js-angular/README.md +++ b/samples/js-angular/README.md @@ -5,13 +5,20 @@ This is a simple UI for streaming RPG character generator. To build: ```bash +npm i npm run build ``` -Run: +The sample is using Vertex AI, so you'll need to auth: ```bash -npm run start -- --project YOUR_PROJECT_ID +gcloud auth application-default login ``` -Point your browser to http://127.0.0.1:5000 +To run the sample: + +```bash +npm start +``` + +Point your browser to http://localhost:4200/ diff --git a/samples/js-angular/genkit-app/src/app/app.routes.ts b/samples/js-angular/genkit-app/src/app/app.routes.ts index 40b29894c8..d985651923 100644 --- a/samples/js-angular/genkit-app/src/app/app.routes.ts +++ b/samples/js-angular/genkit-app/src/app/app.routes.ts @@ -1,7 +1,6 @@ import { Routes } from '@angular/router'; import { StreamingJSONComponent } from './samples/streaming-json/streaming-json.component'; import { HomeComponent } from './home/home.component'; -import { ToolStreamingComponent } from './samples/tool-streaming/tool-streaming.component'; import { ChatbotComponent } from './samples/chatbot/chatbot.component'; export const routes: Routes = [ @@ -13,10 +12,6 @@ export const routes: Routes = [ path: 'samples/streaming-json', component: StreamingJSONComponent, }, - { - path: 'samples/tool-streaming', - component: ToolStreamingComponent, - }, { path: 'samples/chatbot', component: ChatbotComponent, diff --git a/samples/js-angular/genkit-app/src/app/home/home.component.html b/samples/js-angular/genkit-app/src/app/home/home.component.html index 1cee8b29ba..0b14e23c3a 100644 --- a/samples/js-angular/genkit-app/src/app/home/home.component.html +++ b/samples/js-angular/genkit-app/src/app/home/home.component.html @@ -8,13 +8,6 @@

Samples

-
diff --git a/samples/js-angular/genkit-app/src/app/home/home.component.scss b/samples/js-angular/genkit-app/src/app/home/home.component.scss index 9912614217..da80fa8b3e 100644 --- a/samples/js-angular/genkit-app/src/app/home/home.component.scss +++ b/samples/js-angular/genkit-app/src/app/home/home.component.scss @@ -1,3 +1,19 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + .wrapper { padding: 20px; -} \ No newline at end of file +} diff --git a/samples/js-angular/genkit-app/src/app/home/home.component.spec.ts b/samples/js-angular/genkit-app/src/app/home/home.component.spec.ts index 60c47c4115..19eda49ae5 100644 --- a/samples/js-angular/genkit-app/src/app/home/home.component.spec.ts +++ b/samples/js-angular/genkit-app/src/app/home/home.component.spec.ts @@ -1,3 +1,19 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import { ComponentFixture, TestBed } from '@angular/core/testing'; import { HomeComponent } from './home.component'; @@ -8,10 +24,9 @@ describe('HomeComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [HomeComponent] - }) - .compileComponents(); - + imports: [HomeComponent], + }).compileComponents(); + fixture = TestBed.createComponent(HomeComponent); component = fixture.componentInstance; fixture.detectChanges(); diff --git a/samples/js-angular/genkit-app/src/app/home/home.component.ts b/samples/js-angular/genkit-app/src/app/home/home.component.ts index 6c1570665c..f1e1997c12 100644 --- a/samples/js-angular/genkit-app/src/app/home/home.component.ts +++ b/samples/js-angular/genkit-app/src/app/home/home.component.ts @@ -1,3 +1,19 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import { Component } from '@angular/core'; import { RouterLink, RouterLinkActive } from '@angular/router'; diff --git a/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.html b/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.html index 8798340fa1..00829ec6db 100644 --- a/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.html +++ b/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.html @@ -1,3 +1,19 @@ + +

Chat with Agent Smith

@@ -19,13 +35,20 @@

Choose a date - + MM/DD/YYYY - + - + diff --git a/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.scss b/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.scss index e11ae92c40..da62c1c123 100644 --- a/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.scss +++ b/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.scss @@ -1,8 +1,24 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + .wrapper { - padding: 20px; - width: 800px; margin-left: auto; margin-right: auto; + padding: 20px; + width: 800px; } .user-bubble { diff --git a/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.spec.ts b/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.spec.ts index 23b6d56897..c79a6e1f7b 100644 --- a/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.spec.ts +++ b/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.spec.ts @@ -1,3 +1,19 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ChatbotComponent } from './chatbot.component'; @@ -8,10 +24,9 @@ describe('ChatbotComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [ChatbotComponent] - }) - .compileComponents(); - + imports: [ChatbotComponent], + }).compileComponents(); + fixture = TestBed.createComponent(ChatbotComponent); component = fixture.componentInstance; fixture.detectChanges(); diff --git a/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.ts b/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.ts index d68f43c5a3..02012c7f02 100644 --- a/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.ts +++ b/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.ts @@ -1,3 +1,19 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import { CommonModule } from '@angular/common'; import { Component } from '@angular/core'; import { diff --git a/samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.html b/samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.html index ac3a01af40..e32ce2ae00 100644 --- a/samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.html +++ b/samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.html @@ -1,3 +1,19 @@ + +

Stream JSON from LLM

This is a Game Character Generator.
diff --git a/samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.scss b/samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.scss index 978f5970f2..70fcce28eb 100644 --- a/samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.scss +++ b/samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.scss @@ -1,7 +1,23 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + .wrapper { padding: 20px; } .characters { margin-top: 20px; -} \ No newline at end of file +} diff --git a/samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.spec.ts b/samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.spec.ts index dde9d94128..d853731059 100644 --- a/samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.spec.ts +++ b/samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.spec.ts @@ -1,3 +1,19 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import { ComponentFixture, TestBed } from '@angular/core/testing'; import { StreamingJSONComponent } from './streaming-json.component'; @@ -8,10 +24,9 @@ describe('StreamingJSONComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [StreamingJSONComponent] - }) - .compileComponents(); - + imports: [StreamingJSONComponent], + }).compileComponents(); + fixture = TestBed.createComponent(StreamingJSONComponent); component = fixture.componentInstance; fixture.detectChanges(); diff --git a/samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.ts b/samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.ts index ddd7076ac8..fc1a0f8da8 100644 --- a/samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.ts +++ b/samples/js-angular/genkit-app/src/app/samples/streaming-json/streaming-json.component.ts @@ -1,8 +1,24 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { CommonModule } from '@angular/common'; import { Component } from '@angular/core'; -import { streamFlow } from '../../../utils/flow'; import { FormsModule } from '@angular/forms'; -import { CommonModule } from '@angular/common'; import { MatButtonModule } from '@angular/material/button'; +import { streamFlow } from '../../../utils/flow'; const url = 'http://127.0.0.1:3400/streamCharacters'; @@ -11,7 +27,7 @@ const url = 'http://127.0.0.1:3400/streamCharacters'; standalone: true, imports: [FormsModule, CommonModule, MatButtonModule], templateUrl: './streaming-json.component.html', - styleUrl: './streaming-json.component.scss' + styleUrl: './streaming-json.component.scss', }) export class StreamingJSONComponent { count: string = '3'; diff --git a/samples/js-angular/genkit-app/src/index.html b/samples/js-angular/genkit-app/src/index.html index f6b16719a7..822a28173d 100644 --- a/samples/js-angular/genkit-app/src/index.html +++ b/samples/js-angular/genkit-app/src/index.html @@ -1,15 +1,35 @@ + + - - - GenkitApp - - - - - - - - - + + + GenkitApp + + + + + + + + + diff --git a/samples/js-angular/genkit-app/src/main.ts b/samples/js-angular/genkit-app/src/main.ts index 35b00f3463..b1be530a21 100644 --- a/samples/js-angular/genkit-app/src/main.ts +++ b/samples/js-angular/genkit-app/src/main.ts @@ -1,6 +1,23 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import { bootstrapApplication } from '@angular/platform-browser'; -import { appConfig } from './app/app.config'; import { AppComponent } from './app/app.component'; +import { appConfig } from './app/app.config'; -bootstrapApplication(AppComponent, appConfig) - .catch((err) => console.error(err)); +bootstrapApplication(AppComponent, appConfig).catch((err) => + console.error(err) +); diff --git a/samples/js-angular/genkit-app/src/styles.scss b/samples/js-angular/genkit-app/src/styles.scss index c5e09da0d7..0ff0b59e1b 100644 --- a/samples/js-angular/genkit-app/src/styles.scss +++ b/samples/js-angular/genkit-app/src/styles.scss @@ -1,3 +1,19 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + /* You can add global styles to this file, and also import other style files */ :root { @@ -33,8 +49,7 @@ pre { white-space: pre-wrap; } - // Helper for filling available space in flex layouts .flex-spacer { flex: 1; -} \ No newline at end of file +} diff --git a/samples/js-angular/genkit-app/tsconfig.app.json b/samples/js-angular/genkit-app/tsconfig.app.json index 374cc9d294..84f1f992d2 100644 --- a/samples/js-angular/genkit-app/tsconfig.app.json +++ b/samples/js-angular/genkit-app/tsconfig.app.json @@ -5,10 +5,6 @@ "outDir": "./out-tsc/app", "types": [] }, - "files": [ - "src/main.ts" - ], - "include": [ - "src/**/*.d.ts" - ] + "files": ["src/main.ts"], + "include": ["src/**/*.d.ts"] } diff --git a/samples/js-angular/genkit-app/tsconfig.json b/samples/js-angular/genkit-app/tsconfig.json index 56b644d6a6..437984834c 100644 --- a/samples/js-angular/genkit-app/tsconfig.json +++ b/samples/js-angular/genkit-app/tsconfig.json @@ -18,10 +18,7 @@ "target": "ES2022", "module": "ES2022", "useDefineForClassFields": false, - "lib": [ - "ES2022", - "dom" - ] + "lib": ["ES2022", "dom"] }, "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, diff --git a/samples/js-angular/genkit-app/tsconfig.spec.json b/samples/js-angular/genkit-app/tsconfig.spec.json index be7e9da76f..47e3dd7551 100644 --- a/samples/js-angular/genkit-app/tsconfig.spec.json +++ b/samples/js-angular/genkit-app/tsconfig.spec.json @@ -3,12 +3,7 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./out-tsc/spec", - "types": [ - "jasmine" - ] + "types": ["jasmine"] }, - "include": [ - "src/**/*.spec.ts", - "src/**/*.d.ts" - ] + "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] } diff --git a/samples/js-angular/server/src/agent.ts b/samples/js-angular/server/src/agent.ts index b8e16b8914..4fa9b964f0 100644 --- a/samples/js-angular/server/src/agent.ts +++ b/samples/js-angular/server/src/agent.ts @@ -1,3 +1,19 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import { GenerateResponse, generate } from '@genkit-ai/ai'; import { GenerateResponseSchema, diff --git a/samples/js-angular/server/src/chatbot.ts b/samples/js-angular/server/src/chatbot.ts index bf6e282497..af4c04599c 100644 --- a/samples/js-angular/server/src/chatbot.ts +++ b/samples/js-angular/server/src/chatbot.ts @@ -1,3 +1,19 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import { defineTool } from '@genkit-ai/ai'; import { MessageData } from '@genkit-ai/ai/model'; import { gemini15FlashPreview } from '@genkit-ai/vertexai'; @@ -45,7 +61,6 @@ export const chatbotFlow = defineAgent({ historyStore: inMemoryStore(), }); - const chatHistory: Record = {}; function inMemoryStore(): HistoryStore { diff --git a/samples/js-angular/server/src/index.ts b/samples/js-angular/server/src/index.ts index 595702c54b..615a6185ec 100644 --- a/samples/js-angular/server/src/index.ts +++ b/samples/js-angular/server/src/index.ts @@ -24,7 +24,7 @@ configureGenkit({ enableTracingAndMetrics: true, }); -export * from "./jsonStreaming.js"; -export * from "./chatbot.js"; +export * from './chatbot.js'; +export * from './jsonStreaming.js'; startFlowsServer(); diff --git a/samples/js-angular/server/src/jsonStreaming.ts b/samples/js-angular/server/src/jsonStreaming.ts index f7c5678a48..e75c051db5 100644 --- a/samples/js-angular/server/src/jsonStreaming.ts +++ b/samples/js-angular/server/src/jsonStreaming.ts @@ -1,8 +1,24 @@ -import { generateStream } from "@genkit-ai/ai"; -import { defineFlow } from "@genkit-ai/flow"; -import { gemini15ProPreview } from "@genkit-ai/vertexai"; +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { generateStream } from '@genkit-ai/ai'; +import { defineFlow } from '@genkit-ai/flow'; +import { gemini15ProPreview } from '@genkit-ai/vertexai'; import { Allow, parse } from 'partial-json'; -import { z } from "zod"; +import { z } from 'zod'; const GameCharactersSchema = z.object({ characters: z @@ -54,7 +70,6 @@ export const streamCharacters = defineFlow( } ); - const markdownRegex = /^\s*(```json)?((.|\n)*?)(```)?\s*$/i; function maybeStripMarkdown(withMarkdown: string) { const mdMatch = markdownRegex.exec(withMarkdown); @@ -62,4 +77,4 @@ function maybeStripMarkdown(withMarkdown: string) { return withMarkdown; } return mdMatch[2]; -} \ No newline at end of file +} diff --git a/samples/js-angular/server/tsconfig.json b/samples/js-angular/server/tsconfig.json index 0932e7dc66..efbb566bf7 100644 --- a/samples/js-angular/server/tsconfig.json +++ b/samples/js-angular/server/tsconfig.json @@ -1,8 +1,6 @@ { "compileOnSave": true, - "include": [ - "src" - ], + "include": ["src"], "compilerOptions": { "module": "commonjs", "noImplicitReturns": true, @@ -13,4 +11,4 @@ "skipLibCheck": true, "esModuleInterop": true } -} \ No newline at end of file +}