feat: integrar structured logging en todos los componentes del shell

Se propaga *slog.Logger a todos los componentes impuros del shell:
- shell/bus/ — logs de subscribe, send, reply, timeout, unsubscribe
- shell/effects/ — duración y resultado de cada action ejecutada
- shell/llm/ (anthropic, openai, factory) — request/response con tokens, duración, fallback
- shell/memory/sqlite — open, save, recall, close con detalles
- shell/ssh/ — inicio, fin, errores y duración de comandos SSH
- tools/registry — registro, ejecución y errores de herramientas

Se usa el paquete shell/logger para field names consistentes (FieldDurationMS, FieldTokensUsed, etc.).
Cada componente recibe el logger por inyección de dependencias, sin globals.
Las firmas de New/FromConfig se actualizan para aceptar *slog.Logger.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 21:53:31 +00:00
parent 6858a5f13e
commit 5697b92ab8
11 changed files with 175 additions and 30 deletions
+28 -1
View File
@@ -4,16 +4,19 @@ import (
"context"
"encoding/json"
"fmt"
"log/slog"
"os"
"time"
openai "github.com/sashabaranov/go-openai"
coretypes "github.com/enmanuel/agents/pkg/llm"
"github.com/enmanuel/agents/shell/logger"
)
// NewOpenAIComplete returns a CompleteFunc backed by the OpenAI-compatible API.
// Works with OpenAI, Ollama, vLLM, LMStudio — just change baseURL.
func NewOpenAIComplete(apiKeyEnv, baseURL string) coretypes.CompleteFunc {
func NewOpenAIComplete(apiKeyEnv, baseURL string, log *slog.Logger) coretypes.CompleteFunc {
return func(ctx context.Context, req coretypes.CompletionRequest) (coretypes.CompletionResponse, error) {
apiKey := os.Getenv(apiKeyEnv)
if apiKey == "" {
@@ -49,11 +52,24 @@ func NewOpenAIComplete(apiKeyEnv, baseURL string) coretypes.CompleteFunc {
openReq.Tools = toOpenAITools(req.Tools)
}
log.Info("llm_request",
"provider", "openai",
"model", req.Model,
"messages", len(req.Messages),
"tools", len(req.Tools),
)
start := time.Now()
resp, err := client.CreateChatCompletion(ctx, openReq)
if err != nil {
ms := time.Since(start).Milliseconds()
log.Error("llm_error", "provider", "openai", logger.FieldDurationMS, ms, "err", err)
return coretypes.CompletionResponse{}, fmt.Errorf("openai completion: %w", err)
}
ms := time.Since(start).Milliseconds()
if len(resp.Choices) == 0 {
log.Error("llm_error", "provider", "openai", logger.FieldDurationMS, ms, "err", "empty choices")
return coretypes.CompletionResponse{}, fmt.Errorf("openai: empty choices")
}
@@ -67,6 +83,17 @@ func NewOpenAIComplete(apiKeyEnv, baseURL string) coretypes.CompleteFunc {
})
}
log.Info("llm_response",
"provider", "openai",
"model", req.Model,
logger.FieldDurationMS, ms,
logger.FieldTokensUsed, resp.Usage.TotalTokens,
"input_tokens", resp.Usage.PromptTokens,
"output_tokens", resp.Usage.CompletionTokens,
"tool_calls", len(toolCalls),
"finish_reason", string(choice.FinishReason),
)
return coretypes.CompletionResponse{
Content: choice.Message.Content,
ToolCalls: toolCalls,