Skip to content

Commit

Permalink
ci: setup CI/CD (closes #3) (#21)
Browse files Browse the repository at this point in the history
* ci: add infra validation

* ci: add stale bot

* ci: add build/test workflow

* refactor: rename workflow

* feat: ingest docs after deployment

* refactor: rename uri to url

* chore: fix lint issues

* chore: remove unused env variable

* ci: add deploy workflow

* ci: fix job name
  • Loading branch information
sinedied authored Mar 26, 2024
1 parent 2fc2a95 commit 98c2a78
Show file tree
Hide file tree
Showing 12 changed files with 199 additions and 41 deletions.
40 changes: 40 additions & 0 deletions .github/workflows/build-test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Build and test
on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
build_test:
strategy:
matrix:
platform: [ubuntu-latest, macos-latest, windows-latest]
node-version: ['20']

name: ${{ matrix.platform }} / Node.js v${{ matrix.node-version }}
runs-on: ${{ matrix.platform }}
steps:
- run: git config --global core.autocrlf false # Preserve line endings
- uses: actions/checkout@v4
- name: Setup Node.js v${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
run: npm ci
- name: Build packages
run: npm run build
- name: Lint packages
run: npm run lint
- name: Test packages
run: npm test --if-present

build_test_all:
if: always()
runs-on: ubuntu-latest
needs: build_test
steps:
- name: Check build matrix status
if: ${{ needs.build_test.result != 'success' }}
run: exit 1
66 changes: 66 additions & 0 deletions .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: Deploy on Azure
on:
workflow_dispatch:
push:
# Run when commits are pushed to mainline branch (main)
# Set this to the mainline branch you are using
branches: [main]

# GitHub Actions workflow to deploy to Azure using azd
# To configure required secrets for connecting to Azure, simply run `azd pipeline config`

# Set up permissions for deploying with secretless Azure federated credentials
# https://learn.microsoft.com/en-us/azure/developer/github/connect-from-azure?tabs=azure-portal%2Clinux#set-up-azure-login-with-openid-connect-authentication
permissions:
id-token: write
contents: read

jobs:
build:
runs-on: ubuntu-latest
env:
AZURE_CLIENT_ID: ${{ vars.AZURE_CLIENT_ID }}
AZURE_TENANT_ID: ${{ vars.AZURE_TENANT_ID }}
AZURE_SUBSCRIPTION_ID: ${{ vars.AZURE_SUBSCRIPTION_ID }}
AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }}
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install azd
uses: Azure/setup-azd@v0.1.0

- name: Install Nodejs
uses: actions/setup-node@v4
with:
node-version: 20

- name: Log in with Azure (Federated Credentials)
if: ${{ env.AZURE_CLIENT_ID != '' }}
run: |
azd auth login `
--client-id "$Env:AZURE_CLIENT_ID" `
--federated-credential-provider "github" `
--tenant-id "$Env:AZURE_TENANT_ID"
shell: pwsh

- name: Log in with Azure (Client Credentials)
if: ${{ env.AZURE_CREDENTIALS != '' }}
run: |
$info = $Env:AZURE_CREDENTIALS | ConvertFrom-Json -AsHashtable;
Write-Host "::add-mask::$($info.clientSecret)"
azd auth login `
--client-id "$($info.clientId)" `
--client-secret "$($info.clientSecret)" `
--tenant-id "$($info.tenantId)"
shell: pwsh
env:
AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }}

- name: Provision and deploy application
run: azd up --no-prompt
env:
AZURE_ENV_NAME: ${{ vars.AZURE_ENV_NAME }}
AZURE_LOCATION: ${{ vars.AZURE_LOCATION }}
AZURE_SUBSCRIPTION_ID: ${{ vars.AZURE_SUBSCRIPTION_ID }}
21 changes: 21 additions & 0 deletions .github/workflows/stale-bot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Close stale issues and PRs
on:
schedule:
- cron: '30 1 * * *'

