2023-06-10 05:42:20 +00:00
|
|
|
import pRetry, { FailedAttemptError } from 'p-retry'
|
2023-06-11 07:46:38 +00:00
|
|
|
import { ZodType } from 'zod'
|
2023-05-26 19:06:39 +00:00
|
|
|
|
2023-06-09 17:44:02 +00:00
|
|
|
import * as errors from '@/errors'
|
2023-06-09 01:41:28 +00:00
|
|
|
import * as types from '@/types'
|
|
|
|
import { Agentic } from '@/agentic'
|
2023-05-26 19:06:39 +00:00
|
|
|
|
2023-05-27 00:36:52 +00:00
|
|
|
/**
|
2023-06-11 02:59:33 +00:00
|
|
|
* A `Task` is an async function call that may be non-deterministic. It has
|
|
|
|
* structured input and structured output. Invoking a task is equivalent to
|
|
|
|
* sampling from a probability distribution.
|
2023-06-06 22:20:20 +00:00
|
|
|
*
|
2023-05-27 00:36:52 +00:00
|
|
|
* Examples of tasks include:
|
2023-06-02 06:26:22 +00:00
|
|
|
* - LLM calls
|
2023-06-06 22:20:20 +00:00
|
|
|
* - Chain of LLM calls
|
|
|
|
* - Retrieval task
|
2023-05-27 00:36:52 +00:00
|
|
|
* - API calls
|
2023-06-01 06:03:27 +00:00
|
|
|
* - Native function calls
|
2023-05-27 00:36:52 +00:00
|
|
|
* - Invoking sub-agents
|
|
|
|
*/
|
2023-06-11 07:46:38 +00:00
|
|
|
export abstract class BaseTask<TInput = void, TOutput = string> {
|
2023-06-06 19:46:07 +00:00
|
|
|
protected _agentic: Agentic
|
2023-06-11 02:59:33 +00:00
|
|
|
protected _id: string
|
2023-06-06 22:20:20 +00:00
|
|
|
|
|
|
|
protected _timeoutMs?: number
|
2023-06-09 17:44:02 +00:00
|
|
|
protected _retryConfig: types.RetryConfig
|
2023-06-01 01:53:09 +00:00
|
|
|
|
2023-06-06 19:46:07 +00:00
|
|
|
constructor(options: types.BaseTaskOptions) {
|
|
|
|
this._agentic = options.agentic
|
2023-05-26 19:16:13 +00:00
|
|
|
this._timeoutMs = options.timeoutMs
|
2023-06-09 17:44:02 +00:00
|
|
|
this._retryConfig = options.retryConfig ?? {
|
|
|
|
retries: 3,
|
|
|
|
strategy: 'default'
|
|
|
|
}
|
2023-06-11 02:59:33 +00:00
|
|
|
this._id = options.id ?? this._agentic.idGeneratorFn()
|
2023-05-26 19:06:39 +00:00
|
|
|
}
|
|
|
|
|
2023-06-06 19:46:07 +00:00
|
|
|
public get agentic(): Agentic {
|
|
|
|
return this._agentic
|
|
|
|
}
|
|
|
|
|
2023-06-11 02:59:33 +00:00
|
|
|
public get id(): string {
|
|
|
|
return this._id
|
|
|
|
}
|
|
|
|
|
2023-06-11 07:46:38 +00:00
|
|
|
public abstract get inputSchema(): ZodType<TInput>
|
|
|
|
public abstract get outputSchema(): ZodType<TOutput>
|
2023-05-26 19:06:39 +00:00
|
|
|
|
2023-06-11 02:59:33 +00:00
|
|
|
public abstract get name(): string
|
2023-06-02 06:26:22 +00:00
|
|
|
|
2023-06-11 07:01:14 +00:00
|
|
|
// TODO: is this really necessary?
|
|
|
|
public clone(): BaseTask<TInput, TOutput> {
|
|
|
|
// TODO: override in subclass if needed
|
|
|
|
throw new Error(`clone not implemented for task "${this.name}"`)
|
|
|
|
}
|
|
|
|
|
2023-06-13 00:25:19 +00:00
|
|
|
public retryConfig(retryConfig: types.RetryConfig): this {
|
2023-05-26 19:16:13 +00:00
|
|
|
this._retryConfig = retryConfig
|
2023-05-26 19:06:39 +00:00
|
|
|
return this
|
|
|
|
}
|
|
|
|
|
2023-06-11 07:46:38 +00:00
|
|
|
public async call(input?: TInput): Promise<TOutput> {
|
2023-06-07 06:43:15 +00:00
|
|
|
const res = await this.callWithMetadata(input)
|
|
|
|
return res.result
|
2023-06-06 22:20:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public async callWithMetadata(
|
2023-06-11 07:46:38 +00:00
|
|
|
input?: TInput
|
2023-06-06 22:20:20 +00:00
|
|
|
): Promise<types.TaskResponse<TOutput>> {
|
2023-06-10 06:11:10 +00:00
|
|
|
if (this.inputSchema) {
|
2023-06-11 07:01:14 +00:00
|
|
|
const safeInput = this.inputSchema.safeParse(input)
|
2023-06-11 00:57:33 +00:00
|
|
|
|
2023-06-10 06:11:10 +00:00
|
|
|
if (!safeInput.success) {
|
|
|
|
throw new Error(`Invalid input: ${safeInput.error.message}`)
|
|
|
|
}
|
|
|
|
|
|
|
|
input = safeInput.data
|
|
|
|
}
|
|
|
|
|
|
|
|
const ctx: types.TaskCallContext<TInput> = {
|
2023-06-09 17:44:02 +00:00
|
|
|
input,
|
2023-06-10 05:42:20 +00:00
|
|
|
attemptNumber: 0,
|
2023-06-11 02:59:33 +00:00
|
|
|
metadata: {
|
|
|
|
taskName: this.name,
|
2023-06-13 00:25:19 +00:00
|
|
|
taskId: this.id,
|
|
|
|
callId: this._agentic.idGeneratorFn()
|
2023-06-11 02:59:33 +00:00
|
|
|
}
|
2023-06-09 17:44:02 +00:00
|
|
|
}
|
|
|
|
|
2023-06-10 05:42:20 +00:00
|
|
|
const result = await pRetry(() => this._call(ctx), {
|
|
|
|
...this._retryConfig,
|
|
|
|
onFailedAttempt: async (err: FailedAttemptError) => {
|
|
|
|
if (this._retryConfig.onFailedAttempt) {
|
|
|
|
await Promise.resolve(this._retryConfig.onFailedAttempt(err))
|
|
|
|
}
|
|
|
|
|
2023-06-11 02:59:33 +00:00
|
|
|
// TODO: log this task error
|
2023-06-10 05:42:20 +00:00
|
|
|
ctx.attemptNumber = err.attemptNumber + 1
|
2023-06-11 02:21:09 +00:00
|
|
|
ctx.metadata.error = err
|
2023-06-10 05:42:20 +00:00
|
|
|
|
2023-06-09 17:44:02 +00:00
|
|
|
if (err instanceof errors.ZodOutputValidationError) {
|
2023-06-10 05:42:20 +00:00
|
|
|
ctx.retryMessage = err.message
|
|
|
|
} else if (err instanceof errors.OutputValidationError) {
|
|
|
|
ctx.retryMessage = err.message
|
2023-06-09 17:44:02 +00:00
|
|
|
} else {
|
|
|
|
throw err
|
|
|
|
}
|
|
|
|
}
|
2023-06-10 05:42:20 +00:00
|
|
|
})
|
2023-06-09 17:44:02 +00:00
|
|
|
|
2023-06-11 02:21:09 +00:00
|
|
|
ctx.metadata.success = true
|
|
|
|
ctx.metadata.numRetries = ctx.attemptNumber
|
|
|
|
ctx.metadata.error = undefined
|
|
|
|
|
2023-06-10 05:42:20 +00:00
|
|
|
return {
|
|
|
|
result,
|
|
|
|
metadata: ctx.metadata
|
|
|
|
}
|
2023-06-06 22:20:20 +00:00
|
|
|
}
|
|
|
|
|
2023-06-11 07:46:38 +00:00
|
|
|
protected abstract _call(ctx: types.TaskCallContext<TInput>): Promise<TOutput>
|
2023-05-26 19:06:39 +00:00
|
|
|
|
|
|
|
// TODO
|
|
|
|
// abstract stream({
|
|
|
|
// input: TInput,
|
|
|
|
// onProgress: types.ProgressFunction
|
|
|
|
// }): Promise<TOutput>
|
|
|
|
}
|