Files
pierre-bot/vendor/github.com/google/generative-ai-go/genai/files.go
2026-02-12 21:44:10 +01:00

190 lines
5.9 KiB
Go

// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package genai
//go:generate ../devtools/generate_discovery_client.sh
import (
"context"
"io"
"os"
"strings"
gl "cloud.google.com/go/ai/generativelanguage/apiv1beta"
pb "cloud.google.com/go/ai/generativelanguage/apiv1beta/generativelanguagepb"
gld "github.com/google/generative-ai-go/genai/internal/generativelanguage/v1beta" // discovery client
"google.golang.org/api/googleapi"
"google.golang.org/api/iterator"
)
// UploadFileOptions are options for [Client.UploadFile].
type UploadFileOptions struct {
// A more readable name for the file.
DisplayName string
// The IANA standard MIME type of the file. It will be stored with the file as metadata.
// If omitted, the service will try to infer it. You may instead wish to use
// [http.DetectContentType].
// The supported MIME types are documented on [this page].
//
// [this page]: https://ai.google.dev/gemini-api/docs/document-processing?lang=go#technical-details
MIMEType string
}
// UploadFile copies the contents of the given io.Reader to file storage associated
// with the service, and returns information about the resulting file.
//
// The name is a relatively short, unique identifier for the file (rather than a typical
// filename).
// Typically it should be left empty, in which case a unique name will be generated.
// Otherwise, it can contain up to 40 characters that are lowercase
// alphanumeric or dashes (-), not starting or ending with a dash.
// To generate your own unique names, consider a cryptographic hash algorithm like SHA-1.
// The string "files/" is prepended to the name if it does not contain a '/'.
//
// Use the returned file's URI field with a [FileData] Part to use it for generation.
//
// It is an error to upload a file that already exists.
func (c *Client) UploadFile(ctx context.Context, name string, r io.Reader, opts *UploadFileOptions) (*File, error) {
if name != "" {
name = userNameToServiceName(name)
}
req := &gld.CreateFileRequest{
File: &gld.File{Name: name},
}
if opts != nil && opts.DisplayName != "" {
req.File.DisplayName = opts.DisplayName
}
call := c.ds.Media.Upload(req)
var mopts []googleapi.MediaOption
if opts != nil && opts.MIMEType != "" {
mopts = append(mopts, googleapi.ContentType(opts.MIMEType))
}
call.Media(r, mopts...)
res, err := call.Do()
if err != nil {
return nil, err
}
// Don't return the result, because it contains a file as represented by the
// discovery client and we'd have to write code to convert it to this package's
// File type.
// Instead, make a GetFile call to get the proto file, which our generated code can convert.
return c.GetFile(ctx, res.File.Name)
}
// UploadFileFromPath is a convenience method wrapping [UploadFile]. It takes
// a path to read the file from, and uses a default auto-generated ID for the
// uploaded file.
func (c *Client) UploadFileFromPath(ctx context.Context, path string, opts *UploadFileOptions) (*File, error) {
osf, err := os.Open(path)
if err != nil {
return nil, err
}
defer osf.Close()
return c.UploadFile(ctx, "", osf, opts)
}
// GetFile returns the named file.
func (c *Client) GetFile(ctx context.Context, name string) (*File, error) {
req := &pb.GetFileRequest{Name: userNameToServiceName(name)}
debugPrint(req)
pf, err := c.fc.GetFile(ctx, req)
if err != nil {
return nil, err
}
return (File{}).fromProto(pf), nil
}
// DeleteFile deletes the file with the given name.
// It is an error to delete a file that does not exist.
func (c *Client) DeleteFile(ctx context.Context, name string) error {
req := &pb.DeleteFileRequest{Name: userNameToServiceName(name)}
debugPrint(req)
return c.fc.DeleteFile(ctx, req)
}
// userNameToServiceName converts a name supplied by the user to a name required by the service.
func userNameToServiceName(name string) string {
if strings.ContainsRune(name, '/') {
return name
}
return "files/" + name
}
// ListFiles returns an iterator over the uploaded files.
func (c *Client) ListFiles(ctx context.Context) *FileIterator {
return &FileIterator{
it: c.fc.ListFiles(ctx, &pb.ListFilesRequest{}),
}
}
// A FileIterator iterates over Files.
type FileIterator struct {
it *gl.FileIterator
}
// Next returns the next result. Its second return value is iterator.Done if there are no more
// results. Once Next returns Done, all subsequent calls will return Done.
func (it *FileIterator) Next() (*File, error) {
m, err := it.it.Next()
if err != nil {
return nil, err
}
return (File{}).fromProto(m), nil
}
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
func (it *FileIterator) PageInfo() *iterator.PageInfo {
return it.it.PageInfo()
}
// FileMetadata holds metadata about a file.
type FileMetadata struct {
// Set if the file contains video.
Video *VideoMetadata
}
func populateFileTo(p *pb.File, f *File) {
p.Metadata = nil
if f == nil || f.Metadata == nil {
return
}
if f.Metadata.Video != nil {
p.Metadata = &pb.File_VideoMetadata{
VideoMetadata: f.Metadata.Video.toProto(),
}
}
}
func populateFileFrom(f *File, p *pb.File) {
f.Metadata = nil
if p == nil || p.Metadata == nil {
return
}
if p.Metadata != nil {
switch m := p.Metadata.(type) {
case *pb.File_VideoMetadata:
f.Metadata = &FileMetadata{
Video: (VideoMetadata{}).fromProto(m.VideoMetadata),
}
default:
// ignore other types
// TODO: signal a problem
}
}
}