From aa060438658c43f25196e7ee1365d796fc814e76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Vall=C3=A9s?= Date: Mon, 14 Oct 2024 13:29:01 +0200 Subject: [PATCH] chore(component): remove ArchetypeAI --- pkg/component/ai/archetypeai/v0/README.mdx | 132 ------- .../ai/archetypeai/v0/assets/archetype-ai.svg | 3 - pkg/component/ai/archetypeai/v0/client.go | 34 -- .../ai/archetypeai/v0/component_test.go | 328 ------------------ .../ai/archetypeai/v0/config/definition.json | 23 -- .../ai/archetypeai/v0/config/setup.json | 27 -- .../ai/archetypeai/v0/config/tasks.json | 205 ----------- pkg/component/ai/archetypeai/v0/main.go | 236 ------------- pkg/component/ai/archetypeai/v0/structs.go | 74 ---- 9 files changed, 1062 deletions(-) delete mode 100644 pkg/component/ai/archetypeai/v0/README.mdx delete mode 100644 pkg/component/ai/archetypeai/v0/assets/archetype-ai.svg delete mode 100644 pkg/component/ai/archetypeai/v0/client.go delete mode 100644 pkg/component/ai/archetypeai/v0/component_test.go delete mode 100644 pkg/component/ai/archetypeai/v0/config/definition.json delete mode 100644 pkg/component/ai/archetypeai/v0/config/setup.json delete mode 100644 pkg/component/ai/archetypeai/v0/config/tasks.json delete mode 100644 pkg/component/ai/archetypeai/v0/main.go delete mode 100644 pkg/component/ai/archetypeai/v0/structs.go diff --git a/pkg/component/ai/archetypeai/v0/README.mdx b/pkg/component/ai/archetypeai/v0/README.mdx deleted file mode 100644 index 9e7e3d19..00000000 --- a/pkg/component/ai/archetypeai/v0/README.mdx +++ /dev/null @@ -1,132 +0,0 @@ ---- -title: "Archetype AI" -lang: "en-US" -draft: false -description: "Learn about how to set up a VDP Archetype AI component https://github.com/instill-ai/instill-core" ---- - -The Archetype AI component is an AI component that allows users to connect the AI models served on the Archetype AI Platform. -It can carry out the following tasks: -- [Describe](#describe) -- [Summarize](#summarize) -- [Upload File](#upload-file) - -## Release Stage - -`Alpha` - -## Configuration - -The component definition and tasks are defined in the [definition.json](https://github.com/instill-ai/pipeline-backend/blob/main/pkg/component/ai/archetypeai/v0/config/definition.json) and [tasks.json](https://github.com/instill-ai/pipeline-backend/blob/main/pkg/component/ai/archetypeai/v0/config/tasks.json) files respectively. - -## Setup - - -In order to communicate with Archetype AI, the following connection details need to be -provided. You may specify them directly in a pipeline recipe as key-value pairs -within the component's `setup` block, or you can create a **Connection** from -the [**Integration Settings**](https://www.instill.tech/docs/vdp/integration) -page and reference the whole `setup` as `setup: -${connection.}`. - -
- -| Field | Field ID | Type | Note | -| :--- | :--- | :--- | :--- | -| API Key (required) | `api-key` | string | Fill in your Archetype AI API key | - -
- - - - -## Supported Tasks - -### Describe - -Describe a video. - -
- -| Input | ID | Type | Description | -| :--- | :--- | :--- | :--- | -| Task ID (required) | `task` | string | `TASK_DESCRIBE` | -| Query (required) | `query` | string | A guide to describe the video | -| File IDs (required) | `file-ids` | array[string] | The IDs of the videos to describe. These must have been previously uploaded via TASK_UPLOAD_FILE. | -
- - - - - - -
- -| Output | ID | Type | Description | -| :--- | :--- | :--- | :--- | -| [Descriptions](#describe-descriptions) | `descriptions` | array[object] | A set of descriptions corresponding to different moments in the video | -
- -
- Output Objects in Describe - -

Descriptions

- -
- -| Field | Field ID | Type | Note | -| :--- | :--- | :--- | :--- | -| Description | `description` | string | The description of the frame | -| Frame ID | `frame-id` | integer | The frame number in the video that is being described | -| Timestamp | `timestamp` | number | The moment of the video (in seconds since the start) that is being described | -
-
- -### Summarize - -Summarize the image. - -
- -| Input | ID | Type | Description | -| :--- | :--- | :--- | :--- | -| Task ID (required) | `task` | string | `TASK_SUMMARIZE` | -| Query (required) | `query` | string | A guide to summarize the image | -| File IDs (required) | `file-ids` | array[string] | The IDs of the images to summarize. These must have been previously uploaded via TASK_UPLOAD_FILE. | -
- - - - - - -
- -| Output | ID | Type | Description | -| :--- | :--- | :--- | :--- | -| Response | `response` | string | A text responding to the query | -
- -### Upload File - -Upload file. - -
- -| Input | ID | Type | Description | -| :--- | :--- | :--- | :--- | -| Task ID (required) | `task` | string | `TASK_UPLOAD_FILE` | -| File (required) | `file` | string | The file to upload. Accepted formats are JPEG and PNG for images or MP4 for videos | -
- - - - - - -
- -| Output | ID | Type | Description | -| :--- | :--- | :--- | :--- | -| File ID | `file-id` | string | The ID to reference the file in queries | -
diff --git a/pkg/component/ai/archetypeai/v0/assets/archetype-ai.svg b/pkg/component/ai/archetypeai/v0/assets/archetype-ai.svg deleted file mode 100644 index 9a6cd22e..00000000 --- a/pkg/component/ai/archetypeai/v0/assets/archetype-ai.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/pkg/component/ai/archetypeai/v0/client.go b/pkg/component/ai/archetypeai/v0/client.go deleted file mode 100644 index b955ad71..00000000 --- a/pkg/component/ai/archetypeai/v0/client.go +++ /dev/null @@ -1,34 +0,0 @@ -package archetypeai - -import ( - "go.uber.org/zap" - "google.golang.org/protobuf/types/known/structpb" - - "github.com/instill-ai/pipeline-backend/pkg/component/internal/util/httpclient" -) - -const ( - host = "https://api.archetypeai.dev" - describePath = "/v0.3/describe" - summarizePath = "/v0.3/summarize" - uploadFilePath = "/v0.3/files" -) - -func newClient(setup *structpb.Struct, logger *zap.Logger) *httpclient.Client { - c := httpclient.New("Archetype AI", getBasePath(setup), - httpclient.WithLogger(logger), - httpclient.WithEndUserError(new(errBody)), - ) - - c.SetAuthToken(getAPIKey(setup)) - - return c -} - -type errBody struct { - Error string `json:"error"` -} - -func (e errBody) Message() string { - return e.Error -} diff --git a/pkg/component/ai/archetypeai/v0/component_test.go b/pkg/component/ai/archetypeai/v0/component_test.go deleted file mode 100644 index 8486a9d7..00000000 --- a/pkg/component/ai/archetypeai/v0/component_test.go +++ /dev/null @@ -1,328 +0,0 @@ -package archetypeai - -import ( - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "net/http/httptest" - "testing" - - "google.golang.org/protobuf/types/known/structpb" - - qt "github.com/frankban/quicktest" - - "github.com/instill-ai/pipeline-backend/pkg/component/base" - "github.com/instill-ai/pipeline-backend/pkg/component/internal/mock" - "github.com/instill-ai/pipeline-backend/pkg/component/internal/util/httpclient" - "github.com/instill-ai/x/errmsg" -) - -const ( - apiKey = "213bac" -) - -const errJSON = `{ "error": "Invalid access." }` -const describeJSON = ` -{ - "query_id": "2401242b4d59e48bbf6e0d", - "status": "completed", - "inference_time_sec": 1.6635565757751465, - "query_response_time_sec": 6.018876314163208, - "response": [ - { - "timestamp": 2.0, - "frame_id": 60, - "description": "The group of people is walking across a bridge." - }, - { - "timestamp": 6.0, - "frame_id": 180, - "description": "The man is walking across a bridge, and he is surrounded by people." - } - ] -}` -const describeErrJSON = ` -{ - "query_id": "2401242b4d59e48bbf6e0d", - "status": "failed", - "inference_time_sec": 1.6635565757751465, - "query_response_time_sec": 6.018876314163208, - "response": [ - { - "timestamp": 2.0, - "frame_id": 60, - "description": "The group of people is walking across a bridge." - } - ] -}` -const summarizeJSON = ` -{ - "query_id": "240123b93a83a79e9907a5", - "status": "completed", - "file_ids": [ - "test_image.jpg" - ], - "inference_time_sec": 2.1776912212371826, - "query_response_time_sec": 2.1914472579956055, - "response": { - "processed_text": "A family of four is hiking together on a trail." - } -}` -const summarizeErrJSON = ` -{ - "query_id": "2401233472bde249e60260", - "status": "failed", - "file_ids": [ - "test_image.jpg" - ] -}` -const uploadFileJSON = ` -{ - "is_valid": true, - "file_id": "2084fa42-8452-4fa6-bed9-6aac6d6153bb", - "file_uid": "2401242e3cb25122835a17" -}` -const uploadErrJSON = ` -{ - "is_valid": false, - "errors": [ - "Invalid file type: application/octet-stream. Supported file types are: ('image/jpeg', 'image/png', 'video/mp4')." - ] -}` - -var ( - queryIn = fileQueryParams{ - Query: "Describe what's happening", - FileIDs: []string{"test.file"}, - } - queryReq = fileQueryReq(queryIn) - uploadFileIn = uploadFileParams{ - File: "data:text/plain;base64,aG9sYQ==", - } -) - -func TestComponent_Execute(t *testing.T) { - c := qt.New(t) - ctx := context.Background() - - testcases := []struct { - name string - - task string - in any - want any - wantErr string - - // server expectations and response - wantPath string - wantReq any - wantContentType string - gotStatus int - gotResp string - }{ - { - name: "ok - describe", - - task: taskDescribe, - in: queryIn, - want: describeOutput{ - Descriptions: []frameDescriptionOutput{ - { - Timestamp: 2.0, - FrameID: 60, - Description: "The group of people is walking across a bridge.", - }, - { - Timestamp: 6.0, - FrameID: 180, - Description: "The man is walking across a bridge, and he is surrounded by people.", - }, - }, - }, - - wantPath: describePath, - wantReq: queryReq, - wantContentType: httpclient.MIMETypeJSON, - gotStatus: http.StatusOK, - gotResp: describeJSON, - }, - { - name: "nok - describe error", - - task: taskDescribe, - in: queryIn, - wantErr: `Archetype AI didn't complete query 2401242b4d59e48bbf6e0d: status is "failed".`, - - wantPath: describePath, - wantReq: queryReq, - wantContentType: httpclient.MIMETypeJSON, - gotStatus: http.StatusOK, - gotResp: describeErrJSON, - }, - { - name: "ok - summarize", - - task: taskSummarize, - in: queryIn, - want: summarizeOutput{ - Response: "A family of four is hiking together on a trail.", - }, - - wantPath: summarizePath, - wantReq: queryReq, - wantContentType: httpclient.MIMETypeJSON, - gotStatus: http.StatusOK, - gotResp: summarizeJSON, - }, - { - name: "nok - summarize wrong file", - - task: taskSummarize, - in: queryIn, - wantErr: `Archetype AI didn't complete query 2401233472bde249e60260: status is "failed".`, - - wantPath: summarizePath, - wantReq: queryReq, - wantContentType: httpclient.MIMETypeJSON, - gotStatus: http.StatusOK, - gotResp: summarizeErrJSON, - }, - { - name: "ok - upload file", - - task: taskUploadFile, - in: uploadFileIn, - want: uploadFileOutput{FileID: "2084fa42-8452-4fa6-bed9-6aac6d6153bb"}, - - wantPath: uploadFilePath, - wantReq: "hola", - wantContentType: "multipart/form-data.*", - gotStatus: http.StatusOK, - gotResp: uploadFileJSON, - }, - { - name: "nok - upload invalid file", - - task: taskUploadFile, - in: uploadFileIn, - wantErr: "Couldn't complete upload: Invalid file type.*", - - wantPath: uploadFilePath, - wantReq: "hola", - wantContentType: "multipart/form-data.*", - gotStatus: http.StatusOK, - gotResp: uploadErrJSON, - }, - { - name: "nok - unauthorized", - - task: taskSummarize, - in: queryIn, - wantErr: "Archetype AI responded with a 401 status code. Invalid access.", - - wantPath: summarizePath, - wantReq: queryReq, - wantContentType: httpclient.MIMETypeJSON, - gotStatus: http.StatusUnauthorized, - gotResp: errJSON, - }, - } - - bc := base.Component{} - component := Init(bc) - - for _, tc := range testcases { - c.Run(tc.name, func(c *qt.C) { - h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - c.Check(r.Method, qt.Equals, http.MethodPost) - c.Check(r.URL.Path, qt.Matches, tc.wantPath) - - c.Check(r.Header.Get("Authorization"), qt.Equals, "Bearer "+apiKey) - c.Check(r.Header.Get("Content-Type"), qt.Matches, tc.wantContentType) - - body, err := io.ReadAll(r.Body) - c.Assert(err, qt.IsNil) - if tc.wantContentType == httpclient.MIMETypeJSON { - c.Check(body, qt.JSONEquals, tc.wantReq) - } else { - // We just do partial match to avoid matching every field - // in multipart bodies. - c.Check(string(body), qt.Contains, tc.wantReq) - } - - w.Header().Set("Content-Type", httpclient.MIMETypeJSON) - w.WriteHeader(tc.gotStatus) - fmt.Fprintln(w, tc.gotResp) - }) - - srv := httptest.NewServer(h) - c.Cleanup(srv.Close) - - setup, _ := structpb.NewStruct(map[string]any{ - "base-path": srv.URL, - "api-key": apiKey, - }) - - exec, err := component.CreateExecution(base.ComponentExecution{ - Component: component, - Setup: setup, - Task: tc.task, - }) - c.Assert(err, qt.IsNil) - - pbIn, err := base.ConvertToStructpb(tc.in) - c.Assert(err, qt.IsNil) - - ir, ow, eh, job := mock.GenerateMockJob(c) - ir.ReadMock.Return(pbIn, nil) - ow.WriteMock.Optional().Set(func(ctx context.Context, output *structpb.Struct) (err error) { - wantJSON, err := json.Marshal(tc.want) - c.Assert(err, qt.IsNil) - c.Check(wantJSON, qt.JSONEquals, output.AsMap()) - return nil - }) - eh.ErrorMock.Optional().Set(func(ctx context.Context, err error) { - if tc.wantErr != "" { - c.Check(errmsg.Message(err), qt.Matches, tc.wantErr) - } - }) - - err = exec.Execute(ctx, []*base.Job{job}) - c.Check(err, qt.IsNil) - }) - } -} - -func TestComponent_CreateExecution(t *testing.T) { - c := qt.New(t) - - bc := base.Component{} - component := Init(bc) - - c.Run("nok - unsupported task", func(c *qt.C) { - task := "FOOBAR" - want := fmt.Sprintf("%s task is not supported.", task) - - _, err := component.CreateExecution(base.ComponentExecution{ - Component: component, - Setup: new(structpb.Struct), - Task: task, - }) - c.Check(err, qt.IsNotNil) - c.Check(errmsg.Message(err), qt.Equals, want) - }) -} - -func TestComponent_Test(t *testing.T) { - c := qt.New(t) - - bc := base.Component{} - component := Init(bc) - - c.Run("ok - connected", func(c *qt.C) { - err := component.Test(nil, nil) - c.Check(err, qt.IsNil) - }) -} diff --git a/pkg/component/ai/archetypeai/v0/config/definition.json b/pkg/component/ai/archetypeai/v0/config/definition.json deleted file mode 100644 index a15263f4..00000000 --- a/pkg/component/ai/archetypeai/v0/config/definition.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "availableTasks": [ - "TASK_DESCRIBE", - "TASK_SUMMARIZE", - "TASK_UPLOAD_FILE" - ], - "custom": false, - "documentationUrl": "https://www.instill.tech/docs/component/ai/archetypeai", - "icon": "assets/archetype-ai.svg", - "iconUrl": "", - "id": "archetype-ai", - "public": true, - "title": "Archetype AI", - "description": "Connect the AI models served on the Archetype AI Platform", - "tombstone": false, - "type": "COMPONENT_TYPE_AI", - "uid": "e414a1f8-5fdf-4292-b050-9f9176254a4b", - "vendor": "Archetype AI", - "vendorAttributes": {}, - "version": "0.1.0", - "sourceUrl": "https://github.com/instill-ai/pipeline-backend/blob/main/pkg/component/ai/archetypeai/v0", - "releaseStage": "RELEASE_STAGE_ALPHA" -} diff --git a/pkg/component/ai/archetypeai/v0/config/setup.json b/pkg/component/ai/archetypeai/v0/config/setup.json deleted file mode 100644 index 0260f0a2..00000000 --- a/pkg/component/ai/archetypeai/v0/config/setup.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "additionalProperties": false, - "properties": { - "api-key": { - "description": "Fill in your Archetype AI API key", - "instillUpstreamTypes": [ - "reference" - ], - "instillAcceptFormats": [ - "string" - ], - "instillSecret": true, - "instillUIOrder": 0, - "title": "API Key", - "type": "string" - } - }, - "required": [ - "api-key" - ], - "instillEditOnNodeFields": [ - "api-key" - ], - "title": "Archetype AI Connection", - "type": "object" -} diff --git a/pkg/component/ai/archetypeai/v0/config/tasks.json b/pkg/component/ai/archetypeai/v0/config/tasks.json deleted file mode 100644 index f467ea27..00000000 --- a/pkg/component/ai/archetypeai/v0/config/tasks.json +++ /dev/null @@ -1,205 +0,0 @@ -{ - "TASK_DESCRIBE": { - "instillShortDescription": "Describe a video.", - "input": { - "instillUIOrder": 0, - "properties": { - "query": { - "description": "A guide to describe the video", - "instillAcceptFormats": [ - "string" - ], - "instillUIMultiline": true, - "instillUIOrder": 0, - "instillUpstreamTypes": [ - "value", - "reference", - "template" - ], - "title": "Query", - "type": "string" - }, - "file-ids": { - "description": "The IDs of the videos to describe. These must have been previously uploaded via TASK_UPLOAD_FILE.", - "instillAcceptFormats": [ - "array:string" - ], - "instillUIOrder": 1, - "instillUpstreamTypes": [ - "value", - "reference" - ], - "items": { - "instillUIMultiline": false, - "type": "string" - }, - "minItems": 1, - "title": "File IDs", - "type": "array" - } - }, - "required": [ - "query", - "file-ids" - ], - "title": "Input", - "type": "object" - }, - "output": { - "instillUIOrder": 0, - "properties": { - "descriptions": { - "description": "A set of descriptions corresponding to different moments in the video", - "instillUIOrder": 0, - "title": "Descriptions", - "type": "array", - "items": { - "title": "Frame description", - "type": "object", - "properties": { - "frame-id": { - "description": "The frame number in the video that is being described", - "instillFormat": "integer", - "instillUIOrder": 3, - "required": [], - "title": "Frame ID", - "type": "integer" - }, - "timestamp": { - "description": "The moment of the video (in seconds since the start) that is being described", - "instillFormat": "number", - "instillUIOrder": 1, - "title": "Timestamp", - "type": "number" - }, - "description": { - "description": "The description of the frame", - "instillFormat": "string", - "instillUIOrder": 2, - "title": "Description", - "type": "string" - } - }, - "required": [ - "description", - "timestamp", - "frame-id" - ] - } - } - }, - "required": [ - "descriptions" - ], - "title": "Output", - "type": "object" - } - }, - "TASK_SUMMARIZE": { - "instillShortDescription": "Summarize the image.", - "input": { - "instillUIOrder": 0, - "properties": { - "query": { - "description": "A guide to summarize the image", - "instillAcceptFormats": [ - "string" - ], - "instillUIMultiline": true, - "instillUIOrder": 0, - "instillUpstreamTypes": [ - "value", - "reference", - "template" - ], - "title": "Query", - "type": "string" - }, - "file-ids": { - "description": "The IDs of the images to summarize. These must have been previously uploaded via TASK_UPLOAD_FILE.", - "instillAcceptFormats": [ - "array:string" - ], - "instillUIOrder": 1, - "instillUpstreamTypes": [ - "value", - "reference" - ], - "items": { - "instillUIMultiline": false, - "type": "string" - }, - "minItems": 1, - "title": "File IDs", - "type": "array" - } - }, - "required": [ - "query", - "file-ids" - ], - "title": "Input", - "type": "object" - }, - "output": { - "instillUIOrder": 0, - "properties": { - "response": { - "description": "A text responding to the query", - "instillFormat": "string", - "instillUIOrder": 0, - "title": "Response", - "type": "string" - } - }, - "required": [ - "response" - ], - "title": "Output", - "type": "object" - } - }, - "TASK_UPLOAD_FILE": { - "instillShortDescription": "Upload file.", - "input": { - "instillUIOrder": 0, - "properties": { - "file": { - "title": "File", - "description": "The file to upload. Accepted formats are JPEG and PNG for images or MP4 for videos", - "type": "string", - "instillAcceptFormats": [ - "video/*", - "image/*" - ], - "instillUIOrder": 0, - "instillUpstreamTypes": [ - "reference" - ] - } - }, - "required": [ - "file" - ], - "title": "Input", - "type": "object" - }, - "output": { - "instillUIOrder": 0, - "properties": { - "file-id": { - "instillFormat": "string", - "instillUIOrder": 0, - "title": "File ID", - "description": "The ID to reference the file in queries", - "type": "string" - } - }, - "required": [ - "file-id" - ], - "title": "Output", - "type": "object" - } - } -} diff --git a/pkg/component/ai/archetypeai/v0/main.go b/pkg/component/ai/archetypeai/v0/main.go deleted file mode 100644 index 0e3e2e46..00000000 --- a/pkg/component/ai/archetypeai/v0/main.go +++ /dev/null @@ -1,236 +0,0 @@ -//go:generate compogen readme ./config ./README.mdx -package archetypeai - -import ( - "bytes" - "context" - "fmt" - "strings" - "sync" - - _ "embed" - - "github.com/gofrs/uuid" - "google.golang.org/protobuf/types/known/structpb" - - "github.com/instill-ai/pipeline-backend/pkg/component/base" - "github.com/instill-ai/pipeline-backend/pkg/component/internal/util" - "github.com/instill-ai/pipeline-backend/pkg/component/internal/util/httpclient" - "github.com/instill-ai/x/errmsg" -) - -const ( - taskDescribe = "TASK_DESCRIBE" - taskSummarize = "TASK_SUMMARIZE" - taskUploadFile = "TASK_UPLOAD_FILE" -) - -var ( - //go:embed config/definition.json - definitionJSON []byte - //go:embed config/setup.json - setupJSON []byte - //go:embed config/tasks.json - tasksJSON []byte - - once sync.Once - comp *component -) - -type component struct { - base.Component -} - -type execution struct { - base.ComponentExecution - - execute func(*structpb.Struct) (*structpb.Struct, error) - client *httpclient.Client -} - -// Init returns an implementation of IComponent that interacts with Archetype -// AI. -func Init(bc base.Component) *component { - once.Do(func() { - comp = &component{Component: bc} - err := comp.LoadDefinition(definitionJSON, setupJSON, tasksJSON, nil) - if err != nil { - panic(err) - } - }) - return comp -} - -func (c *component) CreateExecution(x base.ComponentExecution) (base.IExecution, error) { - e := &execution{ - ComponentExecution: x, - client: newClient(x.Setup, c.GetLogger()), - } - - switch x.Task { - case taskDescribe: - e.execute = e.describe - case taskSummarize: - e.execute = e.summarize - case taskUploadFile: - e.execute = e.uploadFile - default: - return nil, errmsg.AddMessage( - fmt.Errorf("not supported task: %s", x.Task), - fmt.Sprintf("%s task is not supported.", x.Task), - ) - } - - return e, nil -} - -// Execute performs calls the Archetype AI API to execute a task. -func (e *execution) Execute(ctx context.Context, jobs []*base.Job) error { - return base.SequentialExecutor(ctx, jobs, e.execute) -} - -func (e *execution) describe(in *structpb.Struct) (*structpb.Struct, error) { - params := fileQueryParams{} - if err := base.ConvertFromStructpb(in, ¶ms); err != nil { - return nil, err - } - - // We have a 1-1 mapping between the VDP user input and the Archetype AI - // request. If this stops being the case in the future, we'll need a - // describeReq structure. - resp := describeResp{} - req := e.client.R().SetBody(fileQueryReq(params)).SetResult(&resp) - - if _, err := req.Post(describePath); err != nil { - return nil, err - } - - // Archetype AI might return a 200 status even if the operation failed - // (e.g. if the file doesn't exist). - if resp.Status != statusCompleted { - return nil, errmsg.AddMessage( - fmt.Errorf("response with non-completed status"), - fmt.Sprintf(`Archetype AI didn't complete query %s: status is "%s".`, resp.QueryID, resp.Status), - ) - } - - frameDescriptionOutputs := make([]frameDescriptionOutput, len(resp.Response)) - for i := range resp.Response { - frameDescriptionOutputs[i] = frameDescriptionOutput(resp.Response[i]) - } - out, err := base.ConvertToStructpb(describeOutput{ - Descriptions: frameDescriptionOutputs, - }) - if err != nil { - return nil, err - } - - return out, nil -} - -func (e *execution) summarize(in *structpb.Struct) (*structpb.Struct, error) { - params := fileQueryParams{} - if err := base.ConvertFromStructpb(in, ¶ms); err != nil { - return nil, err - } - - // We have a 1-1 mapping between the VDP user input and the Archetype AI - // request. If this stops being the case in the future, we'll need a - // summarizeReq structure. - resp := summarizeResp{} - req := e.client.R().SetBody(fileQueryReq(params)).SetResult(&resp) - - if _, err := req.Post(summarizePath); err != nil { - return nil, err - } - - // Archetype AI might return a 200 status even if the operation failed - // (e.g. if the file doesn't exist). - if resp.Status != statusCompleted { - return nil, errmsg.AddMessage( - fmt.Errorf("response with non-completed status"), - fmt.Sprintf(`Archetype AI didn't complete query %s: status is "%s".`, resp.QueryID, resp.Status), - ) - } - - out, err := base.ConvertToStructpb(summarizeOutput{ - Response: resp.Response.ProcessedText, - }) - if err != nil { - return nil, err - } - - return out, nil -} - -func (e *execution) uploadFile(in *structpb.Struct) (*structpb.Struct, error) { - params := uploadFileParams{} - if err := base.ConvertFromStructpb(in, ¶ms); err != nil { - return nil, err - } - - resp := uploadFileResp{} - req := e.client.R().SetResult(&resp) - - b, err := util.DecodeBase64(params.File) - if err != nil { - return nil, err - } - - id, err := uuid.NewV4() - if err != nil { - return nil, err - } - - req.SetFileReader("file", id.String(), bytes.NewReader(b)) - if _, err := req.Post(uploadFilePath); err != nil { - return nil, err - } - - if !resp.IsValid { - errMsg := "invalid file." - if len(resp.Errors) > 0 { - errMsg = strings.Join(resp.Errors, " ") - } - - return nil, errmsg.AddMessage( - fmt.Errorf("file upload failed"), - fmt.Sprintf(`Couldn't complete upload: %s`, errMsg), - ) - } - - out, err := base.ConvertToStructpb(uploadFileOutput{FileID: resp.FileID}) - if err != nil { - return nil, err - } - - return out, nil -} - -// Test checks the connectivity of the component. - -func (c *component) Test(sysVars map[string]any, setup *structpb.Struct) error { - // TODO Archetype AI API is not public yet. We could test the setup - // by calling one of the endpoints used in the available tasks. However, - // these are not designed for specifically for this purpose. When we know - // of an endpoint that's more suited for this, it should be used instead. - return nil -} - -func getAPIKey(setup *structpb.Struct) string { - return setup.GetFields()["api-key"].GetStringValue() -} - -// getBasePath returns Archetype AI's API URL. This configuration param allows -// us to override the API the component will point to. It isn't meant to be -// exposed to users. Rather, it can serve to test the logic against a fake -// server. -// TODO instead of having the API value hardcoded in the codebase, it should -// be read from a setup file or environment variable. -func getBasePath(setup *structpb.Struct) string { - v, ok := setup.GetFields()["base-path"] - if !ok { - return host - } - return v.GetStringValue() -} diff --git a/pkg/component/ai/archetypeai/v0/structs.go b/pkg/component/ai/archetypeai/v0/structs.go deleted file mode 100644 index ce8dd37c..00000000 --- a/pkg/component/ai/archetypeai/v0/structs.go +++ /dev/null @@ -1,74 +0,0 @@ -package archetypeai - -// fileQueryParams holds a query about an file. It is used as the input in -// e.g. video description or image summarization tasks. -type fileQueryParams struct { - Query string `json:"query"` - FileIDs []string `json:"file-ids"` -} - -type fileQueryReq struct { - Query string `json:"query"` - FileIDs []string `json:"file_ids"` -} - -// summarizeOutput is used to return the output of a TASK_SUMMARIZE execution. -type summarizeOutput struct { - Response string `json:"response"` -} - -const ( - statusCompleted = "completed" - statusFailed = "failed" -) - -// summarizeResp holds the response from the Archetype AI API call. -type summarizeResp struct { - QueryID string `json:"query_id"` - Status string `json:"status"` - Response struct { - ProcessedText string `json:"processed_text"` - } `json:"response"` -} - -type frameDescriptionResp struct { - Timestamp float32 `json:"timestamp"` - FrameID uint64 `json:"frame_id"` - Description string `json:"description"` -} - -type frameDescriptionOutput struct { - Timestamp float32 `json:"timestamp"` - FrameID uint64 `json:"frame-id"` - Description string `json:"description"` -} - -// describeResp holds the response from the Archetype AI API call. -type describeResp struct { - QueryID string `json:"query_id"` - Status string `json:"status"` - Response []frameDescriptionResp `json:"response"` -} - -// summarizeOutput is used to return the output of a TASK_DESCRIBE execution. -type describeOutput struct { - Descriptions []frameDescriptionOutput `json:"descriptions"` -} - -// uploadFileParams holds the input of a file upload task. -type uploadFileParams struct { - File string `json:"file"` -} - -// uploadFileOutput is used to return the output of a file TASK_UPLOAD_FILE -// execution. -type uploadFileOutput struct { - FileID string `json:"file-id"` -} - -// uploadFileResp holds the response from the Archetype AI API call. -type uploadFileResp struct { - FileID string `json:"file_id"` - IsValid bool `json:"is_valid"` - Errors []string `json:"errors"` -}