From 4b4e7e051009f925983a544c655f67aee9e6ab79 Mon Sep 17 00:00:00 2001
From: Travis Fischer <fisch0920@gmail.com>
Date: Sun, 2 Jun 2024 17:30:39 -0500
Subject: [PATCH] feat: add openai example

---
 examples/ai-sdk/weather.ts    |  2 +-
 examples/dexter/weather.ts    |  2 +-
 examples/genkit/weather.ts    |  2 +-
 examples/langchain/weather.ts |  2 +-
 examples/openai/weather.ts    | 57 +++++++++++++++++++++++++
 examples/package.json         |  2 +
 package.json                  |  5 ++-
 pnpm-lock.yaml                | 80 ++++++++++++++++++++++++++++++-----
 readme.md                     | 12 ++----
 src/ai-function-set.ts        | 11 +++++
 src/ai-tool-set.ts            |  8 ++++
 src/types.ts                  |  4 +-
 tsconfig.json                 |  5 ++-
 13 files changed, 166 insertions(+), 26 deletions(-)
 create mode 100644 examples/openai/weather.ts

diff --git a/examples/ai-sdk/weather.ts b/examples/ai-sdk/weather.ts
index 35b60a8..0ef7ae4 100644
--- a/examples/ai-sdk/weather.ts
+++ b/examples/ai-sdk/weather.ts
@@ -15,7 +15,7 @@ async function main() {
     tools: createAISDKTools(weather),
     toolChoice: 'required',
     temperature: 0,
-    system: 'You are a weather assistant. Be as concise as possible.',
+    system: 'You are a helpful assistant. Be as concise as possible.',
     prompt: 'What is the weather in San Francisco?'
   })
 
