Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Batching and Retrying for bulk operations #1035

Merged
merged 8 commits into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions .github/workflows/run-frontend-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,16 @@ jobs:
node-version: 18

- name: Install Frontend Depedencies
env:
NEXT_PUBLIC_OPENAI_API_KEY: ${{ secrets.NEXT_PUBLIC_OPENAI_API_KEY }}
run: |
cd agenta-web/ && npm install
- name: Run Cypress
env:
NEXT_PUBLIC_OPENAI_API_KEY: ${{ secrets.NEXT_PUBLIC_OPENAI_API_KEY }}
run: cd agenta-web/ && npm run cypress:run
run: cd agenta-web/ && npm run test

- name: Docker logs
if: always() #
if: always() #
run: docker ps -q | xargs -I {} docker logs {}

- name: Stop Docker Compose
Expand Down
10 changes: 9 additions & 1 deletion agenta-web/cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,17 @@ export default defineConfig({
video: false,
screenshotOnRunFailure: false,
e2e: {
baseUrl: "http://localhost",
baseUrl: "http://localhost:3000",
defaultCommandTimeout: 30000,
requestTimeout: 10000,
setupNodeEvents(on) {
on("task", {
log(message) {
console.log(message)
return null
},
})
},
},
env: {
baseApiURL: "http://localhost/api",
Expand Down
8 changes: 7 additions & 1 deletion agenta-web/cypress/support/commands/evaluations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,14 @@ Cypress.Commands.add("createVariant", () => {
})
})

cy.get('[data-cy^="create-app-button"]').eq(1).click()
cy.contains("Single Prompt")
.parentsUntil('[data-cy^="app-template-card"]')
.last()
.contains("create app", {matchCase: false})
.click()

const appName = randString(5)
cy.task("log", `App name: ${appName}`)

cy.get('[data-cy="enter-app-name-modal"]')
.should("exist")
Expand Down
1,333 changes: 844 additions & 489 deletions agenta-web/package-lock.json

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions agenta-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
"name": "dashboard",
"version": "0.1.0",
"private": true,
"engines": {
"node": ">=18"
},
"scripts": {
"dev": "next dev",
"build": "next build",
Expand Down Expand Up @@ -30,6 +33,7 @@
"@types/lodash": "^4.14.201",
"@types/mdx": "^2.0.6",
"@types/papaparse": "^5.3.14",
"@types/promise-retry": "^1.1.6",
"@types/react-dom": "18.2.1",
"@types/react-highlight-words": "^0.16.4",
"@types/react-syntax-highlighter": "^15.5.7",
Expand All @@ -51,6 +55,7 @@
"papaparse": "^5.4.1",
"postcss": "8.4.23",
"posthog-js": "^1.87.2",
"promise-retry": "^2.0.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-error-boundary": "^4.0.11",
Expand Down
9 changes: 2 additions & 7 deletions agenta-web/src/components/AppSelector/AppTemplateCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ interface Props {
const AppTemplateCard: React.FC<Props> = ({title, tag, onClick, body, noTemplate}) => {
const classes = useStyles({tag} as StylesProp)
return (
<Card className={classes.card}>
<Card className={classes.card} data-cy="app-template-card">
{tag && (
<Tag color="blue" className={classes.tag}>
{tag}
Expand All @@ -68,12 +68,7 @@ const AppTemplateCard: React.FC<Props> = ({title, tag, onClick, body, noTemplate
<Text type="secondary">
<p>{body}</p>
</Text>
<Button
shape="round"
onClick={onClick}
className={classes.createBtn}
data-cy="create-app-button"
>
<Button shape="round" onClick={onClick} className={classes.createBtn}>
Create App
</Button>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import SecondaryButton from "../SecondaryButton/SecondaryButton"
import {useQueryParam} from "@/hooks/useQuery"
import EvaluationCardView, {VARIANT_COLORS} from "../Evaluations/EvaluationCardView"
import {Evaluation, EvaluationScenario, KeyValuePair, Variant} from "@/lib/Types"
import {EvaluationTypeLabels, camelToSnake} from "@/lib/helpers/utils"
import {EvaluationTypeLabels, batchExecute, camelToSnake} from "@/lib/helpers/utils"
import {testsetRowToChatMessages} from "@/lib/helpers/testset"
import EvaluationVotePanel from "../Evaluations/EvaluationCardView/EvaluationVotePanel"
import VariantAlphabet from "../Evaluations/EvaluationCardView/VariantAlphabet"
Expand Down Expand Up @@ -200,7 +200,10 @@ const ABTestingEvaluationTable: React.FC<EvaluationTableProps> = ({

const runAllEvaluations = async () => {
setEvaluationStatus(EvaluationFlow.EVALUATION_STARTED)
Promise.all(rows.map((row) => runEvaluation(row.id!, rows.length - 1, false)))
batchExecute(
rows.map((row) => () => runEvaluation(row.id!, rows.length - 1, false)),
{allowRetry: true, batchSize: 10},
)
.then(() => {
setEvaluationStatus(EvaluationFlow.EVALUATION_FINISHED)
message.success("Evaluations Updated!")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import SecondaryButton from "../SecondaryButton/SecondaryButton"
import {useQueryParam} from "@/hooks/useQuery"
import EvaluationCardView from "../Evaluations/EvaluationCardView"
import {Evaluation, EvaluationScenario, KeyValuePair, Variant} from "@/lib/Types"
import {EvaluationTypeLabels, camelToSnake} from "@/lib/helpers/utils"
import {EvaluationTypeLabels, batchExecute, camelToSnake} from "@/lib/helpers/utils"
import {testsetRowToChatMessages} from "@/lib/helpers/testset"
import {debounce} from "lodash"
import EvaluationVotePanel from "../Evaluations/EvaluationCardView/EvaluationVotePanel"
Expand Down Expand Up @@ -260,7 +260,10 @@ const SingleModelEvaluationTable: React.FC<EvaluationTableProps> = ({

const runAllEvaluations = async () => {
setEvaluationStatus(EvaluationFlow.EVALUATION_STARTED)
Promise.all(rows.map((row) => runEvaluation(row.id!, rows.length - 1, false)))
batchExecute(
rows.map((row) => () => runEvaluation(row.id!, rows.length - 1, false)),
{allowRetry: true, batchSize: 10},
)
.then(() => {
setEvaluationStatus(EvaluationFlow.EVALUATION_FINISHED)
message.success("Evaluations Updated!")
Expand Down
7 changes: 5 additions & 2 deletions agenta-web/src/components/Playground/Views/TestView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {Button, Input, Card, Row, Col, Space, Form} from "antd"
import {CaretRightOutlined, PlusOutlined} from "@ant-design/icons"
import {callVariant} from "@/lib/services/api"
import {ChatMessage, ChatRole, GenericObject, Parameter, Variant} from "@/lib/Types"
import {randString, removeKeys} from "@/lib/helpers/utils"
import {batchExecute, randString, removeKeys} from "@/lib/helpers/utils"
import LoadTestsModal from "../LoadTestsModal"
import AddToTestSetDrawer from "../AddToTestSetDrawer/AddToTestSetDrawer"
import {DeleteOutlined} from "@ant-design/icons"
Expand Down Expand Up @@ -278,9 +278,12 @@ const App: React.FC<TestViewProps> = ({inputParams, optParams, variant, isChatVa
}

const handleRunAll = () => {
const funcs: Function[] = []
rootRef.current
?.querySelectorAll("[data-cy=testview-input-parameters-run-button]")
.forEach((btn) => (btn as HTMLButtonElement).click())
.forEach((btn) => funcs.push(() => (btn as HTMLButtonElement).click()))

batchExecute(funcs, {allowRetry: true, batchSize: 10})
}

const handleAddRow = () => {
Expand Down
80 changes: 80 additions & 0 deletions agenta-web/src/lib/helpers/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import dynamic from "next/dynamic"
import {EvaluationType} from "../enums"
import {GenericObject} from "../Types"
import promiseRetry from "promise-retry"
import {getErrorMessage} from "./errorHandler"

const llmAvailableProvidersToken = "llmAvailableProvidersToken"

Expand Down Expand Up @@ -219,3 +221,81 @@ export const getAgentaApiUrl = () => {

return apiUrl
}

export function promisifyFunction(fn: Function, ...args: any[]) {
return async () => {
return fn(...args)
}
}

export const withRetry = (
fn: Function,
options?: Parameters<typeof promiseRetry>[0] & {logErrors?: boolean},
) => {
const {logErrors = true, ...config} = options || {}
const func = promisifyFunction(fn)

return promiseRetry(
(retry, attempt) =>
func().catch((e) => {
if (logErrors) {
console.log("Error: ", getErrorMessage(e))
console.log("Retry attempt: ", attempt)
}
retry(e)
}),
{
retries: 3,
...config,
},
)
}

export async function batchExecute(
functions: Function[],
options?: {
batchSize?: number
supressErrors?: boolean
batchDelayMs?: number
logErrors?: boolean
allowRetry?: boolean
retryConfig?: Parameters<typeof promiseRetry>[0]
},
) {
const {
batchSize = 20,
supressErrors = false,
batchDelayMs = 2000,
logErrors = true,
allowRetry = false,
retryConfig,
} = options || {}

functions = functions.map((f) => async () => {
try {
return await (allowRetry ? withRetry(f, {logErrors, ...(retryConfig || {})}) : f())
} catch (e) {
if (supressErrors) {
if (logErrors) console.log("Ignored error:", getErrorMessage(e))
return {__error: e}
}
throw e
}
})

if (!batchSize || !Number.isInteger(batchSize) || batchSize <= 0)
return Promise.all(functions.map((f) => f()))

let position = 0
let results: any[] = []

while (position < functions.length) {
const batch = functions.slice(position, position + batchSize)
results = [...results, ...(await Promise.all(batch.map((f) => f())))]
position += batchSize
if (batchDelayMs) {
await delay(batchDelayMs)
}
}
return results
}
Loading