-
Notifications
You must be signed in to change notification settings - Fork 19
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
feat(pinecone): Add rerank task for Pinecone component #773
base: main
Are you sure you want to change the base?
Changes from all commits
f2cf030
54639c1
5cf3cda
6084ae3
1b82d4f
dd8c27f
979bc0b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -291,5 +291,97 @@ | |||||
"title": "Output", | ||||||
"type": "object" | ||||||
} | ||||||
}, | ||||||
"TASK_RERANK": { | ||||||
"instillShortDescription": "Rerank documents, such as text passages, according to their relevance to a query.", | ||||||
"description": "Rerank documents, such as text passages, according to their relevance to a query. The input is a list of documents and a query. The output is a list of documents, sorted by relevance to the query.", | ||||||
"input": { | ||||||
"instillUIOrder": 0, | ||||||
"properties": { | ||||||
"query": { | ||||||
"description": "The query to rerank the documents", | ||||||
"instillAcceptFormats": [ | ||||||
"string" | ||||||
], | ||||||
"instullUIMultiline": false, | ||||||
"instillUIOrder": 0, | ||||||
"instillUpstreamTypes": [ | ||||||
"value", | ||||||
"reference", | ||||||
"template" | ||||||
], | ||||||
"title": "Query", | ||||||
"type": "string" | ||||||
}, | ||||||
"documents": { | ||||||
"description": "The documents to rerank", | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
"instillUIOrder": 1, | ||||||
"instillUpstreamTypes": [ | ||||||
"value", | ||||||
"reference" | ||||||
], | ||||||
"items": { | ||||||
"type": "string" | ||||||
}, | ||||||
"minItems": 1, | ||||||
"title": "Documents", | ||||||
"type": "array" | ||||||
}, | ||||||
"top-n": { | ||||||
"description": "The number of results to return sorted by relevance. Defaults to the number of inputs.\n\n", | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
"instillAcceptFormats": [ | ||||||
"integer" | ||||||
], | ||||||
"instillUIOrder": 2, | ||||||
"instillUpstreamTypes": [ | ||||||
"value", | ||||||
"reference" | ||||||
], | ||||||
"title": "Top N", | ||||||
"type": "integer" | ||||||
} | ||||||
}, | ||||||
"required": [ | ||||||
"query", | ||||||
"documents" | ||||||
], | ||||||
"title": "Input", | ||||||
"type": "object" | ||||||
}, | ||||||
"output": { | ||||||
"instillUIOrder": 0, | ||||||
"properties": { | ||||||
"documents": { | ||||||
"description": "Reranked documents", | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
"instillFormat": "array:string", | ||||||
"items": { | ||||||
"instillFormat": "string", | ||||||
"title": "Documents", | ||||||
"type": "string" | ||||||
}, | ||||||
"instillUIOrder": 0, | ||||||
"title": "Reranked documents", | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
"type": "array" | ||||||
}, | ||||||
"scores": { | ||||||
"description": "The relevance score of the documents normalized between 0 and 1.", | ||||||
"instillFormat": "array:number", | ||||||
"items": { | ||||||
"instillFormat": "number", | ||||||
"title": "Score", | ||||||
"type": "number" | ||||||
}, | ||||||
"instillUIOrder": 1, | ||||||
"title": "Scores", | ||||||
"type": "array" | ||||||
} | ||||||
}, | ||||||
"required": [ | ||||||
"documents", | ||||||
"scores" | ||||||
], | ||||||
"title": "Output", | ||||||
"type": "object" | ||||||
} | ||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,9 +17,11 @@ import ( | |
const ( | ||
taskQuery = "TASK_QUERY" | ||
taskUpsert = "TASK_UPSERT" | ||
taskRerank = "TASK_RERANK" | ||
|
||
upsertPath = "/vectors/upsert" | ||
queryPath = "/query" | ||
rerankPath = "/rerank" | ||
) | ||
|
||
//go:embed config/definition.json | ||
|
@@ -59,7 +61,8 @@ func (c *component) CreateExecution(x base.ComponentExecution) (base.IExecution, | |
}, nil | ||
} | ||
|
||
func newClient(setup *structpb.Struct, logger *zap.Logger) *httpclient.Client { | ||
// newIndexClient creates a new httpclient.Client with the index URL provided in setup | ||
func newIndexClient(setup *structpb.Struct, logger *zap.Logger) *httpclient.Client { | ||
c := httpclient.New("Pinecone", getURL(setup), | ||
httpclient.WithLogger(logger), | ||
httpclient.WithEndUserError(new(errBody)), | ||
|
@@ -71,6 +74,23 @@ func newClient(setup *structpb.Struct, logger *zap.Logger) *httpclient.Client { | |
return c | ||
} | ||
|
||
// newBaseClient creates a new httpclient.Client with the default Pinecone API URL. | ||
func newBaseClient(setup *structpb.Struct, logger *zap.Logger) *httpclient.Client { | ||
c := httpclient.New("Pinecone", "https://api.pinecone.io", | ||
httpclient.WithLogger(logger), | ||
httpclient.WithEndUserError(new(errBody)), | ||
) | ||
|
||
c.SetHeader("Api-Key", getAPIKey(setup)) | ||
c.SetHeader("User-Agent", "source_tag=instillai") | ||
|
||
// Currently, by default Pinecone API redirects request to OLDEST stable version i.e. 2024-04 right now and does not support Rerank | ||
// It is recommended by Pinecone to specify API version to use: https://docs.pinecone.io/reference/api/versioning#specify-an-api-version | ||
c.SetHeader("X-Pinecone-API-Version", "2024-10") | ||
|
||
return c | ||
} | ||
|
||
func getAPIKey(setup *structpb.Struct) string { | ||
return setup.GetFields()["api-key"].GetStringValue() | ||
} | ||
|
@@ -81,8 +101,6 @@ func getURL(setup *structpb.Struct) string { | |
|
||
func (e *execution) Execute(ctx context.Context, jobs []*base.Job) error { | ||
|
||
req := newClient(e.Setup, e.GetLogger()).R() | ||
|
||
for _, job := range jobs { | ||
input, err := job.Input.Read(ctx) | ||
if err != nil { | ||
|
@@ -92,6 +110,8 @@ func (e *execution) Execute(ctx context.Context, jobs []*base.Job) error { | |
var output *structpb.Struct | ||
switch e.Task { | ||
case taskQuery: | ||
req := newIndexClient(e.Setup, e.GetLogger()).R() | ||
|
||
inputStruct := queryInput{} | ||
err := base.ConvertFromStructpb(input, &inputStruct) | ||
if err != nil { | ||
|
@@ -122,6 +142,8 @@ func (e *execution) Execute(ctx context.Context, jobs []*base.Job) error { | |
continue | ||
} | ||
case taskUpsert: | ||
req := newIndexClient(e.Setup, e.GetLogger()).R() | ||
|
||
v := upsertInput{} | ||
err := base.ConvertFromStructpb(input, &v) | ||
if err != nil { | ||
|
@@ -145,7 +167,34 @@ func (e *execution) Execute(ctx context.Context, jobs []*base.Job) error { | |
job.Error.Error(ctx, err) | ||
continue | ||
} | ||
case taskRerank: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It'd be good to have the test code if possible. |
||
// rerank task does not need index URL, so using the base client with the default pinecone API URL. | ||
req := newBaseClient(e.Setup, e.GetLogger()).R() | ||
|
||
// parse input struct | ||
inputStruct := rerankInput{} | ||
err := base.ConvertFromStructpb(input, &inputStruct) | ||
if err != nil { | ||
job.Error.Error(ctx, err) | ||
continue | ||
} | ||
|
||
// make API request to rerank task | ||
resp := rerankResp{} | ||
req.SetResult(&resp).SetBody(inputStruct.asRequest()) | ||
if _, err := req.Post(rerankPath); err != nil { | ||
job.Error.Error(ctx, httpclient.WrapURLError(err)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Recently, I found the clients don't reply the meaningful error message in Could you add the errMsg like this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. WrapURLError is a helper to add an end-user message to http transport errors. |
||
continue | ||
} | ||
|
||
// convert response to output struct | ||
output, err = base.ConvertToStructpb(resp.toOutput()) | ||
if err != nil { | ||
job.Error.Error(ctx, err) | ||
continue | ||
} | ||
} | ||
|
||
err = job.Output.Write(ctx, output) | ||
if err != nil { | ||
job.Error.Error(ctx, err) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -83,6 +83,65 @@ type upsertOutput struct { | |
RecordsUpserted int64 `json:"upserted-count"` | ||
} | ||
|
||
type Document struct { | ||
Text string `json:"text"` | ||
} | ||
|
||
type rerankInput struct { | ||
// not taking model as input for now as only one model is supported for rerank task: https://docs.pinecone.io/guides/inference/understanding-inference#models | ||
//ModelName string `json:"model-name"` | ||
Query string `json:"query"` | ||
Documents []string `json:"documents"` | ||
TopN int `json:"top-n"` | ||
} | ||
|
||
func (r *rerankInput) asRequest() *rerankReq { | ||
reqDocuments := make([]Document, 0, len(r.Documents)) | ||
for _, doc := range r.Documents { | ||
reqDocuments = append(reqDocuments, Document{Text: doc}) | ||
} | ||
|
||
return &rerankReq{ | ||
Model: "bge-reranker-v2-m3", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Though it only provides a model now, we can make it a field with a default value. You can refer to this. |
||
Query: r.Query, | ||
TopN: r.TopN, | ||
Documents: reqDocuments, | ||
} | ||
} | ||
|
||
type rerankReq struct { | ||
Model string `json:"model"` | ||
Query string `json:"query"` | ||
TopN int `json:"top_n,omitempty"` | ||
Documents []Document `json:"documents"` | ||
} | ||
|
||
type rerankResp struct { | ||
Data []struct { | ||
Index int `json:"index"` | ||
Document Document `json:"document"` | ||
Score float64 `json:"score"` | ||
} `json:"data"` | ||
} | ||
|
||
func (r *rerankResp) toOutput() rerankOutput { | ||
documents := make([]string, 0, len(r.Data)) | ||
scores := make([]float64, 0, len(r.Data)) | ||
for _, d := range r.Data { | ||
documents = append(documents, d.Document.Text) | ||
scores = append(scores, d.Score) | ||
} | ||
return rerankOutput{ | ||
Documents: documents, | ||
Scores: scores, | ||
} | ||
} | ||
|
||
type rerankOutput struct { | ||
Documents []string `json:"documents"` | ||
Scores []float64 `json:"scores"` | ||
} | ||
|
||
type errBody struct { | ||
Msg string `json:"message"` | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now, we always add period in the end for description fields.