diff --git a/examples/dexter/weather.ts b/examples/dexter/weather.ts
index 78ac72d..511ee30 100644
--- a/examples/dexter/weather.ts
+++ b/examples/dexter/weather.ts
@@ -12,7 +12,7 @@ async function main() {
   const runner = createAIRunner({
     chatModel: new ChatModel({ params: { model: 'gpt-4o', temperature: 0 } }),
     functions: createDexterFunctions(weather),
-    systemMessage: 'You are a weather assistant. Be as concise as possible.'
+    systemMessage: 'You are a helpful assistant. Be as concise as possible.'
   })
 
   const result = await runner('What is the weather in San Francisco?')
diff --git a/examples/genkit/weather.ts b/examples/genkit/weather.ts
index 6371592..7f6557f 100644
--- a/examples/genkit/weather.ts
+++ b/examples/genkit/weather.ts
@@ -23,7 +23,7 @@ async function main() {
         role: 'system',
         content: [
           {
-            text: 'You are a weather assistant. Be as concise as possible.'
+            text: 'You are a helpful assistant. Be as concise as possible.'
           }
         ]
       }
diff --git a/examples/langchain/weather.ts b/examples/langchain/weather.ts
index 7508173..112c90a 100644
--- a/examples/langchain/weather.ts
+++ b/examples/langchain/weather.ts
@@ -16,7 +16,7 @@ async function main() {
     llm: new ChatOpenAI({ model: 'gpt-4o', temperature: 0 }),
     tools,
     prompt: ChatPromptTemplate.fromMessages([
-      ['system', 'You are a weather assistant. Be as concise as possible.'],
+      ['system', 'You are a helpful assistant. Be as concise as possible.'],
       ['placeholder', '{chat_history}'],
       ['human', '{input}'],
       ['placeholder', '{agent_scratchpad}']
diff --git a/examples/openai/weather.ts b/examples/openai/weather.ts
new file mode 100644
index 0000000..39ac02d
--- /dev/null
+++ b/examples/openai/weather.ts
@@ -0,0 +1,57 @@
+#!/usr/bin/env node
+import 'dotenv/config'
+
+import OpenAI from 'openai'
+import { default as assert } from 'tiny-invariant'
+
+import { WeatherClient } from '../../src/index.js'
+
+async function main() {
+  const weather = new WeatherClient()
+  const openai = new OpenAI()
+
+  const messages: OpenAI.ChatCompletionMessageParam[] = [
+    {
+      role: 'system',
+      content: 'You are a helpful assistant. Be as concise as possible.'
+    },
+    { role: 'user', content: 'What is the weather in San Francisco?' }
+  ]
+
+  const res0 = await openai.chat.completions.create({
+    messages,
+    model: 'gpt-4o',
+    temperature: 0,
+    tools: weather.tools.specs,
+    tool_choice: 'required'
+  })
+  const message0 = res0.choices[0]?.message!
+  console.log(JSON.stringify(message0, null, 2))
+  assert(message0.role === 'assistant')
+  assert(message0.tool_calls?.[0]?.function?.name === 'get_current_weather')
+
+  const getCurrentWeather = weather.tools.get('get_current_weather')!.function
+  assert(getCurrentWeather)
+
+  const toolParams = message0.tool_calls[0].function.arguments
+  assert(typeof toolParams === 'string')
+  const toolResult = await getCurrentWeather(toolParams)
+
+  messages.push(message0)
+  messages.push({
+    role: 'tool',
+    tool_call_id: message0.tool_calls[0].id,
+    content: JSON.stringify(toolResult)
+  })
+
+  const res1 = await openai.chat.completions.create({
+    messages,
+    model: 'gpt-4o',
+    temperature: 0,
+    tools: weather.tools.specs
+  })
+  const message1 = res1.choices[0].message
+  console.log(JSON.stringify(message1, null, 2))
+}
+
+await main()
diff --git a/examples/package.json b/examples/package.json
index 89b28cf..5e90477 100644
--- a/examples/package.json
+++ b/examples/package.json
@@ -28,12 +28,14 @@
     "@dexaai/dexter": "^2.0.3",
     "@genkit-ai/ai": "^0.5.2",
     "@genkit-ai/core": "^0.5.2",
+    "@instructor-ai/instructor": "^1.3.0",
     "@langchain/core": "^0.2.5",
     "@langchain/openai": "^0.1.1",
     "ai": "^3.1.22",
     "dotenv": "^16.4.5",
     "genkitx-openai": "^0.9.0",
     "langchain": "^0.2.4",
+    "openai": "^4.47.3",
     "zod": "^3.23.3"
   }
 }
diff --git a/package.json b/package.json
index 5c38bb5..ba061f3 100644
--- a/package.json
+++ b/package.json
@@ -72,11 +72,12 @@
   },
   "devDependencies": {
     "@dexaai/dexter": "^2.0.3",
-    "@fisch0920/eslint-config": "^1.3.1",
+    "@fisch0920/eslint-config": "^1.3.3",
     "@genkit-ai/ai": "^0.5.2",
+    "@instructor-ai/instructor": "^1.3.0",
     "@langchain/core": "^0.2.5",
     "@total-typescript/ts-reset": "^0.5.1",
-    "@types/node": "^20.12.7",
+    "@types/node": "^20.13.0",
     "ai": "^3.1.22",
     "del-cli": "^5.1.0",
     "dotenv": "^16.4.5",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f199db3..5f1eacd 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -46,11 +46,14 @@ importers:
         specifier: ^2.0.3
         version: 2.1.0
       '@fisch0920/eslint-config':
-        specifier: ^1.3.1
-        version: 1.3.2(eslint@8.57.0)(typescript@5.4.5)
+        specifier: ^1.3.3
+        version: 1.3.3(eslint@8.57.0)(typescript@5.4.5)
       '@genkit-ai/ai':
         specifier: ^0.5.2
         version: 0.5.2
+      '@instructor-ai/instructor':
+        specifier: ^1.3.0
+        version: 1.3.0(openai@4.47.3)(zod@3.23.8)
       '@langchain/core':
         specifier: ^0.2.5
         version: 0.2.5(langchain@0.2.4(openai@4.47.3))(openai@4.47.3)
@@ -58,7 +61,7 @@ importers:
         specifier: ^0.5.1
         version: 0.5.1
       '@types/node':
-        specifier: ^20.12.7
+        specifier: ^20.13.0
         version: 20.13.0
       ai:
         specifier: ^3.1.22
@@ -126,6 +129,9 @@ importers:
       '@genkit-ai/core':
         specifier: ^0.5.2
         version: 0.5.2
+      '@instructor-ai/instructor':
+        specifier: ^1.3.0
+        version: 1.3.0(openai@4.47.3)(zod@3.23.8)
       '@langchain/core':
         specifier: ^0.2.5
         version: 0.2.5(langchain@0.2.4(axios@1.7.2)(ignore@5.3.1)(openai@4.47.3))(openai@4.47.3)
@@ -144,6 +150,9 @@ importers:
       langchain:
         specifier: ^0.2.4
         version: 0.2.4(axios@1.7.2)(ignore@5.3.1)(openai@4.47.3)
+      openai:
+        specifier: ^4.47.3
+        version: 4.47.3
       zod:
         specifier: ^3.23.3
         version: 3.23.8
@@ -369,8 +378,8 @@ packages:
   '@fastify/deepmerge@1.3.0':
     resolution: {integrity: sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A==}
 
-  '@fisch0920/eslint-config@1.3.2':
-    resolution: {integrity: sha512-CUpMxCPWzoxvWFlmvH0OthooRlenqrezrVVleKEZv+NbibwgT4phyZadBLYsF8aB7Et2OoEzk4LKg8Xsdu5spA==}
+  '@fisch0920/eslint-config@1.3.3':
+    resolution: {integrity: sha512-BhjKXkVVSmdj1M60rAbJMaM1yFUFMlOeaWNmxOL4SNgLF2TR+LFNlcUkVH7bb2A5sq4BEc+2POBCCDXwKOlbUg==}
     engines: {node: '>=18'}
     peerDependencies:
       typescript: ^5.0.0
@@ -405,6 +414,12 @@ packages:
     resolution: {integrity: sha512-ErXXzENMH5pJt5/ssXV0DfWUZqly8nGzf0UcBV9xTnP+KyffE2mqyxIMBrZ8ijQck2nU0TQm40EQB53YreyWHw==}
     engines: {node: '>=18'}
 
+  '@instructor-ai/instructor@1.3.0':
+    resolution: {integrity: sha512-nzadwGSkiVLcK3NAVeinPUrA6KwV5mkyWDGTXLMR8eXKbzlUwmLwQ+PTabPn2hCR+LtQ+7MvtuoR2acSTXgy6Q==}
+    peerDependencies:
+      openai: '>=4.28.0'
+      zod: '>=3.22.4'
+
   '@isaacs/cliui@8.0.2':
     resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
     engines: {node: '>=12'}
@@ -1265,8 +1280,8 @@ packages:
     resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==}
     engines: {node: '>=14.16'}
 
-  caniuse-lite@1.0.30001625:
-    resolution: {integrity: sha512-4KE9N2gcRH+HQhpeiRZXd+1niLB/XNLAhSy4z7fI8EzcbcPoAqjNInxVHTiTwWfTIV4w096XG8OtCOCQQKPv3w==}
+  caniuse-lite@1.0.30001626:
+    resolution: {integrity: sha512-JRW7kAH8PFJzoPCJhLSHgDgKg5348hsQ68aqb+slnzuB5QFERv846oA/mRChmlLAOdEDeOkRn3ynb1gSFnjt3w==}
 
   chai@5.1.1:
     resolution: {integrity: sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==}
@@ -3508,6 +3523,9 @@ packages:
     resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
     engines: {node: '>=10'}
 
+  ramda@0.29.1:
+    resolution: {integrity: sha512-OfxIeWzd4xdUNxlWhgFazxsA/nl3mS4/jGZI5n00uWOoSSFRhC1b6gl6xvmzUamgmqELraWp0J/qqVlXYPDPyA==}
+
   range-parser@1.2.1:
     resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
     engines: {node: '>= 0.6'}
@@ -3719,6 +3737,11 @@ packages:
   safer-buffer@2.1.2:
     resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
 
+  schema-stream@3.1.0:
+    resolution: {integrity: sha512-R4PoSFJnMORRGJ5i5BTHRO2Ed3Lf2h8DMofHd5XBU4ZE0lEBCTGRqOXBurjTEO2QntPSYAhv93jLt8PFMqDEPw==}
+    peerDependencies:
+      zod: 3.22.4
+
   scoped-regex@3.0.0:
     resolution: {integrity: sha512-yEsN6TuxZhZ1Tl9iB81frTNS292m0I/IG7+w8lTvfcJQP2x3vnpOoevjBoE3Np5A6KnZM2+RtVenihj9t6NiYg==}
     engines: {node: '>=12'}
@@ -4458,6 +4481,12 @@ packages:
     resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==}
     engines: {node: '>=12.20'}
 
