From 530097c82919504812148ec822251614c31d458d Mon Sep 17 00:00:00 2001 From: theronburger Date: Wed, 20 Sep 2023 17:05:57 +0200 Subject: [PATCH 1/8] add ts-node as dev dependency --- package-scripts.js | 2 +- package.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package-scripts.js b/package-scripts.js index 681ae30..2cb0b20 100644 --- a/package-scripts.js +++ b/package-scripts.js @@ -15,7 +15,7 @@ requiredEnvVariables.forEach((variable) => { }); module.exports = { scripts: { - describe: "nodemon --watch functions --exec ts-node server/describe.ts", + describe: "nodemon --watch functions --exec yarn ts-node server/describe.ts", service: "ts-node server/service.ts", studio: "react-scripts start", start: npsUtils.concurrent.nps("describe", "service", "studio"), diff --git a/package.json b/package.json index fba04e3..b1fb80d 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "xstate": "^4.38.2" }, "devDependencies": { + "ts-node": "^10.9.1", "modularscale-sass": "^3.0.10", "nodemon": "^3.0.1", "typescript": "^5.2.2" From c4cc8c52066a4319db6c328e44b21162c56000a7 Mon Sep 17 00:00:00 2001 From: jeremyphilemon Date: Wed, 20 Sep 2023 15:50:22 +0300 Subject: [PATCH 2/8] Support python functions --- .gitignore | 1 + functions/getCoordinates.py | 30 ++++++++++ functions/getWeather.ts | 8 +-- package-scripts.js | 9 ++- server/python/describe.py | 69 +++++++++++++++++++++++ server/python/descriptions.json | 18 ++++++ server/service.ts | 67 +++++++++++++++------- server/{ => typescript}/describe.ts | 36 ++++++------ server/{ => typescript}/descriptions.json | 8 +-- src/index.js | 5 +- 10 files changed, 204 insertions(+), 47 deletions(-) create mode 100644 functions/getCoordinates.py create mode 100644 server/python/describe.py create mode 100644 server/python/descriptions.json rename server/{ => typescript}/describe.ts (82%) rename server/{ => typescript}/descriptions.json (84%) diff --git a/.gitignore b/.gitignore index c4a487a..6a718c6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /node_modules +/venv .DS_Store .env npm-debug.log* diff --git a/functions/getCoordinates.py b/functions/getCoordinates.py new file mode 100644 index 0000000..49e9a88 --- /dev/null +++ b/functions/getCoordinates.py @@ -0,0 +1,30 @@ +from typing import Optional, TypedDict +import geocoder +import json + + +class SymphonyRequest(TypedDict): + address: str + + +class SymphonyResponse(TypedDict): + lat: float + lng: float + + +def handler(request: SymphonyRequest) -> SymphonyResponse: + address = request["address"] + + g = geocoder.ip(address) + lat, lng = g.latlng + + return { + "lat": lat, + "lng": lng + } + + +if __name__ == "__main__": + request = {"address": "me"} + response = handler("me") + print(json.dumps(response)) diff --git a/functions/getWeather.ts b/functions/getWeather.ts index fa48da3..5aae42f 100644 --- a/functions/getWeather.ts +++ b/functions/getWeather.ts @@ -1,12 +1,12 @@ import axios from "axios"; /** - * @property {string} lat Latitude of the city. - * @property {string} lon Longitude of the city. + * @property {number} lat Latitude of the city. + * @property {number} lon Longitude of the city. */ interface SymphonyRequest { - lat: string; - lon: string; + lat: number; + lon: number; } /** diff --git a/package-scripts.js b/package-scripts.js index 2cb0b20..29f972f 100644 --- a/package-scripts.js +++ b/package-scripts.js @@ -13,12 +13,17 @@ requiredEnvVariables.forEach((variable) => { process.exit(1); } }); + module.exports = { scripts: { - describe: "nodemon --watch functions --exec yarn ts-node server/describe.ts", + describe: { + ts: "nodemon --watch functions --exec yarn ts-node server/typescript/describe.ts", + py: "nodemon --watch functions --exec python3 server/python/describe.py", + all: npsUtils.concurrent.nps("describe.ts", "describe.py"), + }, service: "ts-node server/service.ts", studio: "react-scripts start", - start: npsUtils.concurrent.nps("describe", "service", "studio"), + start: npsUtils.concurrent.nps("describe.all", "service", "studio"), clean: "yarn rimraf node_modules", }, }; diff --git a/server/python/describe.py b/server/python/describe.py new file mode 100644 index 0000000..4316ecb --- /dev/null +++ b/server/python/describe.py @@ -0,0 +1,69 @@ +import ast +import os +import json + + +def extract_function_signature_from_file(file_path): + with open(file_path, 'r') as source: + name = file_path.split("/")[2].replace('.', '-') + source_content = source.read() + node = ast.parse(source_content) + + classes = {cls.name: cls for cls in node.body if isinstance( + cls, ast.ClassDef)} + + properties = {} + required = [] + + for _, class_node in classes.items(): + if class_node.name == 'SymphonyRequest': + for n in class_node.body: + if isinstance(n, ast.AnnAssign): + if isinstance(n.target, ast.Name): + property_name = n.target.id + property_type = ast.get_source_segment( + source_content, n.annotation) + + if 'Optional' in property_type: + property_type = property_type.replace( + 'Optional', '').replace('(', '').replace(')', '') + else: + required.append(property_name) + + properties[property_name] = { + 'type': property_type, + 'description': 'x' + } + + return { + 'name': name, + 'description': '', + 'parameters': { + 'type': 'object', + 'properties': properties + }, + 'required': required + } + + +def get_all_python_files_in_directory(directory): + return [os.path.join(directory, f) for f in os.listdir(directory) if f.endswith('.py')] + + +def main(directory): + python_files = get_all_python_files_in_directory(directory) + descriptions = [] + + for python_file in python_files: + description = extract_function_signature_from_file( + python_file) + + descriptions.append(description) + + with open('./server/python/descriptions.json', 'w') as f: + json.dump(descriptions, f, indent=4) + + +if __name__ == '__main__': + directory = './functions' + main(directory) diff --git a/server/python/descriptions.json b/server/python/descriptions.json new file mode 100644 index 0000000..f1da3bf --- /dev/null +++ b/server/python/descriptions.json @@ -0,0 +1,18 @@ +[ + { + "name": "getCoordinates-py", + "description": "", + "parameters": { + "type": "object", + "properties": { + "address": { + "type": "str", + "description": "x" + } + } + }, + "required": [ + "address" + ] + } +] \ No newline at end of file diff --git a/server/service.ts b/server/service.ts index 280d562..b871ab6 100644 --- a/server/service.ts +++ b/server/service.ts @@ -2,11 +2,13 @@ import { Server } from "ws"; import { createServer } from "http"; import { createMachine, interpret, EventObject, assign } from "xstate"; import OpenAI from "openai"; -import * as functionDescriptions from "./descriptions.json"; +import * as typescriptFunctions from "./typescript/descriptions.json"; +import * as pythonFunctions from "./python/descriptions.json"; import { pipe } from "fp-ts/lib/function"; import * as RAR from "fp-ts/ReadonlyArray"; import * as O from "fp-ts/Option"; import * as dotenv from "dotenv"; +import { exec } from "child_process"; dotenv.config(); @@ -61,25 +63,52 @@ const machine = createMachine( ); if (O.isSome(functionCall)) { - const name = functionCall.value.name; + const name = functionCall.value.name.replace("-", "."); const args = JSON.parse(functionCall.value.arguments); - import(`../functions/${name}.ts`) - .then(async (module) => { - const result = await module.default(args); - - const message = { - role: "function", - name, - content: JSON.stringify(result), - }; - - resolve(message); - }) - .catch((err) => { - console.error(`Failed to load function ${name}:`, err); - resolve(null); - }); + if (name.includes(".ts")) { + import(`../functions/${name}`) + .then(async (module) => { + const result = await module.default(args); + + const message = { + role: "function", + name: name.replace(".", "-"), + content: JSON.stringify(result), + }; + + resolve(message); + }) + .catch((err) => { + console.error(`Failed to load function ${name}:`, err); + resolve(null); + }); + } else if (name.includes(".py")) { + const pythonInterpreterPath = "venv/bin/python3"; + const pythonScriptPath = `functions/${name}`; + + exec( + `${pythonInterpreterPath} ${pythonScriptPath}`, + (error, stdout) => { + if (error) { + console.error( + `Failed to execute python script ${name}:`, + error + ); + + resolve(null); + } else { + const message = { + role: "function", + name: name.replace(".", "-"), + content: stdout, + }; + + resolve(message); + } + } + ); + } } else { resolve(null); } @@ -102,7 +131,7 @@ const machine = createMachine( openai.chat.completions.create({ messages: [...context.messages, event.data], model: "gpt-4", - functions: functionDescriptions, + functions: [...typescriptFunctions, ...pythonFunctions], }), onDone: { target: "function", diff --git a/server/describe.ts b/server/typescript/describe.ts similarity index 82% rename from server/describe.ts rename to server/typescript/describe.ts index f17e485..3b66027 100644 --- a/server/describe.ts +++ b/server/typescript/describe.ts @@ -47,7 +47,7 @@ function generateMetadata(sourceFile: ts.SourceFile, fileName: string) { } }); - metadata.name = fileName.replace(".ts", ""); + metadata.name = fileName.replace(".", "-"); return metadata; } @@ -100,21 +100,23 @@ const readFiles = new Promise((resolve, reject) => { reject(error); } - files.forEach((fileName) => { - const code = fs.readFileSync( - `${FUNCTIONS_DIRECTORY}/${fileName}`, - "utf8" - ); - const sourceFile = ts.createSourceFile( - "temp.ts", - code, - ts.ScriptTarget.Latest, - true - ); - - const metadata = generateMetadata(sourceFile, fileName); - metadatas.push(metadata); - }); + files + .filter((fileName) => fileName.endsWith(".ts")) + .forEach((fileName) => { + const code = fs.readFileSync( + `${FUNCTIONS_DIRECTORY}/${fileName}`, + "utf8" + ); + const sourceFile = ts.createSourceFile( + "temp.ts", + code, + ts.ScriptTarget.Latest, + true + ); + + const metadata = generateMetadata(sourceFile, fileName); + metadatas.push(metadata); + }); resolve(metadatas); }); @@ -123,7 +125,7 @@ const readFiles = new Promise((resolve, reject) => { readFiles .then((metadatas) => { fs.writeFileSync( - "./server/descriptions.json", + "./server/typescript/descriptions.json", JSON.stringify(metadatas, null, 2) ); }) diff --git a/server/descriptions.json b/server/typescript/descriptions.json similarity index 84% rename from server/descriptions.json rename to server/typescript/descriptions.json index 04bd62d..4edabc5 100644 --- a/server/descriptions.json +++ b/server/typescript/descriptions.json @@ -1,16 +1,16 @@ [ { - "name": "getWeather", + "name": "getWeather-ts", "description": "Gets temperature of a city.", "parameters": { "type": "object", "properties": { "lat": { - "type": "string", + "type": "number", "description": "Latitude of the city." }, "lon": { - "type": "string", + "type": "number", "description": "Longitude of the city." } }, @@ -21,7 +21,7 @@ } }, { - "name": "kelvinToCelsius", + "name": "kelvinToCelsius-ts", "description": "Converts Kelvin to Celsius.", "parameters": { "type": "object", diff --git a/src/index.js b/src/index.js index 05f949b..889ddfa 100644 --- a/src/index.js +++ b/src/index.js @@ -9,7 +9,10 @@ const Message = ({ message }) => {
{message.function_call ? (
-
{`Calling ${message.function_call.name}`}
+
{`Calling ${message.function_call.name.replace( + "-", + "." + )}`}
             {JSON.stringify(
               JSON.parse(message.function_call.arguments),

From fda871a7bd2b0510f25b336fd63977fc53a91fc9 Mon Sep 17 00:00:00 2001
From: jeremyphilemon 
Date: Thu, 21 Sep 2023 01:56:59 +0300
Subject: [PATCH 3/8] Convert to OpenAI schema types

---
 functions/getCoordinates.py     |  5 +-
 server/python/describe.py       | 19 +++++--
 server/python/descriptions.json |  6 +--
 yarn.lock                       | 93 ++++++++++++++++++++++++++++++++-
 4 files changed, 114 insertions(+), 9 deletions(-)

diff --git a/functions/getCoordinates.py b/functions/getCoordinates.py
index 49e9a88..6474156 100644
--- a/functions/getCoordinates.py
+++ b/functions/getCoordinates.py
@@ -4,7 +4,7 @@
 
 
 class SymphonyRequest(TypedDict):
-    address: str
+    address: str  # The IP address to get the coordinates of. Defaults to 'me'.
 
 
 class SymphonyResponse(TypedDict):
@@ -13,6 +13,9 @@ class SymphonyResponse(TypedDict):
 
 
 def handler(request: SymphonyRequest) -> SymphonyResponse:
+    """
+    Returns the latitude and longitude of the given IP address.
+    """
     address = request["address"]
 
     g = geocoder.ip(address)
diff --git a/server/python/describe.py b/server/python/describe.py
index 4316ecb..279b43c 100644
--- a/server/python/describe.py
+++ b/server/python/describe.py
@@ -2,6 +2,11 @@
 import os
 import json
 
+OPENAI_TYPES = {
+    'str': 'string',
+    'int': 'integer',
+}
+
 
 def extract_function_signature_from_file(file_path):
     with open(file_path, 'r') as source:
@@ -11,9 +16,11 @@ def extract_function_signature_from_file(file_path):
 
         classes = {cls.name: cls for cls in node.body if isinstance(
             cls, ast.ClassDef)}
+        functions = [f for f in node.body if isinstance(f, ast.FunctionDef)]
 
         properties = {}
         required = []
+        function_descriptions = {}
 
         for _, class_node in classes.items():
             if class_node.name == 'SymphonyRequest':
@@ -30,14 +37,20 @@ def extract_function_signature_from_file(file_path):
                             else:
                                 required.append(property_name)
 
+                            property_description = n.value.s if isinstance(
+                                n.value, ast.Str) else 'No description provided'
+
                             properties[property_name] = {
-                                'type': property_type,
-                                'description': 'x'
+                                'type': OPENAI_TYPES[property_type],
+                                'description': property_description
                             }
 
+        for function in functions:
+            function_descriptions[function.name] = ast.get_docstring(function)
+
         return {
             'name': name,
-            'description': '',
+            'description': function_descriptions['handler'],
             'parameters': {
                 'type': 'object',
                 'properties': properties
diff --git a/server/python/descriptions.json b/server/python/descriptions.json
index f1da3bf..4131ba1 100644
--- a/server/python/descriptions.json
+++ b/server/python/descriptions.json
@@ -1,13 +1,13 @@
 [
     {
         "name": "getCoordinates-py",
-        "description": "",
+        "description": "Returns the latitude and longitude of the given IP address.",
         "parameters": {
             "type": "object",
             "properties": {
                 "address": {
-                    "type": "str",
-                    "description": "x"
+                    "type": "string",
+                    "description": "No description provided"
                 }
             }
         },
diff --git a/yarn.lock b/yarn.lock
index edc81ab..f3386b1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1147,6 +1147,13 @@
   resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
   integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
 
+"@cspotcode/source-map-support@^0.8.0":
+  version "0.8.1"
+  resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1"
+  integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==
+  dependencies:
+    "@jridgewell/trace-mapping" "0.3.9"
+
 "@csstools/normalize.css@*":
   version "12.0.0"
   resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-12.0.0.tgz#a9583a75c3f150667771f30b60d9f059473e62c4"
@@ -1544,7 +1551,7 @@
     "@jridgewell/sourcemap-codec" "^1.4.10"
     "@jridgewell/trace-mapping" "^0.3.9"
 
-"@jridgewell/resolve-uri@^3.1.0":
+"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0":
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721"
   integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==
@@ -1567,6 +1574,14 @@
   resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
   integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
 
+"@jridgewell/trace-mapping@0.3.9":
+  version "0.3.9"
+  resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9"
+  integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==
+  dependencies:
+    "@jridgewell/resolve-uri" "^3.0.3"
+    "@jridgewell/sourcemap-codec" "^1.4.10"
+
 "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9":
   version "0.3.19"
   resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz#f8a3249862f91be48d3127c3cfe992f79b4b8811"
@@ -1807,6 +1822,26 @@
   resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad"
   integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==
 
+"@tsconfig/node10@^1.0.7":
+  version "1.0.9"
+  resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2"
+  integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==
+
+"@tsconfig/node12@^1.0.7":
+  version "1.0.11"
+  resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d"
+  integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==
+
+"@tsconfig/node14@^1.0.0":
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1"
+  integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==
+
+"@tsconfig/node16@^1.0.2":
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9"
+  integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==
+
 "@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14":
   version "7.20.1"
   resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.1.tgz#916ecea274b0c776fec721e333e55762d3a9614b"
@@ -2376,12 +2411,17 @@ acorn-walk@^7.1.1:
   resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc"
   integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==
 
+acorn-walk@^8.1.1:
+  version "8.2.0"
+  resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1"
+  integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==
+
 acorn@^7.1.1:
   version "7.4.1"
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
   integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
 
-acorn@^8.2.4, acorn@^8.7.1, acorn@^8.8.2, acorn@^8.9.0:
+acorn@^8.2.4, acorn@^8.4.1, acorn@^8.7.1, acorn@^8.8.2, acorn@^8.9.0:
   version "8.10.0"
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5"
   integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==
@@ -2516,6 +2556,11 @@ anymatch@^3.0.3, anymatch@~3.1.2:
     normalize-path "^3.0.0"
     picomatch "^2.0.4"
 
+arg@^4.1.0:
+  version "4.1.3"
+  resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
+  integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
+
 arg@^5.0.2:
   version "5.0.2"
   resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c"
@@ -3426,6 +3471,11 @@ cpy@^4.0.0:
     object-assign "^4.0.1"
     pinkie-promise "^2.0.0"
 
+create-require@^1.1.0:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
+  integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
+
 cross-env@^3.1.4:
   version "3.2.4"
   resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-3.2.4.tgz#9e0585f277864ed421ce756f81a980ff0d698aba"
@@ -3802,6 +3852,11 @@ diff-sequences@^27.5.1:
   resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327"
   integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==
 
+diff@^4.0.1:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
+  integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
+
 digest-fetch@^1.3.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/digest-fetch/-/digest-fetch-1.3.0.tgz#898e69264d00012a23cf26e8a3e40320143fc661"
@@ -6574,6 +6629,11 @@ make-dir@^4.0.0:
   dependencies:
     semver "^7.5.3"
 
+make-error@^1.1.1:
+  version "1.3.6"
+  resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
+  integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
+
 makeerror@1.0.12:
   version "1.0.12"
   resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a"
@@ -9510,6 +9570,25 @@ ts-interface-checker@^0.1.9:
   resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699"
   integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
 
+ts-node@^10.9.1:
+  version "10.9.1"
+  resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b"
+  integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==
+  dependencies:
+    "@cspotcode/source-map-support" "^0.8.0"
+    "@tsconfig/node10" "^1.0.7"
+    "@tsconfig/node12" "^1.0.7"
+    "@tsconfig/node14" "^1.0.0"
+    "@tsconfig/node16" "^1.0.2"
+    acorn "^8.4.1"
+    acorn-walk "^8.1.1"
+    arg "^4.1.0"
+    create-require "^1.1.0"
+    diff "^4.0.1"
+    make-error "^1.1.1"
+    v8-compile-cache-lib "^3.0.1"
+    yn "3.1.1"
+
 tsconfig-paths@^3.14.2:
   version "3.14.2"
   resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz#6e32f1f79412decd261f92d633a9dc1cfa99f088"
@@ -9763,6 +9842,11 @@ uuid@^8.3.2:
   resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
   integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
 
+v8-compile-cache-lib@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
+  integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==
+
 v8-to-istanbul@^8.1.0:
   version "8.1.1"
   resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz#77b752fd3975e31bbcef938f85e9bd1c7a8d60ed"
@@ -10388,6 +10472,11 @@ yargs@^16.2.0:
     y18n "^5.0.5"
     yargs-parser "^20.2.2"
 
+yn@3.1.1:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
+  integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
+
 yocto-queue@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"

From ece498b5eeaf9a6cf8cfc717d6964dcb1cd32a4b Mon Sep 17 00:00:00 2001
From: Vishhvak Srinivasan 
Date: Thu, 21 Sep 2023 03:06:23 -0400
Subject: [PATCH 4/8] Add pydantic support for function descriptions

---
 server/python/describe.py | 98 +++++++++++++--------------------------
 1 file changed, 32 insertions(+), 66 deletions(-)

diff --git a/server/python/describe.py b/server/python/describe.py
index 279b43c..edc35cd 100644
--- a/server/python/describe.py
+++ b/server/python/describe.py
@@ -1,82 +1,48 @@
-import ast
 import os
 import json
+from pydantic import BaseModel
+from typing import Any, Callable, Type
+import sys
 
-OPENAI_TYPES = {
-    'str': 'string',
-    'int': 'integer',
-}
+sys.path.insert(0, os.path.abspath('functions'))
 
+def generate_function_description(name, function: Callable[..., Any], request_model: Type[BaseModel], response_model: Type[BaseModel]) -> dict:
+    request_schema = request_model.model_json_schema()
+    response_schema = response_model.model_json_schema()
 
-def extract_function_signature_from_file(file_path):
-    with open(file_path, 'r') as source:
-        name = file_path.split("/")[2].replace('.', '-')
-        source_content = source.read()
-        node = ast.parse(source_content)
+    # Remove the title field
+    request_schema.pop('title', None)
+    response_schema.pop('title', None)
 
-        classes = {cls.name: cls for cls in node.body if isinstance(
-            cls, ast.ClassDef)}
-        functions = [f for f in node.body if isinstance(f, ast.FunctionDef)]
-
-        properties = {}
-        required = []
-        function_descriptions = {}
-
-        for _, class_node in classes.items():
-            if class_node.name == 'SymphonyRequest':
-                for n in class_node.body:
-                    if isinstance(n, ast.AnnAssign):
-                        if isinstance(n.target, ast.Name):
-                            property_name = n.target.id
-                            property_type = ast.get_source_segment(
-                                source_content, n.annotation)
-
-                            if 'Optional' in property_type:
-                                property_type = property_type.replace(
-                                    'Optional', '').replace('(', '').replace(')', '')
-                            else:
-                                required.append(property_name)
-
-                            property_description = n.value.s if isinstance(
-                                n.value, ast.Str) else 'No description provided'
-
-                            properties[property_name] = {
-                                'type': OPENAI_TYPES[property_type],
-                                'description': property_description
-                            }
-
-        for function in functions:
-            function_descriptions[function.name] = ast.get_docstring(function)
-
-        return {
-            'name': name,
-            'description': function_descriptions['handler'],
-            'parameters': {
-                'type': 'object',
-                'properties': properties
-            },
-            'required': required
-        }
-
-
-def get_all_python_files_in_directory(directory):
-    return [os.path.join(directory, f) for f in os.listdir(directory) if f.endswith('.py')]
+    # Reorder the fields so that 'type' comes first
+    request_schema = {'type': request_schema['type'], **request_schema}
+    response_schema = {'type': response_schema['type'], **response_schema}
 
+    
+    function_description = {
+        "name": name,
+        "description": function.__doc__,
+        "parameters": request_schema,
+        "returns": response_schema,
+    }
+    return function_description
 
 def main(directory):
-    python_files = get_all_python_files_in_directory(directory)
     descriptions = []
-
-    for python_file in python_files:
-        description = extract_function_signature_from_file(
-            python_file)
-
-        descriptions.append(description)
+    for filename in os.listdir(directory):
+        if filename.endswith('.py'):
+            module_name = filename[:-3] 
+            module = __import__(f'{module_name}')
+            function = getattr(module, 'handler')
+            symphony_request = getattr(module, 'SymphonyRequest')
+            symphony_response = getattr(module, 'SymphonyResponse')
+            fn_name = module_name + '-py'
+            description = generate_function_description(fn_name, function, symphony_request, symphony_response)
+            descriptions.append(description)
 
     with open('./server/python/descriptions.json', 'w') as f:
         json.dump(descriptions, f, indent=4)
 
-
 if __name__ == '__main__':
-    directory = './functions'
+    directory = 'functions'
     main(directory)

From f79bd0ec3a5104b8e621b565a5c0984633b6c351 Mon Sep 17 00:00:00 2001
From: Vishhvak Srinivasan 
Date: Thu, 21 Sep 2023 03:07:09 -0400
Subject: [PATCH 5/8] Add examples for py files

---
 functions/getCoordinates.py | 23 ++++++++++-------------
 functions/getDogFacts.py    | 25 +++++++++++++++++++++++++
 functions/getMatrixMul.py   | 28 ++++++++++++++++++++++++++++
 3 files changed, 63 insertions(+), 13 deletions(-)
 create mode 100644 functions/getDogFacts.py
 create mode 100644 functions/getMatrixMul.py

diff --git a/functions/getCoordinates.py b/functions/getCoordinates.py
index 6474156..e231da6 100644
--- a/functions/getCoordinates.py
+++ b/functions/getCoordinates.py
@@ -1,33 +1,30 @@
 from typing import Optional, TypedDict
 import geocoder
 import json
+import sys
+from pydantic import BaseModel
 
-
-class SymphonyRequest(TypedDict):
+class SymphonyRequest(BaseModel):
     address: str  # The IP address to get the coordinates of. Defaults to 'me'.
 
-
-class SymphonyResponse(TypedDict):
+class SymphonyResponse(BaseModel):
     lat: float
     lng: float
 
-
 def handler(request: SymphonyRequest) -> SymphonyResponse:
     """
     Returns the latitude and longitude of the given IP address.
     """
-    address = request["address"]
+    address = request.address
 
     g = geocoder.ip(address)
     lat, lng = g.latlng
 
-    return {
-        "lat": lat,
-        "lng": lng
-    }
+    return SymphonyResponse(lat=lat, lng=lng)
 
 
 if __name__ == "__main__":
-    request = {"address": "me"}
-    response = handler("me")
-    print(json.dumps(response))
+    args = json.loads(sys.argv[1])
+    request = SymphonyRequest(address=args['address'])
+    response = handler(request)
+    print(response.json())
diff --git a/functions/getDogFacts.py b/functions/getDogFacts.py
new file mode 100644
index 0000000..6b004da
--- /dev/null
+++ b/functions/getDogFacts.py
@@ -0,0 +1,25 @@
+import requests
+import json
+import sys
+from pydantic import BaseModel
+
+class SymphonyRequest(BaseModel):
+    pass  # No input required for this function
+
+class SymphonyResponse(BaseModel):
+    facts: list  # The list of dog facts
+
+def handler(request: SymphonyRequest) -> SymphonyResponse:
+    """
+    Fetches dog facts from an API.
+    """
+    response = requests.get('https://dog-api.kinduff.com/api/facts')
+    data = response.json()
+
+    return SymphonyResponse(facts=data['facts'])
+
+if __name__ == "__main__":
+    args = json.loads(sys.argv[1])
+    request = SymphonyRequest()
+    response = handler(request)
+    print(response.json())
\ No newline at end of file
diff --git a/functions/getMatrixMul.py b/functions/getMatrixMul.py
new file mode 100644
index 0000000..e4f85ab
--- /dev/null
+++ b/functions/getMatrixMul.py
@@ -0,0 +1,28 @@
+from typing import List
+from pydantic import BaseModel
+import json
+import sys
+
+class SymphonyRequest(BaseModel):
+    matrix1: List[List[float]]  # The first matrix to be multiplied.
+    matrix2: List[List[float]]  # The second matrix to be multiplied.
+
+class SymphonyResponse(BaseModel):
+    result: List[List[float]]  # The result of the matrix multiplication.
+
+def handler(request: SymphonyRequest) -> SymphonyResponse:
+    """
+    Multiplies two matrices.
+    """
+    matrix1 = request.matrix1
+    matrix2 = request.matrix2
+
+    result = [[sum(a*b for a, b in zip(X_row, Y_col)) for Y_col in zip(*matrix2)] for X_row in matrix1]
+
+    return SymphonyResponse(result=result)
+
+if __name__ == "__main__":
+    args = json.loads(sys.argv[1])
+    request = SymphonyRequest(matrix1=args['matrix1'], matrix2=args['matrix2'])
+    response = handler(request)
+    print(response.json())
\ No newline at end of file

From 57a282bc36228f5392224cc38716ab05dc270e62 Mon Sep 17 00:00:00 2001
From: Vishhvak Srinivasan 
Date: Thu, 21 Sep 2023 03:07:40 -0400
Subject: [PATCH 6/8] Add py execution with args

---
 .gitignore        | 3 ++-
 server/service.ts | 5 +++--
 2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/.gitignore b/.gitignore
index 6a718c6..9d2eb84 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,4 +4,5 @@
 .env
 npm-debug.log*
 yarn-debug.log*
-yarn-error.log*
\ No newline at end of file
+yarn-error.log*
+__pycache__/
\ No newline at end of file
diff --git a/server/service.ts b/server/service.ts
index b871ab6..3801f68 100644
--- a/server/service.ts
+++ b/server/service.ts
@@ -86,9 +86,10 @@ const machine = createMachine(
                 } else if (name.includes(".py")) {
                   const pythonInterpreterPath = "venv/bin/python3";
                   const pythonScriptPath = `functions/${name}`;
-
+                  const argsString = JSON.stringify(args);
+                  
                   exec(
-                    `${pythonInterpreterPath} ${pythonScriptPath}`,
+                    `${pythonInterpreterPath} ${pythonScriptPath} '${argsString}'`,
                     (error, stdout) => {
                       if (error) {
                         console.error(

From 9e1b691d54922578cb4e4d28b4271a62114dc3e4 Mon Sep 17 00:00:00 2001
From: Vishhvak Srinivasan 
Date: Thu, 21 Sep 2023 03:08:44 -0400
Subject: [PATCH 7/8] Add example descriptions json for python

---
 server/python/descriptions.json | 103 +++++++++++++++++++++++++++++---
 1 file changed, 96 insertions(+), 7 deletions(-)

diff --git a/server/python/descriptions.json b/server/python/descriptions.json
index 4131ba1..993fb33 100644
--- a/server/python/descriptions.json
+++ b/server/python/descriptions.json
@@ -1,18 +1,107 @@
 [
+    {
+        "name": "getDogFacts-py",
+        "description": "\n    Fetches dog facts from an API.\n    ",
+        "parameters": {
+            "type": "object",
+            "properties": {}
+        },
+        "returns": {
+            "type": "object",
+            "properties": {
+                "facts": {
+                    "items": {},
+                    "title": "Facts",
+                    "type": "array"
+                }
+            },
+            "required": [
+                "facts"
+            ]
+        }
+    },
+    {
+        "name": "getMatrixMul-py",
+        "description": "\n    Multiplies two matrices.\n    ",
+        "parameters": {
+            "type": "object",
+            "properties": {
+                "matrix1": {
+                    "items": {
+                        "items": {
+                            "type": "number"
+                        },
+                        "type": "array"
+                    },
+                    "title": "Matrix1",
+                    "type": "array"
+                },
+                "matrix2": {
+                    "items": {
+                        "items": {
+                            "type": "number"
+                        },
+                        "type": "array"
+                    },
+                    "title": "Matrix2",
+                    "type": "array"
+                }
+            },
+            "required": [
+                "matrix1",
+                "matrix2"
+            ]
+        },
+        "returns": {
+            "type": "object",
+            "properties": {
+                "result": {
+                    "items": {
+                        "items": {
+                            "type": "number"
+                        },
+                        "type": "array"
+                    },
+                    "title": "Result",
+                    "type": "array"
+                }
+            },
+            "required": [
+                "result"
+            ]
+        }
+    },
     {
         "name": "getCoordinates-py",
-        "description": "Returns the latitude and longitude of the given IP address.",
+        "description": "\n    Returns the latitude and longitude of the given IP address.\n    ",
         "parameters": {
             "type": "object",
             "properties": {
                 "address": {
-                    "type": "string",
-                    "description": "No description provided"
+                    "title": "Address",
+                    "type": "string"
                 }
-            }
+            },
+            "required": [
+                "address"
+            ]
         },
-        "required": [
-            "address"
-        ]
+        "returns": {
+            "type": "object",
+            "properties": {
+                "lat": {
+                    "title": "Lat",
+                    "type": "number"
+                },
+                "lng": {
+                    "title": "Lng",
+                    "type": "number"
+                }
+            },
+            "required": [
+                "lat",
+                "lng"
+            ]
+        }
     }
 ]
\ No newline at end of file

From 9c3c7b1ad760ee055c4b5648fa810af8df1dd706 Mon Sep 17 00:00:00 2001
From: Vishhvak Srinivasan 
Date: Thu, 21 Sep 2023 04:03:27 -0400
Subject: [PATCH 8/8] Add chain of thought wrapper around intermediate outputs

---
 src/index.js   | 40 +++++++++++++++++++++++++++++++++++++---
 src/index.scss | 25 ++++++++++++++++++++++++-
 2 files changed, 61 insertions(+), 4 deletions(-)

diff --git a/src/index.js b/src/index.js
index 889ddfa..4770096 100644
--- a/src/index.js
+++ b/src/index.js
@@ -35,6 +35,17 @@ const Message = ({ message }) => {
   );
 };
 
+const ChainOfThought = ({ messages }) => {
+  return (
+    
+ Chain of Thought + {messages.map((message, index) => ( + + ))} +
+ ); +}; + const App = () => { const socketRef = useRef(null); const [messages, setMessages] = useState([]); @@ -86,10 +97,33 @@ const App = () => {
+
- {messages.map((message, index) => ( - - ))} + {messages.map((message, index, allMessages) => { + if (message.role === "user") { + // Find all function messages after this user message and before the next user message + const functionMessages = []; + for (let i = index + 1; i < allMessages.length; i++) { + if (allMessages[i].role === "user") break; + if ( + allMessages[i].function_call || + allMessages[i].role === "function" + ) { + functionMessages.push(allMessages[i]); + } + } + return ( + + + {functionMessages.length > 0 && ( + + )} + + ); + } else if (!message.function_call && message.role !== "function") { + return ; + } + })}
diff --git a/src/index.scss b/src/index.scss index a8c10d3..8b1b401 100644 --- a/src/index.scss +++ b/src/index.scss @@ -29,6 +29,25 @@ body { font-family: "DM Sans", sans-serif; } +details { + margin: 10px 0; + border: 1px solid $neutral-300; + border-radius: 5px; + padding: 10px; + background-color: rgba(245, 245, 245, 0.7); /* Light gray with 70% opacity */ + + .message .avatar { + margin-top: 10px; /* Add a top margin to align with the message text */ + } +} + +summary { + font-size: 0.9em; + font-weight: bold; + color: $neutral-700; + cursor: pointer; +} + .page { height: 100dvh; width: 100vw; @@ -45,7 +64,7 @@ body { .name { font-size: ms(-1); - color: $neutral-500; + color: $neutral-600; } .status { @@ -119,6 +138,9 @@ body { } .function { + margin-top: 10px; + font-size: 0.9em; + color: $neutral-600; border-radius: ms(-8); } @@ -153,4 +175,5 @@ body { .send { font-size: ms(0); } + }