]> git.giacomolli.uk - onechat/blobdiff - chain.go
Reflow comments and expand event literals
[onechat] / chain.go
index 3ab9fe114e7d4a3d09c459bc7be534ec89d7943c..f201812d31ba2440309c7e151e2d0b9df2c26f45 100644 (file)
--- a/chain.go
+++ b/chain.go
@@ -22,31 +22,47 @@ type ChainClient interface {
 // 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" {
                request.Input = input
                response, err := c.client.Create(ctx, request)
                if err != nil {
-                       return "", err
+                       return "", fmt.Errorf("create model response: %w", err)
                }
 
                var text string
+
                input, text, err = c.handle(ctx, input, response)
                if err != nil {
                        return "", err
@@ -59,12 +75,15 @@ func (c *Chain) Run(ctx context.Context, input []any) (string, error) {
 }
 
 // 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
-       var replies []string
+       var (
+               outputs []any
+               replies []string
+       )
+
        for _, raw := range response.Output {
                item, err := chainInputItem(raw)
                if err != nil {
@@ -74,6 +93,7 @@ func (c *Chain) handle(ctx context.Context, input []any, response ModelResponse)
                input = append(input, item)
 
                var call ChainFunctionCall
+
                err = json.Unmarshal(raw, &call)
                if err != nil {
                        return nil, "", fmt.Errorf("decode output item: %w", err)
@@ -117,33 +137,23 @@ func (c *Chain) handle(ctx context.Context, input []any, response ModelResponse)
 
        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.
 func chainInputItem(raw json.RawMessage) (map[string]any, error) {
        var item map[string]any
+
        err := json.Unmarshal(raw, &item)
        if err != nil {
                return nil, fmt.Errorf("decode output item: %w", err)