+  zod-stream@1.0.3:
+    resolution: {integrity: sha512-HxK/PZ0faOMgI1Pgjhcwin22StvlwiaYo6R7jWCT3jOFcPa4wGyFLVpeC5TwmpT6YlZhNJfDoB0qJoa+rQLYmQ==}
+    peerDependencies:
+      openai: '>=4.24.1'
+      zod: '>=3.22.4'
+
   zod-to-json-schema@3.22.5:
     resolution: {integrity: sha512-+akaPo6a0zpVCCseDed504KBJUQpEW5QZw7RMneNmKw+fGaML1Z9tUNLnHHAC8x6dzVRO1eB2oEMyZRnuBZg7Q==}
     peerDependencies:
@@ -4468,6 +4497,12 @@ packages:
     peerDependencies:
       zod: ^3.23.3
 
+  zod-validation-error@2.1.0:
+    resolution: {integrity: sha512-VJh93e2wb4c3tWtGgTa0OF/dTt/zoPCPzXq4V11ZjxmEAFaPi/Zss1xIZdEB5RD8GD00U0/iVXgqkF77RV7pdQ==}
+    engines: {node: '>=18.0.0'}
+    peerDependencies:
+      zod: ^3.18.0
+
   zod-validation-error@3.3.0:
     resolution: {integrity: sha512-Syib9oumw1NTqEv4LT0e6U83Td9aVRk9iTXPUQr1otyV1PuXQKOvOwhMNqZIq5hluzHP2pMgnOmHEo7kPdI2mw==}
     engines: {node: '>=18.0.0'}
