]> git.giacomolli.uk - onechat/blobdiff - model.go
Give each subagent its own prompt cache key
[onechat] / model.go
index bd1258b80d586aeece9bd174e8cc1baaf2e65ba6..61dbc5829a4bbfd4fc360ec9d67a68704697e8ed 100644 (file)
--- a/model.go
+++ b/model.go
@@ -45,7 +45,7 @@ var errModelCallLimit = errors.New("call limit reached")
 // OPENAI_API_KEY, and there is no fallback from one to the other. All clients
 // share the authorizer, so one refresh runs at a time.
 func modelEndpoint(config Config, dir string) (string, ModelAuthorizer, error) {
-       if config.Endpoint == endpointAPI {
+       if config.Endpoint == configEndpointAPI {
                key := os.Getenv("OPENAI_API_KEY")
                if key == "" {
                        return "", nil, errors.New("endpoint api needs OPENAI_API_KEY")
@@ -54,7 +54,7 @@ func modelEndpoint(config Config, dir string) (string, ModelAuthorizer, error) {
                return modelAPIURL, ModelAPIKey(key), nil
        }
 
-       return modelChatGPTURL, newChatGPTAuth(filepath.Join(dir, tokenFileName), authIssuer), nil
+       return modelChatGPTURL, newChatGPTAuth(filepath.Join(dir, chatgptTokenFileName), chatgptIssuer), nil
 }
 
 // newModelClient returns a client for the Responses endpoint at url. It records
@@ -138,9 +138,11 @@ func (k ModelAPIKey) Authorize(ctx context.Context, header http.Header) error {
 // ChatGPT backend and the OpenAI API take the same request body and differ only
 // in the URL and the credentials. Every call sends the fixed cache key of the
 // agent, so the turns and the compactions of the agent share one prompt cache.
-// The ChatGPT backend caches only with the key in the session_id header, so a
-// call to it sends the key there and in the body. Every call is recorded in the
-// usage file.
+// A call of a subagent sends the key of the agent with "-" and the name of the
+// subagent, so that the calls of a subagent do not share a cache entry with the
+// calls of the agent that run at the same time. The ChatGPT backend caches only
+// with the key in the session_id header, so a call to it sends the key there
+// and in the body. Every call is recorded in the usage file.
 //
 // The request never holds max_output_tokens or metadata, because the ChatGPT
 // backend refuses them. It sets store to false, so the client sends the whole
@@ -173,6 +175,11 @@ func (c *ModelClient) Create(ctx context.Context, request ModelRequest) (ModelRe
                return ModelResponse{}, errModelCallLimit
        }
 
+       cacheKey := c.cacheKey
+       if request.Agent != "" {
+               cacheKey += "-" + request.Agent
+       }
+
        body, err := json.Marshal(ModelRequestBody{
                Model:        request.Model,
                Instructions: request.Instructions,
@@ -184,7 +191,7 @@ func (c *ModelClient) Create(ctx context.Context, request ModelRequest) (ModelRe
                Store:          false,
                Stream:         true,
                Include:        []string{"reasoning.encrypted_content"},
-               PromptCacheKey: c.cacheKey,
+               PromptCacheKey: cacheKey,
        })
        if err != nil {
                return ModelResponse{}, fmt.Errorf("encode request: %w", err)
@@ -202,9 +209,9 @@ func (c *ModelClient) Create(ctx context.Context, request ModelRequest) (ModelRe
                return ModelResponse{}, fmt.Errorf("authorize model call: %w", err)
        }
 
-       _, chatgpt := c.auth.(*chatgptAuth)
+       _, chatgpt := c.auth.(*ChatGPTAuth)
        if chatgpt {
-               httpRequest.Header.Set("session_id", c.cacheKey)
+               httpRequest.Header.Set("session_id", cacheKey)
        }
 
        start := time.Now()
@@ -218,6 +225,7 @@ func (c *ModelClient) Create(ctx context.Context, request ModelRequest) (ModelRe
        }
 
        record.Turn = request.Turn
+       record.Agent = request.Agent
        response, err := c.send(httpRequest, &record)
        if err != nil {
                record.Error = err.Error()
@@ -302,16 +310,19 @@ type ModelOutputItem struct {
 // ModelRequest is one model call. Input holds the input items in order, such as
 // modelUserMessage values or output items of an earlier response. Node names
 // the node that a compaction builds, or is nil. Turn names the request of a
-// turn: first or next. Node and Turn go only to the usage file, never to the
-// endpoint.
+// turn: first or next. Agent names the subagent that makes the call, or is
+// empty for a call of the agent itself. Node, Turn, and Agent go to the usage
+// file, never to the endpoint as fields. Agent also gives the call the cache
+// key of its subagent.
 type ModelRequest struct {
        Model        string
        Effort       string
        Instructions string
        Tools        []ModelTool
        Input        []any
-       Node         *nodeKey
+       Node         *NodeKey
        Turn         string
+       Agent        string
 }
 
 // ModelResponse is the result of a model call. Output holds the output items as
@@ -342,14 +353,16 @@ type ModelReasoning struct {
 // ModelUsageRecord is one line of usage.jsonl. Date is the start of the call,
 // and Seconds its length. Node is the node [l, i] of a compaction. Status is
 // the HTTP status, or 0 when no response came. TextBytes is the size of the
-// text of the reply. Turn names the request of a turn. Quota holds the
-// x-codex-* headers of the response, without the turn state.
+// text of the reply. Turn names the request of a turn. Agent names the subagent
+// of the call. Quota holds the x-codex-* headers of the response, without the
+// turn state.
 type ModelUsageRecord struct {
        Date         string            `json:"date"`
        Seconds      float64           `json:"seconds"`
        Model        string            `json:"model"`
        Node         *[2]int           `json:"node,omitempty"`
        Turn         string            `json:"turn,omitempty"`
+       Agent        string            `json:"agent,omitempty"`
        Status       int               `json:"status"`
        InputTokens  int               `json:"input_tokens"`
        CachedTokens int               `json:"cached_tokens"`