jobs:
stale:
runs-on: ubuntu-latest
permissions:
contents: write
issues: write
pull-requests: write
steps:
- uses: actions/stale@v9
with:
stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this issue will be closed.'
stale-pr-message: 'This PR is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed.'
close-issue-message: 'This issue was closed because it has been stalled for 7 days with no activity.'
close-pr-message: 'This PR was closed because it has been stalled for 7 days with no activity.'
days-before-issue-stale: 60
days-before-pr-stale: 60
35 changes: 35 additions & 0 deletions .github/workflows/validate-infra.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Validate AZD template
on:
push:
branches: [main]
paths:
- 'infra/**'
pull_request:
branches: [main]
paths:
- 'infra/**'

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Build Bicep for linting
uses: azure/CLI@v1
with:
inlineScript: az config set bicep.use_binary_from_path=false && az bicep build -f infra/main.bicep --stdout

- name: Run Microsoft Security DevOps Analysis
uses: microsoft/security-devops-action@preview
id: msdo
continue-on-error: true
with:
tools: templateanalyzer

- name: Upload alerts to Security tab
if: github.repository == 'Azure-Samples/azure-search-openai-javascript'
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: ${{ steps.msdo.outputs.sarifFile }}
10 changes: 6 additions & 4 deletions azure.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ services:
predeploy:
windows:
shell: pwsh
run: Export-ModuleMember -Variable API_URI && npm run build:webapp
run: Export-ModuleMember -Variable API_URL && npm run build
posix:
shell: sh
run: export API_URI && npm run build:webapp
run: export API_URL && npm run build

api:
project: ./packages/api
Expand All @@ -30,5 +30,7 @@ hooks:
run: azd env get-values > .env
postup:
shell: sh
# TODO: add curl to ingest the data
run: echo ""
run: |
curl -F "file=@./data/privacy-policy.pdf" "$API_URL/api/documents"
curl -F "file=@./data/support.pdf" "$API_URL/api/documents"
curl -F "file=@./data/terms-of-service.pdf" "$API_URL/api/documents"
7 changes: 2 additions & 5 deletions infra/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ param appServicePlanName string = ''
param storageAccountName string = ''
param cosmosAccountName string = ''
param mongoDbSkuName string = 'Free'
param indexName string // Set in main.parameters.json

@description('Location for the OpenAI resource group')
@allowed(['australiaeast', 'canadaeast', 'eastus', 'eastus2', 'francecentral', 'japaneast', 'northcentralus', 'swedencentral', 'switzerlandnorth', 'uksouth', 'westeurope'])
Expand Down Expand Up @@ -95,7 +94,6 @@ module api './core/host/functions.bicep' = {
AZURE_OPENAI_API_DEPLOYMENT_NAME: chatDeploymentName
AZURE_OPENAI_API_EMBEDDINGS_DEPLOYMENT_NAME: embeddingsDeploymentName
AZURE_COSMOSDB_CONNECTION_STRING: cosmos.outputs.connectionString
INDEX_NAME: indexName
}
}
}
Expand Down Expand Up @@ -189,7 +187,6 @@ output AZURE_OPENAI_API_EMBEDDINGS_MODEL string = embeddingsModelName
output AZURE_OPENAI_API_EMBEDDINGS_MODEL_VERSION string = embeddingsModelVersion

output AZURE_COSMOSDB_CONNECTION_STRING string = cosmos.outputs.connectionString
output INDEX_NAME string = indexName