@@ -4650,7 +4685,7 @@ snapshots:
 
   '@fastify/deepmerge@1.3.0': {}
 
-  '@fisch0920/eslint-config@1.3.2(eslint@8.57.0)(typescript@5.4.5)':
+  '@fisch0920/eslint-config@1.3.3(eslint@8.57.0)(typescript@5.4.5)':
     dependencies:
       '@rushstack/eslint-patch': 1.10.3
       '@typescript-eslint/eslint-plugin': 7.11.0(@typescript-eslint/parser@7.11.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)
@@ -4731,6 +4766,13 @@ snapshots:
 
   '@inquirer/figures@1.0.3': {}
 
+  '@instructor-ai/instructor@1.3.0(openai@4.47.3)(zod@3.23.8)':
+    dependencies:
+      openai: 4.47.3
+      zod: 3.23.8
+      zod-stream: 1.0.3(openai@4.47.3)(zod@3.23.8)
+      zod-validation-error: 2.1.0(zod@3.23.8)
+
   '@isaacs/cliui@8.0.2':
     dependencies:
       string-width: 5.1.2
@@ -5666,7 +5708,7 @@ snapshots:
 
   browserslist@4.23.0:
     dependencies:
-      caniuse-lite: 1.0.30001625
+      caniuse-lite: 1.0.30001626
       electron-to-chromium: 1.4.788
       node-releases: 2.0.14
       update-browserslist-db: 1.0.16(browserslist@4.23.0)
@@ -5724,7 +5766,7 @@ snapshots:
 
   camelcase@7.0.1: {}
 
-  caniuse-lite@1.0.30001625: {}
+  caniuse-lite@1.0.30001626: {}
 
   chai@5.1.1:
     dependencies:
@@ -8041,6 +8083,8 @@ snapshots:
 
   quick-lru@5.1.1: {}
 
+  ramda@0.29.1: {}
+
   range-parser@1.2.1: {}
 
   raw-body@2.5.2:
@@ -8291,6 +8335,11 @@ snapshots:
 
   safer-buffer@2.1.2: {}
 
