chats.go

  1// Copyright 2025 Google LLC
  2//
  3// Licensed under the Apache License, Version 2.0 (the "License");
  4// you may not use this file except in compliance with the License.
  5// You may obtain a copy of the License at
  6//
  7//      http://www.apache.org/licenses/LICENSE-2.0
  8//
  9// Unless required by applicable law or agreed to in writing, software
 10// distributed under the License is distributed on an "AS IS" BASIS,
 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12// See the License for the specific language governing permissions and
 13// limitations under the License.
 14
 15// Chats client.
 16
 17package genai
 18
 19import (
 20	"context"
 21	"io"
 22	"iter"
 23	"log"
 24)
 25
 26// Chats provides util functions for creating a new chat session.
 27// You don't need to initiate this struct. Create a client instance via NewClient, and
 28// then access Chats through client.Models field.
 29type Chats struct {
 30	apiClient *apiClient
 31}
 32
 33// Chat represents a single chat session (multi-turn conversation) with the model.
 34//
 35//		client, _ := genai.NewClient(ctx, &genai.ClientConfig{})
 36//		chat, _ := client.Chats.Create(ctx, "gemini-2.0-flash", nil, nil)
 37//	  result, err = chat.SendMessage(ctx, genai.Part{Text: "What is 1 + 2?"})
 38type Chat struct {
 39	Models
 40	apiClient *apiClient
 41	model     string
 42	config    *GenerateContentConfig
 43	// History of the chat.
 44	comprehensiveHistory []*Content
 45}
 46
 47// Create initializes a new chat session.
 48func (c *Chats) Create(ctx context.Context, model string, config *GenerateContentConfig, history []*Content) (*Chat, error) {
 49	chat := &Chat{
 50		apiClient:            c.apiClient,
 51		model:                model,
 52		config:               config,
 53		comprehensiveHistory: history,
 54	}
 55	chat.Models.apiClient = c.apiClient
 56	return chat, nil
 57}
 58
 59func (c *Chat) recordHistory(ctx context.Context, inputContent *Content, outputContents []*Content) {
 60	c.comprehensiveHistory = append(c.comprehensiveHistory, inputContent)
 61
 62	for _, outputContent := range outputContents {
 63		c.comprehensiveHistory = append(c.comprehensiveHistory, copySanitizedModelContent(outputContent))
 64	}
 65}
 66
 67// copySanitizedModelContent creates a (shallow) copy of modelContent with role set to
 68// model and all Parts copied verbatim.
 69func copySanitizedModelContent(modelContent *Content) *Content {
 70	newContent := &Content{Role: RoleModel}
 71	newContent.Parts = append(newContent.Parts, modelContent.Parts...)
 72	return newContent
 73}
 74
 75// History returns the chat history. Curated (valid only) history is not supported yet.
 76func (c *Chat) History(curated bool) []*Content {
 77	if curated {
 78		log.Println("curated history is not supported yet")
 79		return nil
 80	}
 81	return c.comprehensiveHistory
 82}
 83
 84// SendMessage sends the conversation history with the additional user's message and returns the model's response.
 85func (c *Chat) SendMessage(ctx context.Context, parts ...Part) (*GenerateContentResponse, error) {
 86	// Transform Parts to single Content
 87	p := make([]*Part, len(parts))
 88	for i, part := range parts {
 89		p[i] = &part
 90	}
 91	inputContent := &Content{Parts: p, Role: RoleUser}
 92
 93	// Combine history with input content to send to model
 94	contents := append(c.comprehensiveHistory, inputContent)
 95
 96	// Generate Content
 97	modelOutput, err := c.GenerateContent(ctx, c.model, contents, c.config)
 98	if err != nil {
 99		return nil, err
100	}
101
102	// Record history. By default, use the first candidate for history.
103	var outputContents []*Content
104	if len(modelOutput.Candidates) > 0 && modelOutput.Candidates[0].Content != nil {
105		outputContents = append(outputContents, modelOutput.Candidates[0].Content)
106	}
107	c.recordHistory(ctx, inputContent, outputContents)
108
109	return modelOutput, err
110}
111
112// SendMessageStream sends the conversation history with the additional user's message and returns the model's response.
113func (c *Chat) SendMessageStream(ctx context.Context, parts ...Part) iter.Seq2[*GenerateContentResponse, error] {
114	// Transform Parts to single Content
115	p := make([]*Part, len(parts))
116	for i, part := range parts {
117		p[i] = &part
118	}
119	inputContent := &Content{Parts: p, Role: "user"}
120
121	// Combine history with input content to send to model
122	contents := append(c.comprehensiveHistory, inputContent)
123
124	// Generate Content
125	response := c.GenerateContentStream(ctx, c.model, contents, c.config)
126
127	// Return a new iterator that will yield the responses and record history with merged response.
128	return func(yield func(*GenerateContentResponse, error) bool) {
129		var outputContents []*Content
130		for chunk, err := range response {
131			if err == io.EOF {
132				break
133			}
134			if err != nil {
135				yield(nil, err)
136				return
137			}
138			if len(chunk.Candidates) > 0 && chunk.Candidates[0].Content != nil {
139				outputContents = append(outputContents, chunk.Candidates[0].Content)
140			}
141			yield(chunk, nil)
142		}
143		// Record history. By default, use the first candidate for history.
144		c.recordHistory(ctx, inputContent, outputContents)
145	}
146}