├── .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 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 32 | 35 | 38 | 41 | 44 | 47 | 50 | 51 | 52 | 53 | 56 | 59 | 62 | 65 | 68 | 71 | 74 | 75 | 76 | 77 | 80 | 83 | 86 | 89 | 92 | 95 | 98 | 99 | 100 | 101 | 104 | 107 | 110 | 113 | 116 | 119 | 122 | 123 | 124 | 125 | 128 | 131 | 134 | 137 | 140 | 143 | 146 | 147 | 148 |
ProviderChat CompletionText GenerationText EmbeddingsText to ImageImage to TextText to AudioAudio to Text
OpenAI 30 | ✅ 31 | 33 | ⏳ 34 | 36 | ⏳ 37 | 39 | ⏳ 40 | 42 | ⏳ 43 | 45 | ⏳ 46 | 48 | ⏳ 49 |
Azure OpenAI 54 | ⏳ 55 | 57 | ⏳ 58 | 60 | ⏳ 61 | 63 | ⏳ 64 | 66 | ⏳ 67 | 69 | ⏳ 70 | 72 | ⏳ 73 |
Anthropic 78 | ⏳ 79 | 81 | ⏳ 82 | 84 | ⏳ 85 | 87 | ⏳ 88 | 90 | ⏳ 91 | 93 | ⏳ 94 | 96 | ⏳ 97 |
Mistral 102 | ⏳ 103 | 105 | ⏳ 106 | 108 | ⏳ 109 | 111 | ⏳ 112 | 114 | ⏳ 115 | 117 | ⏳ 118 | 120 | ⏳ 121 |
Amaozon Bedrock 126 | ⏳ 127 | 129 | ⏳ 130 | 132 | ⏳ 133 | 135 | ⏳ 136 | 138 | ⏳ 139 | 141 | ⏳ 142 | 144 | ⏳ 145 |
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 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 30 | 31 | 32 | 33 | 36 | 37 | 38 | 39 | 42 | 43 | 44 | 45 | 48 | 49 | 50 | 51 | 54 | 55 | 56 | 57 | 60 | 61 | 62 |
ServicesAvailability
Chat completion 22 | ✅ 23 |
Text generation 28 | ⏳ 29 |
Embedding generation 34 | ⏳ 35 |
Text-to-image 40 | ⏳ 41 |
Image-to-text 46 | ⏳ 47 |
Text-to-audio 52 | ⏳ 53 |
Audio-to-text 58 | ⏳ 59 |
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 | ![The Kernel](/the-kernel-is-at-the-center-of-everything.png) 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 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 27 | 28 | 29 | 30 | 33 | 34 | 35 |
ComponentsDescription
Services 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 |
Plugins 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 |
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 | ![Semantic Kernel Plugins](/designed-for-modular-extensibility-vertical.png) 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 | ![Orchestrating plugins with planner](/kernel-infographic.png) 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 | 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 | 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 | 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 |
55 |
56 | setPrompt(e.currentTarget.value)} 63 | /> 64 | 67 |
68 |
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 | ![Orchestrating plugins with planner](https://learn.microsoft.com/en-us/semantic-kernel/media/kernel-infographic.png) 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 | --------------------------------------------------------------------------------