11 // errChainLogWrite marks an error of a log write, which ends the chain.
12 var errChainLogWrite = errors.New("log write failed")
14 // ChainClient sends model calls.
15 type ChainClient interface {
16 Create(ctx context.Context, request ModelRequest) (ModelResponse, error)
19 // Chain is one request chain of a turn or of a subagent. It sends request with
20 // the input of the first request, runs the tool calls of each response, and
21 // sends their results back, until a response has no tool call: the final
22 // answer. The replies, the tool calls, and the results go to the log of tools
23 // as they happen. The thoughts never do.
25 // Each later request begins with the whole input of the request before it,
26 // then its output items, then the tool results. The output items hold the
27 // reasoning with its encrypted content, because the calls do not store it. So
28 // each request begins with all of the previous one and reads it from the prompt
29 // cache. The messages of take join each later request after the tool results.
38 // Run runs the chain from input, the input of its first request, and returns
39 // the text of the final answer. An error of a log write wraps
41 func (c *Chain) Run(ctx context.Context, input []any) (string, error) {
43 for request.Turn = "first"; ; request.Turn = "next" {
45 response, err := c.client.Create(ctx, request)
47 return "", fmt.Errorf("create model response: %w", err)
52 input, text, err = c.handle(ctx, input, response)
63 // handle logs the replies of response and runs its tool calls. It returns the
64 // input of the next request: input, the output items, the tool results, and
65 // the messages of take. After the final answer it returns no input, and the
66 // replies of the answer as its text.
67 func (c *Chain) handle(ctx context.Context, input []any, response ModelResponse) ([]any, string, error) {
73 for _, raw := range response.Output {
74 item, err := chainInputItem(raw)
79 input = append(input, item)
81 var call ChainFunctionCall
83 err = json.Unmarshal(raw, &call)
85 return nil, "", fmt.Errorf("decode output item: %w", err)
90 text, err := modelOutputText([]json.RawMessage{raw})
95 text = strings.TrimSpace(text)
100 _, err = c.tools.log.Append("reply", text)
102 return nil, "", fmt.Errorf("%w: %w", errChainLogWrite, err)
105 replies = append(replies, text)
106 case "function_call":
107 output, err := c.tools.Call(ctx, call.Name, call.Arguments)
109 return nil, "", fmt.Errorf("%w: %w", errChainLogWrite, err)
112 outputs = append(outputs, ChainFunctionCallOutput{
113 Type: "function_call_output",
120 if len(outputs) == 0 {
121 return nil, strings.Join(replies, "\n\n"), nil
124 input = append(input, outputs...)
125 for _, message := range c.take() {
126 item, err := imageUserMessage(c.images, message.Text)
128 return nil, "", fmt.Errorf("read queued images: %w", err)
131 input = append(input, item)
134 return input, "", nil
137 // ChainFunctionCall holds the fields of a function call output item.
138 type ChainFunctionCall struct {
139 Type string `json:"type"`
140 Name string `json:"name"`
141 Arguments string `json:"arguments"`
142 CallID string `json:"call_id"`
145 // ChainFunctionCallOutput is the input item with the result of a function
147 type ChainFunctionCallOutput struct {
148 Type string `json:"type"`
149 CallID string `json:"call_id"`
150 Output any `json:"output"`
153 // chainInputItem returns an output item as an input item for the next request.
154 // The calls do not store their items, so the item goes back whole, without its
156 func chainInputItem(raw json.RawMessage) (map[string]any, error) {
157 var item map[string]any
159 err := json.Unmarshal(raw, &item)
161 return nil, fmt.Errorf("decode output item: %w", err)