+  schema-stream@3.1.0(zod@3.23.8):
+    dependencies:
+      ramda: 0.29.1
+      zod: 3.23.8
+
   scoped-regex@3.0.0: {}
 
   secure-json-parse@2.7.0: {}
@@ -9070,6 +9119,13 @@ snapshots:
 
   yocto-queue@1.0.0: {}
 
+  zod-stream@1.0.3(openai@4.47.3)(zod@3.23.8):
+    dependencies:
+      openai: 4.47.3
+      schema-stream: 3.1.0(zod@3.23.8)
+      zod: 3.23.8
+      zod-to-json-schema: 3.23.0(zod@3.23.8)
+
   zod-to-json-schema@3.22.5(zod@3.23.8):
     dependencies:
       zod: 3.23.8
@@ -9078,6 +9134,10 @@ snapshots:
     dependencies:
       zod: 3.23.8
 
+  zod-validation-error@2.1.0(zod@3.23.8):
+    dependencies:
+      zod: 3.23.8
+
   zod-validation-error@3.3.0(zod@3.23.8):
     dependencies:
       zod: 3.23.8
diff --git a/readme.md b/readme.md
index 97655a4..6732ff2 100644
--- a/readme.md
+++ b/readme.md
@@ -13,7 +13,7 @@
   <a href="https://twitter.com/transitive_bs"><img alt="Discuss on Twitter" src="https://img.shields.io/badge/twitter-discussion-blue" /></a>
 </p>
 
-# Walter <!-- omit from toc -->
+# Agentic <!-- omit from toc -->
 
 **Coming soon**
 
@@ -44,15 +44,11 @@
 
 ## TODO
 
-- core
-  - company schema
-  - person schema
-  - database
-  - move out to a separate project
-    - agentic
-    - walter
+- rename this repo to agentic
+- change license to MIT
 - sdks
   - instructor-js
+  - TODO
 - services
   - wolfram alpha
   - midjourney
diff --git a/src/ai-function-set.ts b/src/ai-function-set.ts
index 6dfdf20..c011e68 100644
--- a/src/ai-function-set.ts
+++ b/src/ai-function-set.ts
@@ -58,6 +58,17 @@ export class AIFunctionSet implements Iterable<types.AIFunction> {
     return [...this.entries].map(fn)
   }
 
+  get specs(): types.AIFunctionSpec[] {
+    return this.map((fn) => fn.spec)
+  }
+
+  get toolSpecs(): types.AIToolSpec[] {
+    return this.map((fn) => ({
+      type: 'function' as const,
+      function: fn.spec
+    }))
+  }
+
   get entries(): IterableIterator<types.AIFunction> {
     return this._map.values()
   }
diff --git a/src/ai-tool-set.ts b/src/ai-tool-set.ts
index 0748aa2..d7b21c6 100644
--- a/src/ai-tool-set.ts
+++ b/src/ai-tool-set.ts
@@ -62,6 +62,14 @@ export class AIToolSet implements Iterable<types.AITool> {
     return [...this.entries].map(fn)
   }
 
+  get functionSpecs(): types.AIFunctionSpec[] {
+    return this.map((fn) => fn.function.spec)
+  }
+
+  get specs(): types.AIToolSpec[] {
+    return this.map((fn) => fn.spec)
+  }
+
   get entries(): IterableIterator<types.AITool> {
     return this._map.values()
   }
diff --git a/src/types.ts b/src/types.ts
index 49becde..17a5215 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -34,7 +34,9 @@ export type AIFunctionImpl<Return> = Omit<
 export interface AIFunction<
   InputSchema extends z.ZodObject<any> = z.ZodObject<any>,
   Return = any
-> extends AIFunctionImpl<Return> {
+> {
+  (input: string | Msg): MaybePromise<Return>
+
   /** The Zod schema for the arguments string. */
   inputSchema: InputSchema
 
diff --git a/tsconfig.json b/tsconfig.json
index 4f2456e..c47b9c4 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -25,5 +25,8 @@
     "outDir": "dist",
     "sourceMap": true
   },
-  "include": ["src"]
+  "include": ["src"],
+  "ts-node": {
+    "transpileOnly": true
+  }
 }