From 79c0740e2ca607fc1dd9572e355a5cf3d80310af Mon Sep 17 00:00:00 2001 From: Shoujian Zheng Date: Wed, 22 May 2024 12:25:02 +0800 Subject: [PATCH] chore(example): add a simpleaichat integration example --- .changeset/config.json | 3 +- examples/simpleaichat-example/.env | 2 + examples/simpleaichat-example/.gitignore | 37 +++++++ .../simpleaichat-example/.pluto/pluto.yml | 7 ++ examples/simpleaichat-example/README.md | 3 + examples/simpleaichat-example/app/main.py | 104 ++++++++++++++++++ examples/simpleaichat-example/demo.cast | 76 +++++++++++++ examples/simpleaichat-example/package.json | 20 ++++ .../simpleaichat-example/requirements.txt | 2 + pnpm-lock.yaml | 24 +++- 10 files changed, 272 insertions(+), 6 deletions(-) create mode 100644 examples/simpleaichat-example/.env create mode 100644 examples/simpleaichat-example/.gitignore create mode 100644 examples/simpleaichat-example/.pluto/pluto.yml create mode 100644 examples/simpleaichat-example/README.md create mode 100644 examples/simpleaichat-example/app/main.py create mode 100644 examples/simpleaichat-example/demo.cast create mode 100644 examples/simpleaichat-example/package.json create mode 100644 examples/simpleaichat-example/requirements.txt diff --git a/.changeset/config.json b/.changeset/config.json index f9392b22..0baf4e20 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -21,6 +21,7 @@ "fastapi", "deploy-langserve-to-aws", "rag-qa-bot-llama3", - "rag-qa-bot-with-web" + "rag-qa-bot-with-web", + "simpleaichat-example" ] } diff --git a/examples/simpleaichat-example/.env b/examples/simpleaichat-example/.env new file mode 100644 index 00000000..d694d827 --- /dev/null +++ b/examples/simpleaichat-example/.env @@ -0,0 +1,2 @@ +OPENAI_API_KEY= +CHAT_API_URL=https://api.openai.com/v1/chat/completions \ No newline at end of file diff --git a/examples/simpleaichat-example/.gitignore b/examples/simpleaichat-example/.gitignore new file mode 100644 index 00000000..176fd8ff --- /dev/null +++ b/examples/simpleaichat-example/.gitignore @@ -0,0 +1,37 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# vscode +.vscode/ + +# dependencies +node_modules +.pnp +.pnp.js + +# testing +coverage + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# turbo +.turbo + +# vercel +.vercel + +# compilation result +**/dist/ + +# pluto +.pluto/**/state/ +.pluto/*.test.*/ \ No newline at end of file diff --git a/examples/simpleaichat-example/.pluto/pluto.yml b/examples/simpleaichat-example/.pluto/pluto.yml new file mode 100644 index 00000000..33bfaa6c --- /dev/null +++ b/examples/simpleaichat-example/.pluto/pluto.yml @@ -0,0 +1,7 @@ +current: aws +language: python +stacks: + - configs: {} + name: aws + platformType: AWS + provisionType: Pulumi diff --git a/examples/simpleaichat-example/README.md b/examples/simpleaichat-example/README.md new file mode 100644 index 00000000..06c9532a --- /dev/null +++ b/examples/simpleaichat-example/README.md @@ -0,0 +1,3 @@ +# A Pluto Application + +https://asciinema.org/a/PjekPNsKI0vv0u2Q0RaYeTWsC diff --git a/examples/simpleaichat-example/app/main.py b/examples/simpleaichat-example/app/main.py new file mode 100644 index 00000000..71f834ae --- /dev/null +++ b/examples/simpleaichat-example/app/main.py @@ -0,0 +1,104 @@ +import os +import json + +from simpleaichat import AIChat +from simpleaichat.utils import wikipedia_search, wikipedia_search_lookup + +from pluto_client import Router, HttpRequest, HttpResponse, Bucket + + +router = Router("router") +sessions = Bucket("sessions") + +basic_args = { + "console": False, + "api_key": os.environ.get("OPENAI_API_KEY"), + "api_url": os.environ.get("CHAT_API_URL"), +} + + +def chat_handler(req: HttpRequest) -> HttpResponse: + """Chat in a session with the AI using simpleaichat.""" + session_id = req.query.get("session_id") + query = req.query.get("query") + if session_id is None or query is None: + return HttpResponse( + status_code=400, + body=json.dumps({"error": "session_id and query are required."}), + ) + + ai = AIChat(**basic_args) + + # Load the session from bucket if it exists + ai_chat_sess_id = None + output_path = f"/tmp/${session_id}.json" + try: + sessions.get(session_id, output_path) + ai.load_session(input_path=output_path, **basic_args) + with open(output_path, "r") as f: + ai_chat_sess_id = json.load(f)["id"] + except Exception: + ai_chat_sess_id = ai.default_session.id + pass + + response = ai(query, id=ai_chat_sess_id) + + # Save the session to the bucket + ai.save_session( + output_path=output_path, + format="json", + minify=True, + id=ai_chat_sess_id, + ) + sessions.put(session_id, output_path) + + return HttpResponse(status_code=200, body=json.dumps(response)) + + +def qa_handler(req: HttpRequest) -> HttpResponse: + """Ask a question to the AI using simpleaichat.""" + query = req.query.get("query") + if query is None: + return HttpResponse( + status_code=400, + body=json.dumps({"error": "query is required."}), + ) + + ai = AIChat(**basic_args) + response = ai(query) + return HttpResponse(status_code=200, body=json.dumps(response)) + + +# This uses the Wikipedia Search API. +# Results from it are nondeterministic, your mileage will vary. +def search(query): + """Search the internet.""" + wiki_matches = wikipedia_search(query, n=3) + return {"context": ", ".join(wiki_matches), "titles": wiki_matches} + + +def lookup(query): + """Lookup more information about a topic.""" + page = wikipedia_search_lookup(query, sentences=3) + return page + + +def wikipedia_handler(req: HttpRequest) -> HttpResponse: + """Get information from Wikipedia using simpleaichat.""" + query = req.query.get("query") + if query is None: + return HttpResponse( + status_code=400, + body=json.dumps({"error": "query is required."}), + ) + + params = {"temperature": 0.0, "max_tokens": 100} + ai = AIChat(params=params, **basic_args) + response = ai(query, tools=[search, lookup]) + return HttpResponse(status_code=200, body=json.dumps(response)) + + +# Define routes for the ApiGateway +router.get("/chat", chat_handler) +router.get("/qa", qa_handler) +router.get("/wikipedia", wikipedia_handler) diff --git a/examples/simpleaichat-example/demo.cast b/examples/simpleaichat-example/demo.cast new file mode 100644 index 00000000..0da1b1db --- /dev/null +++ b/examples/simpleaichat-example/demo.cast @@ -0,0 +1,76 @@ +{"version": 2, "width": 161, "height": 34, "timestamp": 1716350814, "env": {"SHELL": "/bin/bash", "TERM": "xterm"}} +[1.674221, "o", "\u001b]0;admin@egumomng-0-006000117109:~/zhengshoujian.zsj/pluto-lang/pluto/examples/simpleaichat-example\u0007"] +[1.675091, "o", "\r\r\n\u001b[1;37m[\u001b[m\u001b[1;32madmin\u001b[m\u001b[1;33m@\u001b[m\u001b[1;35megumomng-0-006000117109\u001b[m \u001b[4m~/zhengshoujian.zsj/pluto-lang/pluto/examples/simpleaichat-example\u001b[m\u001b[1;37m]\u001b[m\u001b[1;36m\u001b[m\r\r\n$"] +[2.234862, "o", "p"] +[2.404695, "o", "l"] +[2.554805, "o", "u"] +[2.679612, "o", "t"] +[2.737914, "o", "o"] +[2.869536, "o", " "] +[3.020949, "o", "d"] +[3.161149, "o", "e"] +[3.259531, "o", "p"] +[3.400547, "o", "l"] +[3.534976, "o", "o"] +[3.686663, "o", "y"] +[4.207589, "o", "\r\n"] +[4.534471, "o", "\u001b[34mInfo: \u001b[39m Generating reference architecture...\r\n"] +[6.541664, "o", "Bundling dependencies, this may take a while...\r\n"] +[7.792584, "o", "╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════╗\r\n║ Architecture Entities ║\r\n╟───────────────────────────────────────────────────────────┬──────────┬─────────────────────────┬─────────────╢\r\n║ ID │ Name │ Resource Type │ Entity Type ║\r\n╟───────────────────────────────────────────────────────────┼──────────┼─────────────────────────┼─────────────╢\r\n║ simpleaichat_example_aws__plutolang_pluto_Router_router │ router │ @plutolang/pluto.Router │ Resource ║\r\n║ simpleaichat_example_aws__plutolang_pluto_Bucket_sessions │ sessions │ @plutolang/pluto.Bucket │ Resource ║\r\n╟───────────────────────────────────────────────────────────┼──────────┼─────────────────────────┼─────────────╢\r\n║ router_0_get_1_fn │ - │ - │ Closure ║\r\n║ router_1_get_1_fn │ - │ - │ Closure ║\r\n║ router_2_get_1_fn │ - │ - │ Closure ║\r\n╚═══════════════════════════════════════════════════════════╧══════════╧═════════════════════════╧═════════════╝\r\n\r\n"] +[7.796776, "o", "╔═════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗\r\n║ Entity Relationships ║\r\n╟─────────────────────────────────────────────────────────┬───────────────────────────────────────────────────────────┬───────────────────┬───────────╢\r\n║ Source Entity ID │ Target Entity ID │ Relationship Type │ Operation ║\r\n╟─────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────┼───────────────────┼───────────╢\r\n║ simpleaichat_example_aws__plutolang_pluto_Router_router │ router_0_get_1_fn │ Create │ get ║\r\n║ simpleaichat_example_aws__plutolang_pluto_Router_router │ router_1_get_1_fn │ Create │ get ║\r\n║ simpleaichat_example_aws__plutolang_pluto_Router_router │ router_2_get_1_fn │ Create │ get ║\r\n║ router_0_get_1_fn │ simpleaichat_example_aws__plutolang_pluto_Bucket_sessions │ MethodCall │ get ║\r\n║ router_0_get_1_fn │ simpleaichat_example_aws__plutolang_pluto_Bucket_sessions │ MethodCall │ put ║\r\n╚═════════════════════════════════════════════════════════╧═══════════════════════════════════════════════════════════╧═══════════════════╧═══════════╝\r\n\r\n"] +[7.803189, "o", "\u001b[32m?\u001b[39m \u001b[1mDoes this reference architecture satisfy the design of your application?\u001b[22m\u001b[2m (Y/n)\u001b[22m\u001b[82G"] +[8.930925, "o", "\u001b[2K\u001b[G"] +[8.931244, "o", "\u001b[32m?\u001b[39m \u001b[1mDoes this reference architecture satisfy the design of your application?\u001b[22m\u001b[2m (Y/n)\u001b[22m Y\u001b[83G"] +[9.744059, "o", "\u001b[2K\u001b[G"] +[9.744415, "o", "\u001b[32m?\u001b[39m \u001b[1mDoes this reference architecture satisfy the design of your application?\u001b[22m \u001b[36myes\u001b[39m\u001b[79G"] +[9.745027, "o", "\r\n"] +[9.745142, "o", "\u001b[?25h"] +[9.746067, "o", "\u001b[34mInfo: \u001b[39m Generating the IaC Code and computing modules...\r\n"] +[10.362047, "o", "\u001b[34mInfo: \u001b[39m Applying...\r\n"] +[13.079646, "o", " "] +[14.17864, "o", "\b \b"] +[30.307129, "o", " "] +[31.606592, "o", "\b \b"] +[35.546415, "o", "\u001b[34mInfo: \u001b[39m Successfully applied!\r\n"] +[35.546554, "o", "\u001b[34mInfo: \u001b[39m Here are the resource outputs:\r\n"] +[35.546715, "o", "\u001b[34mInfo: \u001b[39m simpleaichat_example_aws__plutolang_pluto_Router_router: https://jinr43521d.execute-api.us-east-1.amazonaws.com/dev\r\n"] +[35.568094, "o", "\u001b]0;admin@egumomng-0-006000117109:~/zhengshoujian.zsj/pluto-lang/pluto/examples/simpleaichat-example\u0007"] +[35.569003, "o", "\r\r\n\u001b[1;37m[\u001b[m\u001b[1;32madmin\u001b[m\u001b[1;33m@\u001b[m\u001b[1;35megumomng-0-006000117109\u001b[m \u001b[4m~/zhengshoujian.zsj/pluto-lang/pluto/examples/simpleaichat-example\u001b[m\u001b[1;37m]\u001b[m\u001b[1;36m\u001b[m\r\r\n$"] +[43.017747, "o", "export SESSION_ID=$(uuidgen)"] +[43.376681, "o", "\r\n"] +[43.382193, "o", "\u001b]0;admin@egumomng-0-006000117109:~/zhengshoujian.zsj/pluto-lang/pluto/examples/simpleaichat-example\u0007"] +[43.38283, "o", "\r\r\n\u001b[1;37m[\u001b[m\u001b[1;32madmin\u001b[m\u001b[1;33m@\u001b[m\u001b[1;35megumomng-0-006000117109\u001b[m \u001b[4m~/zhengshoujian.zsj/pluto-lang/pluto/examples/simpleaichat-example\u001b[m\u001b[1;37m]\u001b[m\u001b[1;36m\u001b[m\r\r\n$"] +[50.637717, "o", "curl -G https://jinr43521d.execute-api.us-east-1.amazonaws.com/dev/chat \\\r\n"] +[50.63796, "o", "> --data-urlencode 'query=Where is the capital of France?' \\\r\n> "] +[50.638034, "o", "--data-urlencode \"session_id=$SESSION_ID\""] +[50.857375, "o", "\r\n"] +[81.785015, "o", "{\"message\":\"Service Unavailable\"}"] +[81.78753, "o", "\u001b]0;admin@egumomng-0-006000117109:~/zhengshoujian.zsj/pluto-lang/pluto/examples/simpleaichat-example\u0007"] +[81.789196, "o", "\r\r\n\u001b[1;37m[\u001b[m\u001b[1;32madmin\u001b[m\u001b[1;33m@\u001b[m\u001b[1;35megumomng-0-006000117109\u001b[m \u001b[4m~/zhengshoujian.zsj/pluto-lang/pluto/examples/simpleaichat-example\u001b[m\u001b[1;37m]\u001b[m\u001b[1;36m\u001b[m\r\r\n$"] +[89.915906, "o", "curl -G https://jinr43521d.execute-api.us-east-1.amazonaws.com/dev/chat --data-urlencode 'query=Where is the capital of France?' --data-urlencode \"session_id=$SE\rESSION_ID\""] +[90.184803, "o", "\r\n"] +[93.775745, "o", "\"The capital of France is Paris. It is known for its iconic landmarks such as the Eiffel Tower, Louvre Museum, and Notre-Dame Cathedral.\""] +[93.779151, "o", "\u001b]0;admin@egumomng-0-006000117109:~/zhengshoujian.zsj/pluto-lang/pluto/examples/simpleaichat-example\u0007"] +[93.779972, "o", "\r\r\n\u001b[1;37m[\u001b[m\u001b[1;32madmin\u001b[m\u001b[1;33m@\u001b[m\u001b[1;35megumomng-0-006000117109\u001b[m \u001b[4m~/zhengshoujian.zsj/pluto-lang/pluto/examples/simpleaichat-example\u001b[m\u001b[1;37m]\u001b[m\u001b[1;36m\u001b[m\r\r\n$"] +[95.637691, "o", "curl -G https://jinr43521d.execute-api.us-east-1.amazonaws.com/dev/chat \\\r\n"] +[95.637872, "o", "> "] +[95.637934, "o", "--data-urlencode 'query=What is the most famous food there?' \\\r\n> "] +[95.637979, "o", "--data-urlencode \"session_id=$SESSION_ID\""] +[95.789189, "o", "\r\n"] +[100.97087, "o", "\"One of the most famous foods in Paris, France is the croissant. This buttery and flaky pastry is a staple of French cuisine and is often enjoyed for breakfast or as a snack. Paris is also known for its delicious pastries, cheeses, and fine dining restaurants.\""] +[100.973826, "o", "\u001b]0;admin@egumomng-0-006000117109:~/zhengshoujian.zsj/pluto-lang/pluto/examples/simpleaichat-example\u0007"] +[100.974475, "o", "\r\r\n\u001b[1;37m[\u001b[m\u001b[1;32madmin\u001b[m\u001b[1;33m@\u001b[m\u001b[1;35megumomng-0-006000117109\u001b[m \u001b[4m~/zhengshoujian.zsj/pluto-lang/pluto/examples/simpleaichat-example\u001b[m\u001b[1;37m]\u001b[m\u001b[1;36m\u001b[m\r\r\n$"] +[103.311176, "o", "curl -G https://jinr43521d.execute-api.us-east-1.amazonaws.com/dev/qa \\\r\n"] +[103.311418, "o", "> "] +[103.311466, "o", "--data-urlencode 'query=Where is the capital of France?'"] +[103.488492, "o", "\r\n"] +[122.230673, "o", "\"The capital of France is Paris. It is known for its iconic landmarks such as the Eiffel Tower, Louvre Museum, and Notre-Dame Cathedral.\""] +[122.233672, "o", "\u001b]0;admin@egumomng-0-006000117109:~/zhengshoujian.zsj/pluto-lang/pluto/examples/simpleaichat-example\u0007"] +[122.234284, "o", "\r\r\n\u001b[1;37m[\u001b[m\u001b[1;32madmin\u001b[m\u001b[1;33m@\u001b[m\u001b[1;35megumomng-0-006000117109\u001b[m \u001b[4m~/zhengshoujian.zsj/pluto-lang/pluto/examples/simpleaichat-example\u001b[m\u001b[1;37m]\u001b[m\u001b[1;36m\u001b[m\r\r\n$"] +[124.843955, "o", "curl -G https://jinr43521d.execute-api.us-east-1.amazonaws.com/dev/wikipedia \\\r\n"] +[124.844165, "o", "> --data-urlencode 'query=San Francisco tourist attractions'"] +[125.022787, "o", "\r\n"] +[145.950725, "o", "{\"context\": \"Tourist attractions in the United States, Fisherman's Wharf, San Francisco, Lombard Street (San Francisco)\", \"titles\": [\"Tourist attractions in the United States\", \"Fisherman's Wharf, San Francisco\", \"Lombard Street (San Francisco)\"], \"tool\": \"search\", \"response\": \"San Francisco is a city known for its diverse attractions. Some popular tourist spots include Fisherman's Wharf, where you can enjoy seafood restaurants, shops, and beautiful views of the bay. Lombard Street, famous for its steep, winding section, offers a unique driving experience and great photo opportunities. These are just a couple of the many attractions that San Francisco has to offer.\"}"] +[145.953329, "o", "\u001b]0;admin@egumomng-0-006000117109:~/zhengshoujian.zsj/pluto-lang/pluto/examples/simpleaichat-example\u0007"] +[145.954033, "o", "\r\r\n\u001b[1;37m[\u001b[m\u001b[1;32madmin\u001b[m\u001b[1;33m@\u001b[m\u001b[1;35megumomng-0-006000117109\u001b[m \u001b[4m~/zhengshoujian.zsj/pluto-lang/pluto/examples/simpleaichat-example\u001b[m\u001b[1;37m]\u001b[m\u001b[1;36m\u001b[m\r\r\n$"] +[149.867429, "o", "exit\r\n"] diff --git a/examples/simpleaichat-example/package.json b/examples/simpleaichat-example/package.json new file mode 100644 index 00000000..e9908c81 --- /dev/null +++ b/examples/simpleaichat-example/package.json @@ -0,0 +1,20 @@ +{ + "name": "simpleaichat-example", + "private": true, + "version": "0.0.0", + "scripts": { + "test:dev": "pluto test --sim", + "test:prod": "pluto test", + "deploy": "pluto deploy", + "destroy": "pluto destroy" + }, + "dependencies": {}, + "devDependencies": { + "@types/node": "^20", + "typescript": "^5.2.2", + "@plutolang/base": "workspace:*", + "@plutolang/pluto-infra": "workspace:*", + "@pulumi/pulumi": "^3.88.0" + }, + "main": "dist/index.js" +} diff --git a/examples/simpleaichat-example/requirements.txt b/examples/simpleaichat-example/requirements.txt new file mode 100644 index 00000000..71549b6c --- /dev/null +++ b/examples/simpleaichat-example/requirements.txt @@ -0,0 +1,2 @@ +pluto_client +simpleaichat \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 38c0d283..21e8dd8c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -592,6 +592,24 @@ importers: specifier: ^5.2.2 version: 5.2.2 + examples/simpleaichat-example: + devDependencies: + '@plutolang/base': + specifier: workspace:* + version: link:../../packages/base + '@plutolang/pluto-infra': + specifier: workspace:* + version: link:../../packages/pluto-infra + '@pulumi/pulumi': + specifier: ^3.88.0 + version: 3.88.0 + '@types/node': + specifier: ^20 + version: 20.10.4 + typescript: + specifier: ^5.2.2 + version: 5.2.2 + packages/base: dependencies: fs-extra: @@ -4650,11 +4668,7 @@ packages: engines: {node: '>=14'} deprecated: Please use @opentelemetry/api >= 1.3.0 dependencies: - '@opentelemetry/api': 1.7.0 - - /@opentelemetry/api@1.7.0: - resolution: {integrity: sha512-AdY5wvN0P2vXBi3b29hxZgSFvdhdxPB9+f0B6s//P9Q8nibRWeA3cHm8UmLpio9ABigkVHJ5NMPk+Mz8VCCyrw==} - engines: {node: '>=8.0.0'} + '@opentelemetry/api': 1.8.0 /@opentelemetry/api@1.8.0: resolution: {integrity: sha512-I/s6F7yKUDdtMsoBWXJe8Qz40Tui5vsuKCWJEWVL+5q9sSWRzzx6v2KeNsOBEwd94j0eWkpWCH4yB6rZg9Mf0w==}