// answer. The replies, the tool calls, and the results go to the log of tools
// as they happen. The thoughts never do.
//
-// Each later request begins with the whole input of the request before it,
-// then its output items, then the tool results. The output items hold the
-// reasoning with its encrypted content, because the calls do not store it. So
-// each request begins with all of the previous one and reads it from the prompt
+// Each later request begins with the whole input of the request before it, then
+// its output items, then the tool results. The output items hold the reasoning
+// with its encrypted content, because the calls do not store it. So each
+// request begins with all of the previous one and reads it from the prompt
// cache. The messages of take join each later request after the tool results.
type Chain struct {
client ChainClient
tools *Toolbox
request ModelRequest
take func() []Message
+ images *ImageStore
+}
+
+// ChainFunctionCall holds the fields of a function call output item.
+type ChainFunctionCall struct {
+ Type string `json:"type"`
+ Name string `json:"name"`
+ Arguments string `json:"arguments"`
+ CallID string `json:"call_id"`
+}
+
+// ChainFunctionCallOutput is the input item with the result of a function call.
+type ChainFunctionCallOutput struct {
+ Type string `json:"type"`
+ CallID string `json:"call_id"`
+ Output any `json:"output"`
}
// Run runs the chain from input, the input of its first request, and returns
-// the text of the final answer. An error of a log write wraps
-// errChainLogWrite.
+// the text of the final answer. An error of a log write wraps errChainLogWrite.
func (c *Chain) Run(ctx context.Context, input []any) (string, error) {
request := c.request
for request.Turn = "first"; ; request.Turn = "next" {
input, text, err = c.handle(ctx, input, response)
if err != nil {
- return "", err
+ return "", fmt.Errorf("handle model response: %w", err)
}
if len(input) == 0 {
}
// handle logs the replies of response and runs its tool calls. It returns the
-// input of the next request: input, the output items, the tool results, and
-// the messages of take. After the final answer it returns no input, and the
-// replies of the answer as its text.
+// input of the next request: input, the output items, the tool results, and the
+// messages of take. After the final answer it returns no input, and the replies
+// of the answer as its text.
func (c *Chain) handle(ctx context.Context, input []any, response ModelResponse) ([]any, string, error) {
var (
outputs []any
for _, raw := range response.Output {
item, err := chainInputItem(raw)
if err != nil {
- return nil, "", err
+ return nil, "", fmt.Errorf("decode chain input: %w", err)
}
input = append(input, item)
case "message":
text, err := modelOutputText([]json.RawMessage{raw})
if err != nil {
- return nil, "", err
+ return nil, "", fmt.Errorf("read response text: %w", err)
}
text = strings.TrimSpace(text)
input = append(input, outputs...)
for _, message := range c.take() {
- input = append(input, modelUserMessage(message.Text))
+ item, err := imageUserMessage(c.images, message.Text)
+ if err != nil {
+ return nil, "", fmt.Errorf("read queued images: %w", err)
+ }
+
+ input = append(input, item)
}
return input, "", nil
}
-// ChainFunctionCall holds the fields of a function call output item.
-type ChainFunctionCall struct {
- Type string `json:"type"`
- Name string `json:"name"`
- Arguments string `json:"arguments"`
- CallID string `json:"call_id"`
-}
-
-// ChainFunctionCallOutput is the input item with the result of a function
-// call.
-type ChainFunctionCallOutput struct {
- Type string `json:"type"`
- CallID string `json:"call_id"`
- Output string `json:"output"`
-}
-
// chainInputItem returns an output item as an input item for the next request.
// The calls do not store their items, so the item goes back whole, without its
// id.