output API_URI string = api.outputs.uri
output WEBAPP_URI string = webapp.outputs.uri
output API_URL string = api.outputs.uri
output WEBAPP_URL string = webapp.outputs.uri
3 changes: 0 additions & 3 deletions infra/main.parameters.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,6 @@
},
"mongoDbSkuName": {
"value": "${AZURE_MONGODB_SKU=Free}"
},
"indexName": {
"value": "${INDEX_NAME=kbindex}"
}
}
}
11 changes: 10 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,16 @@
"@typescript-eslint/consistent-type-imports": "off",
"import/no-unassigned-import": "off",
"import/extensions": "off",
"n/prefer-global/process": "off"
"n/prefer-global/process": "off",
"unicorn/prevent-abbreviations": [
"error",
{
"allowList": {
"combineDocsChain": true,
"env": true
}
}
]
}
},
"prettier": {
Expand Down
22 changes: 9 additions & 13 deletions packages/api/src/functions/chat.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { HttpRequest, InvocationContext, HttpResponseInit } from '@azure/functions';
import { AzureOpenAIEmbeddings, AzureChatOpenAI } from '@langchain/azure-openai';
import { badRequest, serviceUnavailable, ok } from '../utils';
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { createStuffDocumentsChain } from "langchain/chains/combine_documents";
import { AzureCosmosDBVectorStore } from "@langchain/community/vectorstores/azure_cosmosdb";
import { createRetrievalChain } from "langchain/chains/retrieval";

import { ChatPromptTemplate } from '@langchain/core/prompts';
import { createStuffDocumentsChain } from 'langchain/chains/combine_documents';
import { AzureCosmosDBVectorStore } from '@langchain/community/vectorstores/azure_cosmosdb';
import { createRetrievalChain } from 'langchain/chains/retrieval';
import 'dotenv/config';
import { badRequest, serviceUnavailable, ok } from '../utils';

export async function chat(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> {
context.log(`Http function processed request for url "${request.url}"`);
Expand All @@ -28,18 +27,15 @@ export async function chat(request: HttpRequest, context: InvocationContext): Pr
const model = new AzureChatOpenAI();

const questionAnsweringPrompt = ChatPromptTemplate.fromMessages([
[
"system",
"Answer the user's questions based on the below context:\n\n{context}",
],
["human", "{input}"],
['system', "Answer the user's questions based on the below context:\n\n{context}"],
['human', '{input}'],
]);

const combineDocsChain = await createStuffDocumentsChain({
llm: model,
prompt: questionAnsweringPrompt,
});

const store = new AzureCosmosDBVectorStore(embeddings, {});

const chain = await createRetrievalChain({
Expand All @@ -48,7 +44,7 @@ export async function chat(request: HttpRequest, context: InvocationContext): Pr
});

const response = await chain.invoke({
input: question
input: question,
});

return response
Expand Down
19 changes: 7 additions & 12 deletions packages/api/src/functions/upload.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions';
import { AzureOpenAIEmbeddings } from '@langchain/azure-openai';
import { badRequest, serviceUnavailable, ok } from '../utils';
import { PDFLoader } from "langchain/document_loaders/fs/pdf";
import { PDFLoader } from 'langchain/document_loaders/fs/pdf';
import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter';
import {
AzureCosmosDBVectorStore,
AzureCosmosDBSimilarityType,
} from "@langchain/community/vectorstores/azure_cosmosdb";

} from '@langchain/community/vectorstores/azure_cosmosdb';
import 'dotenv/config';
import { badRequest, serviceUnavailable, ok } from '../utils';

export async function upload(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> {
try {
Expand All @@ -33,16 +32,12 @@ export async function upload(request: HttpRequest, context: InvocationContext):

const documents = await splitter.splitDocuments(rawDocument);

const store = await AzureCosmosDBVectorStore.fromDocuments(
documents,
new AzureOpenAIEmbeddings(),
{},
);
const store = await AzureCosmosDBVectorStore.fromDocuments(documents, new AzureOpenAIEmbeddings(), {});

const numLists = 100;
const numberLists = 100;
const dimensions = 1536;
const similarity = AzureCosmosDBSimilarityType.COS;
await store.createIndex(numLists, dimensions, similarity);
await store.createIndex(numberLists, dimensions, similarity);

await store.close();

Expand All @@ -53,4 +48,4 @@ export async function upload(request: HttpRequest, context: InvocationContext):

return serviceUnavailable(new Error('Service temporarily unavailable. Please try again later.'));
}
};
}
2 changes: 1 addition & 1 deletion packages/webapp/src/api.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { type ChatResponse, type ChatRequestOptions, type ChatResponseChunk } from './models.js';

export const apiBaseUrl: string = import.meta.env.VITE_API_URI || 'api';
export const apiBaseUrl: string = import.meta.env.VITE_API_URL || 'api';

export async function getCompletion(options: ChatRequestOptions) {
const apiUrl = options.apiUrl || apiBaseUrl;
Expand Down
4 changes: 2 additions & 2 deletions packages/webapp/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import process from 'node:process';
import { defineConfig } from 'vite';

// Expose environment variables to the client
process.env.VITE_API_URI = process.env.API_URI ?? '';
console.log(`Using chat API base URL: "${process.env.VITE_API_URI}"`);
process.env.VITE_API_URL = process.env.API_URL ?? '';
console.log(`Using chat API base URL: "${process.env.VITE_API_URL}"`);

export default defineConfig({
build: {
Expand Down

0 comments on commit 98c2a78

Please sign in to comment.