├── .changeset
├── README.md
└── config.json
├── .github
└── workflows
│ └── node.js.yml
├── .gitignore
├── .prettierrc
├── LICENSE
├── README.md
├── docs
├── next.config.mjs
├── package-lock.json
├── package.json
├── pages
│ ├── _app.jsx
│ ├── _meta.js
│ ├── concepts
│ │ ├── _meta.js
│ │ ├── ai-services
│ │ │ ├── _meta.js
│ │ │ ├── ai-integrations.mdx
│ │ │ └── index.mdx
│ │ ├── kernel.mdx
│ │ └── plugins
│ │ │ ├── _meta.js
│ │ │ └── index.mdx
│ ├── getting-started
│ │ ├── _meta.js
│ │ ├── quick-start-guide.mdx
│ │ └── using-azure.mdx
│ └── index.mdx
├── public
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── apple-touch-icon.png
│ ├── designed-for-modular-extensibility-vertical.png
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon.ico
│ ├── kernel-infographic.png
│ ├── site.webmanifest
│ ├── sk.png
│ └── the-kernel-is-at-the-center-of-everything.png
└── theme.config.jsx
├── package-lock.json
├── package.json
├── samples
├── azure
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ │ └── index.ts
│ └── tsconfig.json
├── functions
│ └── functionCalling.ts
└── react
│ └── chat
│ ├── .gitignore
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ └── index.html
│ ├── src
│ ├── App.tsx
│ ├── Chat.tsx
│ ├── index.css
│ ├── index.tsx
│ └── react-app-env.d.ts
│ └── tsconfig.json
├── src
├── AI
│ ├── eslint.config.mjs
│ ├── jest.config.ts
│ ├── package.json
│ ├── src
│ │ ├── AITool.ts
│ │ ├── AdditionalProperties.ts
│ │ ├── UsageDetails.ts
│ │ ├── chatCompletion
│ │ │ ├── AutoChatToolMode.ts
│ │ │ ├── ChatClient.ts
│ │ │ ├── ChatClientBuilder.test.ts
│ │ │ ├── ChatClientBuilder.ts
│ │ │ ├── ChatClientMetadata.ts
│ │ │ ├── ChatFinishReason.ts
│ │ │ ├── ChatOptions.ts
│ │ │ ├── ChatResponse.ts
│ │ │ ├── ChatResponseFormat.ts
│ │ │ ├── ChatResponseFormatBase.ts
│ │ │ ├── ChatResponseFormatJson.ts
│ │ │ ├── ChatResponseFormatText.ts
│ │ │ ├── ChatResponseUpdate.ts
│ │ │ ├── ChatRole.ts
│ │ │ ├── ChatToolMode.ts
│ │ │ ├── ChatToolModeBase.ts
│ │ │ ├── DelegatingChatClient.ts
│ │ │ ├── FunctionInvocationContext.ts
│ │ │ ├── FunctionInvokingChatClient.ts
│ │ │ ├── NoneChatToolMode.ts
│ │ │ ├── RequiredChatToolMode.ts
│ │ │ └── index.ts
│ │ ├── contents
│ │ │ ├── AIContent.ts
│ │ │ ├── AIContentHelper.test.ts
│ │ │ ├── AIContentHelper.ts
│ │ │ ├── ChatMessage.ts
│ │ │ ├── FunctionCallContent.ts
│ │ │ ├── FunctionResultContent.ts
│ │ │ ├── TextContent.ts
│ │ │ ├── UsageContent.ts
│ │ │ └── index.ts
│ │ ├── functions
│ │ │ ├── AIFunction.ts
│ │ │ ├── AIFunctionArguments.ts
│ │ │ ├── AIFunctionFactory.ts
│ │ │ └── index.ts
│ │ ├── index.ts
│ │ └── jsonSchema
│ │ │ ├── fromSchema.ts
│ │ │ ├── index.ts
│ │ │ └── jsonSchema.ts
│ ├── tsconfig.json
│ └── tsup.config.ts
├── abstractions
│ ├── CHANGELOG.md
│ ├── eslint.config.mjs
│ ├── jest.config.ts
│ ├── package.json
│ ├── src
│ │ ├── Kernel.ts
│ │ ├── functionChoiceBehaviors
│ │ │ ├── AutoFunctionChoiceBehavior.ts
│ │ │ ├── FunctionChoice.ts
│ │ │ ├── FunctionChoiceBehavior.ts
│ │ │ ├── FunctionChoiceBehaviorBase.ts
│ │ │ ├── FunctionChoiceBehaviorConfiguration.ts
│ │ │ ├── FunctionChoiceBehaviorOptions.ts
│ │ │ ├── NoneFunctionChoiceBehavior.ts
│ │ │ └── index.ts
│ │ ├── functions
│ │ │ ├── FunctionName.test.ts
│ │ │ ├── FunctionName.ts
│ │ │ ├── FunctionResult.ts
│ │ │ ├── KernelArguments.ts
│ │ │ ├── KernelFunction.test.ts
│ │ │ ├── KernelFunction.ts
│ │ │ ├── KernelFunctionFromPrompt.test.ts
│ │ │ ├── KernelFunctionFromPrompt.ts
│ │ │ ├── KernelPlugin.ts
│ │ │ ├── KernelPlugins.test.ts
│ │ │ ├── KernelPlugins.ts
│ │ │ └── index.ts
│ │ ├── index.ts
│ │ ├── internalUtilities
│ │ │ ├── http
│ │ │ │ ├── httpHeaderConstant.ts
│ │ │ │ └── index.ts
│ │ │ └── index.ts
│ │ ├── promptExecutionSettings
│ │ │ ├── PromptExecutionSettings.ts
│ │ │ ├── PromptExecutionSettingsMapper.ts
│ │ │ └── index.ts
│ │ ├── promptTemplate
│ │ │ ├── PassThroughPromptTemplate.ts
│ │ │ ├── PromptTemplate.ts
│ │ │ ├── PromptTemplateConfig.ts
│ │ │ └── index.ts
│ │ ├── serviceProviderExtension.test.ts
│ │ ├── serviceProviderExtension.ts
│ │ └── serviceProviderExtention.d.ts
│ ├── tsconfig.json
│ └── tsup.config.ts
├── openai
│ ├── CHANGELOG.md
│ ├── eslint.config.mjs
│ ├── jest.config.ts
│ ├── package.json
│ ├── src
│ │ ├── OpenAIChatClient.test.ts
│ │ ├── OpenAIChatClient.ts
│ │ ├── index.ts
│ │ └── mapper
│ │ │ ├── chatCompletionMapper.ts
│ │ │ └── index.ts
│ ├── tsconfig.json
│ └── tsup.config.ts
├── prompt-templates
│ ├── CHANGELOG.md
│ ├── eslint.config.mjs
│ ├── jest.config.ts
│ ├── package.json
│ ├── src
│ │ ├── handlebars
│ │ │ └── handlebarsPromptTemplate.ts
│ │ └── index.ts
│ ├── tsconfig.json
│ └── tsup.config.ts
├── react
│ ├── CHANGELOG.md
│ ├── babel.config.js
│ ├── eslint.config.mjs
│ ├── jest.config.js
│ ├── jest.setup.js
│ ├── package.json
│ ├── src
│ │ ├── hooks
│ │ │ ├── index.ts
│ │ │ ├── useChat
│ │ │ │ ├── index.ts
│ │ │ │ ├── useChat.test.ts
│ │ │ │ └── useChat.ts
│ │ │ └── useKernel
│ │ │ │ ├── index.ts
│ │ │ │ ├── useKernel.test.ts
│ │ │ │ └── useKernel.ts
│ │ ├── index.ts
│ │ └── tests
│ │ │ └── mockChatCompletionService.ts
│ ├── tsconfig.json
│ └── tsup.config.ts
├── semantic-kernel
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── eslint.config.mjs
│ ├── package.json
│ ├── src
│ │ └── index.ts
│ ├── tsconfig.json
│ └── tsup.config.ts
├── service-provider
│ ├── eslint.config.mjs
│ ├── jest.config.ts
│ ├── package.json
│ ├── src
│ │ ├── EmptyServiceProvider.ts
│ │ ├── MapServiceProvider.test.ts
│ │ ├── MapServiceProvider.ts
│ │ ├── Service.ts
│ │ ├── ServiceProvider.ts
│ │ └── index.ts
│ ├── tsconfig.json
│ └── tsup.config.ts
└── tsconfig
│ ├── package.json
│ └── tsconfig.base.json
└── turbo.json
/.changeset/README.md:
--------------------------------------------------------------------------------
1 | # Changesets
2 |
3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
4 | with multi-package repos, or single-package repos to help you version and publish your code. You can
5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets)
6 |
7 | We have a quick list of common questions to get you started engaging with this project in
8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
9 |
--------------------------------------------------------------------------------
/.changeset/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://unpkg.com/@changesets/config@3.0.3/schema.json",
3 | "changelog": "@changesets/cli/changelog",
4 | "commit": false,
5 | "fixed": [],
6 | "linked": [],
7 | "access": "public",
8 | "baseBranch": "main",
9 | "updateInternalDependencies": "patch",
10 | "ignore": []
11 | }
12 |
--------------------------------------------------------------------------------
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
3 |
4 | name: Node.js CI
5 |
6 | on:
7 | push:
8 | branches: ['main']
9 | pull_request:
10 | branches: ['main']
11 |
12 | jobs:
13 | build:
14 | runs-on: ubuntu-latest
15 |
16 | strategy:
17 | matrix:
18 | node-version: [18.x, 20.x, 22.x]
19 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
20 |
21 | steps:
22 | - uses: actions/checkout@v4
23 | - name: Use Node.js ${{ matrix.node-version }}
24 | uses: actions/setup-node@v4
25 | with:
26 | node-version: ${{ matrix.node-version }}
27 | cache: 'npm'
28 | - run: npm ci
29 | - run: npm run build --if-present
30 | - run: npm test
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | lib-cov
2 | *.seed
3 | *.log
4 | *.csv
5 | *.dat
6 | *.out
7 | *.pid
8 | *.gz
9 | *.swp
10 | *.tsbuildinfo
11 |
12 | pids
13 | logs
14 | results
15 | tmp
16 | playground
17 |
18 | # Coverage reports
19 | coverage
20 |
21 | # API keys and secrets
22 | .env
23 |
24 | # Dependency directory
25 | node_modules
26 | bower_components
27 |
28 | # Editors
29 | .idea
30 | *.iml
31 |
32 | # OS metadata
33 | .DS_Store
34 | Thumbs.db
35 |
36 | # Ignore built ts files
37 | dist
38 |
39 | # ignore yarn.lock
40 | yarn.lock
41 |
42 | # Turbobuild cache
43 | .turbo
44 |
45 | # Next.js cache
46 | .next
47 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": true,
3 | "singleQuote": true,
4 | "trailingComma": "es5",
5 | "printWidth": 120,
6 | "plugins": ["prettier-plugin-organize-imports"]
7 | }
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Afshin Mehrabani
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | src/semantic-kernel/README.md
--------------------------------------------------------------------------------
/docs/next.config.mjs:
--------------------------------------------------------------------------------
1 | import nextra from 'nextra'
2 |
3 | const nextjsConfigs = {
4 | redirects: () => {
5 | return [
6 | {
7 | source: '/getting-started',
8 | destination: '/getting-started/quick-start-guide',
9 | permanent: true,
10 | },
11 | {
12 | source: '/azure',
13 | destination: '/getting-started/using-azure',
14 | permanent: true,
15 | }
16 | ]
17 | },
18 |
19 | }
20 |
21 | const withNextra = nextra({
22 | theme: 'nextra-theme-docs',
23 | themeConfig: './theme.config.jsx'
24 | })
25 |
26 | export default withNextra(nextjsConfigs);
27 |
--------------------------------------------------------------------------------
/docs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "kerneljs.com",
3 | "version": "1.0.0",
4 | "description": "The documentation for Semantic Kernel for JavaScript",
5 | "main": "index.js",
6 | "scripts": {
7 | "dev": "next",
8 | "build": "next build",
9 | "start": "next start"
10 | },
11 | "author": "Afshin Mehrabani",
12 | "license": "MIT",
13 | "dependencies": {
14 | "next": "^15.0.3",
15 | "nextra": "^3.2.4",
16 | "nextra-theme-docs": "^3.2.4",
17 | "react": "^18.3.1",
18 | "react-dom": "^18.3.1"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/docs/pages/_app.jsx:
--------------------------------------------------------------------------------
1 | export default function App({ Component, pageProps }) {
2 | return ;
3 | }
4 |
--------------------------------------------------------------------------------
/docs/pages/_meta.js:
--------------------------------------------------------------------------------
1 | export default {
2 | index: 'Overview',
3 | "getting-started": 'Getting Started',
4 | "concepts": 'Concepts',
5 | }
6 |
--------------------------------------------------------------------------------
/docs/pages/concepts/_meta.js:
--------------------------------------------------------------------------------
1 | export default {
2 | kernel: 'Kernel',
3 | "ai-services": 'AI Services',
4 | "plugins": 'Plugins',
5 | }
6 |
--------------------------------------------------------------------------------
/docs/pages/concepts/ai-services/_meta.js:
--------------------------------------------------------------------------------
1 | export default {
2 | index: 'Overview',
3 | "ai-integrations": 'AI Integrations',
4 | }
5 |
--------------------------------------------------------------------------------
/docs/pages/concepts/ai-services/ai-integrations.mdx:
--------------------------------------------------------------------------------
1 | import { Table, Td, Th, Tr } from 'nextra/components'
2 |
3 | # AI Integrations for Semantic Kernel
4 |
5 | Semantic Kernel provides a wide range of AI service integrations to help you build powerful AI agents. Additionally, Semantic Kernel integrates with other Microsoft services to provide additional functionality via plugins.
6 |
7 | ## Out-of-the-box integrations
8 |
9 | With the available AI connectors, developers can easily build AI agents with swappable components. This allows you to experiment with different AI services to find the best combination for your use case.
10 |
11 | ### AI Services
12 |
13 |
14 |
15 |
16 | Provider
17 | Chat Completion
18 | Text Generation
19 | Text Embeddings
20 | Text to Image
21 | Image to Text
22 | Text to Audio
23 | Audio to Text
24 |
25 |
26 |
27 |
28 | OpenAI
29 |
30 | ✅
31 |
32 |
33 | ⏳
34 |
35 |
36 | ⏳
37 |
38 |
39 | ⏳
40 |
41 |
42 | ⏳
43 |
44 |
45 | ⏳
46 |
47 |
48 | ⏳
49 |
50 |
51 |
52 | Azure OpenAI
53 |
54 | ⏳
55 |
56 |
57 | ⏳
58 |
59 |
60 | ⏳
61 |
62 |
63 | ⏳
64 |
65 |
66 | ⏳
67 |
68 |
69 | ⏳
70 |
71 |
72 | ⏳
73 |
74 |
75 |
76 | Anthropic
77 |
78 | ⏳
79 |
80 |
81 | ⏳
82 |
83 |
84 | ⏳
85 |
86 |
87 | ⏳
88 |
89 |
90 | ⏳
91 |
92 |
93 | ⏳
94 |
95 |
96 | ⏳
97 |
98 |
99 |
100 | Mistral
101 |
102 | ⏳
103 |
104 |
105 | ⏳
106 |
107 |
108 | ⏳
109 |
110 |
111 | ⏳
112 |
113 |
114 | ⏳
115 |
116 |
117 | ⏳
118 |
119 |
120 | ⏳
121 |
122 |
123 |
124 | Amaozon Bedrock
125 |
126 | ⏳
127 |
128 |
129 | ⏳
130 |
131 |
132 | ⏳
133 |
134 |
135 | ⏳
136 |
137 |
138 | ⏳
139 |
140 |
141 | ⏳
142 |
143 |
144 | ⏳
145 |
146 |
147 |
148 |
149 |
150 | ## Providers
151 |
152 | List of available Semantic Kernel providers:
153 |
154 | - [OpenAI Provider](https://www.npmjs.com/package/@semantic-kernel/openai) (`@semantic-kernel/openai`)
155 |
--------------------------------------------------------------------------------
/docs/pages/concepts/ai-services/index.mdx:
--------------------------------------------------------------------------------
1 | import { Table, Td, Th, Tr } from 'nextra/components'
2 |
3 | # Adding AI services to Semantic Kernel
4 |
5 | One of the main features of Semantic Kernel is its ability to add different AI services to the kernel.
6 | This allows you to easily swap out different AI services to compare their performance and to leverage the best model for your needs.
7 | In this section, we will provide sample code for adding different AI services to the kernel.
8 |
9 | Within Semantic Kernel, there are interfaces for the most popular AI tasks. In the table below, you can see the services that are supported by each of the SDKs:
10 |
11 |
12 |
13 |
14 | Services
15 | Availability
16 |
17 |
18 |
19 |
20 | Chat completion
21 |
22 | ✅
23 |
24 |
25 |
26 | Text generation
27 |
28 | ⏳
29 |
30 |
31 |
32 | Embedding generation
33 |
34 | ⏳
35 |
36 |
37 |
38 | Text-to-image
39 |
40 | ⏳
41 |
42 |
43 |
44 | Image-to-text
45 |
46 | ⏳
47 |
48 |
49 |
50 | Text-to-audio
51 |
52 | ⏳
53 |
54 |
55 |
56 | Audio-to-text
57 |
58 | ⏳
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/docs/pages/concepts/kernel.mdx:
--------------------------------------------------------------------------------
1 | import { Table, Td, Th, Tr } from 'nextra/components'
2 |
3 | # Understanding the kernel
4 |
5 | The kernel is the central component of Semantic Kernel. At its simplest, the kernel is a Dependency Injection container that manages all of the services and plugins necessary to run your AI application.
6 | If you provide all of your services and plugins to the kernel, they will then be seamlessly used by the AI as needed.
7 |
8 | 
9 |
10 | ## Build a kernel with services and plugins
11 |
12 | Before building a kernel, you should first understand the two types of components that exist:
13 |
14 |
15 |
16 |
17 | Components
18 | Description
19 |
20 |
21 |
22 |
23 | Services
24 |
25 | These consist of both AI services (e.g., chat completion) and other services (e.g., logging and HTTP clients) that are necessary to run your application.
26 |
27 |
28 |
29 | Plugins
30 |
31 | These are the components that are used by your AI services and prompt templates to perform work. AI services, for example, can use plugins to retrieve data from a database or call an external API to perform actions.
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/docs/pages/concepts/plugins/_meta.js:
--------------------------------------------------------------------------------
1 | export default {
2 | index: 'Overview',
3 | }
4 |
--------------------------------------------------------------------------------
/docs/pages/concepts/plugins/index.mdx:
--------------------------------------------------------------------------------
1 | # What is a Plugin?
2 |
3 | Plugins are a key component of Semantic Kernel. If you have already used plugins from ChatGPT or Copilot extensions in Microsoft 365, you’re already familiar with them. With plugins, you can encapsulate your existing APIs into a collection that can be used by an AI. This allows you to give your AI the ability to perform actions that it wouldn’t be able to do otherwise.
4 |
5 |
6 | 
7 |
8 |
9 | Behind the scenes, Semantic Kernel leverages function calling, a native feature of most of the latest LLMs to allow LLMs, to perform planning and to invoke your APIs. With function calling, LLMs can request (i.e., call) a particular function. Semantic Kernel then marshals the request to the appropriate function in your codebase and returns the results back to the LLM so the LLM can generate a final response.
10 |
11 | ## Anatomy of a plugin
12 |
13 | At a high-level, a plugin is a group of functions that can be exposed to AI apps and services. The functions within plugins can then be orchestrated by an AI application to accomplish user requests. Within Semantic Kernel, you can invoke these functions automatically with function calling.
14 |
--------------------------------------------------------------------------------
/docs/pages/getting-started/_meta.js:
--------------------------------------------------------------------------------
1 | export default {
2 | "quick-start-guide": 'Quick start guide',
3 | }
4 |
--------------------------------------------------------------------------------
/docs/pages/getting-started/quick-start-guide.mdx:
--------------------------------------------------------------------------------
1 | import { Steps } from 'nextra/components'
2 |
3 | ## Getting started with Semantic Kernel
4 |
5 | In just a few steps, you can build your first AI agent with Semantic Kernel in JavaScript. This guide will show you how to...
6 |
7 | - Install the necessary packages
8 | - Create a back-and-forth conversation with an AI
9 | - Give an AI agent the ability to run your code
10 | - Watch the AI create plans on the fly
11 |
12 |
13 | ### Install the package
14 |
15 | ```bash
16 | npm install --save semantic-kernel @semantic-kernel/openai
17 | ```
18 |
19 | ### Initialize the Kernel
20 |
21 | ```typescript
22 | import { OpenAIChatCompletionService } from '@semantic-kernel/openai';
23 | import { FunctionChoiceBehavior, kernel, kernelFunction } from 'semantic-kernel';
24 |
25 | const sk = kernel().addService(
26 | new OpenAIChatCompletionService({
27 | model: 'gpt-3.5-turbo',
28 | apiKey:
29 | 'YOUR_OPENAI_API_KEY',
30 | })
31 | );
32 | ```
33 |
34 | ### Add your plugins
35 |
36 | ```typescript
37 | const temperature = kernelFunction(({ loc }) => (loc === 'Dublin' ? 10 : 24), {
38 | name: 'temperature',
39 | description: 'Returns the temperature for the given location',
40 | schema: {
41 | type: 'object',
42 | properties: {
43 | loc: { type: 'string', description: 'The location to return the temperature for' },
44 | },
45 | },
46 | });
47 |
48 | sk.addPlugin({
49 | name: 'weather',
50 | description: 'Weather plugin',
51 | functions: [temperature],
52 | });
53 | ```
54 |
55 | ### Invoke prompt
56 |
57 | ```typescript
58 | const result = await sk.invokePrompt({
59 | promptTemplate: 'Return the current temperature in Dublin',
60 | executionSettings: {
61 | functionChoiceBehavior: FunctionChoiceBehavior.Auto(),
62 | },
63 | });
64 | ```
65 |
66 |
67 |
68 | Here is the full example:
69 |
70 | ```typescript
71 | import { OpenAIChatCompletionService } from '@semantic-kernel/openai';
72 | import { FunctionChoiceBehavior, kernel, kernelFunction } from 'semantic-kernel';
73 |
74 | const sk = kernel().addService(
75 | new OpenAIChatCompletionService({
76 | model: 'gpt-3.5-turbo',
77 | apiKey:
78 | 'YOUR_OPENAI_API_KEY',
79 | })
80 | );
81 |
82 | const temperature = kernelFunction(({ loc }) => (loc === 'Dublin' ? 10 : 24), {
83 | name: 'temperature',
84 | description: 'Returns the temperature for the given location',
85 | schema: {
86 | type: 'object',
87 | properties: {
88 | loc: { type: 'string', description: 'The location to return the temperature for' },
89 | },
90 | },
91 | });
92 |
93 | sk.addPlugin({
94 | name: 'weather',
95 | description: 'Weather plugin',
96 | functions: [temperature],
97 | });
98 |
99 | const result = await sk.invokePrompt({
100 | promptTemplate: 'Return the current temperature in Dublin',
101 | executionSettings: {
102 | functionChoiceBehavior: FunctionChoiceBehavior.Auto(),
103 | },
104 | });
105 |
106 | // Prints the output after executing the plugin and the given prompt
107 | console.log(result);
108 | ```
109 |
--------------------------------------------------------------------------------
/docs/pages/getting-started/using-azure.mdx:
--------------------------------------------------------------------------------
1 | import { Steps } from 'nextra/components'
2 |
3 | ## Using Azure OpenAI with Semantic Kernel
4 |
5 | Building an AI agent with Semantic Kernel that uses Azure OpenAI can be done following this guide. When using Azure OpenAI authentication can use either API keys or EntraID (OAuth) shown in the initialization step below.
6 |
7 |
8 | ### Install the package
9 |
10 | ```bash
11 | npm install --save semantic-kernel @semantic-kernel/azure-openai
12 | ```
13 |
14 | ### Initialize the Kernel
15 |
16 | Using __EntraID__ based authentication. Use __az login__ to set the context used for login.
17 |
18 | ```typescript
19 | import { AzureOpenAIChatCompletionService } from '@semantic-kernel/azure-openai';
20 | import { FunctionChoiceBehavior, kernel, kernelFunction } from "semantic-kernel";
21 |
22 | const sk = kernel().addService(
23 | new AzureOpenAIChatCompletionService({
24 | deploymentName: '',
25 | endpoint: '',
26 | apiVersion: ''
27 | })
28 | );
29 | ```
30 |
31 | Using __API key__ based authentication.
32 |
33 | ```typescript
34 | import { AzureOpenAIChatCompletionService } from '@semantic-kernel/azure-openai';
35 | import { FunctionChoiceBehavior, kernel, kernelFunction } from "semantic-kernel";
36 |
37 | const sk = kernel().addService(
38 | new AzureOpenAIChatCompletionService({
39 | apiKey: '',
40 | deploymentName: '',
41 | endpoint: '',
42 | apiVersion: ''
43 | })
44 | );
45 | ```
46 |
47 | ### Add your plugins
48 |
49 | ```typescript
50 | const temperature = kernelFunction(({ loc }) => (loc === 'Dublin' ? 10 : 24), {
51 | name: 'temperature',
52 | description: 'Returns the temperature for the given location',
53 | schema: {
54 | type: 'object',
55 | properties: {
56 | loc: { type: 'string', description: 'The location to return the temperature for' },
57 | },
58 | },
59 | });
60 |
61 | sk.addPlugin({
62 | name: 'weather',
63 | description: 'Weather plugin',
64 | functions: [temperature],
65 | });
66 | ```
67 |
68 | ### Invoke prompt
69 |
70 | ```typescript
71 | function test() {
72 | sk.invokePrompt({
73 | promptTemplate: 'Return the current temperature in Dublin',
74 | executionSettings: {
75 | functionChoiceBehavior: FunctionChoiceBehavior.Auto(),
76 | },
77 | }).then((result) => {
78 | console.log(result?.value);
79 | });
80 | }
81 |
82 | test();
83 | ```
84 |
85 |
86 |
87 | Here is the full example:
88 |
89 | ```typescript
90 | import { AzureOpenAIChatCompletionService } from '@semantic-kernel/azure-openai';
91 | import { FunctionChoiceBehavior, kernel, kernelFunction } from "semantic-kernel";
92 |
93 | const sk = kernel().addService(
94 | new AzureOpenAIChatCompletionService({
95 | deploymentName: '',
96 | endpoint: '',
97 | apiVersion: ''
98 | })
99 | );
100 |
101 | const temperature = kernelFunction(({ loc }) => (loc === 'Dublin' ? 10 : 24), {
102 | name: 'temperature',
103 | description: 'Returns the temperature in a given location',
104 | schema: {
105 | type: 'object',
106 | properties: {
107 | loc: { type: 'string', description: 'The location to get the temperature for' },
108 | },
109 | },
110 | });
111 |
112 | sk.addPlugin({
113 | name: 'weather',
114 | description: 'Weather plugin',
115 | functions: [temperature],
116 | });
117 |
118 | function test() {
119 | sk.invokePrompt({
120 | promptTemplate: 'Return the current temperature in Dublin',
121 | executionSettings: {
122 | functionChoiceBehavior: FunctionChoiceBehavior.Auto(),
123 | },
124 | }).then((result) => {
125 | console.log(result?.value);
126 | });
127 | }
128 |
129 | test();
130 | ```
--------------------------------------------------------------------------------
/docs/pages/index.mdx:
--------------------------------------------------------------------------------
1 | # Semantic Kernel for JavaScript
2 |
3 | Welcome to the Semantic Kernel for JavaScript 👋
4 |
5 | This project brings [Semantic Kernel](https://learn.microsoft.com/en-us/semantic-kernel/overview/) to JavaScript, but it’s an independent creation, not an official Microsoft product.
6 |
7 | 
8 |
9 | Semantic Kernel is a lightweight, open-source development kit that lets you easily build AI agents and integrate the latest AI models. It serves as an efficient middleware that enables rapid delivery of enterprise-grade solutions.
10 |
11 |
12 | VIDEO
13 |
14 | ## License
15 |
16 | Licensed under the **MIT** license.
17 |
18 | This project is not an official Microsoft product.
19 |
--------------------------------------------------------------------------------
/docs/public/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afshinm/semantic-kernel-js/4845c62fbe32817f2af0900b47be5a7e8ca8c045/docs/public/android-chrome-192x192.png
--------------------------------------------------------------------------------
/docs/public/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afshinm/semantic-kernel-js/4845c62fbe32817f2af0900b47be5a7e8ca8c045/docs/public/android-chrome-512x512.png
--------------------------------------------------------------------------------
/docs/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afshinm/semantic-kernel-js/4845c62fbe32817f2af0900b47be5a7e8ca8c045/docs/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/docs/public/designed-for-modular-extensibility-vertical.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afshinm/semantic-kernel-js/4845c62fbe32817f2af0900b47be5a7e8ca8c045/docs/public/designed-for-modular-extensibility-vertical.png
--------------------------------------------------------------------------------
/docs/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afshinm/semantic-kernel-js/4845c62fbe32817f2af0900b47be5a7e8ca8c045/docs/public/favicon-16x16.png
--------------------------------------------------------------------------------
/docs/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afshinm/semantic-kernel-js/4845c62fbe32817f2af0900b47be5a7e8ca8c045/docs/public/favicon-32x32.png
--------------------------------------------------------------------------------
/docs/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afshinm/semantic-kernel-js/4845c62fbe32817f2af0900b47be5a7e8ca8c045/docs/public/favicon.ico
--------------------------------------------------------------------------------
/docs/public/kernel-infographic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afshinm/semantic-kernel-js/4845c62fbe32817f2af0900b47be5a7e8ca8c045/docs/public/kernel-infographic.png
--------------------------------------------------------------------------------
/docs/public/site.webmanifest:
--------------------------------------------------------------------------------
1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
--------------------------------------------------------------------------------
/docs/public/sk.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afshinm/semantic-kernel-js/4845c62fbe32817f2af0900b47be5a7e8ca8c045/docs/public/sk.png
--------------------------------------------------------------------------------
/docs/public/the-kernel-is-at-the-center-of-everything.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afshinm/semantic-kernel-js/4845c62fbe32817f2af0900b47be5a7e8ca8c045/docs/public/the-kernel-is-at-the-center-of-everything.png
--------------------------------------------------------------------------------
/docs/theme.config.jsx:
--------------------------------------------------------------------------------
1 | import { useRouter } from 'next/router';
2 | import { useConfig } from 'nextra-theme-docs';
3 |
4 | export default {
5 | logo: (
6 | <>
7 |
8 | Semantic Kernel for JavaScript
9 | >
10 | ),
11 | docsRepositoryBase: 'https://github.com/afshinm/semantic-kernel-js/tree/main/docs',
12 | project: {
13 | link: 'https://github.com/afshinm/semantic-kernel-js',
14 | },
15 | head: function useHead() {
16 | const config = useConfig();
17 | const { route } = useRouter();
18 | const image = 'kernel-infographic.jpeg';
19 |
20 | const description =
21 | config.frontMatter.description ||
22 | 'Semantic Kernel is a lightweight, open-source development kit that lets you easily build AI agents and integrate the latest AI models';
23 | const title = config.title + (route === '/' ? '' : ' - Semantic Kernel for JavaScript');
24 |
25 | return (
26 | <>
27 | {title}
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | >
44 | );
45 | },
46 | sidebar: {
47 | defaultMenuCollapseLevel: 1,
48 | toggleButton: true,
49 | },
50 | themeSwitch: {
51 | useOptions() {
52 | return {
53 | light: 'Light',
54 | dark: 'Dark',
55 | system: 'System',
56 | };
57 | },
58 | },
59 | footer: {
60 | content: (
61 |
62 | MIT {new Date().getFullYear()} ©{' '}
63 |
64 | Semantic Kernel for JavaScript
65 |
66 | .
67 |
68 | ),
69 | },
70 | };
71 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "semantic-kernel-root",
3 | "private": true,
4 | "scripts": {
5 | "build": "turbo build",
6 | "build:watch": "turbo watch build",
7 | "prettier": "prettier --write src",
8 | "clean": "turbo clean",
9 | "test": "turbo test",
10 | "version-packages": "changeset",
11 | "publish-packages": "turbo run build test && changeset version && changeset publish"
12 | },
13 | "packageManager": "npm@10.5.0",
14 | "workspaces": [
15 | "src/*"
16 | ],
17 | "files": [
18 | "dist/**/**",
19 | "docs/**/**",
20 | "!**/*.spec.*",
21 | "!**/*.json",
22 | "!**/*.tsbuildinfo",
23 | "LICENSE",
24 | "README.md"
25 | ],
26 | "repository": {
27 | "type": "git",
28 | "url": "git+https://github.com/afshinm/semantic-kernel-js.git"
29 | },
30 | "keywords": [
31 | "semantic-kernel"
32 | ],
33 | "author": "Afshin Mehrabani",
34 | "license": "MIT",
35 | "bugs": {
36 | "url": "https://github.com/afshinm/semantic-kernel-js/issues"
37 | },
38 | "homepage": "https://github.com/afshinm/semantic-kernel-js#readme",
39 | "devDependencies": {
40 | "@eslint/js": "^9.9.0",
41 | "@types/eslint__js": "^8.42.3",
42 | "@types/jest": "^29.5.12",
43 | "@types/node": "^22.5.0",
44 | "eslint": "^9.9.0",
45 | "jest": "^29.7.0",
46 | "npm-run-all": "^4.1.5",
47 | "prettier": "^3.3.3",
48 | "prettier-plugin-organize-imports": "^4.1.0",
49 | "ts-jest": "^29.2.5",
50 | "ts-node": "^10.9.2",
51 | "turbo": "^2.0.14",
52 | "typescript": "^5.5.4",
53 | "typescript-eslint": "^8.2.0"
54 | },
55 | "dependencies": {
56 | "@changesets/cli": "^2.27.9"
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/samples/azure/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "azure",
3 | "version": "1.0.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "azure",
9 | "version": "1.0.0",
10 | "license": "ISC",
11 | "dependencies": {
12 | "@semantic-kernel/azureopenai": "file:../../src/azureopenai",
13 | "semantic-kernel": "file:../../src/semantic-kernel"
14 | }
15 | },
16 | "../../src/azureopenai": {},
17 | "../../src/semantic-kernel": {
18 | "version": "0.1.0",
19 | "license": "MIT",
20 | "dependencies": {
21 | "@semantic-kernel/abstractions": "*"
22 | },
23 | "devDependencies": {
24 | "@eslint/js": "^9.9.0",
25 | "@semantic-kernel/tsconfig": "*",
26 | "@types/eslint__js": "^8.42.3",
27 | "eslint": "^9.9.0",
28 | "tsup": "^8.2.4",
29 | "typescript": "^5.5.4",
30 | "typescript-eslint": "^8.2.0"
31 | }
32 | },
33 | "node_modules/@semantic-kernel/azureopenai": {
34 | "resolved": "../../src/azureopenai",
35 | "link": true
36 | },
37 | "node_modules/semantic-kernel": {
38 | "resolved": "../../src/semantic-kernel",
39 | "link": true
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/samples/azure/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "azure",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "scripts": {
6 | "test": "echo \"Error: no test specified\" && exit 1"
7 | },
8 | "keywords": [],
9 | "author": "",
10 | "license": "ISC",
11 | "description": "",
12 | "dependencies": {
13 | "semantic-kernel": "file:../../src/semantic-kernel",
14 | "@semantic-kernel/azureopenai": "file:../../src/azureopenai"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/samples/azure/src/index.ts:
--------------------------------------------------------------------------------
1 | import { AzureOpenAIChatCompletionService } from '@semantic-kernel/azure-openai';
2 | import { FunctionChoiceBehavior, kernel, kernelFunction } from "semantic-kernel";
3 |
4 | const sk = kernel().addService(
5 | new AzureOpenAIChatCompletionService({
6 | deploymentName: '',
7 | endpoint: '',
8 | apiVersion: ''
9 | })
10 | );
11 |
12 | const temperature = kernelFunction(({ loc }) => (loc === 'Dublin' ? 10 : 24), {
13 | name: 'temperature',
14 | description: 'Returns the temperature in a given location',
15 | schema: {
16 | type: 'object',
17 | properties: {
18 | loc: { type: 'string', description: 'The location to get the temperature for' },
19 | },
20 | },
21 | });
22 |
23 | sk.addPlugin({
24 | name: 'weather',
25 | description: 'Weather plugin',
26 | functions: [temperature],
27 | });
28 |
29 | function test() {
30 | sk.invokePrompt({
31 | promptTemplate: 'Return the current temperature in Dublin',
32 | executionSettings: {
33 | functionChoiceBehavior: FunctionChoiceBehavior.Auto(),
34 | },
35 | }).then((result) => {
36 | console.log(result?.value);
37 | });
38 | }
39 |
40 | test();
--------------------------------------------------------------------------------
/samples/azure/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig to read more about this file */
4 |
5 | /* Projects */
6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
12 |
13 | /* Language and Environment */
14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
16 | // "jsx": "preserve", /* Specify what JSX code is generated. */
17 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
26 |
27 | /* Modules */
28 | "module": "commonjs", /* Specify what module code is generated. */
29 | // "rootDir": "./", /* Specify the root folder within your source files. */
30 | // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */
36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
38 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
39 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
40 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
41 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
42 | // "noUncheckedSideEffectImports": true, /* Check side effect imports. */
43 | // "resolveJsonModule": true, /* Enable importing .json files. */
44 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
45 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */
46 |
47 | /* JavaScript Support */
48 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
49 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
50 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
51 |
52 | /* Emit */
53 | "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
54 | "declarationMap": true, /* Create sourcemaps for d.ts files. */
55 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
56 | "sourceMap": true, /* Create source map files for emitted JavaScript files. */
57 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
58 | // "noEmit": true, /* Disable emitting files from a compilation. */
59 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
60 | "outDir": "dist/", /* Specify an output folder for all emitted files. */
61 | // "removeComments": true, /* Disable emitting comments. */
62 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
63 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
64 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
65 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
66 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
67 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
68 | // "newLine": "crlf", /* Set the newline character for emitting files. */
69 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
70 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
71 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
72 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
73 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
74 |
75 | /* Interop Constraints */
76 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
77 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
78 | // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
79 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
80 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
81 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
82 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
83 |
84 | /* Type Checking */
85 | "strict": true, /* Enable all strict type-checking options. */
86 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
87 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
88 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
89 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
90 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
91 | // "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */
92 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
93 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
94 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
95 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
96 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
97 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
98 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
99 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
100 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
101 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
102 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
103 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
104 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
105 |
106 | /* Completeness */
107 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
108 | "skipLibCheck": true /* Skip type checking all .d.ts files. */
109 | },
110 | "include": ["src/**/*"]
111 | }
112 |
--------------------------------------------------------------------------------
/samples/functions/functionCalling.ts:
--------------------------------------------------------------------------------
1 | import { AutoInvokeKernelFunctions, OpenAIPromptExecutionSettings, openAIChatCompletionService } from '@semantic-kernel/openai';
2 | import { kernel, kernelFunction } from 'semantic-kernel';
3 |
4 |
5 | const sk = kernel().addService(
6 | openAIChatCompletionService({
7 | model: 'gpt-3.5-turbo',
8 | apiKey: '',
9 | })
10 | );
11 |
12 | const encrypt = kernelFunction(({ msg }) => `** ${msg} **`, {
13 | description: 'Creates an encrypted message',
14 | name: 'encrypt',
15 | pluginName: 'encryptor',
16 | parameters: {
17 | type: 'object',
18 | properties: {
19 | msg: { type: 'string', description: 'The raw message to encrypt' },
20 | },
21 | },
22 | });
23 |
24 | sk.plugins.addPlugin({
25 | name: 'encryptor',
26 | description: 'Encryptor plugin',
27 | functions: {
28 | encrypt,
29 | },
30 | });
31 |
32 |
33 | (async () => {
34 | const res = await sk.invokePrompt({
35 | promptTemplate: 'Encrypt this raw input message "Hello World" then return the encrypted message',
36 | executionSettings: {
37 | toolCallBehavior: AutoInvokeKernelFunctions,
38 | } as OpenAIPromptExecutionSettings,
39 | });
40 |
41 | console.log(JSON.stringify(res));
42 | })();
43 |
--------------------------------------------------------------------------------
/samples/react/chat/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/samples/react/chat/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "chat",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@semantic-kernel/react": "file:../../../src/react",
7 | "semantic-kernel": "file:../../../src/semantic-kernel",
8 | "@testing-library/jest-dom": "^5.17.0",
9 | "@testing-library/react": "^13.4.0",
10 | "@testing-library/user-event": "^13.5.0",
11 | "@types/jest": "^27.5.2",
12 | "@types/node": "^16.18.108",
13 | "@types/react": "^18.3.8",
14 | "@types/react-dom": "^18.3.0",
15 | "react": "^18.3.1",
16 | "react-dom": "^18.3.1",
17 | "react-scripts": "5.0.1",
18 | "typescript": "^4.9.5",
19 | "web-vitals": "^2.1.4"
20 | },
21 | "scripts": {
22 | "start": "react-scripts start",
23 | "build": "react-scripts build",
24 | "test": "react-scripts test",
25 | "eject": "react-scripts eject"
26 | },
27 | "eslintConfig": {
28 | "extends": [
29 | "react-app",
30 | "react-app/jest"
31 | ]
32 | },
33 | "browserslist": {
34 | "production": [
35 | ">0.2%",
36 | "not dead",
37 | "not op_mini all"
38 | ],
39 | "development": [
40 | "last 1 chrome version",
41 | "last 1 firefox version",
42 | "last 1 safari version"
43 | ]
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/samples/react/chat/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Semantic Kernel Chat
10 |
11 |
12 |
13 | You need to enable JavaScript to run this app.
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/samples/react/chat/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { Chat } from './Chat';
2 | import React from 'react';
3 |
4 | function App() {
5 | return (
6 |
7 |
8 |
9 |
10 | Semantic Kernel Chat
11 |
12 |
13 |
18 |
19 |
20 |
21 |
22 | );
23 | }
24 |
25 | export default App;
26 |
--------------------------------------------------------------------------------
/samples/react/chat/src/Chat.tsx:
--------------------------------------------------------------------------------
1 | import { useChat } from '@semantic-kernel/react';
2 | import { useState } from 'react';
3 | import { TextContent } from 'semantic-kernel';
4 |
5 | export const Chat = () => {
6 | const [prompt, setPrompt] = useState('');
7 | const [loading, setLoading] = useState(false);
8 | const chat = useChat({
9 | model: 'gpt-3.5-turbo',
10 | apiKey: '',
11 | });
12 |
13 | const handleSubmit = async (e: React.FormEvent) => {
14 | e.preventDefault();
15 |
16 | if (!prompt) {
17 | return;
18 | }
19 |
20 | setLoading(true);
21 | try {
22 | await chat.prompt(prompt);
23 | setPrompt('');
24 | } finally {
25 | setLoading(false);
26 | }
27 | };
28 |
29 | return (
30 |
31 |
32 | {chat.chatHistory.length === 0 && (
33 |
34 | Type a message to start the conversation ✨
35 |
36 | )}
37 |
38 | {chat.chatHistory.map((chatMessage, i) => {
39 | const isUser = chatMessage.role === 'user';
40 |
41 | return (
42 |
43 | {isUser ?
: <>>}
44 |
45 | {(chatMessage.items as TextContent[])[0].text}
46 |
47 | {!isUser ?
: <>>}
48 |
49 | );
50 | })}
51 | {loading &&
}
52 |
53 |
54 |
69 |
70 | );
71 | };
72 |
--------------------------------------------------------------------------------
/samples/react/chat/src/index.css:
--------------------------------------------------------------------------------
1 | html,
2 | body,
3 | #root {
4 | height: 100%;
5 | }
6 |
7 | body {
8 | margin: 0;
9 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
10 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
11 | sans-serif;
12 | -webkit-font-smoothing: antialiased;
13 | -moz-osx-font-smoothing: grayscale;
14 | height: 100%;
15 | }
16 |
17 | code {
18 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
19 | monospace;
20 | }
21 |
--------------------------------------------------------------------------------
/samples/react/chat/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import './index.css';
4 | import App from './App';
5 |
6 | const root = ReactDOM.createRoot(
7 | document.getElementById('root') as HTMLElement
8 | );
9 | root.render(
10 |
11 |
12 |
13 | );
14 |
--------------------------------------------------------------------------------
/samples/react/chat/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/samples/react/chat/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/src/AI/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | import eslint from '@eslint/js';
3 | import tseslint from 'typescript-eslint';
4 |
5 | export default tseslint.config(eslint.configs.recommended, ...tseslint.configs.strict);
6 |
--------------------------------------------------------------------------------
/src/AI/jest.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from '@jest/types';
2 |
3 | // Sync object
4 | const config: Config.InitialOptions = {
5 | verbose: true,
6 | testEnvironment: 'node',
7 | transform: {
8 | '^.+.tsx?$': ['ts-jest', {}],
9 | },
10 | };
11 |
12 | export default config;
13 |
--------------------------------------------------------------------------------
/src/AI/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@semantic-kernel/ai",
3 | "description": "Semantic Kernel AI",
4 | "version": "0.1.0",
5 | "main": "./dist/index.js",
6 | "module": "./dist/index.mjs",
7 | "types": "./dist/index.d.ts",
8 | "homepage": "https://kerneljs.com",
9 | "scripts": {
10 | "build": "tsup",
11 | "clean": "rm -rf dist",
12 | "test:eslint": "eslint \"src/**/*.ts*\"",
13 | "test:prettier": "prettier --check \"src/**/*.ts*\"",
14 | "test:jest": "jest",
15 | "test": "run-p test:*"
16 | },
17 | "files": [
18 | "dist/**/*"
19 | ],
20 | "exports": {
21 | "./package.json": "./package.json",
22 | ".": {
23 | "types": "./dist/index.d.ts",
24 | "import": "./dist/index.mjs",
25 | "require": "./dist/index.js"
26 | }
27 | },
28 | "devDependencies": {
29 | "@eslint/js": "^9.9.0",
30 | "@semantic-kernel/tsconfig": "*",
31 | "@semantic-kernel/service-provider": "*",
32 | "@types/eslint__js": "^8.42.3",
33 | "eslint": "^9.9.0",
34 | "tsup": "^8.2.4",
35 | "typescript": "^5.5.4",
36 | "typescript-eslint": "^8.2.0"
37 | },
38 | "dependencies": {
39 | "json-schema-to-ts": "^3.1.1"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/AI/src/AITool.ts:
--------------------------------------------------------------------------------
1 | import { AdditionalProperties } from './AdditionalProperties';
2 |
3 | export class AITool {
4 | protected constructor() {}
5 |
6 | public name: string = '';
7 | public description: string = '';
8 | public additionalProperties: AdditionalProperties = new AdditionalProperties();
9 | }
10 |
--------------------------------------------------------------------------------
/src/AI/src/AdditionalProperties.ts:
--------------------------------------------------------------------------------
1 | export class AdditionalProperties extends Map {}
2 |
--------------------------------------------------------------------------------
/src/AI/src/UsageDetails.ts:
--------------------------------------------------------------------------------
1 | import { AdditionalProperties } from './AdditionalProperties';
2 |
3 | /**
4 | * Provides usage details about a request/response.
5 | */
6 | export class UsageDetails {
7 | /**
8 | * Gets or sets the number of tokens in the input.
9 | */
10 | inputTokenCount?: number;
11 |
12 | /**
13 | * Gets or sets the number of tokens in the output.
14 | */
15 | outputTokenCount?: number;
16 |
17 | /**
18 | * Gets or sets the total number of tokens used to produce the response.
19 | */
20 | totalTokenCount?: number;
21 |
22 | /**
23 | * Gets or sets a dictionary of additional usage counts.
24 | *
25 | * All values set here are assumed to be summable. For example, when middleware makes multiple calls to an underlying
26 | * service, it may sum the counts from multiple results to produce an overall UsageDetail
27 | */
28 | additionalCounts?: AdditionalProperties;
29 |
30 | /**
31 | * Adds usage data from another UsageDetails into this instance
32 | */
33 | add(usage: UsageDetails) {
34 | if (usage.inputTokenCount !== undefined) {
35 | if (this.inputTokenCount !== undefined) {
36 | this.inputTokenCount += usage.inputTokenCount;
37 | } else {
38 | this.inputTokenCount = usage.inputTokenCount;
39 | }
40 | }
41 |
42 | if (usage.outputTokenCount !== undefined) {
43 | if (this.outputTokenCount !== undefined) {
44 | this.outputTokenCount += usage.outputTokenCount;
45 | } else {
46 | this.outputTokenCount = usage.outputTokenCount;
47 | }
48 | }
49 |
50 | if (usage.totalTokenCount !== undefined) {
51 | if (this.totalTokenCount !== undefined) {
52 | this.totalTokenCount += usage.totalTokenCount;
53 | } else {
54 | this.totalTokenCount = usage.totalTokenCount;
55 | }
56 | }
57 |
58 | if (usage.additionalCounts) {
59 | if (!this.additionalCounts) {
60 | this.additionalCounts = new AdditionalProperties(usage.additionalCounts);
61 | } else {
62 | for (const [key, value] of usage.additionalCounts) {
63 | const existingValue = this.additionalCounts.get(key);
64 | const newValue = existingValue !== undefined ? value + existingValue : value;
65 | this.additionalCounts.set(key, newValue);
66 | }
67 | }
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/AI/src/chatCompletion/AutoChatToolMode.ts:
--------------------------------------------------------------------------------
1 | import { ChatToolModeBase } from './ChatToolModeBase';
2 |
3 | export class AutoChatToolMode extends ChatToolModeBase {
4 | // eslint-disable-next-line @typescript-eslint/no-useless-constructor
5 | constructor() {
6 | super();
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/AI/src/chatCompletion/ChatClient.ts:
--------------------------------------------------------------------------------
1 | import { ChatMessage } from '../contents';
2 | import { ChatClientBuilder } from './ChatClientBuilder';
3 | import { ChatClientMetadata } from './ChatClientMetadata';
4 | import { ChatOptions } from './ChatOptions';
5 | import { ChatResponse } from './ChatResponse';
6 | import { ChatResponseUpdate } from './ChatResponseUpdate';
7 |
8 | export abstract class ChatClient {
9 | abstract complete(chatMessages: string | ChatMessage[], options?: ChatOptions): Promise;
10 |
11 | abstract completeStreaming(
12 | chatMessages: string | ChatMessage[],
13 | options?: ChatOptions
14 | ): AsyncGenerator;
15 |
16 | abstract get metadata(): ChatClientMetadata;
17 | abstract getService(serviceType: T, serviceKey?: string): object | undefined;
18 |
19 | asBuilder(): ChatClientBuilder {
20 | return new ChatClientBuilder({ innerClient: this });
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/AI/src/chatCompletion/ChatClientBuilder.test.ts:
--------------------------------------------------------------------------------
1 | import { ChatClientBuilder } from './ChatClientBuilder';
2 |
3 | describe('ChatClientBuilder', () => {
4 | it('should throw an error if neither innerClient nor innerClientFactory is provided', () => {
5 | expect(() => new ChatClientBuilder({})).toThrowError('Either innerClient or innerClientFactory must be provided');
6 | });
7 | });
8 |
--------------------------------------------------------------------------------
/src/AI/src/chatCompletion/ChatClientBuilder.ts:
--------------------------------------------------------------------------------
1 | import { EmptyServiceProvider, ServiceProvider } from '@semantic-kernel/service-provider';
2 | import { ChatClient } from './ChatClient';
3 |
4 | export class ChatClientBuilder {
5 | private readonly _innerClientFactory: (serviceProvider: ServiceProvider) => ChatClient;
6 | private _clientFactories?: ((chatClient: ChatClient, serviceProvider: ServiceProvider) => ChatClient)[];
7 |
8 | constructor({
9 | innerClient,
10 | innerClientFactory,
11 | }: {
12 | innerClient?: ChatClient;
13 | innerClientFactory?: (serviceProvider: ServiceProvider) => ChatClient;
14 | }) {
15 | if (!innerClient && !innerClientFactory) {
16 | throw new Error('Either innerClient or innerClientFactory must be provided');
17 | }
18 |
19 | if (innerClientFactory) {
20 | this._innerClientFactory = innerClientFactory;
21 | } else {
22 | this._innerClientFactory = () => innerClient as ChatClient;
23 | }
24 | }
25 |
26 | build(serviceProvider?: ServiceProvider): ChatClient {
27 | serviceProvider ??= new EmptyServiceProvider();
28 |
29 | let chatClient = this._innerClientFactory(serviceProvider);
30 |
31 | if (this._clientFactories) {
32 | for (let i = this._clientFactories.length - 1; i >= 0; i--) {
33 | chatClient =
34 | this._clientFactories[i](chatClient, serviceProvider) ??
35 | new Error(
36 | `The ChatClientBuilder entry at index ${i} returned null. Ensure that the callbacks passed to Use return non-null ChatClient instances.`
37 | );
38 | }
39 | }
40 |
41 | return chatClient;
42 | }
43 |
44 | use(clientFactory: (chatClient: ChatClient, serviceProvider?: ServiceProvider) => ChatClient) {
45 | (this._clientFactories ??= []).push(clientFactory);
46 | return this;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/AI/src/chatCompletion/ChatClientMetadata.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Provides metadata about an ChatClient
3 | */
4 | export class ChatClientMetadata {
5 | /**
6 | * Initializes a new instance of the ChatClientMetadata class.
7 | *
8 | * @param providerName
9 | * The name of the chat completion provider, if applicable. Where possible, this should map to the
10 | * appropriate name defined in the OpenTelemetry Semantic Conventions for Generative AI systems.
11 | * @param providerUri The URL for accessing the chat completion provider, if applicable.
12 | * @param modelId The ID of the chat completion model used, if applicable.
13 | */
14 | constructor({
15 | providerName,
16 | providerUri,
17 | modelId,
18 | }: {
19 | providerName?: string;
20 | providerUri?: string;
21 | modelId?: string;
22 | }) {
23 | this.modelId = modelId;
24 | this.providerName = providerName;
25 | this.providerUri = providerUri;
26 | }
27 |
28 | /**
29 | * Gets the name of the chat completion provider.
30 | * Where possible, this maps to the appropriate name defined in the
31 | * OpenTelemetry Semantic Conventions for Generative AI systems.
32 | */
33 | readonly providerName?: string;
34 |
35 | /**
36 | * Gets the URL for accessing the chat completion provider.
37 | */
38 | readonly providerUri?: string;
39 |
40 | /**
41 | * Gets the ID of the model used by this chat completion provider.
42 | * This value can be null if either the name is unknown or there are multiple possible models associated with this instance.
43 | * An individual request may override this value via ChatOptions.ModelId
44 | */
45 | readonly modelId?: string;
46 | }
47 |
--------------------------------------------------------------------------------
/src/AI/src/chatCompletion/ChatFinishReason.ts:
--------------------------------------------------------------------------------
1 | export type ChatFinishReason = 'stop' | 'length' | 'tool_calls' | 'content_filter';
2 |
--------------------------------------------------------------------------------
/src/AI/src/chatCompletion/ChatOptions.ts:
--------------------------------------------------------------------------------
1 | import { AITool } from '../AITool';
2 | import { AdditionalProperties } from '../AdditionalProperties';
3 | import { ChatResponseFormat } from './ChatResponseFormat';
4 | import { ChatToolMode } from './ChatToolMode';
5 |
6 | export class ChatOptions {
7 | /**
8 | * Gets or sets the temperature for generating chat responses.
9 | */
10 | public temperature?: number;
11 |
12 | /**
13 | *Gets or sets the maximum number of tokens in the generated chat response.
14 | */
15 | public maxOutputTokens?: number;
16 |
17 | /*
18 | * Gets or sets the "nucleus sampling" factor (or "top p") for generating chat responses.
19 | */
20 | public topP?: number;
21 |
22 | /**
23 | * Gets or sets a count indicating how many of the most probable tokens the model should consider when generating the next part of the text.
24 | */
25 | public topK?: number;
26 |
27 | /**
28 | * Gets or sets the frequency penalty for generating chat responses.
29 | */
30 | public frequencyPenalty?: number;
31 |
32 | /**
33 | * Gets or sets the presence penalty for generating chat responses.
34 | */
35 | public presencePenalty?: number;
36 |
37 | /**
38 | * Gets or sets a seed value used by a service to control the reproducibility of results.
39 | */
40 | public seed?: number;
41 |
42 | /**
43 | * Gets or sets the chat response format.
44 | */
45 | public responseFormat?: ChatResponseFormat;
46 |
47 | /**
48 | * Gets or sets the model ID for the chat request.
49 | */
50 | public modelId?: string;
51 |
52 | /**
53 | * Gets or sets the stop sequences for generating chat responses.
54 | */
55 | public stopSequences?: string[];
56 |
57 | /**
58 | * Gets or sets the tool mode for the chat request.
59 | */
60 | public toolMode?: ChatToolMode = ChatToolMode.Auto;
61 |
62 | /**
63 | * Gets or sets the list of tools to include with a chat request.
64 | */
65 | public tools?: AITool[];
66 |
67 | /**
68 | * Gets or sets any additional properties associated with the options.
69 | */
70 | public additionalProperties?: AdditionalProperties;
71 |
72 | constructor(props?: Omit) {
73 | this.temperature = props?.temperature;
74 | this.maxOutputTokens = props?.maxOutputTokens;
75 | this.topP = props?.topP;
76 | this.topK = props?.topK;
77 | this.frequencyPenalty = props?.frequencyPenalty;
78 | this.presencePenalty = props?.presencePenalty;
79 | this.seed = props?.seed;
80 | this.responseFormat = props?.responseFormat;
81 | this.modelId = props?.modelId;
82 | this.stopSequences = props?.stopSequences;
83 | this.toolMode = props?.toolMode;
84 | this.tools = props?.tools;
85 | this.additionalProperties = props?.additionalProperties;
86 | }
87 |
88 | clone(): ChatOptions {
89 | const options = new ChatOptions({
90 | temperature: this.temperature,
91 | maxOutputTokens: this.maxOutputTokens,
92 | topP: this.topP,
93 | topK: this.topK,
94 | frequencyPenalty: this.frequencyPenalty,
95 | presencePenalty: this.presencePenalty,
96 | seed: this.seed,
97 | responseFormat: this.responseFormat,
98 | modelId: this.modelId,
99 | stopSequences: this.stopSequences,
100 | toolMode: this.toolMode,
101 | tools: this.tools,
102 | additionalProperties: new AdditionalProperties(this.additionalProperties),
103 | });
104 |
105 | if (this.stopSequences) {
106 | options.stopSequences = [...this.stopSequences];
107 | }
108 |
109 | if (this.tools) {
110 | options.tools = [...this.tools];
111 | }
112 |
113 | return options;
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/AI/src/chatCompletion/ChatResponse.ts:
--------------------------------------------------------------------------------
1 | import { AdditionalProperties } from '../AdditionalProperties';
2 | import { UsageDetails } from '../UsageDetails';
3 | import { ChatMessage } from '../contents';
4 | import { concatText } from '../contents/AIContentHelper';
5 | import { ChatFinishReason } from './ChatFinishReason';
6 |
7 | /**
8 | * Represents the result of a chat completion request.
9 | */
10 | export class ChatResponse {
11 | public choices: ChatMessage[] = [];
12 |
13 | constructor({ choices, message }: { choices?: ChatMessage[]; message?: ChatMessage }) {
14 | if (!choices && !message) {
15 | throw new Error('Either choices or message must be provided.');
16 | }
17 |
18 | if (choices) {
19 | this.choices = choices;
20 | } else if (message) {
21 | this.choices = [message];
22 | }
23 | }
24 |
25 | get message() {
26 | if (!this.choices || !this.choices.length) {
27 | throw new Error(`The ChatResponse instance does not contain any ChatMessage choices.`);
28 | }
29 |
30 | return this.choices[0];
31 | }
32 |
33 | get text() {
34 | return concatText(this.choices);
35 | }
36 |
37 | toString(): string {
38 | return this.text;
39 | }
40 |
41 | /**
42 | * Gets or sets the ID of the chat completion.
43 | */
44 | completionId?: string;
45 |
46 | /**
47 | * Gets or sets the model ID used in the creation of the chat completion.
48 | */
49 | modelId?: string;
50 |
51 | /**
52 | * Gets or sets a timestamp for the chat completion.
53 | */
54 | createdAt?: number;
55 |
56 | /**
57 | * Gets or sets the reason for the chat completion.
58 | */
59 | finishReason?: ChatFinishReason;
60 |
61 | /**
62 | * Gets or sets usage details for the chat completion.
63 | */
64 | usage?: UsageDetails;
65 |
66 | /**
67 | * Gets or sets the raw representation of the chat completion from an underlying implementation.
68 | */
69 | rawRepresentation?: unknown;
70 |
71 | /**
72 | * Gets or sets any additional properties associated with the chat completion.
73 | */
74 | additionalProperties?: AdditionalProperties;
75 | }
76 |
--------------------------------------------------------------------------------
/src/AI/src/chatCompletion/ChatResponseFormat.ts:
--------------------------------------------------------------------------------
1 | import { ChatResponseFormatJson } from './ChatResponseFormatJson';
2 | import { ChatResponseFormatText } from './ChatResponseFormatText';
3 |
4 | // eslint-disable-next-line @typescript-eslint/no-extraneous-class
5 | export class ChatResponseFormat {
6 | private constructor() {}
7 |
8 | static Text = new ChatResponseFormatText();
9 | static Json = new ChatResponseFormatJson({});
10 | }
11 |
--------------------------------------------------------------------------------
/src/AI/src/chatCompletion/ChatResponseFormatBase.ts:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line @typescript-eslint/no-extraneous-class
2 | export class ChatResponseFormatBase {
3 | protected constructor() {}
4 | }
5 |
--------------------------------------------------------------------------------
/src/AI/src/chatCompletion/ChatResponseFormatJson.ts:
--------------------------------------------------------------------------------
1 | import { JsonSchema } from '../jsonSchema';
2 | import { ChatResponseFormatBase } from './ChatResponseFormatBase';
3 |
4 | export class ChatResponseFormatJson extends ChatResponseFormatBase {
5 | constructor({
6 | schema,
7 | schemaName,
8 | schemaDescription,
9 | }: {
10 | schema?: JsonSchema;
11 | schemaName?: string;
12 | schemaDescription?: string;
13 | }) {
14 | super();
15 |
16 | if (!schema && (schemaName || schemaDescription)) {
17 | throw Error('Schema name and description can only be specified if a schema is provided.');
18 | }
19 |
20 | this.schema = schema;
21 | this.schemaName = schemaName;
22 | this.schemaDescription = schemaDescription;
23 | }
24 |
25 | readonly schema?: JsonSchema;
26 | readonly schemaName?: string;
27 | readonly schemaDescription?: string;
28 | }
29 |
--------------------------------------------------------------------------------
/src/AI/src/chatCompletion/ChatResponseFormatText.ts:
--------------------------------------------------------------------------------
1 | import { ChatResponseFormatBase } from './ChatResponseFormatBase';
2 |
3 | export class ChatResponseFormatText extends ChatResponseFormatBase {
4 | public constructor() {
5 | super();
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/AI/src/chatCompletion/ChatResponseUpdate.ts:
--------------------------------------------------------------------------------
1 | import { AdditionalProperties } from '../AdditionalProperties';
2 | import { AIContent } from '../contents';
3 | import { concatText } from '../contents/AIContentHelper';
4 | import { ChatFinishReason } from './ChatFinishReason';
5 | import { ChatRole } from './ChatRole';
6 |
7 | /**
8 | * Represents a single streaming response chunk from a ChatClient.
9 | */
10 | export class ChatResponseUpdate {
11 | /**
12 | * The completion update content items.
13 | */
14 | public contents: AIContent[] = [];
15 |
16 | /**
17 | * The name of the author of the update.
18 | */
19 | public authorName?: string;
20 |
21 | /**
22 | * Gets or sets the role of the author of the completion update.
23 | */
24 | role?: ChatRole;
25 |
26 | get text(): string {
27 | return concatText(this.contents);
28 | }
29 |
30 | rawRepresentation: unknown;
31 |
32 | additionalProperties?: AdditionalProperties;
33 |
34 | /**
35 | * Gets or sets the ID of the completion of which this update is a part.
36 | */
37 | completionId?: string;
38 |
39 | /**
40 | * Gets or sets a timestamp for the completion update.
41 | */
42 | createdAt?: number;
43 |
44 | /**
45 | * Gets or sets the zero-based index of the choice with which this update is associated in the streaming sequence.
46 | */
47 | choiceIndex: number = 0;
48 |
49 | /**
50 | * Gets or sets the finish reason for the operation.
51 | */
52 | finishReason?: ChatFinishReason;
53 |
54 | /**
55 | * Gets or sets the model ID using in the creation of the chat completion of which this update is a part.
56 | */
57 | modelId?: string;
58 |
59 | toString(): string {
60 | return this.text;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/AI/src/chatCompletion/ChatRole.ts:
--------------------------------------------------------------------------------
1 | export type ChatRole = 'system' | 'assistant' | 'user' | 'tool';
2 |
--------------------------------------------------------------------------------
/src/AI/src/chatCompletion/ChatToolMode.ts:
--------------------------------------------------------------------------------
1 | import { AutoChatToolMode } from './AutoChatToolMode';
2 | import { NoneChatToolMode } from './NoneChatToolMode';
3 | import { RequiredChatToolMode } from './RequiredChatToolMode';
4 |
5 | // eslint-disable-next-line @typescript-eslint/no-extraneous-class
6 | export class ChatToolMode {
7 | protected constructor() {}
8 |
9 | public static Auto = new AutoChatToolMode();
10 |
11 | public static None = new NoneChatToolMode();
12 |
13 | public static RequireAny = new RequiredChatToolMode(undefined);
14 |
15 | public static RequireSpecific(functionName: string) {
16 | return new RequiredChatToolMode(functionName);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/AI/src/chatCompletion/ChatToolModeBase.ts:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line @typescript-eslint/no-extraneous-class
2 | export class ChatToolModeBase {
3 | protected constructor() {}
4 | }
5 |
--------------------------------------------------------------------------------
/src/AI/src/chatCompletion/DelegatingChatClient.ts:
--------------------------------------------------------------------------------
1 | import { ChatClient } from '.';
2 | import { ChatMessage } from '../contents';
3 | import { ChatOptions } from './ChatOptions';
4 | import { ChatResponse } from './ChatResponse';
5 | import { ChatResponseUpdate } from './ChatResponseUpdate';
6 |
7 | export class DelegatingChatClient extends ChatClient {
8 | protected _innerClient: ChatClient;
9 |
10 | protected constructor(innerClient: ChatClient) {
11 | super();
12 | this._innerClient = innerClient;
13 | }
14 |
15 | get metadata() {
16 | return this._innerClient.metadata;
17 | }
18 |
19 | getService(serviceType: T, serviceKey?: string): object | undefined {
20 | // If the key is non-null, we don't know what it means so pass through to the inner service.
21 | if (!serviceKey && serviceType === DelegatingChatClient) {
22 | return this;
23 | }
24 |
25 | return this._innerClient.getService(serviceType, serviceKey);
26 | }
27 |
28 | override complete(chatMessages: string | ChatMessage[], options?: ChatOptions): Promise {
29 | return this._innerClient.complete(chatMessages, options);
30 | }
31 |
32 | override completeStreaming(
33 | chatMessages: string | ChatMessage[],
34 | options?: ChatOptions
35 | ): AsyncGenerator {
36 | return this._innerClient.completeStreaming(chatMessages, options);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/AI/src/chatCompletion/FunctionInvocationContext.ts:
--------------------------------------------------------------------------------
1 | import { ChatMessage, FunctionCallContent } from '../contents';
2 | import { AIFunction } from '../functions';
3 | import { AIFunctionArguments } from '../functions/AIFunctionArguments';
4 |
5 | export class FunctionInvocationContext {
6 | function: AIFunction;
7 | callContent: FunctionCallContent;
8 | arguments: AIFunctionArguments;
9 | chatMessages: ChatMessage[];
10 | iteration?: number;
11 | functionCallIndex?: number;
12 | functionCount?: number;
13 | termination?: boolean;
14 |
15 | constructor({
16 | chatMessages,
17 | args,
18 | functionCallContent,
19 | func,
20 | }: {
21 | chatMessages: ChatMessage[];
22 | args: AIFunctionArguments;
23 | functionCallContent: FunctionCallContent;
24 | func: AIFunction;
25 | }) {
26 | this.chatMessages = chatMessages;
27 | this.arguments = args;
28 | this.callContent = functionCallContent;
29 | this.function = func;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/AI/src/chatCompletion/NoneChatToolMode.ts:
--------------------------------------------------------------------------------
1 | import { ChatToolModeBase } from './ChatToolModeBase';
2 |
3 | export class NoneChatToolMode extends ChatToolModeBase {
4 | // eslint-disable-next-line @typescript-eslint/no-useless-constructor
5 | constructor() {
6 | super();
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/AI/src/chatCompletion/RequiredChatToolMode.ts:
--------------------------------------------------------------------------------
1 | import { ChatToolModeBase } from './ChatToolModeBase';
2 |
3 | export class RequiredChatToolMode extends ChatToolModeBase {
4 | requiredFunctionName?: string;
5 |
6 | constructor(requiredFunctionName: string | undefined) {
7 | super();
8 |
9 | if (requiredFunctionName) {
10 | this.requiredFunctionName = requiredFunctionName;
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/AI/src/chatCompletion/index.ts:
--------------------------------------------------------------------------------
1 | export * from './AutoChatToolMode';
2 | export * from './ChatClient';
3 | export * from './ChatClientBuilder';
4 | export * from './ChatClientMetadata';
5 | export * from './ChatFinishReason';
6 | export * from './ChatOptions';
7 | export * from './ChatResponse';
8 | export * from './ChatResponseFormat';
9 | export * from './ChatResponseFormatJson';
10 | export * from './ChatResponseFormatText';
11 | export * from './ChatResponseUpdate';
12 | export * from './ChatRole';
13 | export * from './ChatToolMode';
14 | export * from './DelegatingChatClient';
15 | export * from './FunctionInvocationContext';
16 | export * from './FunctionInvokingChatClient';
17 | export * from './NoneChatToolMode';
18 | export * from './RequiredChatToolMode';
19 |
--------------------------------------------------------------------------------
/src/AI/src/contents/AIContent.ts:
--------------------------------------------------------------------------------
1 | import { AdditionalProperties } from '../AdditionalProperties';
2 |
3 | export class AIContent {
4 | public rawRepresentation?: unknown;
5 | public additionalProperties?: AdditionalProperties;
6 |
7 | protected AIContent() {}
8 | }
9 |
--------------------------------------------------------------------------------
/src/AI/src/contents/AIContentHelper.test.ts:
--------------------------------------------------------------------------------
1 | import { concatText } from './AIContentHelper';
2 | import { ChatMessage } from './ChatMessage';
3 | import { TextContent } from './TextContent';
4 |
5 | describe('AIContentHelper', () => {
6 | it('should concatenate text from AIContent array', () => {
7 | // Arrange
8 | const text1 = new TextContent('Hello');
9 | const text2 = new TextContent('World');
10 | const chatMessage1 = new ChatMessage({ role: 'user', content: 'This is a message.' });
11 | const chatMessage2 = new ChatMessage({ role: 'assistant', content: 'This is a response.' });
12 |
13 | const contents = [text1, text2, chatMessage1, chatMessage2];
14 |
15 | // Act
16 | const result = concatText(contents);
17 |
18 | // Assert
19 | expect(result).toBe('HelloWorldThis is a message.This is a response.');
20 | });
21 |
22 | it('should return empty string for empty array', () => {
23 | // Arrange
24 | const contents: ChatMessage[] = [];
25 |
26 | // Act
27 | const result = concatText(contents);
28 |
29 | // Assert
30 | expect(result).toBe('');
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/src/AI/src/contents/AIContentHelper.ts:
--------------------------------------------------------------------------------
1 | import { AIContent } from './AIContent';
2 | import { ChatMessage } from './ChatMessage';
3 | import { TextContent } from './TextContent';
4 |
5 | /**
6 | * Concatenates the text content of an array of AIContent or ChatMessage objects.
7 | * @param contents An array of AIContent or ChatMessage objects to concatenate.
8 | * @returns A string containing the concatenated text from all provided contents.
9 | */
10 | export function concatText(contents: (AIContent | ChatMessage)[]): string {
11 | const count = contents.length;
12 |
13 | switch (count) {
14 | case 0:
15 | return '';
16 |
17 | case 1: {
18 | const current = contents[0];
19 | if (current instanceof ChatMessage) {
20 | return current.text;
21 | } else {
22 | return (current as TextContent)?.text || '';
23 | }
24 | }
25 |
26 | default: {
27 | let builder = '';
28 | for (let i = 0; i < count; i++) {
29 | const current = contents[i];
30 |
31 | if (current instanceof ChatMessage) {
32 | builder += current.text;
33 | } else {
34 | builder += (current as TextContent)?.text || '';
35 | }
36 | }
37 | return builder;
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/AI/src/contents/ChatMessage.ts:
--------------------------------------------------------------------------------
1 | import { AdditionalProperties } from '../AdditionalProperties';
2 | import { ChatRole } from '../chatCompletion';
3 | import { AIContent } from './AIContent';
4 | import { concatText } from './AIContentHelper';
5 | import { TextContent } from './TextContent';
6 |
7 | export class ChatMessage {
8 | public contents: AIContent[] = [];
9 | public authorName?: string;
10 | public role: ChatRole;
11 | public rawRepresentation: unknown;
12 | public additionalProperties?: AdditionalProperties;
13 |
14 | constructor({ role, content, contents }: { role: ChatRole; content?: string | null; contents?: AIContent[] }) {
15 | if (content) {
16 | this.contents = [new TextContent(content)];
17 | }
18 | if (contents) {
19 | this.contents = contents;
20 | }
21 | this.role = role;
22 | }
23 |
24 | get text(): string {
25 | return concatText(this.contents);
26 | }
27 |
28 | static create(chatMessages: string | ChatMessage[]): ChatMessage[] {
29 | if (typeof chatMessages === 'string') {
30 | return [new ChatMessage({ role: 'user', content: chatMessages })];
31 | }
32 |
33 | return chatMessages;
34 | }
35 |
36 | toString(): string {
37 | return this.text;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/AI/src/contents/FunctionCallContent.ts:
--------------------------------------------------------------------------------
1 | import { AIContent } from './AIContent';
2 |
3 | export class FunctionCallContent extends AIContent {
4 | callId: string;
5 | name: string;
6 | arguments?: Record;
7 | exception?: Error;
8 |
9 | constructor(params: { callId: string; name: string; arguments?: Record }) {
10 | super();
11 | this.callId = params.callId;
12 | this.name = params.name;
13 | this.arguments = params.arguments;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/AI/src/contents/FunctionResultContent.ts:
--------------------------------------------------------------------------------
1 | import { AIContent } from './AIContent';
2 |
3 | export class FunctionResultContent extends AIContent {
4 | callId: string;
5 | name: string;
6 | result: unknown;
7 | exception?: Error;
8 |
9 | constructor({ callId, name, result }: { callId: string; name: string; result: unknown }) {
10 | super();
11 | this.callId = callId;
12 | this.name = name;
13 | this.result = result;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/AI/src/contents/TextContent.ts:
--------------------------------------------------------------------------------
1 | import { AIContent } from './AIContent';
2 |
3 | export class TextContent extends AIContent {
4 | private _text?: string;
5 |
6 | constructor(text?: string) {
7 | super();
8 | this._text = text;
9 | }
10 |
11 | get text(): string {
12 | return this._text ?? '';
13 | }
14 |
15 | set text(value: string) {
16 | this._text = value;
17 | }
18 |
19 | override toString(): string {
20 | return this.text;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/AI/src/contents/UsageContent.ts:
--------------------------------------------------------------------------------
1 | import { UsageDetails } from '../UsageDetails';
2 | import { AIContent } from './AIContent';
3 |
4 | /**
5 | * Represents usage information associated with a chat response.
6 | */
7 | export class UsageContent extends AIContent {
8 | private _details: UsageDetails;
9 |
10 | constructor(details?: UsageDetails) {
11 | super();
12 | if (details) {
13 | this._details = details;
14 | } else {
15 | this._details = new UsageDetails();
16 | }
17 | }
18 |
19 | get details() {
20 | return this._details;
21 | }
22 |
23 | set details(value: UsageDetails) {
24 | this._details = value;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/AI/src/contents/index.ts:
--------------------------------------------------------------------------------
1 | export * from './AIContent';
2 | export * from './ChatMessage';
3 | export * from './FunctionCallContent';
4 | export * from './FunctionResultContent';
5 | export * from './TextContent';
6 | export * from './UsageContent';
7 |
--------------------------------------------------------------------------------
/src/AI/src/functions/AIFunction.ts:
--------------------------------------------------------------------------------
1 | import { AITool } from '../AITool';
2 | import { DefaultJsonSchema, FromSchema, JsonSchema } from '../jsonSchema';
3 | import { AIFunctionArguments } from './AIFunctionArguments';
4 |
5 | export abstract class AIFunction<
6 | ReturnType = unknown,
7 | Schema extends JsonSchema = typeof DefaultJsonSchema,
8 | Args = FromSchema,
9 | > extends AITool {
10 | public schema: Schema | undefined;
11 |
12 | invoke(args?: AIFunctionArguments): Promise {
13 | return this.invokeCore(args);
14 | }
15 |
16 | protected abstract invokeCore(args?: AIFunctionArguments): Promise;
17 | }
18 |
--------------------------------------------------------------------------------
/src/AI/src/functions/AIFunctionArguments.ts:
--------------------------------------------------------------------------------
1 | import { DefaultJsonSchema, FromSchema, JsonSchema } from '../jsonSchema';
2 |
3 | /**
4 | * Represents the arguments for an AI function.
5 | */
6 | export class AIFunctionArguments> {
7 | private _arguments: Args;
8 |
9 | public constructor(args?: Args) {
10 | this._arguments = args || ({} as Args);
11 | }
12 |
13 | /**
14 | * Get the arguments for the kernel function.
15 | */
16 | public get arguments(): Args {
17 | return this._arguments;
18 | }
19 |
20 | /**
21 | * Set the arguments for the kernel function.
22 | */
23 | public set arguments(args: Args) {
24 | this._arguments = args;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/AI/src/functions/AIFunctionFactory.ts:
--------------------------------------------------------------------------------
1 | import { DefaultJsonSchema, FromSchema, JsonSchema } from '../jsonSchema';
2 | import { AIFunction } from './AIFunction';
3 | import { AIFunctionArguments } from './AIFunctionArguments';
4 |
5 | // eslint-disable-next-line @typescript-eslint/no-extraneous-class
6 | export class AIFunctionFactory {
7 | static create>(
8 | delegate: (args: Args) => ReturnType | Promise,
9 | metadata?: {
10 | name?: string;
11 | description?: string;
12 | schema?: Schema;
13 | }
14 | ): AIFunction {
15 | return new (class extends AIFunction {
16 | public constructor() {
17 | super();
18 |
19 | // Take the name from the metadata, or the delegate name, or default to an empty string
20 | this.name = metadata?.name ?? delegate.name ?? '';
21 | this.description = metadata?.description ?? '';
22 | this.schema = metadata?.schema;
23 | }
24 |
25 | protected async invokeCore(args: AIFunctionArguments): Promise {
26 | return delegate(args.arguments);
27 | }
28 | })();
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/AI/src/functions/index.ts:
--------------------------------------------------------------------------------
1 | export * from './AIFunction';
2 | export * from './AIFunctionArguments';
3 | export * from './AIFunctionFactory';
4 |
--------------------------------------------------------------------------------
/src/AI/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './AdditionalProperties';
2 | export * from './AITool';
3 | export * from './chatCompletion';
4 | export * from './contents';
5 | export * from './functions';
6 | export * from './jsonSchema';
7 | export * from './UsageDetails';
8 |
--------------------------------------------------------------------------------
/src/AI/src/jsonSchema/fromSchema.ts:
--------------------------------------------------------------------------------
1 | export { FromSchema } from 'json-schema-to-ts';
2 |
--------------------------------------------------------------------------------
/src/AI/src/jsonSchema/index.ts:
--------------------------------------------------------------------------------
1 | export * from './fromSchema';
2 | export * from './jsonSchema';
3 |
--------------------------------------------------------------------------------
/src/AI/src/jsonSchema/jsonSchema.ts:
--------------------------------------------------------------------------------
1 | import { JSONSchema as JsonSchema } from 'json-schema-to-ts';
2 |
3 | export const DefaultJsonSchema = {} as const satisfies JsonSchema;
4 |
5 | export type { JsonSchema };
6 |
--------------------------------------------------------------------------------
/src/AI/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@semantic-kernel/tsconfig/tsconfig.base.json",
3 | "compilerOptions": {
4 | "rootDir": "src",
5 | "outDir": "dist",
6 | "resolveJsonModule": true,
7 | "esModuleInterop": true
8 | },
9 | "include": ["src"],
10 | "exclude": ["node_modules"]
11 | }
12 |
--------------------------------------------------------------------------------
/src/AI/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'tsup';
2 |
3 | export default defineConfig([
4 | {
5 | entry: ['src/index.ts'],
6 | format: ['cjs', 'esm'],
7 | dts: true,
8 | sourcemap: true,
9 | noExternal: ['@semantic-kernel/service-provider'],
10 | },
11 | ]);
12 |
--------------------------------------------------------------------------------
/src/abstractions/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @semantic-kernel/abstractions
2 |
3 | ## 0.1.0
4 |
5 | ### Minor Changes
6 |
7 | - Include readme and update homepage
8 |
--------------------------------------------------------------------------------
/src/abstractions/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | import eslint from '@eslint/js';
3 | import tseslint from 'typescript-eslint';
4 |
5 | export default tseslint.config(eslint.configs.recommended, ...tseslint.configs.strict);
6 |
--------------------------------------------------------------------------------
/src/abstractions/jest.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from '@jest/types';
2 |
3 | // Sync object
4 | const config: Config.InitialOptions = {
5 | verbose: true,
6 | testEnvironment: 'node',
7 | transform: {
8 | '^.+.tsx?$': ['ts-jest', {}],
9 | },
10 | };
11 |
12 | export default config;
13 |
--------------------------------------------------------------------------------
/src/abstractions/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@semantic-kernel/abstractions",
3 | "description": "Semantic Kernel Abstractions",
4 | "version": "0.1.0",
5 | "main": "./dist/index.js",
6 | "module": "./dist/index.mjs",
7 | "types": "./dist/index.d.ts",
8 | "homepage": "https://kerneljs.com",
9 | "scripts": {
10 | "build": "tsup",
11 | "clean": "rm -rf dist",
12 | "test:eslint": "eslint \"src/**/*.ts*\"",
13 | "test:prettier": "prettier --check \"src/**/*.ts*\"",
14 | "test:jest": "jest",
15 | "test": "run-p test:*"
16 | },
17 | "files": [
18 | "dist/**/*"
19 | ],
20 | "exports": {
21 | "./package.json": "./package.json",
22 | ".": {
23 | "types": "./dist/index.d.ts",
24 | "import": "./dist/index.mjs",
25 | "require": "./dist/index.js"
26 | }
27 | },
28 | "devDependencies": {
29 | "@eslint/js": "^9.9.0",
30 | "@semantic-kernel/service-provider": "*",
31 | "@semantic-kernel/tsconfig": "*",
32 | "@types/eslint__js": "^8.42.3",
33 | "@types/node": "^22.13.9",
34 | "eslint": "^9.9.0",
35 | "tsup": "^8.2.4",
36 | "typescript": "^5.5.4",
37 | "typescript-eslint": "^8.2.0"
38 | },
39 | "dependencies": {
40 | "@semantic-kernel/ai": "*"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/abstractions/src/Kernel.ts:
--------------------------------------------------------------------------------
1 | import { ChatResponseUpdate, type DefaultJsonSchema, type FromSchema, type JsonSchema } from '@semantic-kernel/ai';
2 | import { MapServiceProvider, type ServiceProvider } from '@semantic-kernel/service-provider';
3 | import {
4 | type KernelArguments,
5 | type KernelFunction,
6 | KernelFunctionFromPrompt,
7 | type KernelPlugin,
8 | type KernelPlugins,
9 | MapKernelPlugins,
10 | } from './functions';
11 | import { type PromptExecutionSettings } from './promptExecutionSettings';
12 | import { KernelFunctionFromPromptMetadata } from './promptTemplate';
13 |
14 | /**
15 | * Represents a kernel.
16 | */
17 | export class Kernel {
18 | private readonly _serviceProvider: ServiceProvider;
19 | private readonly _plugins: KernelPlugins;
20 |
21 | /**
22 | * Creates a new kernel.
23 | */
24 | public constructor() {
25 | this._serviceProvider = new MapServiceProvider();
26 | this._plugins = new MapKernelPlugins();
27 | }
28 |
29 | /**
30 | * Gets the {@link KernelPlugins} instance.
31 | */
32 | public get plugins() {
33 | return this._plugins;
34 | }
35 |
36 | /**
37 | * Gets the {@link ServiceProvider} instance.
38 | */
39 | public get services() {
40 | return this._serviceProvider;
41 | }
42 |
43 | /**
44 | * Adds a service to the kernel.
45 | * @param service The service to add.
46 | * @returns The kernel.
47 | */
48 | public addService(...props: Parameters) {
49 | this._serviceProvider.addService(...props);
50 | return this;
51 | }
52 |
53 | /**
54 | * Adds a plugin to the kernel.
55 | * @param plugin The plugin to add.
56 | * @returns The kernel.
57 | */
58 | public addPlugin(plugin: KernelPlugin) {
59 | this._plugins.addPlugin(plugin);
60 | return this;
61 | }
62 |
63 | /**
64 | * Invokes a KernelFunction.
65 | * @param params The parameters for the kernel function.
66 | * @param params.kernelFunction The kernel function to invoke.
67 | * @param params.args The arguments to pass to the kernel function (optional).
68 | * @param params.executionSettings The execution settings to pass to the kernel function (optional).
69 | * @returns The result of the KernelFunction.
70 | */
71 | public async invoke<
72 | ReturnType = unknown,
73 | Schema extends JsonSchema = typeof DefaultJsonSchema,
74 | Args = FromSchema,
75 | >({
76 | kernelFunction,
77 | args,
78 | executionSettings,
79 | }: {
80 | kernelFunction: KernelFunction;
81 | args?: KernelArguments;
82 | executionSettings?: Map | PromptExecutionSettings[] | PromptExecutionSettings;
83 | }) {
84 | if (executionSettings) {
85 | kernelFunction.executionSettings = executionSettings;
86 | }
87 |
88 | const functionResult = await kernelFunction.invoke(this, args);
89 | return functionResult.value;
90 | }
91 |
92 | public invokeStreaming<
93 | T,
94 | ReturnType = unknown,
95 | Schema extends JsonSchema = typeof DefaultJsonSchema,
96 | Args = FromSchema,
97 | >({
98 | kernelFunction,
99 | args,
100 | executionSettings,
101 | }: {
102 | kernelFunction: KernelFunction;
103 | args?: KernelArguments;
104 | executionSettings?: Map | PromptExecutionSettings[] | PromptExecutionSettings;
105 | }) {
106 | if (executionSettings) {
107 | kernelFunction.executionSettings = executionSettings;
108 | }
109 |
110 | return kernelFunction.invokeStreaming(this, args);
111 | }
112 |
113 | /**
114 | * Invokes a prompt.
115 | * @param prompt Prompt to invoke.
116 | * @param params The parameters for the prompt.
117 | * @returns The result of the prompt invocation.
118 | */
119 | public async invokePrompt(
120 | prompt: string,
121 | {
122 | args,
123 | executionSettings,
124 | ...props
125 | }: Omit, 'executionSettings'> & {
126 | args?: KernelArguments;
127 | executionSettings?: Map | PromptExecutionSettings[] | PromptExecutionSettings;
128 | }
129 | ) {
130 | const kernelFunctionFromPrompt = KernelFunctionFromPrompt.create(prompt, props);
131 |
132 | return this.invoke({ kernelFunction: kernelFunctionFromPrompt, args, executionSettings });
133 | }
134 |
135 | /**
136 | * Invokes a streaming prompt.
137 | * @param prompt Prompt to invoke.
138 | * @param params The parameters for the prompt.
139 | * @returns A stream of {@link ChatResponseUpdate} objects.
140 | */
141 | public invokeStreamingPrompt(
142 | prompt: string,
143 | {
144 | args,
145 | executionSettings,
146 | ...props
147 | }: Omit, 'executionSettings'> & {
148 | args?: KernelArguments;
149 | executionSettings?: Map | PromptExecutionSettings[] | PromptExecutionSettings;
150 | }
151 | ) {
152 | const kernelFunctionFromPrompt = KernelFunctionFromPrompt.create(prompt, props);
153 |
154 | return this.invokeStreaming({
155 | kernelFunction: kernelFunctionFromPrompt,
156 | args,
157 | executionSettings,
158 | });
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/src/abstractions/src/functionChoiceBehaviors/AutoFunctionChoiceBehavior.ts:
--------------------------------------------------------------------------------
1 | import { type Kernel } from '../Kernel';
2 | import { FunctionName } from '../functions/FunctionName';
3 | import { KernelFunction } from '../functions/KernelFunction';
4 | import { FunctionChoiceBehaviorBase } from './FunctionChoiceBehaviorBase';
5 | import { type FunctionChoiceBehaviorConfiguration } from './FunctionChoiceBehaviorConfiguration';
6 | import { type FunctionChoiceBehaviorOptions } from './FunctionChoiceBehaviorOptions';
7 |
8 | export class AutoFunctionChoiceBehavior extends FunctionChoiceBehaviorBase {
9 | private readonly autoInvoke: boolean;
10 | private readonly functions: Array | undefined;
11 | public readonly options?: FunctionChoiceBehaviorOptions;
12 |
13 | constructor(functions?: Array, autoInvoke: boolean = true, options?: FunctionChoiceBehaviorOptions) {
14 | super(functions);
15 | this.options = options;
16 | this.autoInvoke = autoInvoke;
17 | this.functions = functions
18 | ?.map(
19 | (f) =>
20 | f.metadata &&
21 | FunctionName.fullyQualifiedName({ functionName: f.metadata.name, pluginName: f.metadata.pluginName })
22 | )
23 | .filter((fqn) => fqn) as Array;
24 | }
25 |
26 | override getConfiguredOptions({ kernel }: { kernel?: Kernel }): FunctionChoiceBehaviorConfiguration {
27 | const functions = this.getFunctions({
28 | functionFQNs: this.functions,
29 | kernel,
30 | autoInvoke: this.autoInvoke,
31 | });
32 |
33 | return {
34 | choice: 'auto',
35 | autoInvoke: this.autoInvoke,
36 | functions,
37 | options: this.options ?? this.defaultOptions,
38 | };
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/abstractions/src/functionChoiceBehaviors/FunctionChoice.ts:
--------------------------------------------------------------------------------
1 | export type FunctionChoice = 'auto' | 'required' | 'none';
2 |
--------------------------------------------------------------------------------
/src/abstractions/src/functionChoiceBehaviors/FunctionChoiceBehavior.ts:
--------------------------------------------------------------------------------
1 | import { type KernelFunction } from '..';
2 | import { AutoFunctionChoiceBehavior } from './AutoFunctionChoiceBehavior';
3 | import { type FunctionChoiceBehaviorOptions } from './FunctionChoiceBehaviorOptions';
4 | import { NoneFunctionChoiceBehavior } from './NoneFunctionChoiceBehavior';
5 |
6 | // eslint-disable-next-line @typescript-eslint/no-extraneous-class
7 | export class FunctionChoiceBehavior {
8 | static Auto(functions?: Array, autoInvoke: boolean = true, options?: FunctionChoiceBehaviorOptions) {
9 | return new AutoFunctionChoiceBehavior(functions, autoInvoke, options);
10 | }
11 |
12 | static None(functions?: Array, options?: FunctionChoiceBehaviorOptions) {
13 | return new NoneFunctionChoiceBehavior(functions, options);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/abstractions/src/functionChoiceBehaviors/FunctionChoiceBehaviorBase.ts:
--------------------------------------------------------------------------------
1 | import { ChatMessage } from '@semantic-kernel/ai';
2 | import { type Kernel } from '../Kernel';
3 | import { FunctionName } from '../functions/FunctionName';
4 | import { type KernelFunction } from '../functions/KernelFunction';
5 | import { type FunctionChoiceBehaviorConfiguration } from './FunctionChoiceBehaviorConfiguration';
6 | import { type FunctionChoiceBehaviorOptions } from './FunctionChoiceBehaviorOptions';
7 |
8 | export abstract class FunctionChoiceBehaviorBase {
9 | protected _functions: Array | undefined;
10 | protected defaultOptions: FunctionChoiceBehaviorOptions = {};
11 |
12 | protected constructor(functions?: Array) {
13 | this._functions = functions;
14 | }
15 |
16 | abstract getConfiguredOptions(context: {
17 | requestSequenceIndex: number;
18 | chatHistory: ChatMessage[];
19 | kernel?: Kernel;
20 | }): FunctionChoiceBehaviorConfiguration;
21 |
22 | protected getFunctions({
23 | functionFQNs,
24 | kernel,
25 | autoInvoke,
26 | }: {
27 | functionFQNs?: string[];
28 | kernel?: Kernel;
29 | autoInvoke?: boolean;
30 | }): Array | undefined {
31 | if (autoInvoke && !kernel) {
32 | throw new Error('Auto-invocation is not supported when no kernel is provided.');
33 | }
34 |
35 | const availableFunctions: Array = [];
36 |
37 | if (functionFQNs && functionFQNs.length > 0) {
38 | for (const functionFQN of functionFQNs) {
39 | const functionNameParts = FunctionName.parse(functionFQN);
40 |
41 | const kernelFunction = kernel?.plugins.getFunction(
42 | functionNameParts.functionName,
43 | functionNameParts.pluginName
44 | );
45 | if (kernelFunction) {
46 | availableFunctions.push(kernelFunction);
47 | continue;
48 | }
49 |
50 | if (autoInvoke) {
51 | throw new Error(`The specified function ${functionFQN} is not available in the kernel.`);
52 | }
53 |
54 | const fn = this._functions?.find(
55 | (f) =>
56 | f.metadata?.name === functionNameParts.functionName &&
57 | f.metadata?.pluginName === functionNameParts.pluginName
58 | );
59 | if (fn) {
60 | availableFunctions.push(fn);
61 | }
62 |
63 | throw new Error(`The specified function ${functionFQN} was not found.`);
64 | }
65 | } else if (functionFQNs?.length === 0) {
66 | return undefined;
67 | } else if (kernel) {
68 | for (const plugin of kernel.plugins.getPlugins()) {
69 | for (const fn of plugin.functions.values()) {
70 | availableFunctions.push(fn);
71 | }
72 | }
73 | }
74 |
75 | return availableFunctions;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/abstractions/src/functionChoiceBehaviors/FunctionChoiceBehaviorConfiguration.ts:
--------------------------------------------------------------------------------
1 | import { type FunctionChoice, type FunctionChoiceBehaviorOptions } from '.';
2 | import { type KernelFunction } from '../functions';
3 |
4 | export interface FunctionChoiceBehaviorConfiguration {
5 | choice: FunctionChoice;
6 | autoInvoke: boolean;
7 | options: FunctionChoiceBehaviorOptions;
8 | functions?: Array;
9 | }
10 |
--------------------------------------------------------------------------------
/src/abstractions/src/functionChoiceBehaviors/FunctionChoiceBehaviorOptions.ts:
--------------------------------------------------------------------------------
1 | export type FunctionChoiceBehaviorOptions = {
2 | allowParallelCalls?: boolean;
3 | allowConcurrentInvocation?: boolean;
4 | };
5 |
--------------------------------------------------------------------------------
/src/abstractions/src/functionChoiceBehaviors/NoneFunctionChoiceBehavior.ts:
--------------------------------------------------------------------------------
1 | import { type Kernel } from '../Kernel';
2 | import { FunctionName } from '../functions/FunctionName';
3 | import { type KernelFunction } from '../functions/KernelFunction';
4 | import { FunctionChoiceBehaviorBase } from './FunctionChoiceBehaviorBase';
5 | import { type FunctionChoiceBehaviorConfiguration } from './FunctionChoiceBehaviorConfiguration';
6 | import { type FunctionChoiceBehaviorOptions } from './FunctionChoiceBehaviorOptions';
7 |
8 | export class NoneFunctionChoiceBehavior extends FunctionChoiceBehaviorBase {
9 | private readonly functions: Array | undefined;
10 | public readonly options?: FunctionChoiceBehaviorOptions;
11 |
12 | constructor(functions?: Array, options?: FunctionChoiceBehaviorOptions) {
13 | super(functions);
14 | this.options = options;
15 | this.functions = functions
16 | ?.map(
17 | (f) =>
18 | f.metadata &&
19 | FunctionName.fullyQualifiedName({ functionName: f.metadata.name, pluginName: f.metadata.pluginName })
20 | )
21 | .filter((fqn) => fqn) as Array;
22 | }
23 |
24 | override getConfiguredOptions({ kernel }: { kernel?: Kernel }): FunctionChoiceBehaviorConfiguration {
25 | const functions = this.getFunctions({
26 | functionFQNs: this.functions,
27 | kernel,
28 | autoInvoke: false,
29 | });
30 |
31 | return {
32 | choice: 'none',
33 | autoInvoke: false,
34 | functions,
35 | options: this.options ?? this.defaultOptions,
36 | };
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/abstractions/src/functionChoiceBehaviors/index.ts:
--------------------------------------------------------------------------------
1 | export * from './AutoFunctionChoiceBehavior';
2 | export * from './FunctionChoice';
3 | export * from './FunctionChoiceBehavior';
4 | export * from './FunctionChoiceBehaviorBase';
5 | export * from './FunctionChoiceBehaviorConfiguration';
6 | export * from './FunctionChoiceBehaviorOptions';
7 | export * from './NoneFunctionChoiceBehavior';
8 |
--------------------------------------------------------------------------------
/src/abstractions/src/functions/FunctionName.test.ts:
--------------------------------------------------------------------------------
1 | import { FunctionName } from './FunctionName';
2 |
3 | describe('FunctionName', () => {
4 | describe('parse', () => {
5 | it('should be able to parse a function name without a plugin name', () => {
6 | // Arrange
7 | const fullyQualifiedName = 'test';
8 |
9 | // Act
10 | const result = FunctionName.parse(fullyQualifiedName);
11 |
12 | // Assert
13 | expect(result).toEqual({
14 | functionName: fullyQualifiedName,
15 | pluginName: undefined,
16 | });
17 | });
18 |
19 | it('should be able to parse a function name with a plugin name and a custom NameSeparator', () => {
20 | // Arrange
21 | const fullyQualifiedName = 'plugin-test';
22 |
23 | // Act
24 | const result = FunctionName.parse(fullyQualifiedName, '-');
25 |
26 | // Assert
27 | expect(result).toEqual({
28 | functionName: 'test',
29 | pluginName: 'plugin',
30 | });
31 | });
32 | });
33 |
34 | describe('fullyQualifiedName', () => {
35 | it('should be able to create a fully qualified name without a plugin name', () => {
36 | // Arrange
37 | const functionName = 'test';
38 |
39 | // Act
40 | const result = FunctionName.fullyQualifiedName({ functionName });
41 |
42 | // Assert
43 | expect(result).toEqual(functionName);
44 | });
45 |
46 | it('should be able to create a fully qualified name with a plugin name', () => {
47 | // Arrange
48 | const functionName = 'test';
49 | const pluginName = 'plugin';
50 |
51 | // Act
52 | const result = FunctionName.fullyQualifiedName({ functionName, pluginName });
53 |
54 | // Assert
55 | expect(result).toEqual(`${pluginName}${FunctionName.NameSeparator}${functionName}`);
56 | });
57 | });
58 | });
59 |
--------------------------------------------------------------------------------
/src/abstractions/src/functions/FunctionName.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Represents a function name, which may include a plugin name.
3 | */
4 | // eslint-disable-next-line @typescript-eslint/no-extraneous-class
5 | export class FunctionName {
6 | /**
7 | * The separator used between the plugin name and the function name, if a plugin name is present.
8 | */
9 | public static readonly NameSeparator = '_';
10 |
11 | static parse = (
12 | fullyQualifiedName: string,
13 | functionNameSeparator: string = FunctionName.NameSeparator
14 | ): {
15 | functionName: string;
16 | pluginName?: string;
17 | } => {
18 | if (!fullyQualifiedName) {
19 | throw new Error('fullyQualifiedName is required');
20 | }
21 |
22 | if (fullyQualifiedName.indexOf(functionNameSeparator) !== -1) {
23 | const parts = fullyQualifiedName.split(functionNameSeparator);
24 | const pluginName = parts[0].trim();
25 | const functionName = parts[1].trim();
26 |
27 | return {
28 | functionName,
29 | pluginName,
30 | };
31 | }
32 |
33 | return {
34 | functionName: fullyQualifiedName,
35 | pluginName: undefined,
36 | };
37 | };
38 |
39 | static fullyQualifiedName = ({
40 | functionName,
41 | pluginName,
42 | nameSeparator,
43 | }: {
44 | functionName: string;
45 | pluginName?: string;
46 | nameSeparator?: string;
47 | }) => (!pluginName ? functionName : `${pluginName}${nameSeparator ?? FunctionName.NameSeparator}${functionName}`);
48 | }
49 |
--------------------------------------------------------------------------------
/src/abstractions/src/functions/FunctionResult.ts:
--------------------------------------------------------------------------------
1 | import { type DefaultJsonSchema, type FromSchema, type JsonSchema } from '@semantic-kernel/ai';
2 | import { type KernelFunction } from '.';
3 |
4 | export type FunctionResult<
5 | ReturnType = unknown,
6 | Schema extends JsonSchema = typeof DefaultJsonSchema,
7 | Args = FromSchema,
8 | > = {
9 | function: KernelFunction;
10 | value?: ReturnType;
11 | renderedPrompt?: string;
12 | };
13 |
--------------------------------------------------------------------------------
/src/abstractions/src/functions/KernelArguments.ts:
--------------------------------------------------------------------------------
1 | import { DefaultJsonSchema, FromSchema, JsonSchema } from '@semantic-kernel/ai';
2 | import { PromptExecutionSettings, defaultServiceId } from '../promptExecutionSettings/PromptExecutionSettings';
3 |
4 | /**
5 | * Represents the arguments for a kernel function.
6 | */
7 | export class KernelArguments> {
8 | private _arguments?: Args;
9 | private _executionSettings?: Map;
10 |
11 | public constructor(
12 | args?: Args,
13 | executionSettings?: Map | PromptExecutionSettings[] | PromptExecutionSettings
14 | ) {
15 | this._arguments = args;
16 |
17 | if (executionSettings) {
18 | if (Array.isArray(executionSettings)) {
19 | const newExecutionSettings = new Map();
20 |
21 | for (const settings of executionSettings) {
22 | const targetServiceId = settings.serviceId ?? defaultServiceId;
23 |
24 | if (this.executionSettings?.has(targetServiceId)) {
25 | throw new Error(`Execution settings for service ID ${targetServiceId} already exists.`);
26 | }
27 |
28 | newExecutionSettings.set(targetServiceId, settings);
29 | }
30 |
31 | this.executionSettings = newExecutionSettings;
32 | } else {
33 | this.executionSettings = executionSettings;
34 | }
35 | }
36 | }
37 |
38 | /**
39 | * Get the arguments for the kernel function.
40 | */
41 | public get arguments(): Args {
42 | return this._arguments ?? ({} as Args);
43 | }
44 |
45 | /**
46 | * Set the arguments for the kernel function.
47 | */
48 | public set arguments(args: Args) {
49 | this._arguments = args;
50 | }
51 |
52 | /**
53 | * Get the execution settings for the kernel function.
54 | */
55 | public get executionSettings(): Map | undefined {
56 | return this._executionSettings;
57 | }
58 |
59 | /**
60 | * Set the execution settings for the kernel function.
61 | */
62 | public set executionSettings(settings: PromptExecutionSettings | Map) {
63 | if (settings instanceof Map) {
64 | if (settings.size > 0) {
65 | for (const [key, setting] of settings.entries()) {
66 | if (setting.serviceId && key !== setting.serviceId) {
67 | throw new Error(`Service ID ${setting.serviceId} must match the key ${key}.`);
68 | }
69 | }
70 | }
71 |
72 | this._executionSettings = settings;
73 | } else {
74 | this._executionSettings = new Map([[defaultServiceId, settings]]);
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/abstractions/src/functions/KernelFunction.test.ts:
--------------------------------------------------------------------------------
1 | import { Kernel } from '../Kernel';
2 | import { KernelArguments } from './KernelArguments';
3 | import { kernelFunction } from './KernelFunction';
4 |
5 | describe('kernelFunction', () => {
6 | describe('creating', () => {
7 | it('should create a kernel function with no params', () => {
8 | // Arrange
9 | const fn = () => 'testResult';
10 | const metadata = {
11 | name: 'testFunction',
12 | parameters: {},
13 | };
14 |
15 | // Act
16 | const result = kernelFunction(fn, metadata);
17 |
18 | // Assert
19 | expect(result.metadata).toEqual(metadata);
20 | });
21 | });
22 |
23 | describe('invoke', () => {
24 | let sk: Kernel;
25 |
26 | beforeEach(() => {
27 | sk = new Kernel();
28 | });
29 |
30 | it('should invoke a function with no params', async () => {
31 | // Arrange
32 | const metadata = {
33 | name: 'testFunction',
34 | };
35 | const fn = kernelFunction(() => 'testResult', metadata);
36 |
37 | // Act
38 | const result = await fn.invoke(sk);
39 |
40 | // Assert
41 | expect(result).toEqual({
42 | function: fn,
43 | value: 'testResult',
44 | });
45 | });
46 |
47 | it('should invoke a function with non-object one param', async () => {
48 | // Arrange
49 | const kernelArguments = new KernelArguments('testValue');
50 |
51 | // Act
52 | const result = await kernelFunction((value) => `**${value}**`, {
53 | name: 'testFunction',
54 | schema: {
55 | type: 'string',
56 | } as const,
57 | }).invoke(sk, kernelArguments);
58 |
59 | kernelFunction((value) => `**${value}**`, {
60 | name: 'testFunction',
61 | });
62 |
63 | // Assert
64 | expect(result.value).toEqual('**testValue**');
65 | });
66 |
67 | it('should invoke a function with one param', async () => {
68 | // Arrange
69 | const kernelArguments = new KernelArguments({ value: 'testValue' });
70 |
71 | // Act
72 | const result = await kernelFunction(({ value }) => `**${value}**`, {
73 | name: 'testFunction',
74 | schema: {
75 | type: 'object',
76 | properties: {
77 | value: {
78 | type: 'string',
79 | },
80 | },
81 | required: ['value'],
82 | } as const,
83 | }).invoke(sk, kernelArguments);
84 |
85 | // Assert
86 | expect(result.value).toEqual('**testValue**');
87 | });
88 |
89 | it('should invoke a function with two optional params', async () => {
90 | // Arrange
91 | const kernelArguments = new KernelArguments({});
92 |
93 | // Act
94 | const result = await kernelFunction(({ p1, p2 }) => `**${p1 ?? 'first'} ${p2 ?? 'second'}**`, {
95 | name: 'testFunction',
96 | schema: {
97 | type: 'object',
98 | properties: {
99 | p1: {
100 | type: 'string',
101 | },
102 | p2: {
103 | type: 'string',
104 | },
105 | },
106 | },
107 | }).invoke(sk, kernelArguments);
108 |
109 | // Assert
110 | expect(result.value).toEqual('**first second**');
111 | });
112 |
113 | it('should invoke a function with one required and one optional property', async () => {
114 | // Arrange
115 | const props = new KernelArguments({ p1: 'hello' });
116 |
117 | // Act
118 | const result = await kernelFunction(({ p1, p2 }) => `**${p1} ${p2}**`, {
119 | name: 'testFunction',
120 | schema: {
121 | type: 'object',
122 | properties: {
123 | p1: {
124 | type: 'string',
125 | },
126 | p2: {
127 | type: 'string',
128 | },
129 | },
130 | required: ['p1'],
131 | } as const,
132 | }).invoke(sk, props);
133 |
134 | // Assert
135 | expect(result.value).toEqual('**hello undefined**');
136 | });
137 |
138 | it('should invoke a function with required mixed string and number datatypes', async () => {
139 | // Arrange
140 | const props = new KernelArguments({ p1: 'hello', p2: 42 });
141 |
142 | // Act
143 | const result = await kernelFunction(({ p1, p2 }) => `**${p1} ${p2}**`, {
144 | name: 'testFunction',
145 | schema: {
146 | type: 'object',
147 | properties: {
148 | p1: {
149 | type: 'string',
150 | },
151 | p2: {
152 | type: 'number',
153 | },
154 | },
155 | required: ['p1', 'p2'],
156 | } as const,
157 | }).invoke(sk, props);
158 |
159 | // Assert
160 | expect(result.value).toEqual('**hello 42**');
161 | });
162 |
163 | it('should invoke a function with mixed optional number datatypes', async () => {
164 | // Arrange
165 | const props = new KernelArguments({ p1: 41, p2: 42 });
166 |
167 | // Act
168 | const result = await kernelFunction(({ p1, p2 }) => Math.min(p1 ?? 0, p2 ?? 0), {
169 | name: 'testFunction',
170 | schema: {
171 | type: 'object',
172 | properties: {
173 | p1: {
174 | type: 'number',
175 | },
176 | p2: {
177 | type: 'number',
178 | },
179 | },
180 | },
181 | }).invoke(sk, props);
182 |
183 | // Assert
184 | expect(result.value).toEqual(41);
185 | });
186 |
187 | it('should invoke a function with nested parameters', async () => {
188 | // Arrange
189 | const props = new KernelArguments({ p1: 41, nested_p1: { p2: 42 } });
190 |
191 | // Act
192 | const result = await kernelFunction(({ p1, nested_p1 }) => Math.max(p1, nested_p1.p2), {
193 | name: 'testFunction',
194 | schema: {
195 | type: 'object',
196 | properties: {
197 | p1: {
198 | type: 'number',
199 | },
200 | nested_p1: {
201 | type: 'object',
202 | properties: {
203 | p2: {
204 | type: 'number',
205 | },
206 | },
207 | required: ['p2'],
208 | additionalProperties: false,
209 | },
210 | },
211 | required: ['p1', 'nested_p1'],
212 | additionalProperties: false,
213 | } as const,
214 | }).invoke(sk, props);
215 |
216 | // Assert
217 | expect(result.value).toEqual(42);
218 | });
219 | });
220 |
221 | // describe('functionInvocationFilters', () => {
222 | // it('should call functionInvocationFilters', async () => {
223 | // // Arrange
224 | // const fn = () => 'testResult';
225 | // const metadata = {
226 | // name: 'testFunction',
227 | // parameters: {},
228 | // };
229 | // const sk = new Kernel();
230 | // const filterCallsHistory: number[] = [];
231 |
232 | // sk.functionInvocationFilters.push({
233 | // onFunctionInvocationFilter: async ({ context, next }) => {
234 | // filterCallsHistory.push(Date.now());
235 | // await next(context);
236 | // },
237 | // });
238 | // sk.functionInvocationFilters.push({
239 | // onFunctionInvocationFilter: async ({ context, next }) => {
240 | // filterCallsHistory.push(Date.now() + 5);
241 | // await next(context);
242 | // },
243 | // });
244 |
245 | // // Act
246 | // await kernelFunction(fn, metadata).invoke(sk);
247 |
248 | // // Assert
249 | // expect(filterCallsHistory).toHaveLength(2);
250 | // expect(filterCallsHistory[0]).toBeLessThan(filterCallsHistory[1]);
251 | // });
252 | // });
253 | });
254 |
--------------------------------------------------------------------------------
/src/abstractions/src/functions/KernelFunction.ts:
--------------------------------------------------------------------------------
1 | import { AIFunctionFactory, type DefaultJsonSchema, type FromSchema, type JsonSchema } from '@semantic-kernel/ai';
2 | import { defaultServiceId } from '@semantic-kernel/service-provider';
3 | import { type Kernel } from '../Kernel';
4 | import { type PromptExecutionSettings } from '../promptExecutionSettings';
5 | import { FunctionName } from './FunctionName';
6 | import { type FunctionResult } from './FunctionResult';
7 | import { KernelArguments } from './KernelArguments';
8 |
9 | export class KernelFunctionMetadata {
10 | name: string = '';
11 | description?: string;
12 | schema?: Schema;
13 | pluginName?: string;
14 | executionSettings?: Map;
15 | }
16 |
17 | export abstract class KernelFunction<
18 | ReturnType = unknown,
19 | Schema extends JsonSchema = typeof DefaultJsonSchema,
20 | Args = FromSchema,
21 | > {
22 | private _metadata: KernelFunctionMetadata;
23 |
24 | constructor(metadata: KernelFunctionMetadata) {
25 | this._metadata = metadata;
26 | }
27 |
28 | get metadata(): KernelFunctionMetadata {
29 | return this._metadata;
30 | }
31 |
32 | set metadata(metadata: KernelFunctionMetadata) {
33 | this._metadata = metadata;
34 | }
35 |
36 | get executionSettings(): Map | undefined {
37 | return this.metadata.executionSettings;
38 | }
39 |
40 | set executionSettings(
41 | settings: Map | PromptExecutionSettings[] | PromptExecutionSettings
42 | ) {
43 | if (Array.isArray(settings)) {
44 | const newExecutionSettings = new Map();
45 |
46 | for (const _settings of settings) {
47 | const targetServiceId = _settings.serviceId ?? defaultServiceId;
48 |
49 | if (this._metadata.executionSettings?.has(targetServiceId)) {
50 | throw new Error(`Execution settings for service ID ${targetServiceId} already exists.`);
51 | }
52 |
53 | newExecutionSettings.set(targetServiceId, _settings);
54 | }
55 |
56 | this._metadata.executionSettings = newExecutionSettings;
57 | } else if (settings instanceof Map) {
58 | this._metadata.executionSettings = settings;
59 | } else {
60 | this._metadata.executionSettings = new Map([[settings.serviceId ?? defaultServiceId, settings]]);
61 | }
62 | }
63 |
64 | protected abstract invokeCore(
65 | kernel: Kernel,
66 | args: KernelArguments
67 | ): Promise>;
68 |
69 | protected abstract invokeStreamingCore(kernel: Kernel, args: KernelArguments): AsyncGenerator;
70 |
71 | async invoke(
72 | kernel: Kernel,
73 | args?: KernelArguments
74 | ): Promise> {
75 | return this.invokeCore(kernel, args ?? new KernelArguments());
76 | }
77 |
78 | async *invokeStreaming(kernel: Kernel, args?: KernelArguments): AsyncGenerator {
79 | const enumerable = this.invokeStreamingCore(kernel, args ?? new KernelArguments());
80 |
81 | for await (const value of enumerable) {
82 | yield value;
83 | }
84 | }
85 |
86 | asAIFunction(kernel: Kernel) {
87 | return AIFunctionFactory.create(
88 | async (args: Args) =>
89 | (await this.invoke(kernel, new KernelArguments(args, this.metadata.executionSettings))).value,
90 | {
91 | ...this.metadata,
92 | name: FunctionName.fullyQualifiedName({
93 | functionName: this.metadata.name,
94 | pluginName: this.metadata.pluginName,
95 | }),
96 | }
97 | );
98 | }
99 | }
100 |
101 | export const kernelFunction = <
102 | ReturnType = unknown,
103 | Schema extends JsonSchema = typeof DefaultJsonSchema,
104 | Args = FromSchema,
105 | >(
106 | fn: (args: Args, kernel?: Kernel) => ReturnType,
107 | metadata: KernelFunctionMetadata
108 | ): KernelFunction => {
109 | return new (class extends KernelFunction {
110 | public constructor() {
111 | super(metadata);
112 | }
113 |
114 | protected override invokeStreamingCore(): AsyncGenerator {
115 | throw new Error('Method not implemented.');
116 | }
117 |
118 | override async invokeCore(
119 | kernel: Kernel,
120 | args: KernelArguments
121 | ): Promise> {
122 | const value = await fn(args.arguments, kernel);
123 |
124 | return {
125 | function: this,
126 | value,
127 | };
128 | }
129 | })();
130 | };
131 |
--------------------------------------------------------------------------------
/src/abstractions/src/functions/KernelFunctionFromPrompt.test.ts:
--------------------------------------------------------------------------------
1 | import { ChatClient, ChatMessage, ChatResponse, ChatResponseUpdate } from '@semantic-kernel/ai';
2 | import { Kernel } from '../Kernel';
3 | import { PromptTemplateFormat } from '../promptTemplate';
4 | import { KernelFunctionFromPrompt } from './KernelFunctionFromPrompt';
5 |
6 | class MockChatClient extends ChatClient {
7 | metadata = {};
8 |
9 | override complete(chatMessage: string): Promise {
10 | return Promise.resolve(
11 | new ChatResponse({ message: new ChatMessage({ content: `** ${chatMessage} **`, role: 'assistant' }) })
12 | );
13 | }
14 |
15 | override completeStreaming(): AsyncGenerator {
16 | throw new Error('Method not implemented.');
17 | }
18 | override getService(): object | undefined {
19 | throw new Error('Method not implemented.');
20 | }
21 | }
22 |
23 | const getMockKernel = () => new Kernel().addService(new MockChatClient());
24 |
25 | describe('kernelFunctionFromPrompt', () => {
26 | it('should render a prompt with a string template', async () => {
27 | // Arrange
28 | const mockKernel = getMockKernel();
29 | const prompt = 'testPrompt';
30 |
31 | // Act
32 | const result = (await KernelFunctionFromPrompt.create(prompt, {}).invoke(mockKernel)) as {
33 | value: ChatResponse;
34 | renderedPrompt: string;
35 | };
36 |
37 | // Assert
38 | expect(result.value.choices[0].text).toEqual('** testPrompt **');
39 | expect(result.renderedPrompt).toEqual('testPrompt');
40 | });
41 |
42 | it('should throw an error if the template format is not supported', async () => {
43 | // Arrange
44 | const mockKernel = getMockKernel();
45 | const prompt = 'testPrompt';
46 |
47 | // Act
48 | const result = KernelFunctionFromPrompt.create(prompt, {
49 | templateFormat: 'unsupported' as PromptTemplateFormat,
50 | });
51 |
52 | // Assert
53 | await expect(result.invoke(mockKernel)).rejects.toThrow('unsupported template rendering not implemented');
54 | });
55 |
56 | it('should throw an error if no AIService is found', async () => {
57 | // Arrange
58 | const prompt = 'testPrompt';
59 |
60 | // Act
61 | const result = KernelFunctionFromPrompt.create(prompt, {});
62 |
63 | // Assert
64 | await expect(result.invoke(new Kernel())).rejects.toThrow('Service not found in kernel');
65 | });
66 | });
67 |
--------------------------------------------------------------------------------
/src/abstractions/src/functions/KernelFunctionFromPrompt.ts:
--------------------------------------------------------------------------------
1 | import { ChatClient, ChatResponse } from '@semantic-kernel/ai';
2 | import { type Kernel } from '../Kernel';
3 | import { type PromptExecutionSettings } from '../promptExecutionSettings/PromptExecutionSettings';
4 | import { toChatOptions } from '../promptExecutionSettings/PromptExecutionSettingsMapper';
5 | import {
6 | type KernelFunctionFromPromptMetadata,
7 | PassThroughPromptTemplate,
8 | type PromptTemplate,
9 | } from '../promptTemplate';
10 | import '../serviceProviderExtension';
11 | import { FunctionResult } from './FunctionResult';
12 | import { type KernelArguments } from './KernelArguments';
13 | import { KernelFunction } from './KernelFunction';
14 |
15 | export class KernelFunctionFromPrompt extends KernelFunction {
16 | private constructor(kernelFunctionFromPromptMetadata: KernelFunctionFromPromptMetadata) {
17 | super(kernelFunctionFromPromptMetadata);
18 | }
19 |
20 | /**
21 | * Creates a new kernel function from a prompt.
22 | * @param params The parameters to create the kernel function from a prompt.
23 | * @param params.template The template for the prompt.
24 | * @param params.name The name of the kernel function (optional).
25 | * @param params.description The description of the kernel function (optional).
26 | * @param params.templateFormat The format of the template (optional).
27 | * @param params.inputVariables The input variables for the prompt (optional).
28 | * @param params.allowDangerouslySetContent Whether to allow dangerously set content (optional).
29 | * @returns A new kernel function from a prompt.
30 | */
31 | static create(
32 | prompt: string,
33 | { name, description, templateFormat, ...props }: Partial
34 | ) {
35 | return new KernelFunctionFromPrompt({
36 | prompt,
37 | name: name ?? KernelFunctionFromPrompt.createRandomFunctionName(),
38 | description: description ?? 'Generic function, unknown purpose',
39 | templateFormat: templateFormat ?? 'passthrough',
40 | ...props,
41 | });
42 | }
43 |
44 | override async invokeCore(kernel: Kernel, args: KernelArguments): Promise> {
45 | const { renderedPrompt, service, executionSettings } = await this.renderPrompt(kernel, args);
46 |
47 | if (!service) {
48 | throw new Error('Service not found in kernel');
49 | }
50 |
51 | if (service instanceof ChatClient) {
52 | const chatCompletionResult = await service.complete(renderedPrompt, toChatOptions(kernel, executionSettings));
53 |
54 | return {
55 | function: this,
56 | value: chatCompletionResult,
57 | renderedPrompt: renderedPrompt,
58 | };
59 | }
60 |
61 | throw new Error(`Unsupported service type: ${service}`);
62 | }
63 |
64 | override async *invokeStreamingCore(kernel: Kernel, args: KernelArguments): AsyncGenerator {
65 | const { renderedPrompt, service, executionSettings } = await this.renderPrompt(kernel, args);
66 |
67 | if (!service) {
68 | throw new Error('Service not found in kernel');
69 | }
70 |
71 | if (service instanceof ChatClient) {
72 | const chatCompletionUpdates = service.completeStreaming(renderedPrompt, toChatOptions(kernel, executionSettings));
73 |
74 | for await (const chatCompletionUpdate of chatCompletionUpdates) {
75 | yield chatCompletionUpdate as T;
76 | }
77 |
78 | return;
79 | }
80 |
81 | throw new Error(`Unsupported service type: ${service}`);
82 | }
83 |
84 | private getPromptTemplate = (): PromptTemplate => {
85 | const metadata = this.metadata as KernelFunctionFromPromptMetadata;
86 | switch (metadata.templateFormat) {
87 | case 'passthrough':
88 | return new PassThroughPromptTemplate(metadata.prompt);
89 | default:
90 | throw new Error(`${metadata.templateFormat} template rendering not implemented`);
91 | }
92 | };
93 |
94 | private async renderPrompt(
95 | kernel: Kernel,
96 | args: KernelArguments
97 | ): Promise<{
98 | renderedPrompt: string;
99 | executionSettings?: PromptExecutionSettings;
100 | service: ChatClient;
101 | }> {
102 | if (!kernel) {
103 | throw new Error('Kernel is required to render prompt');
104 | }
105 |
106 | const { service, executionSettings } =
107 | kernel.services.trySelectService({
108 | serviceType: ChatClient,
109 | kernelFunction: this,
110 | }) ?? {};
111 |
112 | if (!service) {
113 | throw new Error('Service not found in kernel');
114 | }
115 |
116 | const promptTemplate = this.getPromptTemplate();
117 | const renderedPrompt = await promptTemplate.render(kernel, args);
118 |
119 | return {
120 | renderedPrompt,
121 | executionSettings,
122 | service,
123 | };
124 | }
125 |
126 | private static createRandomFunctionName() {
127 | return `function_${Math.random().toString(36).substring(7)}`;
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/abstractions/src/functions/KernelPlugin.ts:
--------------------------------------------------------------------------------
1 | import { type KernelFunction } from './KernelFunction';
2 |
3 | type BaseKernelPlugin = {
4 | name: string;
5 | description: string;
6 | };
7 |
8 | /**
9 | * ArrayKernelPlugin represents a plugin that contains an array of functions.
10 | *This type is used in {@link KernelPlugins} to represent a plugin.
11 | */
12 | type ArrayKernelPlugin = BaseKernelPlugin & {
13 | functions: Array;
14 | };
15 |
16 | export type KernelPlugin = ArrayKernelPlugin;
17 |
18 | /**
19 | * MapKernelPlugin represents a plugin that contains a map of functions.
20 | * This type is the internal representation of a plugin in {@link KernelPlugins}.
21 | */
22 | export type MapKernelPlugin = BaseKernelPlugin & {
23 | functions: Map;
24 | };
25 |
--------------------------------------------------------------------------------
/src/abstractions/src/functions/KernelPlugins.test.ts:
--------------------------------------------------------------------------------
1 | import { type JsonSchema } from '@semantic-kernel/ai';
2 | import { type KernelFunction, kernelFunction } from './KernelFunction';
3 | import { type KernelPlugin } from './KernelPlugin';
4 | import { MapKernelPlugins } from './KernelPlugins';
5 |
6 | const getMockFunction = (functionName?: string, functionDescription?: string, schema?: JsonSchema) => {
7 | return kernelFunction(() => 'testResult', {
8 | name: functionName ?? 'testFunction',
9 | description: functionDescription ?? 'testDescription',
10 | schema: schema,
11 | });
12 | };
13 |
14 | const getMockPlugin = (
15 | pluginFunctions: KernelFunction[],
16 | pluginName?: string,
17 | pluginDescription?: string
18 | ): KernelPlugin => {
19 | return {
20 | name: pluginName ?? 'testPlugin',
21 | description: pluginDescription ?? 'testDescription',
22 | functions: pluginFunctions,
23 | };
24 | };
25 |
26 | const getMockKernelPlugins = () => new MapKernelPlugins();
27 |
28 | describe('kernelPlugins', () => {
29 | describe('getPlugins', () => {
30 | it('should return an object with the correct properties', () => {
31 | // Arrange
32 | const kernelPlugins = new MapKernelPlugins();
33 |
34 | // Act
35 | const plugins = [...kernelPlugins.getPlugins()];
36 |
37 | // Assert
38 | expect(plugins).toHaveLength(0);
39 | });
40 |
41 | it('should return all plugins', () => {
42 | // Arrange
43 | const mockKernelPlugins = getMockKernelPlugins();
44 | const mockPlugin1 = getMockPlugin(
45 | [getMockFunction('testFunction1'), getMockFunction('testFunction2')],
46 | 'testPlugin1',
47 | 'testDescription1'
48 | );
49 | const mockPlugin2 = getMockPlugin(
50 | [getMockFunction('testFunction1'), getMockFunction('testFunction2')],
51 | 'testPlugin2',
52 | 'testDescription2'
53 | );
54 |
55 | mockKernelPlugins.addPlugin(mockPlugin1);
56 | mockKernelPlugins.addPlugin(mockPlugin2);
57 |
58 | // Act
59 | const plugins = [...mockKernelPlugins.getPlugins()];
60 |
61 | // Assert
62 | expect(plugins).toHaveLength(2);
63 | expect(plugins[0].name).toBe('testPlugin1');
64 | expect(plugins[0].description).toBe('testDescription1');
65 | expect(plugins[1].name).toBe('testPlugin2');
66 | expect(plugins[1].description).toBe('testDescription2');
67 | });
68 | });
69 |
70 | describe('addPlugin', () => {
71 | it('should add a plugin with multiple functions', () => {
72 | // Arrange
73 | const mockKernelPlugins = getMockKernelPlugins();
74 |
75 | // Act
76 | mockKernelPlugins.addPlugin(getMockPlugin([getMockFunction('testFunction1'), getMockFunction('testFunction2')]));
77 |
78 | // Assert
79 | const plugins = [...mockKernelPlugins.getPlugins()];
80 | expect(plugins).toHaveLength(1);
81 | expect([...plugins][0].name).toEqual('testPlugin');
82 | expect([...plugins][0].functions.size).toBe(2);
83 | });
84 |
85 | it('should add a plugin with correct functions', () => {
86 | // Arrange
87 | const mockKernelPlugins = getMockKernelPlugins();
88 |
89 | // Act
90 | mockKernelPlugins.addPlugin(
91 | getMockPlugin([
92 | getMockFunction('testFunction1', 'testDescription1', { type: 'string' }),
93 | getMockFunction('testFunction2', 'testDescription2', { type: 'number' }),
94 | ])
95 | );
96 |
97 | // Assert
98 | const plugins = [...mockKernelPlugins.getPlugins()];
99 | const firstPlugin = plugins[0];
100 | expect([...firstPlugin.functions.entries()]).toHaveLength(2);
101 | expect(firstPlugin.functions.get('testFunction1')?.metadata).toStrictEqual({
102 | name: 'testFunction1',
103 | description: 'testDescription1',
104 | pluginName: 'testPlugin',
105 | schema: { type: 'string' },
106 | });
107 | expect(firstPlugin.functions.get('testFunction2')?.metadata).toStrictEqual({
108 | name: 'testFunction2',
109 | description: 'testDescription2',
110 | pluginName: 'testPlugin',
111 | schema: { type: 'number' },
112 | });
113 | });
114 |
115 | it('should not add plugin without functions', () => {
116 | // Arrange
117 | const mockKernelPlugins = getMockKernelPlugins();
118 |
119 | // Act
120 | // Assert
121 | expect(() => {
122 | mockKernelPlugins.addPlugin(getMockPlugin([], 'testPlugin'));
123 | }).toThrow();
124 | });
125 |
126 | it('should not add the same plugin twice', () => {
127 | // Arrange
128 | const mockKernelPlugins = getMockKernelPlugins();
129 |
130 | // Act
131 | // Assert
132 | mockKernelPlugins.addPlugin(
133 | getMockPlugin([getMockFunction('testFunction2', 'testDescription2', { type: 'number' })], 'testPlugin')
134 | );
135 |
136 | expect(() => {
137 | mockKernelPlugins.addPlugin(
138 | getMockPlugin([getMockFunction('testFunction2', 'testDescription2', { type: 'number' })], 'testPlugin')
139 | );
140 | }).toThrow();
141 | });
142 |
143 | it('should set the correct pluginName to functions', () => {
144 | // Arrange
145 | const mockKernelPlugins = getMockKernelPlugins();
146 |
147 | // Act
148 | mockKernelPlugins.addPlugin(getMockPlugin([getMockFunction('testFunction1'), getMockFunction('testFunction2')]));
149 |
150 | // Assert
151 | const firstPlugin = [...mockKernelPlugins.getPlugins()][0];
152 | const firstPluginFunctions = [...firstPlugin.functions.values()];
153 | expect(firstPluginFunctions.map((fn) => [fn.metadata?.name, fn.metadata?.pluginName])).toStrictEqual([
154 | ['testFunction1', 'testPlugin'],
155 | ['testFunction2', 'testPlugin'],
156 | ]);
157 | });
158 | });
159 |
160 | describe('getFunction', () => {
161 | it('should return the correct undefined when function is not found', () => {
162 | // Arrange
163 | const mockKernelPlugins = getMockKernelPlugins();
164 |
165 | // Act
166 | const result = mockKernelPlugins.getFunction('not-found');
167 |
168 | // Assert
169 | expect(result).toBeUndefined();
170 | });
171 |
172 | it('should return the undefined when pluginName is not defined', () => {
173 | // Arrange
174 | const stubPluginName = 'testPlugin';
175 | const stubFunctionName = 'testFunction1';
176 | const mockKernelPlugins = getMockKernelPlugins();
177 | mockKernelPlugins.addPlugin(getMockPlugin([getMockFunction(stubFunctionName)], stubPluginName));
178 |
179 | // Act
180 | const result = mockKernelPlugins.getFunction('testFunction1', 'not-found');
181 |
182 | // Assert
183 | expect(result).toBeUndefined();
184 | });
185 |
186 | it('should return the correct function with functionName', () => {
187 | // Arrange
188 | const stubPluginName = 'testPlugin';
189 | const stubFunctionName = 'testFunction1';
190 | const mockKernelPlugins = getMockKernelPlugins();
191 | mockKernelPlugins.addPlugin(getMockPlugin([getMockFunction(stubFunctionName)], stubPluginName));
192 |
193 | // Act
194 | const result = mockKernelPlugins.getFunction(stubFunctionName);
195 |
196 | // Assert
197 | expect(result?.metadata?.name).toBe(stubFunctionName);
198 | });
199 |
200 | it('should return the correct function with functionName and pluginName', () => {
201 | // Arrange
202 | const stubPluginName = 'testPlugin';
203 | const stubFunctionName = 'testFunction1';
204 | const mockKernelPlugins = getMockKernelPlugins();
205 | mockKernelPlugins.addPlugin(getMockPlugin([getMockFunction(stubFunctionName)], stubPluginName));
206 |
207 | // Act
208 | const result = mockKernelPlugins.getFunction(stubFunctionName);
209 |
210 | // Assert
211 | expect(result?.metadata?.name).toBe(stubFunctionName);
212 | });
213 | });
214 |
215 | describe('getFunctionsMetadata', () => {
216 | it('should return all functions metadata', () => {
217 | // Arrange
218 | const mockKernelPlugins = getMockKernelPlugins();
219 | const mockPlugin1 = getMockPlugin(
220 | [
221 | getMockFunction('testFunction1', 'testDescription1', { type: 'string' }),
222 | getMockFunction('testFunction2', 'testDescription2', { type: 'number' }),
223 | ],
224 | 'testPlugin1',
225 | 'testPluginDescription1'
226 | );
227 | const mockPlugin2 = getMockPlugin(
228 | [
229 | getMockFunction('testFunction3', 'testDescription3', { type: 'boolean' }),
230 | getMockFunction('testFunction4', 'testDescription4', { type: 'object' }),
231 | ],
232 | 'testPlugin2',
233 | 'testPluginDescription2'
234 | );
235 |
236 | mockKernelPlugins.addPlugin(mockPlugin1);
237 | mockKernelPlugins.addPlugin(mockPlugin2);
238 |
239 | // Act
240 | const functionsMetadata = mockKernelPlugins.getFunctionsMetadata();
241 |
242 | // Assert
243 | expect(functionsMetadata).toHaveLength(4);
244 | expect(functionsMetadata).toEqual([
245 | {
246 | name: 'testFunction1',
247 | description: 'testDescription1',
248 | pluginName: 'testPlugin1',
249 | schema: { type: 'string' },
250 | },
251 | {
252 | name: 'testFunction2',
253 | description: 'testDescription2',
254 | pluginName: 'testPlugin1',
255 | schema: { type: 'number' },
256 | },
257 | {
258 | name: 'testFunction3',
259 | description: 'testDescription3',
260 | pluginName: 'testPlugin2',
261 | schema: { type: 'boolean' },
262 | },
263 | {
264 | name: 'testFunction4',
265 | description: 'testDescription4',
266 | pluginName: 'testPlugin2',
267 | schema: { type: 'object' },
268 | },
269 | ]);
270 | });
271 | });
272 | });
273 |
--------------------------------------------------------------------------------
/src/abstractions/src/functions/KernelPlugins.ts:
--------------------------------------------------------------------------------
1 | import type { KernelFunction, KernelFunctionMetadata, KernelPlugin, MapKernelPlugin } from '.';
2 |
3 | export type KernelPlugins = {
4 | addPlugin: (plugin: KernelPlugin) => KernelPlugins;
5 | getPlugins: () => Iterable;
6 | getFunctionsMetadata: () => KernelFunctionMetadata[];
7 | getFunction: (functionName: string, pluginName?: string) => KernelFunction | undefined;
8 | };
9 |
10 | /**
11 | * Creates a new instance of KernelPlugins.
12 | * The internal representation plugins is a map of plugin names to plugins.
13 | * @returns A new instance of KernelPlugins.
14 | */
15 | export class MapKernelPlugins implements KernelPlugins {
16 | private readonly plugins: Map = new Map();
17 |
18 | addPlugin(plugin: KernelPlugin) {
19 | if (plugin.functions.length === 0) {
20 | throw new Error(`Plugin ${plugin.name} does not contain any functions`);
21 | }
22 |
23 | if (this.plugins.has(plugin.name)) {
24 | throw new Error(`Plugin ${plugin.name} has already been added`);
25 | }
26 |
27 | const mapPlugin: MapKernelPlugin = {
28 | ...plugin,
29 | functions: new Map(),
30 | };
31 |
32 | for (const pluginFunction of plugin.functions) {
33 | if (pluginFunction.metadata) {
34 | if (mapPlugin.functions.has(pluginFunction.metadata.name)) {
35 | throw new Error(`Function ${pluginFunction.metadata.name} has already been added to plugin ${plugin.name}`);
36 | }
37 |
38 | // Add the plugin name to the metadata of each function
39 | pluginFunction.metadata = {
40 | ...pluginFunction.metadata,
41 | pluginName: plugin.name,
42 | };
43 | // TODO: is this necessary?
44 | mapPlugin.functions.set(pluginFunction.metadata.name, pluginFunction);
45 | }
46 | }
47 |
48 | this.plugins.set(plugin.name, mapPlugin);
49 |
50 | return this;
51 | }
52 |
53 | getPlugins() {
54 | return this.plugins.values();
55 | }
56 |
57 | /**
58 | * Gets a collection of KernelFunctionMetadata instances, one for every function in this plugin.
59 | */
60 | getFunctionsMetadata() {
61 | return Array.from(this.getPlugins()).flatMap((plugin) =>
62 | Array.from(plugin.functions.values())
63 | .filter((fn) => fn.metadata)
64 | .map((fn) => fn.metadata)
65 | );
66 | }
67 |
68 | getFunction(functionName: string, pluginName?: string) {
69 | if (pluginName) {
70 | // Search a specific plugin
71 | const plugin = this.plugins.get(pluginName);
72 |
73 | if (plugin) {
74 | const fn = plugin.functions.get(functionName);
75 |
76 | if (fn) {
77 | return fn;
78 | }
79 | }
80 | } else {
81 | // Search all plugins
82 | for (const plugin of this.plugins.values()) {
83 | const fn = plugin.functions.get(functionName);
84 |
85 | if (fn) {
86 | return fn;
87 | }
88 | }
89 | }
90 |
91 | return undefined;
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/abstractions/src/functions/index.ts:
--------------------------------------------------------------------------------
1 | export * from './FunctionName';
2 | export * from './FunctionResult';
3 | export * from './KernelArguments';
4 | export * from './KernelFunction';
5 | export * from './KernelFunctionFromPrompt';
6 | export * from './KernelPlugin';
7 | export * from './KernelPlugins';
8 |
--------------------------------------------------------------------------------
/src/abstractions/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './functionChoiceBehaviors';
2 | export * from './functions';
3 | export * from './internalUtilities';
4 | export * from './Kernel';
5 | export * from './promptExecutionSettings';
6 | export * from './promptTemplate';
7 |
--------------------------------------------------------------------------------
/src/abstractions/src/internalUtilities/http/httpHeaderConstant.ts:
--------------------------------------------------------------------------------
1 | import pkg from '../../../package.json';
2 |
3 | /**
4 | * HTTP header name to use to include the Semantic Kernel package version in all HTTP requests issued by Semantic Kernel.
5 | */
6 | export const SemanticKernelVersionHttpHeaderName = 'Semantic-Kernel-JS-Version';
7 |
8 | /**
9 | * HTTP header value to use to include the Semantic Kernel package version in all HTTP requests issued by Semantic Kernel.
10 | */
11 | export const SemanticKernelVersionHttpHeaderValue = pkg.version;
12 |
13 | /**
14 | * User agent string to use for all HTTP requests issued by Semantic Kernel.
15 | */
16 | export const SemanticKernelUserAgent = 'Semantic-Kernel-JS';
17 |
--------------------------------------------------------------------------------
/src/abstractions/src/internalUtilities/http/index.ts:
--------------------------------------------------------------------------------
1 | export * from './httpHeaderConstant';
2 |
--------------------------------------------------------------------------------
/src/abstractions/src/internalUtilities/index.ts:
--------------------------------------------------------------------------------
1 | export * from './http';
2 |
--------------------------------------------------------------------------------
/src/abstractions/src/promptExecutionSettings/PromptExecutionSettings.ts:
--------------------------------------------------------------------------------
1 | import { type FunctionChoiceBehaviorBase } from '../functionChoiceBehaviors';
2 |
3 | /**
4 | * Gets the default service identifier.
5 | * In a dictionary of {@link PromptExecutionSettings}, this is the key that should be used settings considered the default.
6 | */
7 | export const defaultServiceId = 'default';
8 |
9 | /**
10 | * Service identifier.
11 | */
12 | export type ServiceId = string;
13 |
14 | /**
15 | * Provides execution settings for an AI request.
16 | * Implementors of {@link TextGenerationService} or {@link ChatCompletionService} can extend this
17 | * if the service they are calling supports additional properties. For an example, please reference
18 | * the {@link OpenAIPromptExecutionSettings} implementation.
19 | */
20 | export interface PromptExecutionSettings {
21 | /**
22 | * Service identifier.
23 | * This identifies the service these settings are configured for e.g., azure_openai_eastus, openai, ollama, huggingface, etc.
24 | * When provided, this service identifier will be the key in a dictionary collection of execution settings for both KernelArguments and PromptTemplateConfig.
25 | * If not provided the service identifier will be the default value in {@link defaultServiceId}.
26 | */
27 | serviceId?: ServiceId;
28 |
29 | /**
30 | * Model identifier.
31 | * This identifies the AI model these settings are configured for e.g., gpt-4, gpt-3.5-turbo
32 | */
33 | modelId?: string;
34 |
35 | /**
36 | * Extra properties that may be included in the serialized execution settings.
37 | * Avoid using this property if possible. Instead, use one of the classes that extends {@link PromptExecutionSettings}.
38 | */
39 | extensionData?: Map;
40 |
41 | /**
42 | * Gets or sets the behavior defining the way functions are chosen by LLM and how they are invoked by AI connectors.
43 | */
44 | functionChoiceBehavior?: FunctionChoiceBehaviorBase;
45 | }
46 |
--------------------------------------------------------------------------------
/src/abstractions/src/promptExecutionSettings/PromptExecutionSettingsMapper.ts:
--------------------------------------------------------------------------------
1 | import { ChatOptions, ChatResponseFormat, ChatResponseFormatJson, ChatToolMode } from '@semantic-kernel/ai';
2 | import { AutoFunctionChoiceBehavior } from '../functionChoiceBehaviors/AutoFunctionChoiceBehavior';
3 | import { NoneFunctionChoiceBehavior } from '../functionChoiceBehaviors/NoneFunctionChoiceBehavior';
4 | import { type Kernel } from '../Kernel';
5 | import { PromptExecutionSettings } from './PromptExecutionSettings';
6 |
7 | export const toChatOptions = (kernel: Kernel, settings?: PromptExecutionSettings): ChatOptions | undefined => {
8 | if (!settings) {
9 | return undefined;
10 | }
11 |
12 | const chatOptions = new ChatOptions({
13 | modelId: settings.modelId,
14 | });
15 |
16 | if (settings.extensionData) {
17 | for (const [key, value] of settings.extensionData) {
18 | if (key === 'temperature' && typeof value === 'number') {
19 | chatOptions.temperature = value;
20 | } else if (key === 'top_p' && typeof value === 'number') {
21 | chatOptions.topP = value;
22 | } else if (key === 'top_k' && typeof value === 'number') {
23 | chatOptions.topK = value;
24 | } else if (key === 'seed' && typeof value === 'number') {
25 | chatOptions.seed = value;
26 | } else if (key === 'max_tokens' && typeof value === 'number') {
27 | chatOptions.maxOutputTokens = value;
28 | } else if (key === 'frequency_penalty' && typeof value === 'number') {
29 | chatOptions.frequencyPenalty = value;
30 | } else if (key === 'presence_penalty' && typeof value === 'number') {
31 | chatOptions.presencePenalty = value;
32 | } else if (key === 'stop_sequences' && value instanceof Array) {
33 | chatOptions.stopSequences = value;
34 | } else if (key === 'response_format') {
35 | if (value === 'text') {
36 | chatOptions.responseFormat = ChatResponseFormat.Text;
37 | } else if (value === 'json_object') {
38 | chatOptions.responseFormat = ChatResponseFormat.Json;
39 | } else if (value instanceof Object) {
40 | chatOptions.responseFormat = new ChatResponseFormatJson({
41 | schema: value,
42 | });
43 | }
44 | } else {
45 | (chatOptions.additionalProperties ??= new Map()).set(key, value);
46 | }
47 | }
48 | }
49 |
50 | const functionChoiceBehaviorConfiguration = settings.functionChoiceBehavior?.getConfiguredOptions({
51 | // TODO: is this needed?
52 | requestSequenceIndex: 0,
53 | chatHistory: [],
54 | kernel,
55 | });
56 |
57 | const functionChoiceBehaviorFunctions = functionChoiceBehaviorConfiguration?.functions ?? [];
58 |
59 | if (functionChoiceBehaviorFunctions.length > 0) {
60 | if (settings.functionChoiceBehavior instanceof AutoFunctionChoiceBehavior) {
61 | chatOptions.toolMode = ChatToolMode.Auto;
62 | } else if (settings.functionChoiceBehavior instanceof NoneFunctionChoiceBehavior) {
63 | chatOptions.toolMode = ChatToolMode.None;
64 | } else {
65 | // TODO: handle the RequiredFunctionChoiceBehavior mode
66 | chatOptions.toolMode = ChatToolMode.RequireAny;
67 | }
68 |
69 | chatOptions.tools = functionChoiceBehaviorFunctions.map((fn) => fn.asAIFunction(kernel));
70 | }
71 |
72 | return chatOptions;
73 | };
74 |
--------------------------------------------------------------------------------
/src/abstractions/src/promptExecutionSettings/index.ts:
--------------------------------------------------------------------------------
1 | export * from './PromptExecutionSettings';
2 | export * from './PromptExecutionSettingsMapper';
3 |
--------------------------------------------------------------------------------
/src/abstractions/src/promptTemplate/PassThroughPromptTemplate.ts:
--------------------------------------------------------------------------------
1 | import { PromptTemplate } from './PromptTemplate';
2 |
3 | export class PassThroughPromptTemplate implements PromptTemplate {
4 | constructor(private readonly prompt: string) {}
5 |
6 | render() {
7 | return this.prompt;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/abstractions/src/promptTemplate/PromptTemplate.ts:
--------------------------------------------------------------------------------
1 | import { Kernel } from '../Kernel';
2 |
3 | export interface PromptTemplate {
4 | render(kernel: Kernel, Props: Props): string | Promise;
5 | }
6 |
--------------------------------------------------------------------------------
/src/abstractions/src/promptTemplate/PromptTemplateConfig.ts:
--------------------------------------------------------------------------------
1 | import { KernelFunctionMetadata } from '../functions';
2 |
3 | export type PromptTemplateFormat = 'handlebars' | 'passthrough';
4 |
5 | export type KernelFunctionFromPromptMetadata = KernelFunctionMetadata & {
6 | prompt: string;
7 | templateFormat: PromptTemplateFormat;
8 | inputVariables?: string[];
9 | allowDangerouslySetContent?: boolean;
10 | };
11 |
--------------------------------------------------------------------------------
/src/abstractions/src/promptTemplate/index.ts:
--------------------------------------------------------------------------------
1 | export * from './PassThroughPromptTemplate';
2 | export * from './PromptTemplate';
3 | export * from './PromptTemplateConfig';
4 |
--------------------------------------------------------------------------------
/src/abstractions/src/serviceProviderExtension.test.ts:
--------------------------------------------------------------------------------
1 | import { ChatClient } from '@semantic-kernel/ai';
2 | import { MapServiceProvider } from '@semantic-kernel/service-provider';
3 | import { kernelFunction } from './functions';
4 | import { PromptExecutionSettings, ServiceId } from './promptExecutionSettings';
5 |
6 | // eslint-disable-next-line @typescript-eslint/no-extraneous-class
7 | class MockBaseService {}
8 |
9 | class MockService extends MockBaseService {
10 | readonly serviceType = 'ChatCompletion';
11 | readonly metadata = { name: '' };
12 |
13 | constructor(modelId: string = 'gpt') {
14 | super();
15 | this.metadata.name = modelId;
16 | }
17 | }
18 |
19 | class MockServiceWithModelId extends MockBaseService {
20 | readonly serviceType = 'ChatCompletion';
21 | readonly metadata = { modelId: '' };
22 |
23 | constructor(modelId: string) {
24 | super();
25 | this.metadata.modelId = modelId;
26 | }
27 | }
28 |
29 | describe('serviceProviderExtension', () => {
30 | describe('trySelectService', () => {
31 | it('should return undefined when service is not defined', () => {
32 | // Arrange
33 | const serviceProvider = new MapServiceProvider();
34 |
35 | // Act
36 | const service = serviceProvider.trySelectService({
37 | serviceType: ChatClient,
38 | })?.service;
39 |
40 | // Assert
41 | expect(service).toBeUndefined();
42 | });
43 |
44 | it('should get a service without ExecutionSettings', () => {
45 | // Arrange
46 | const serviceProvider = new MapServiceProvider();
47 | const mockService = new MockService();
48 | serviceProvider.addService(mockService);
49 |
50 | // Act
51 | const service = serviceProvider.trySelectService({
52 | serviceType: MockService,
53 | });
54 |
55 | // Assert
56 | expect(service).toEqual({
57 | service: mockService,
58 | executionSettings: undefined,
59 | });
60 | });
61 |
62 | it('should get a service with KernelFunction.ExecutionSettings and serviceKey', () => {
63 | // Arrange
64 | const stubServiceKey = 'MockServiceWithModelId';
65 | const stubPromptExecutionSettings = { modelId: 'gpt' };
66 | const stubExecutionSettings = new Map();
67 | stubExecutionSettings.set(stubServiceKey, stubPromptExecutionSettings);
68 |
69 | const stubKernelFunction = kernelFunction(() => {}, {
70 | name: 'stubKernelFunction',
71 | executionSettings: stubExecutionSettings,
72 | });
73 |
74 | const serviceProvider = new MapServiceProvider();
75 |
76 | const mockService1 = new MockService();
77 | serviceProvider.addService(mockService1);
78 |
79 | const mockService2 = new MockServiceWithModelId('MockModelId');
80 | serviceProvider.addService(mockService2);
81 |
82 | // Act
83 | const service = serviceProvider.trySelectService({
84 | serviceType: MockBaseService,
85 | kernelFunction: stubKernelFunction,
86 | });
87 |
88 | // Assert
89 | expect(service?.service).toEqual(mockService2);
90 | expect(service?.executionSettings).toEqual(stubPromptExecutionSettings);
91 | });
92 |
93 | it('should get a service with KernelFunction.ExecutionSettings and modelId', () => {
94 | // Arrange
95 | const stubPromptExecutionSettings = { modelId: 'gpt' };
96 | const stubExecutionSettings = new Map();
97 | stubExecutionSettings.set('randomService', stubPromptExecutionSettings);
98 |
99 | const stubKernelFunction = kernelFunction(() => {}, {
100 | name: 'stubKernelFunction',
101 | executionSettings: stubExecutionSettings,
102 | });
103 |
104 | const serviceProvider = new MapServiceProvider();
105 |
106 | const mockService1 = new MockService();
107 | serviceProvider.addService(mockService1);
108 |
109 | const mockService2 = new MockServiceWithModelId(stubPromptExecutionSettings.modelId);
110 | serviceProvider.addService(mockService2);
111 |
112 | // Act
113 | const service = serviceProvider.trySelectService({
114 | serviceType: MockBaseService,
115 | kernelFunction: stubKernelFunction,
116 | });
117 |
118 | // Assert
119 | expect(service?.service).toEqual(mockService2);
120 | expect(service?.executionSettings).toEqual(stubPromptExecutionSettings);
121 | });
122 | });
123 | });
124 |
--------------------------------------------------------------------------------
/src/abstractions/src/serviceProviderExtension.ts:
--------------------------------------------------------------------------------
1 | import { MapServiceProvider, Service, defaultServiceId } from '@semantic-kernel/service-provider';
2 | import { KernelFunction } from './functions';
3 | import { PromptExecutionSettings } from './promptExecutionSettings';
4 |
5 | MapServiceProvider.prototype.trySelectService = function ({
6 | serviceType,
7 | kernelFunction,
8 | }: {
9 | serviceType: T;
10 | kernelFunction?: KernelFunction;
11 | }) {
12 | const executionSettings = kernelFunction?.metadata?.executionSettings;
13 | const services = this.getServices(serviceType);
14 |
15 | if (!services.size) {
16 | return undefined;
17 | }
18 |
19 | if (!executionSettings || executionSettings.size === 0) {
20 | // return the first service if no execution settings are provided
21 | return {
22 | service: services.values().next().value as InstanceType,
23 | executionSettings: undefined,
24 | };
25 | }
26 |
27 | let defaultExecutionSettings: PromptExecutionSettings | undefined;
28 |
29 | // search by service id first
30 | for (const [serviceId, _executionSettings] of executionSettings) {
31 | if (!serviceId || serviceId === defaultServiceId) {
32 | defaultExecutionSettings = _executionSettings;
33 | }
34 |
35 | const service = services.get(serviceId);
36 | if (service) {
37 | return {
38 | service: service,
39 | executionSettings: _executionSettings,
40 | };
41 | }
42 | }
43 |
44 | // search by model id next
45 | for (const _executionSettings of executionSettings.values()) {
46 | const modelId = _executionSettings.modelId;
47 | const service = Array.from(services.values()).find((s) => getServiceModelId(s) === modelId);
48 | if (service) {
49 | return {
50 | service: service,
51 | executionSettings: _executionSettings,
52 | };
53 | }
54 | }
55 |
56 | // search by default service id last
57 | if (defaultExecutionSettings) {
58 | return {
59 | service: services.values().next().value as InstanceType,
60 | executionSettings: defaultExecutionSettings,
61 | };
62 | }
63 |
64 | return undefined;
65 | };
66 |
67 | const getServiceModelId = function (service: InstanceType): string | undefined {
68 | // TODO: Improve this to avoid hardcoded `metadata` and `modelId` lookups
69 | if (
70 | service &&
71 | typeof service === 'object' &&
72 | 'metadata' in service &&
73 | service.metadata instanceof Object &&
74 | 'modelId' in service.metadata &&
75 | typeof service.metadata.modelId === 'string'
76 | ) {
77 | return service.metadata.modelId;
78 | }
79 | };
80 |
--------------------------------------------------------------------------------
/src/abstractions/src/serviceProviderExtention.d.ts:
--------------------------------------------------------------------------------
1 | export {};
2 |
3 | declare module '@semantic-kernel/service-provider' {
4 | export interface ServiceProvider {
5 | trySelectService({
6 | serviceType,
7 | kernelFunction,
8 | }: {
9 | serviceType: T;
10 | kernelFunction?: KernelFunction;
11 | }):
12 | | {
13 | service: InstanceType;
14 | executionSettings?: PromptExecutionSettings;
15 | }
16 | | undefined;
17 | }
18 |
19 | export interface MapServiceProvider {
20 | trySelectService({
21 | serviceType,
22 | kernelFunction,
23 | }: {
24 | serviceType: T;
25 | kernelFunction?: KernelFunction;
26 | }):
27 | | {
28 | service: InstanceType;
29 | executionSettings?: PromptExecutionSettings;
30 | }
31 | | undefined;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/abstractions/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@semantic-kernel/tsconfig/tsconfig.base.json",
3 | "compilerOptions": {
4 | "rootDir": "src",
5 | "outDir": "dist",
6 | "resolveJsonModule": true,
7 | "esModuleInterop": true
8 | },
9 | "include": ["src"],
10 | "exclude": ["node_modules"]
11 | }
12 |
--------------------------------------------------------------------------------
/src/abstractions/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'tsup';
2 |
3 | export default defineConfig([
4 | {
5 | entry: ['src/index.ts'],
6 | format: ['cjs', 'esm'],
7 | dts: true,
8 | sourcemap: true,
9 | noExternal: ['@semantic-kernel/service-provider'],
10 | },
11 | ]);
12 |
--------------------------------------------------------------------------------
/src/openai/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @semantic-kernel/openai
2 |
3 | ## 0.1.0
4 |
5 | ### Minor Changes
6 |
7 | - Include readme and update homepage
8 |
9 | ### Patch Changes
10 |
11 | - Updated dependencies
12 | - @semantic-kernel/abstractions@0.1.0
13 |
--------------------------------------------------------------------------------
/src/openai/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | import eslint from '@eslint/js';
3 | import tseslint from 'typescript-eslint';
4 |
5 | export default tseslint.config(eslint.configs.recommended, ...tseslint.configs.strict);
6 |
--------------------------------------------------------------------------------
/src/openai/jest.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from '@jest/types';
2 |
3 | // Sync object
4 | const config: Config.InitialOptions = {
5 | verbose: true,
6 | testEnvironment: 'node',
7 | transform: {
8 | '^.+.tsx?$': ['ts-jest', {}],
9 | },
10 | };
11 |
12 | export default config;
13 |
--------------------------------------------------------------------------------
/src/openai/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@semantic-kernel/openai",
3 | "description": "Semantic Kernel OpenAI",
4 | "version": "0.1.0",
5 | "main": "./dist/index.js",
6 | "module": "./dist/index.mjs",
7 | "types": "./dist/index.d.ts",
8 | "homepage": "https://kerneljs.com",
9 | "scripts": {
10 | "build": "tsup",
11 | "clean": "rm -rf dist",
12 | "test:eslint": "eslint \"src/**/*.ts*\"",
13 | "test:prettier": "prettier --check \"src/**/*.ts*\"",
14 | "test:jest": "jest",
15 | "test": "run-p test:*"
16 | },
17 | "exports": {
18 | "./package.json": "./package.json",
19 | ".": {
20 | "types": "./dist/index.d.ts",
21 | "import": "./dist/index.mjs",
22 | "require": "./dist/index.js"
23 | }
24 | },
25 | "dependencies": {
26 | "@semantic-kernel/ai": "*",
27 | "openai": "^4.56.0"
28 | },
29 | "devDependencies": {
30 | "@eslint/js": "^9.9.0",
31 | "@types/eslint__js": "^8.42.3",
32 | "eslint": "^9.9.0",
33 | "typescript-eslint": "^8.2.0",
34 | "@semantic-kernel/tsconfig": "*",
35 | "tsup": "^8.2.4",
36 | "typescript": "^5.5.4"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/openai/src/OpenAIChatClient.test.ts:
--------------------------------------------------------------------------------
1 | import { OpenAIChatClient } from './OpenAIChatClient';
2 |
3 | describe('OpenAIChatClient', () => {
4 | it('should create a new instance with an API key', () => {
5 | // Arrange
6 | const stubApiKey = 'stubApiKey';
7 |
8 | // Act
9 | const openAIClient = new OpenAIChatClient({ modelId: 'gpt-1', apiKey: stubApiKey });
10 |
11 | // Assert
12 | expect(openAIClient).toBeDefined();
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/src/openai/src/OpenAIChatClient.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ChatClient,
3 | ChatClientMetadata,
4 | ChatMessage,
5 | ChatOptions,
6 | ChatResponse,
7 | ChatResponseUpdate,
8 | } from '@semantic-kernel/ai';
9 | import OpenAI from 'openai';
10 | import {
11 | fromOpenAIChatCompletion,
12 | fromOpenAIStreamingChatCompletion,
13 | toOpenAIChatMessages,
14 | toOpenAIChatOptions,
15 | } from './mapper';
16 |
17 | export class OpenAIChatClient extends ChatClient {
18 | private readonly _openAIClient: OpenAI;
19 | private readonly _metadata: ChatClientMetadata;
20 |
21 | constructor({ apiKey, openAIClient, modelId }: { apiKey?: string; openAIClient?: OpenAI; modelId: string }) {
22 | super();
23 |
24 | if (!openAIClient && !apiKey) {
25 | throw new Error('Either an OpenAI instance or an API key is required');
26 | }
27 |
28 | if (!openAIClient) {
29 | this._openAIClient = new OpenAI({ apiKey });
30 | } else {
31 | this._openAIClient = openAIClient;
32 | }
33 |
34 | const providerUri = this._openAIClient.baseURL;
35 |
36 | this._metadata = new ChatClientMetadata({
37 | providerName: 'openai',
38 | providerUri,
39 | modelId,
40 | });
41 | }
42 |
43 | get metadata(): ChatClientMetadata {
44 | return this._metadata;
45 | }
46 |
47 | getService(serviceType: T, serviceKey?: string) {
48 | if (serviceKey) {
49 | return undefined;
50 | }
51 |
52 | if (serviceType === OpenAI) {
53 | return this._openAIClient;
54 | }
55 |
56 | if (serviceType === OpenAIChatClient) {
57 | return this;
58 | }
59 |
60 | return undefined;
61 | }
62 |
63 | async complete(chatMessages: string | ChatMessage[], options?: ChatOptions): Promise {
64 | chatMessages = ChatMessage.create(chatMessages);
65 | const modelId = this.metadata.modelId ?? options?.modelId;
66 |
67 | if (!modelId) {
68 | throw new Error('Model ID is required');
69 | }
70 |
71 | const openAIChatMessages = toOpenAIChatMessages(chatMessages);
72 | const openAIOptions = toOpenAIChatOptions(options);
73 |
74 | const params: OpenAI.Chat.ChatCompletionCreateParams = {
75 | messages: openAIChatMessages,
76 | model: modelId,
77 | ...openAIOptions,
78 | };
79 |
80 | const response = await this._openAIClient.chat.completions.create({
81 | ...params,
82 | stream: false,
83 | });
84 |
85 | return fromOpenAIChatCompletion({ openAICompletion: response, options });
86 | }
87 |
88 | override completeStreaming(
89 | chatMessages: string | ChatMessage[],
90 | options?: ChatOptions
91 | ): AsyncGenerator {
92 | chatMessages = ChatMessage.create(chatMessages);
93 | const modelId = this.metadata.modelId ?? options?.modelId;
94 |
95 | if (!modelId) {
96 | throw new Error('Model ID is required');
97 | }
98 |
99 | const openAIChatMessages = toOpenAIChatMessages(chatMessages);
100 | const openAIOptions = toOpenAIChatOptions(options);
101 |
102 | const params: OpenAI.Chat.ChatCompletionCreateParams = {
103 | messages: openAIChatMessages,
104 | model: modelId,
105 | ...openAIOptions,
106 | };
107 |
108 | const chatCompletionUpdates = this._openAIClient.chat.completions.create({
109 | ...params,
110 | stream_options: { include_usage: true },
111 | stream: true,
112 | });
113 |
114 | return fromOpenAIStreamingChatCompletion(chatCompletionUpdates);
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/openai/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './OpenAIChatClient';
2 |
--------------------------------------------------------------------------------
/src/openai/src/mapper/chatCompletionMapper.ts:
--------------------------------------------------------------------------------
1 | import {
2 | AIContent,
3 | AIFunction,
4 | AutoChatToolMode,
5 | ChatFinishReason,
6 | ChatMessage,
7 | ChatOptions,
8 | ChatResponse,
9 | ChatResponseUpdate,
10 | ChatRole,
11 | FunctionCallContent,
12 | FunctionResultContent,
13 | NoneChatToolMode,
14 | RequiredChatToolMode,
15 | TextContent,
16 | UsageContent,
17 | UsageDetails,
18 | } from '@semantic-kernel/ai';
19 | import OpenAI from 'openai';
20 | import { ChatCompletionChunk } from 'openai/resources';
21 | import { Stream } from 'openai/streaming';
22 |
23 | export const toOpenAIChatOptions = (
24 | chatOptions?: ChatOptions
25 | ): Omit => {
26 | if (!chatOptions) {
27 | return {};
28 | }
29 |
30 | const chatCompletionCreateParams: Omit = {};
31 |
32 | chatCompletionCreateParams.frequency_penalty = chatOptions.frequencyPenalty;
33 | chatCompletionCreateParams.max_tokens = chatOptions.maxOutputTokens;
34 | chatCompletionCreateParams.top_p = chatOptions.topP;
35 | chatCompletionCreateParams.presence_penalty = chatOptions.presencePenalty;
36 | chatCompletionCreateParams.temperature = chatOptions.temperature;
37 | chatCompletionCreateParams.seed = chatOptions.seed;
38 |
39 | if (chatOptions.stopSequences) {
40 | chatCompletionCreateParams.stop = [...chatOptions.stopSequences];
41 | }
42 |
43 | if (chatOptions.additionalProperties?.size) {
44 | if (chatOptions.additionalProperties.has('user')) {
45 | chatCompletionCreateParams.user = chatOptions.additionalProperties.get('user') as string;
46 | }
47 |
48 | if (chatOptions.additionalProperties.has('logprobs')) {
49 | chatCompletionCreateParams.logprobs = chatOptions.additionalProperties.get('logprobs') as boolean;
50 | }
51 |
52 | if (chatOptions.additionalProperties.has('logit_bias')) {
53 | chatCompletionCreateParams.logit_bias = chatOptions.additionalProperties.get('logit_bias') as Record<
54 | number,
55 | number
56 | >;
57 | }
58 |
59 | if (chatOptions.additionalProperties.has('parallel_tool_calls')) {
60 | chatCompletionCreateParams.parallel_tool_calls = chatOptions.additionalProperties.get(
61 | 'parallel_tool_calls'
62 | ) as boolean;
63 | }
64 |
65 | if (chatOptions.additionalProperties.has('top_logprobs')) {
66 | chatCompletionCreateParams.top_logprobs = chatOptions.additionalProperties.get('top_logprobs') as number;
67 | }
68 | }
69 |
70 | if (chatOptions.tools?.length) {
71 | for (const tool of chatOptions.tools) {
72 | if (tool instanceof AIFunction) {
73 | if (!chatCompletionCreateParams.tools) {
74 | chatCompletionCreateParams.tools = [];
75 | }
76 |
77 | chatCompletionCreateParams.tools.push(toOpenAIChatTool(tool));
78 | }
79 | }
80 |
81 | if (chatOptions.toolMode instanceof AutoChatToolMode) {
82 | chatCompletionCreateParams.tool_choice = 'auto';
83 | } else if (chatOptions.toolMode instanceof NoneChatToolMode) {
84 | chatCompletionCreateParams.tool_choice = 'none';
85 | } else if (chatOptions.toolMode instanceof RequiredChatToolMode) {
86 | chatCompletionCreateParams.tool_choice = chatOptions.toolMode.requiredFunctionName
87 | ? {
88 | function: {
89 | name: chatOptions.toolMode.requiredFunctionName,
90 | },
91 | type: 'function',
92 | }
93 | : 'required';
94 | }
95 | }
96 |
97 | return chatCompletionCreateParams;
98 | };
99 |
100 | const toOpenAIChatTool = (aiFunction: AIFunction): OpenAI.Chat.Completions.ChatCompletionTool => {
101 | const strict = aiFunction.additionalProperties?.get('strict') === true;
102 |
103 | const functionDefinition = {
104 | name: aiFunction.name,
105 | description: aiFunction.description,
106 | parameters: aiFunction.schema,
107 | strict,
108 | };
109 |
110 | return {
111 | type: 'function',
112 | function: functionDefinition,
113 | };
114 | };
115 |
116 | /**
117 | * Converts a list of AIContent to a list of ChatCompletionContentPart"
118 | */
119 | const toOpenAIChatContent = (contents: AIContent[]): OpenAI.Chat.ChatCompletionContentPart[] => {
120 | const parts: OpenAI.Chat.ChatCompletionContentPart[] = [];
121 |
122 | for (const content of contents) {
123 | if (content instanceof TextContent) {
124 | parts.push({ type: 'text', text: content.text });
125 | }
126 | // TODO: cover other content types e.g. ImageContent
127 | }
128 |
129 | if (parts.length === 0) {
130 | parts.push({
131 | type: 'text',
132 | text: '',
133 | });
134 | }
135 |
136 | return parts;
137 | };
138 |
139 | export const toOpenAIChatMessages = (inputs: ChatMessage[]): OpenAI.Chat.ChatCompletionMessageParam[] => {
140 | const messages: OpenAI.Chat.ChatCompletionMessageParam[] = [];
141 |
142 | for (const input of inputs) {
143 | if (input.role === 'system') {
144 | const parts = toOpenAIChatContent(input.contents) as OpenAI.Chat.ChatCompletionContentPartText[];
145 | messages.push({
146 | role: input.role,
147 | name: input.authorName,
148 | content: parts,
149 | });
150 | } else if (input.role === 'user') {
151 | const parts = toOpenAIChatContent(input.contents);
152 | messages.push({
153 | role: 'user',
154 | name: input.authorName,
155 | content: parts,
156 | });
157 | } else if (input.role === 'tool') {
158 | for (const item of input.contents) {
159 | if (item instanceof FunctionResultContent) {
160 | let result = item.result as string;
161 |
162 | if (!result && item.result) {
163 | try {
164 | result = JSON.stringify(item.result);
165 | } catch {
166 | // If the type can't be serialized, skip it.
167 | }
168 | }
169 |
170 | messages.push({
171 | role: 'tool',
172 | tool_call_id: item.callId,
173 | content: result || '',
174 | });
175 | }
176 | }
177 | } else if (input.role === 'assistant') {
178 | const message: OpenAI.Chat.ChatCompletionAssistantMessageParam = {
179 | role: 'assistant',
180 | name: input.authorName,
181 | content: toOpenAIChatContent(input.contents) as OpenAI.Chat.ChatCompletionContentPartText[],
182 | };
183 |
184 | for (const content of input.contents) {
185 | if (content instanceof FunctionCallContent) {
186 | if (!message.tool_calls) {
187 | message.tool_calls = [];
188 | }
189 |
190 | message.tool_calls.push({
191 | id: content.callId,
192 | type: 'function',
193 | function: {
194 | name: content.name,
195 | arguments: JSON.stringify(content.arguments),
196 | },
197 | });
198 | }
199 | }
200 |
201 | message.refusal = input.additionalProperties?.get('refusal') as string | undefined | null;
202 |
203 | messages.push(message);
204 | }
205 | }
206 |
207 | return messages;
208 | };
209 |
210 | /**
211 | * Converts an OpenAI finish reason to an Extensions finish reason.
212 | */
213 | const fromOpenAIFinishReason = (
214 | finishReason: OpenAI.ChatCompletion.Choice['finish_reason']
215 | ): ChatFinishReason | undefined => {
216 | if (!finishReason) {
217 | return undefined;
218 | }
219 |
220 | switch (finishReason) {
221 | case 'stop':
222 | return 'stop';
223 | case 'length':
224 | return 'length';
225 | case 'content_filter':
226 | return 'content_filter';
227 | case 'tool_calls':
228 | case 'function_call':
229 | return 'tool_calls';
230 | }
231 | };
232 |
233 | const fromOpenAIUsage = (tokenUsage: OpenAI.Completions.CompletionUsage): UsageDetails => {
234 | const usageDetails = new UsageDetails();
235 | usageDetails.inputTokenCount = tokenUsage.prompt_tokens;
236 | usageDetails.outputTokenCount = tokenUsage.completion_tokens;
237 | usageDetails.totalTokenCount = tokenUsage.total_tokens;
238 |
239 | return usageDetails;
240 | };
241 |
242 | export const fromOpenAIChatCompletion = ({
243 | openAICompletion,
244 | options,
245 | }: {
246 | openAICompletion: OpenAI.Chat.Completions.ChatCompletion;
247 | options?: ChatOptions;
248 | }): ChatResponse => {
249 | const choice = openAICompletion.choices[0];
250 | const content = choice.message.content;
251 | const role = choice.message.role;
252 |
253 | const returnMessage = new ChatMessage({
254 | content,
255 | role,
256 | });
257 | returnMessage.rawRepresentation = openAICompletion;
258 |
259 | if (choice.message.refusal) {
260 | (returnMessage.additionalProperties ??= new Map()).set('refusal', choice.message.refusal);
261 | }
262 |
263 | if (options?.tools?.length) {
264 | for (const toolCall of choice.message.tool_calls ?? []) {
265 | const callContent = new FunctionCallContent({
266 | name: toolCall.function.name,
267 | callId: toolCall.id,
268 | arguments: JSON.parse(toolCall.function.arguments),
269 | });
270 | callContent.rawRepresentation = toolCall;
271 |
272 | returnMessage.contents.push(callContent);
273 | }
274 | }
275 |
276 | const completion = new ChatResponse({
277 | message: returnMessage,
278 | });
279 | completion.rawRepresentation = openAICompletion;
280 | completion.completionId = openAICompletion.id;
281 | completion.createdAt = openAICompletion.created;
282 | completion.modelId = openAICompletion.model;
283 | completion.finishReason = fromOpenAIFinishReason(choice.finish_reason);
284 |
285 | if (openAICompletion.usage) {
286 | completion.usage = fromOpenAIUsage(openAICompletion.usage);
287 | }
288 |
289 | if (choice.message.refusal) {
290 | (completion.additionalProperties ??= new Map()).set('refusal', choice.message.refusal);
291 | }
292 |
293 | if (openAICompletion.system_fingerprint) {
294 | (completion.additionalProperties ??= new Map()).set('system_fingerprint', openAICompletion.system_fingerprint);
295 | }
296 |
297 | return completion;
298 | };
299 |
300 | export const fromOpenAIStreamingChatCompletion = async function* (
301 | chatCompletionUpdates: Promise>
302 | ) {
303 | let functionCallInfos: Map | undefined = undefined;
304 | let streamedRole: ChatRole | undefined = undefined;
305 | let finishReason: ChatFinishReason | undefined = undefined;
306 | let refusal: string | undefined = undefined;
307 | let completionId: string | undefined = undefined;
308 | let createdAt: number | undefined = undefined;
309 | let modelId: string | undefined = undefined;
310 | let fingerprint: string | undefined = undefined;
311 |
312 | for await (const chatCompletionUpdate of await chatCompletionUpdates) {
313 | // choices can be empty if the model has no more completions to provide.
314 | const choice: ChatCompletionChunk.Choice | undefined = chatCompletionUpdate.choices[0];
315 |
316 | streamedRole ??= choice?.delta.role;
317 | finishReason ??= choice?.finish_reason as ChatFinishReason;
318 | completionId ??= chatCompletionUpdate.id;
319 | createdAt ??= chatCompletionUpdate.created;
320 | modelId ??= chatCompletionUpdate.model;
321 | fingerprint ??= chatCompletionUpdate.system_fingerprint;
322 |
323 | const completionUpdate = new ChatResponseUpdate();
324 | completionUpdate.completionId = chatCompletionUpdate.id;
325 | completionUpdate.createdAt = chatCompletionUpdate.created;
326 | completionUpdate.finishReason = finishReason;
327 | completionUpdate.modelId = modelId;
328 | completionUpdate.rawRepresentation = chatCompletionUpdate;
329 | completionUpdate.role = streamedRole;
330 |
331 | if (choice?.logprobs?.content?.length) {
332 | (completionUpdate.additionalProperties ??= new Map()).set('logprobs.content', choice.logprobs?.content);
333 | }
334 |
335 | if (choice?.logprobs?.refusal?.length) {
336 | (completionUpdate.additionalProperties ??= new Map()).set('logprobs.refusal', choice.logprobs?.refusal);
337 | }
338 |
339 | if (fingerprint) {
340 | (completionUpdate.additionalProperties ??= new Map()).set('system_fingerprint', fingerprint);
341 | }
342 |
343 | if (choice?.delta.content?.length) {
344 | for (const contentPart of choice.delta.content) {
345 | const aiContent = new TextContent(contentPart);
346 | completionUpdate.contents.push(aiContent);
347 | }
348 | }
349 |
350 | if (choice?.delta.refusal) {
351 | refusal = (refusal ?? '') + choice.delta.refusal;
352 | }
353 |
354 | if (choice?.delta.tool_calls?.length) {
355 | for (const toolCallUpdate of choice.delta.tool_calls) {
356 | functionCallInfos ??= new Map();
357 | if (!functionCallInfos.has(toolCallUpdate.index)) {
358 | functionCallInfos.set(toolCallUpdate.index, {
359 | index: toolCallUpdate.index,
360 | });
361 | }
362 |
363 | const existing = functionCallInfos.get(toolCallUpdate.index);
364 |
365 | if (!existing) {
366 | throw new Error('Function call info not found');
367 | }
368 |
369 | existing.id ??= toolCallUpdate.id;
370 |
371 | if (!existing.function) {
372 | existing.function = {};
373 | }
374 |
375 | existing.function.name ??= toolCallUpdate.function?.name;
376 | if (toolCallUpdate.function?.arguments && toolCallUpdate.function.arguments.length > 0) {
377 | existing.function.arguments = (existing.function.arguments ?? '') + toolCallUpdate.function.arguments;
378 | }
379 | }
380 | }
381 |
382 | if (chatCompletionUpdate.usage) {
383 | const usageDetails = fromOpenAIUsage(chatCompletionUpdate.usage);
384 | completionUpdate.contents.push(new UsageContent(usageDetails));
385 | }
386 |
387 | yield completionUpdate;
388 | }
389 |
390 | // Now that we've received all updates, combine any for function calls into a single item to yield.
391 | if (functionCallInfos) {
392 | const completionUpdate = new ChatResponseUpdate();
393 | completionUpdate.completionId = completionId;
394 | completionUpdate.createdAt = createdAt;
395 | completionUpdate.finishReason = finishReason;
396 | completionUpdate.modelId = modelId;
397 | completionUpdate.role = streamedRole;
398 |
399 | for (const toolCall of functionCallInfos.values()) {
400 | if (toolCall.function?.name && toolCall.id) {
401 | const callContent = new FunctionCallContent({
402 | callId: toolCall.id,
403 | name: toolCall.function.name,
404 | arguments: toolCall.function.arguments && JSON.parse(toolCall.function.arguments),
405 | });
406 | completionUpdate.contents.push(callContent);
407 | }
408 | }
409 |
410 | // Refusals are about the model not following the schema for tool calls. As such, if we have any refusal,
411 | // add it to this function calling item.
412 | if (refusal) {
413 | (completionUpdate.additionalProperties ??= new Map()).set('refusal', refusal);
414 | }
415 |
416 | // Propagate additional relevant metadata.
417 | if (fingerprint) {
418 | (completionUpdate.additionalProperties ??= new Map()).set('system_fingerprint', fingerprint);
419 | }
420 |
421 | yield completionUpdate;
422 | }
423 | };
424 |
--------------------------------------------------------------------------------
/src/openai/src/mapper/index.ts:
--------------------------------------------------------------------------------
1 | export * from './chatCompletionMapper';
2 |
--------------------------------------------------------------------------------
/src/openai/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@semantic-kernel/tsconfig/tsconfig.base.json",
3 | "compilerOptions": {
4 | "rootDir": "src",
5 | "outDir": "dist"
6 | },
7 | "include": ["src"],
8 | "exclude": ["node_modules"]
9 | }
10 |
--------------------------------------------------------------------------------
/src/openai/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'tsup';
2 |
3 | export default defineConfig([
4 | {
5 | entry: ['src/index.ts'],
6 | format: ['cjs', 'esm'],
7 | dts: true,
8 | sourcemap: true,
9 | },
10 | ]);
11 |
--------------------------------------------------------------------------------
/src/prompt-templates/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @semantic-kernel/prompt-templates
2 |
3 | ## 0.1.0
4 |
5 | ### Minor Changes
6 |
7 | - Include readme and update homepage
8 |
9 | ### Patch Changes
10 |
11 | - Updated dependencies
12 | - @semantic-kernel/abstractions@0.1.0
13 |
--------------------------------------------------------------------------------
/src/prompt-templates/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | import eslint from '@eslint/js';
3 | import tseslint from 'typescript-eslint';
4 |
5 | export default tseslint.config(eslint.configs.recommended, ...tseslint.configs.strict);
6 |
--------------------------------------------------------------------------------
/src/prompt-templates/jest.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from '@jest/types';
2 |
3 | // Sync object
4 | const config: Config.InitialOptions = {
5 | verbose: true,
6 | testEnvironment: 'node',
7 | transform: {
8 | '^.+.tsx?$': ['ts-jest', {}],
9 | },
10 | };
11 |
12 | export default config;
13 |
--------------------------------------------------------------------------------
/src/prompt-templates/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@semantic-kernel/prompt-templates",
3 | "description": "Semantic Kernel Prompt Templates",
4 | "version": "0.1.0",
5 | "main": "./dist/index.js",
6 | "module": "./dist/index.mjs",
7 | "types": "./dist/index.d.ts",
8 | "homepage": "https://kerneljs.com",
9 | "scripts": {
10 | "build": "tsup",
11 | "clean": "rm -rf dist",
12 | "test:eslint": "eslint \"src/**/*.ts*\"",
13 | "test:prettier": "prettier --check \"src/**/*.ts*\"",
14 | "test": "run-p test:*"
15 | },
16 | "exports": {
17 | "./package.json": "./package.json",
18 | ".": {
19 | "types": "./dist/index.d.ts",
20 | "import": "./dist/index.mjs",
21 | "require": "./dist/index.js"
22 | }
23 | },
24 | "dependencies": {
25 | "@semantic-kernel/abstractions": "*",
26 | "handlebars": "^4.7.8"
27 | },
28 | "devDependencies": {
29 | "@eslint/js": "^9.9.0",
30 | "@types/eslint__js": "^8.42.3",
31 | "eslint": "^9.9.0",
32 | "typescript-eslint": "^8.2.0",
33 | "@semantic-kernel/tsconfig": "*",
34 | "tsup": "^8.2.4",
35 | "typescript": "^5.5.4"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/prompt-templates/src/handlebars/handlebarsPromptTemplate.ts:
--------------------------------------------------------------------------------
1 | import { PromptTemplate } from '@semantic-kernel/abstractions';
2 | import Handlebars from 'handlebars';
3 |
4 | export const handlebarsPromptTemplate = (template: string): PromptTemplate => {
5 | return {
6 | render: async (_, props) => {
7 | const compiledTemplate = Handlebars.compile(template);
8 | // TODO: add Kernel plugins as helpers
9 |
10 | return compiledTemplate(props);
11 | },
12 | };
13 | };
14 |
--------------------------------------------------------------------------------
/src/prompt-templates/src/index.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afshinm/semantic-kernel-js/4845c62fbe32817f2af0900b47be5a7e8ca8c045/src/prompt-templates/src/index.ts
--------------------------------------------------------------------------------
/src/prompt-templates/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@semantic-kernel/tsconfig/tsconfig.base.json",
3 | "compilerOptions": {
4 | "rootDir": "src",
5 | "outDir": "dist"
6 | },
7 | "include": ["src"],
8 | "exclude": ["node_modules"]
9 | }
10 |
--------------------------------------------------------------------------------
/src/prompt-templates/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'tsup';
2 |
3 | export default defineConfig([
4 | {
5 | entry: ['src/index.ts'],
6 | format: ['cjs', 'esm'],
7 | dts: true,
8 | sourcemap: true,
9 | },
10 | ]);
11 |
--------------------------------------------------------------------------------
/src/react/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @semantic-kernel/react
2 |
3 | ## 0.1.0
4 |
5 | ### Minor Changes
6 |
7 | - Include readme and update homepage
8 |
9 | ### Patch Changes
10 |
11 | - Updated dependencies
12 | - @semantic-kernel/abstractions@0.1.0
13 | - @semantic-kernel/openai@0.1.0
14 |
--------------------------------------------------------------------------------
/src/react/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@babel/preset-typescript',
4 | [
5 | '@babel/preset-env',
6 | {
7 | targets: {
8 | node: 'current',
9 | },
10 | },
11 | ],
12 | ],
13 | };
14 |
--------------------------------------------------------------------------------
/src/react/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | import eslint from '@eslint/js';
3 | import tseslint from 'typescript-eslint';
4 |
5 | export default tseslint.config(eslint.configs.recommended, ...tseslint.configs.strict);
6 |
--------------------------------------------------------------------------------
/src/react/jest.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
2 |
3 | module.exports = {
4 | testEnvironment: 'jsdom',
5 | roots: ['/src'],
6 | setupFilesAfterEnv: ['/jest.setup.js'],
7 | testMatch: ['**/__tests__/**/*.+(ts|js)', '**/?(*.)+(spec|test).+(ts|js)'],
8 | transform: {
9 | '^.+\\.(js|ts)$': 'babel-jest',
10 | },
11 | moduleDirectories: ['node_modules', 'src'],
12 | };
13 |
--------------------------------------------------------------------------------
/src/react/jest.setup.js:
--------------------------------------------------------------------------------
1 | require('jest-fetch-mock').enableMocks();
2 |
--------------------------------------------------------------------------------
/src/react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@semantic-kernel/react",
3 | "description": "Semantic Kernel React",
4 | "version": "0.1.0",
5 | "main": "./dist/index.js",
6 | "module": "./dist/index.mjs",
7 | "types": "./dist/index.d.ts",
8 | "homepage": "https://kerneljs.com",
9 | "scripts": {
10 | "build": "tsup",
11 | "clean": "rm -rf dist",
12 | "test:eslint": "eslint \"src/**/*.ts*\"",
13 | "test:prettier": "prettier --check \"src/**/*.ts*\"",
14 | "test:jest": "jest",
15 | "test": "run-p test:*"
16 | },
17 | "exports": {
18 | "./package.json": "./package.json",
19 | ".": {
20 | "types": "./dist/index.d.ts",
21 | "import": "./dist/index.mjs",
22 | "require": "./dist/index.js"
23 | }
24 | },
25 | "dependencies": {
26 | "json-schema-to-ts": "^3.1.1",
27 | "semantic-kernel": "*"
28 | },
29 | "peerDependencies": {
30 | "react": "^18.3.1",
31 | "react-dom": "^18.3.1"
32 | },
33 | "devDependencies": {
34 | "@eslint/js": "^9.9.0",
35 | "@types/eslint__js": "^8.42.3",
36 | "eslint": "^9.9.0",
37 | "typescript-eslint": "^8.2.0",
38 | "@babel/preset-env": "^7.26.0",
39 | "@babel/preset-typescript": "^7.26.0",
40 | "@semantic-kernel/tsconfig": "*",
41 | "@testing-library/dom": "^10.4.0",
42 | "@testing-library/react": "^16.0.1",
43 | "@types/react": "^18.3.8",
44 | "@types/react-dom": "^18.3.0",
45 | "jest": "^29.7.0",
46 | "jest-environment-jsdom": "^29.7.0",
47 | "jest-fetch-mock": "^3.0.3",
48 | "tsup": "^8.2.4",
49 | "typescript": "^5.5.4"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/react/src/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export * from './useChat';
2 | export * from './useKernel';
3 |
--------------------------------------------------------------------------------
/src/react/src/hooks/useChat/index.ts:
--------------------------------------------------------------------------------
1 | export * from './useChat';
2 |
--------------------------------------------------------------------------------
/src/react/src/hooks/useChat/useChat.test.ts:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react';
2 | import { MockChatClient } from '../../tests/mockChatCompletionService';
3 | import { useChat, useChatProps } from './useChat';
4 |
5 | describe('useChat', () => {
6 | it('should be able to send a message', () => {
7 | // Arrange
8 | const props: useChatProps = {
9 | chatClient: new MockChatClient(),
10 | };
11 |
12 | // Act
13 | const result = renderHook(() => useChat(props));
14 |
15 | // Assert
16 | expect(result.result.current).toBeDefined();
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/src/react/src/hooks/useChat/useChat.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import { ChatClient, ChatMessage, TextContent } from 'semantic-kernel';
3 | import { useKernel, useKernelProps } from '../useKernel';
4 |
5 | export type useChatProps = useKernelProps;
6 |
7 | export const useChat = (props: useChatProps) => {
8 | const { kernel } = useKernel(props);
9 | const [chatClient, setChatClient] = useState();
10 | const [chatHistory, setChatHistory] = useState([]);
11 |
12 | useEffect(() => {
13 | if (!kernel) return;
14 |
15 | const chatClient = kernel.services.getService(ChatClient);
16 |
17 | if (!chatClient) {
18 | throw new Error('ChatClient not found');
19 | }
20 |
21 | setChatClient(chatClient);
22 | }, [kernel]);
23 |
24 | const prompt = async (prompt: string) => {
25 | if (!chatClient) {
26 | console.error('ChatClient not found');
27 | return;
28 | }
29 |
30 | const newChatHistory = [
31 | ...chatHistory,
32 | new ChatMessage({
33 | role: 'user',
34 | contents: [new TextContent(prompt)],
35 | }),
36 | ];
37 | setChatHistory(newChatHistory);
38 |
39 | const chatMessageContents = await chatClient.complete(newChatHistory);
40 |
41 | for (const chatMessageContent of chatMessageContents.choices) {
42 | setChatHistory((chatHistory) => [...chatHistory, chatMessageContent]);
43 | }
44 | };
45 |
46 | return {
47 | prompt,
48 | chatHistory,
49 | };
50 | };
51 |
--------------------------------------------------------------------------------
/src/react/src/hooks/useKernel/index.ts:
--------------------------------------------------------------------------------
1 | export * from './useKernel';
2 |
--------------------------------------------------------------------------------
/src/react/src/hooks/useKernel/useKernel.test.ts:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react';
2 | import { MockChatClient } from '../../tests/mockChatCompletionService';
3 | import { useKernel, useKernelProps } from './useKernel';
4 |
5 | describe('useKernel', () => {
6 | it('should create the kernel', () => {
7 | // Arrange
8 | const props: useKernelProps = {
9 | chatClient: new MockChatClient(),
10 | };
11 |
12 | // Act
13 | const result = renderHook(() => useKernel(props));
14 |
15 | // Assert
16 | expect(result.result).toBeDefined();
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/src/react/src/hooks/useKernel/useKernel.ts:
--------------------------------------------------------------------------------
1 | import { OpenAIChatClient } from '@semantic-kernel/openai';
2 | import { useEffect, useState } from 'react';
3 | import { ChatClient, Kernel } from 'semantic-kernel';
4 |
5 | export type useKernelProps = {
6 | openAIModel?: string;
7 | openAIApiKey?: string;
8 | openAIorganization?: string;
9 | kernel?: Kernel;
10 | chatClient?: ChatClient;
11 | };
12 |
13 | export const useKernel = (props: useKernelProps) => {
14 | const [sk, setSK] = useState();
15 |
16 | useEffect(() => {
17 | if (!sk) {
18 | setSK(props.kernel ?? new Kernel());
19 | }
20 | }, []);
21 |
22 | useEffect(() => {
23 | if (!sk) return;
24 |
25 | if (props.chatClient) {
26 | sk.addService(props.chatClient);
27 | } else if (props.openAIApiKey && props.openAIModel) {
28 | sk.addService(
29 | new OpenAIChatClient({
30 | modelId: props.openAIModel,
31 | apiKey: props.openAIApiKey,
32 | })
33 | );
34 | } else {
35 | throw new Error('Either chatCompletionService or openAIModel and openAIApiKey are required.');
36 | }
37 | }, [sk]);
38 |
39 | return {
40 | kernel: sk,
41 | };
42 | };
43 |
--------------------------------------------------------------------------------
/src/react/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './hooks';
2 |
--------------------------------------------------------------------------------
/src/react/src/tests/mockChatCompletionService.ts:
--------------------------------------------------------------------------------
1 | import { ChatClient, ChatClientMetadata, ChatMessage, ChatResponse, ChatResponseUpdate } from 'semantic-kernel';
2 |
3 | export class MockChatClient extends ChatClient {
4 | override complete(chatMessage: string): Promise {
5 | return Promise.resolve(
6 | new ChatResponse({ message: new ChatMessage({ content: `** ${chatMessage} **`, role: 'assistant' }) })
7 | );
8 | }
9 |
10 | override completeStreaming(): AsyncGenerator {
11 | throw new Error('Method not implemented.');
12 | }
13 | override get metadata(): ChatClientMetadata {
14 | throw new Error('Method not implemented.');
15 | }
16 | override getService(): object | undefined {
17 | throw new Error('Method not implemented.');
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/react/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@semantic-kernel/tsconfig/tsconfig.base.json",
3 | "compilerOptions": {
4 | "rootDir": "src",
5 | "outDir": "dist"
6 | },
7 | "include": ["src"],
8 | "exclude": ["node_modules"]
9 | }
10 |
--------------------------------------------------------------------------------
/src/react/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'tsup';
2 |
3 | export default defineConfig([
4 | {
5 | entry: ['src/index.ts'],
6 | format: ['cjs', 'esm'],
7 | external: ['react'],
8 | dts: true,
9 | sourcemap: true,
10 | },
11 | ]);
12 |
--------------------------------------------------------------------------------
/src/semantic-kernel/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # semantic-kernel
2 |
3 | ## 0.1.0
4 |
5 | ### Minor Changes
6 |
7 | - Include readme and update homepage
8 |
9 | ### Patch Changes
10 |
11 | - Updated dependencies
12 | - @semantic-kernel/abstractions@0.1.0
13 |
--------------------------------------------------------------------------------
/src/semantic-kernel/README.md:
--------------------------------------------------------------------------------
1 | # Semantic Kernel for JavaScript
2 |
3 | Welcome to the Semantic Kernel for JavaScript. This project is an unofficial port of [Semantic Kernel](https://learn.microsoft.com/en-us/semantic-kernel/overview/) to JavaScript.
4 |
5 | Semantic Kernel is a lightweight, open-source development kit that lets you easily build AI agents and integrate the latest AI models. It serves as an efficient middleware that enables rapid delivery of enterprise-grade solutions.
6 |
7 | 
8 |
9 | ## Documentation
10 |
11 | - 📖 [Getting Started](https://kerneljs.com/getting-started)
12 | - 🤖 [Concepts](https://kerneljs.com/concepts)
13 | - ✨ [Samples](https://kerneljs.com/samples)
14 |
15 | ## License
16 |
17 | Licensed under the [MIT](LICENSE) license.
18 |
19 | This project is not an official Microsoft product.
20 |
--------------------------------------------------------------------------------
/src/semantic-kernel/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | import eslint from '@eslint/js';
3 | import tseslint from 'typescript-eslint';
4 |
5 | export default tseslint.config(eslint.configs.recommended, ...tseslint.configs.strict);
6 |
--------------------------------------------------------------------------------
/src/semantic-kernel/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "semantic-kernel",
3 | "description": "Typescript version of Semantic Kernel",
4 | "version": "0.1.0",
5 | "main": "./dist/index.js",
6 | "module": "./dist/index.mjs",
7 | "types": "./dist/index.d.ts",
8 | "homepage": "https://kerneljs.com",
9 | "scripts": {
10 | "build": "tsup",
11 | "clean": "rm -rf dist",
12 | "test:eslint": "eslint \"src/**/*.ts*\"",
13 | "test:prettier": "prettier --check \"src/**/*.ts*\"",
14 | "test": "run-p test:*"
15 | },
16 | "files": [
17 | "dist/**/**",
18 | "docs/**/**",
19 | "!**/*.spec.*",
20 | "!**/*.json",
21 | "!**/*.tsbuildinfo",
22 | "LICENSE",
23 | "README.md"
24 | ],
25 | "repository": {
26 | "type": "git",
27 | "url": "git+https://github.com/afshinm/semantic-kernel-js.git"
28 | },
29 | "keywords": [
30 | "semantic-kernel"
31 | ],
32 | "author": "Afshin Mehrabani",
33 | "license": "MIT",
34 | "bugs": {
35 | "url": "https://github.com/afshinm/semantic-kernel-js/issues"
36 | },
37 | "dependencies": {
38 | "@semantic-kernel/abstractions": "*",
39 | "@semantic-kernel/ai": "*"
40 | },
41 | "devDependencies": {
42 | "@eslint/js": "^9.9.0",
43 | "@types/eslint__js": "^8.42.3",
44 | "eslint": "^9.9.0",
45 | "typescript-eslint": "^8.2.0",
46 | "@semantic-kernel/tsconfig": "*",
47 | "tsup": "^8.2.4",
48 | "typescript": "^5.5.4"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/semantic-kernel/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from '@semantic-kernel/abstractions';
2 | export * from '@semantic-kernel/ai';
3 |
--------------------------------------------------------------------------------
/src/semantic-kernel/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@semantic-kernel/tsconfig/tsconfig.base.json",
3 | "compilerOptions": {
4 | "rootDir": "src",
5 | "outDir": "dist"
6 | },
7 | "include": ["src"],
8 | "exclude": ["node_modules"]
9 | }
10 |
--------------------------------------------------------------------------------
/src/semantic-kernel/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'tsup';
2 |
3 | export default defineConfig([
4 | {
5 | entry: ['src/index.ts'],
6 | format: ['cjs', 'esm'],
7 | dts: true,
8 | sourcemap: true,
9 | },
10 | ]);
11 |
--------------------------------------------------------------------------------
/src/service-provider/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | import eslint from '@eslint/js';
3 | import tseslint from 'typescript-eslint';
4 |
5 | export default tseslint.config(eslint.configs.recommended, ...tseslint.configs.strict);
6 |
--------------------------------------------------------------------------------
/src/service-provider/jest.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from '@jest/types';
2 |
3 | // Sync object
4 | const config: Config.InitialOptions = {
5 | verbose: true,
6 | testEnvironment: 'node',
7 | transform: {
8 | '^.+.tsx?$': ['ts-jest', {}],
9 | },
10 | };
11 |
12 | export default config;
13 |
--------------------------------------------------------------------------------
/src/service-provider/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@semantic-kernel/service-provider",
3 | "description": "Semantic Kernel Service Provider",
4 | "private": true,
5 | "version": "0.1.0",
6 | "main": "./dist/index.js",
7 | "module": "./dist/index.mjs",
8 | "types": "./dist/index.d.ts",
9 | "homepage": "https://kerneljs.com",
10 | "scripts": {
11 | "build": "tsup",
12 | "clean": "rm -rf dist",
13 | "test:eslint": "eslint \"src/**/*.ts*\"",
14 | "test:prettier": "prettier --check \"src/**/*.ts*\"",
15 | "test:jest": "jest",
16 | "test": "run-p test:*"
17 | },
18 | "files": [
19 | "dist/**/*"
20 | ],
21 | "exports": {
22 | "./package.json": "./package.json",
23 | ".": {
24 | "types": "./dist/index.d.ts",
25 | "import": "./dist/index.mjs",
26 | "require": "./dist/index.js"
27 | }
28 | },
29 | "devDependencies": {
30 | "@eslint/js": "^9.9.0",
31 | "@semantic-kernel/tsconfig": "*",
32 | "@types/eslint__js": "^8.42.3",
33 | "eslint": "^9.9.0",
34 | "tsup": "^8.2.4",
35 | "typescript": "^5.5.4",
36 | "typescript-eslint": "^8.2.0"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/service-provider/src/EmptyServiceProvider.ts:
--------------------------------------------------------------------------------
1 | import { Service } from './Service';
2 | import { ServiceProvider } from './ServiceProvider';
3 |
4 | export class EmptyServiceProvider implements ServiceProvider {
5 | getService(): undefined {
6 | throw new Error('Method not implemented.');
7 | }
8 | getServices(): Map> {
9 | throw new Error('Method not implemented.');
10 | }
11 | addService(): void {
12 | throw new Error('Method not implemented.');
13 | }
14 | trySelectService(): undefined {
15 | return undefined;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/service-provider/src/MapServiceProvider.test.ts:
--------------------------------------------------------------------------------
1 | import { MapServiceProvider } from './MapServiceProvider';
2 |
3 | class MyChatClient {
4 | complete() {
5 | throw new Error('Method not implemented.');
6 | }
7 | }
8 |
9 | describe('MapServiceProvider', () => {
10 | describe('addService', () => {
11 | it('should add a service', () => {
12 | // Arrange
13 | const serviceProvider = new MapServiceProvider();
14 |
15 | // Act
16 | serviceProvider.addService(new MyChatClient());
17 |
18 | // Assert
19 | expect(serviceProvider.getService(MyChatClient)).toBeDefined();
20 | });
21 |
22 | it('should not add the same serviceKey twice', () => {
23 | // Arrange
24 | const serviceProvider = new MapServiceProvider();
25 | const mockService = new MyChatClient();
26 |
27 | // Act
28 | serviceProvider.addService(mockService);
29 |
30 | // Assert
31 | expect(() => {
32 | serviceProvider.addService(mockService);
33 | }).toThrow('Service id "MyChatClient" is already registered.');
34 | });
35 |
36 | it('should add the same service with different service keys', () => {
37 | // Arrange
38 | const serviceProvider = new MapServiceProvider();
39 | const mockService = new MyChatClient();
40 |
41 | // Act
42 | serviceProvider.addService(mockService);
43 | serviceProvider.addService(mockService, { serviceId: 'MyChatClient2' });
44 |
45 | // Assert
46 | expect(serviceProvider.getServices(MyChatClient).size).toBe(2);
47 | });
48 | });
49 | });
50 |
--------------------------------------------------------------------------------
/src/service-provider/src/MapServiceProvider.ts:
--------------------------------------------------------------------------------
1 | import { Service } from './Service';
2 | import { ServiceProvider, defaultServiceId } from './ServiceProvider';
3 |
4 | /**
5 | * Represents a service provider that uses a map to store services.
6 | */
7 | export class MapServiceProvider implements ServiceProvider {
8 | private readonly services: Map> = new Map();
9 |
10 | getServices(serviceType: T) {
11 | return this.getServicesByType(serviceType);
12 | }
13 |
14 | getService(serviceType: T): InstanceType | undefined {
15 | const servicesByType = this.getServicesByType(serviceType);
16 | if (servicesByType.size > 0) {
17 | return servicesByType.values().next().value;
18 | }
19 | }
20 |
21 | addService(service: InstanceType, options?: { serviceId: string }) {
22 | let serviceId = defaultServiceId;
23 | if (options?.serviceId) {
24 | serviceId = options.serviceId;
25 | } else if (service?.constructor?.name) {
26 | serviceId = service.constructor.name;
27 | }
28 |
29 | if (this.services.has(serviceId)) {
30 | throw new Error(`Service id "${serviceId}" is already registered.`);
31 | }
32 |
33 | this.services.set(serviceId, service);
34 | }
35 |
36 | protected getServicesByType(serviceType: T): Map> {
37 | return new Map(Array.from(this.services.entries()).filter(([, service]) => service instanceof serviceType)) as Map<
38 | string,
39 | InstanceType
40 | >;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/service-provider/src/Service.ts:
--------------------------------------------------------------------------------
1 | export type Service = abstract new (...args: unknown[] | []) => unknown;
2 |
--------------------------------------------------------------------------------
/src/service-provider/src/ServiceProvider.ts:
--------------------------------------------------------------------------------
1 | import { Service } from './Service';
2 |
3 | export const defaultServiceId = 'default';
4 |
5 | /**
6 | * Represents a service provider.
7 | */
8 | export interface ServiceProvider {
9 | /**
10 | * Adds a service.
11 | * @param service The service to add.
12 | */
13 | addService(service: InstanceType, options?: { serviceId: string }): void;
14 |
15 | getService(serviceType: T): InstanceType | undefined;
16 |
17 | getServices(serviceType: T): Map>;
18 | }
19 |
--------------------------------------------------------------------------------
/src/service-provider/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './EmptyServiceProvider';
2 | export * from './MapServiceProvider';
3 | export * from './Service';
4 | export * from './ServiceProvider';
5 |
--------------------------------------------------------------------------------
/src/service-provider/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@semantic-kernel/tsconfig/tsconfig.base.json",
3 | "compilerOptions": {
4 | "rootDir": "src",
5 | "outDir": "dist",
6 | "resolveJsonModule": true,
7 | "esModuleInterop": true
8 | },
9 | "include": ["src"],
10 | "exclude": ["node_modules"]
11 | }
12 |
--------------------------------------------------------------------------------
/src/service-provider/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'tsup';
2 |
3 | export default defineConfig([
4 | {
5 | entry: ['src/index.ts'],
6 | format: ['cjs', 'esm'],
7 | dts: true,
8 | sourcemap: true,
9 | },
10 | ]);
11 |
--------------------------------------------------------------------------------
/src/tsconfig/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@semantic-kernel/tsconfig",
3 | "private": true,
4 | "files": [
5 | "tsconfig.base.json"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/src/tsconfig/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["ES6"],
4 | "target": "ES6",
5 | "module": "ESNext",
6 | "moduleResolution": "node",
7 | "declaration": true,
8 | "declarationMap": true,
9 | "sourceMap": true,
10 | "noEmitOnError": true,
11 | "esModuleInterop": true,
12 | "emitDeclarationOnly": true,
13 | "strict": true,
14 | "strictBindCallApply": false,
15 | "useUnknownInCatchVariables": false,
16 | "noImplicitOverride": true,
17 | "noUnusedLocals": true,
18 | "noUnusedParameters": true,
19 | "allowUnusedLabels": false,
20 | "skipLibCheck": true,
21 | "alwaysStrict": true,
22 | "preserveConstEnums": true,
23 | "types": ["node", "jest"]
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "tasks": {
4 | "build": {
5 | "dependsOn": ["^build"],
6 | "outputs": ["dist/**"]
7 | },
8 | "clean": {
9 | "dependsOn": ["^clean", "clean"]
10 | },
11 | "test": {
12 | "dependsOn": ["^build", "build"]
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------