All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Human mentions always reply; agent→agent uses a decide gate; over the limit Kaya asks Mira to bring a human. Co-authored-by: Cursor <cursoragent@cursor.com>
107 lines
2.8 KiB
Go
107 lines
2.8 KiB
Go
package inference
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/credentials"
|
|
"google.golang.org/grpc/credentials/insecure"
|
|
"google.golang.org/grpc/metadata"
|
|
corepb "l-ai-agent-kaya/proto/l-ai-core-proto"
|
|
)
|
|
|
|
type CoreRunner struct {
|
|
api corepb.AIServiceClient
|
|
token string
|
|
conn *grpc.ClientConn
|
|
}
|
|
|
|
func DialCore(target, token string, insecureTransport bool) (*CoreRunner, error) {
|
|
var tc credentials.TransportCredentials
|
|
if insecureTransport {
|
|
tc = insecure.NewCredentials()
|
|
} else {
|
|
tc = credentials.NewTLS(&tls.Config{MinVersion: tls.VersionTLS12})
|
|
}
|
|
conn, err := grpc.NewClient(target, grpc.WithTransportCredentials(tc))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &CoreRunner{api: corepb.NewAIServiceClient(conn), token: token, conn: conn}, nil
|
|
}
|
|
|
|
func (r *CoreRunner) Close() error {
|
|
if r.conn != nil {
|
|
return r.conn.Close()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *CoreRunner) auth(ctx context.Context) context.Context {
|
|
if strings.TrimSpace(r.token) == "" {
|
|
return ctx
|
|
}
|
|
return metadata.AppendToOutgoingContext(ctx, "authorization", "Bearer "+r.token)
|
|
}
|
|
|
|
func (r *CoreRunner) RunAgent(ctx context.Context, req RunRequest) (RunResult, error) {
|
|
msgs := make([]*corepb.StatelessMessage, 0, len(req.Messages))
|
|
for _, m := range req.Messages {
|
|
msgs = append(msgs, &corepb.StatelessMessage{Role: m.Role, Content: m.Content})
|
|
}
|
|
|
|
format := corepb.AgentOutputFormat_AGENT_OUTPUT_FORMAT_TEXT
|
|
if req.JSONSchema != "" {
|
|
format = corepb.AgentOutputFormat_AGENT_OUTPUT_FORMAT_JSON_SCHEMA
|
|
}
|
|
|
|
toolsEnabled := req.ToolsEnabled
|
|
maxIters := uint32(1)
|
|
maxCalls := uint32(0)
|
|
if toolsEnabled {
|
|
maxIters = 12
|
|
maxCalls = 20
|
|
}
|
|
|
|
resp, err := r.api.RunAgent(r.auth(ctx), &corepb.AgentRequest{
|
|
Messages: msgs,
|
|
ModelName: req.Model,
|
|
AgentId: req.AgentID,
|
|
ToolLoop: &corepb.AgentToolLoopOptions{
|
|
ToolsEnabled: toolsEnabled,
|
|
MaxToolIterations: maxIters,
|
|
MaxTotalToolCalls: maxCalls,
|
|
TotalTimeoutSeconds: req.TimeoutSeconds,
|
|
},
|
|
ResponseContract: &corepb.AgentResponseContract{
|
|
Format: format,
|
|
JsonSchema: req.JSONSchema,
|
|
RepairInvalidOutput: req.Repair,
|
|
},
|
|
RequestId: req.RequestID,
|
|
IdempotencyKey: req.IdempotencyKey,
|
|
SupportsDeferred: false,
|
|
Metadata: req.Metadata,
|
|
})
|
|
if err != nil {
|
|
return RunResult{}, err
|
|
}
|
|
if resp.Error != "" {
|
|
return RunResult{}, fmt.Errorf("core RunAgent response error: %s", resp.Error)
|
|
}
|
|
if resp.Status == corepb.AnswerStatus_ANSWER_STATUS_PENDING {
|
|
return RunResult{}, fmt.Errorf("unexpected deferred RunAgent response")
|
|
}
|
|
out := RunResult{Content: resp.Content, DurationMS: resp.DurationMs}
|
|
if resp.Usage != nil {
|
|
out.ToolsUsed = resp.Usage.ToolsUsed
|
|
}
|
|
if !toolsEnabled && len(out.ToolsUsed) != 0 {
|
|
return RunResult{}, fmt.Errorf("tools used despite tools-disabled request")
|
|
}
|
|
return out, nil
|
|
}
|