Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ module github.com/strrl/lapp
go 1.25.7

require (
github.com/bytedance/sonic v1.15.0
github.com/cloudwego/eino v0.8.0
github.com/cloudwego/eino-ext/adk/backend/local v0.1.2-0.20260306073537-008f82264d85
github.com/cloudwego/eino-ext/callbacks/langfuse v0.0.0-20260227151421-e109b4ff9563
github.com/cloudwego/eino-ext/components/model/openrouter v0.1.2
github.com/duckdb/duckdb-go/v2 v2.5.5
Expand All @@ -24,10 +24,8 @@ require (
require (
github.com/apache/arrow-go/v18 v18.5.1 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic v1.15.0 // indirect
github.com/bytedance/sonic/loader v0.5.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
Expand Down
4 changes: 0 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ github.com/apache/thrift v0.22.0/go.mod h1:1e7J/O1Ae6ZQMTYdy9xa3w9k+XHWPfRvdPyJe
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs=
github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
Expand All @@ -34,8 +32,6 @@ github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/cloudwego/eino v0.8.0 h1:DLbrgEAloA+l7aR2qim7qQocQB48DjPrb8LzG3PYMHY=
github.com/cloudwego/eino v0.8.0/go.mod h1:+2N4nsMPxA6kGBHpH+75JuTfEcGprAMTdsZESrShKpU=
github.com/cloudwego/eino-ext/adk/backend/local v0.1.2-0.20260306073537-008f82264d85 h1:mD47o0GKdeqMdGI5xEqnlO8ZtArvhalIorRtrCmLRkA=
github.com/cloudwego/eino-ext/adk/backend/local v0.1.2-0.20260306073537-008f82264d85/go.mod h1:LfFk+VqZk0JOxIyl5RaerYqlFVLyXOCoSaqqak8hNls=
github.com/cloudwego/eino-ext/callbacks/langfuse v0.0.0-20260227151421-e109b4ff9563 h1:DKTXDDw8ErC4RorZLfB2ZdHChjDKWIqOEO7VRSjjfbg=
github.com/cloudwego/eino-ext/callbacks/langfuse v0.0.0-20260227151421-e109b4ff9563/go.mod h1:lrNKITZR4QUaYl9Rdz9W6qGOolHRy6mPamEZYA8uz7s=
github.com/cloudwego/eino-ext/components/model/openrouter v0.1.2 h1:zDFteouktUsGk4I/7m1b7yT4e9qawy45gWtLoyeHwxI=
Expand Down
10 changes: 9 additions & 1 deletion pkg/analyzer/acp_tool_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@ package analyzer
import (
"context"

"github.com/cloudwego/eino/components"
"github.com/cloudwego/eino/components/model"
"github.com/cloudwego/eino/schema"
einoacp "github.com/strrl/eino-acp"
)

var _ model.ToolCallingChatModel = (*acpToolCallingModel)(nil)
var (
_ model.ToolCallingChatModel = (*acpToolCallingModel)(nil)
_ components.Checker = (*acpToolCallingModel)(nil)
)

// acpToolCallingModel adapts eino-acp ChatModel to ToolCallingChatModel.
// ACP agents manage tools in their own runtime, so WithTools is a no-op.
Expand All @@ -20,6 +24,10 @@ func newACPToolCallingModel(base *einoacp.ChatModel) model.ToolCallingChatModel
return &acpToolCallingModel{base: base}
}

func (m *acpToolCallingModel) IsCallbacksEnabled() bool {
return true
}

func (m *acpToolCallingModel) Generate(ctx context.Context, input []*schema.Message, opts ...model.Option) (*schema.Message, error) {
return m.base.Generate(ctx, input, opts...)
}
Expand Down
46 changes: 22 additions & 24 deletions pkg/analyzer/analyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,30 @@ import (
"path/filepath"
"strings"

"github.com/cloudwego/eino-ext/adk/backend/local"
"github.com/cloudwego/eino/adk"
fsmw "github.com/cloudwego/eino/adk/middlewares/filesystem"
"github.com/go-errors/errors"
"github.com/google/uuid"
einoacp "github.com/strrl/eino-acp"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"

"github.com/strrl/lapp/pkg/tape"
"github.com/strrl/lapp/pkg/tracing"
)

func buildSystemPrompt(workDir string) string {
return fmt.Sprintf(`You are a log analysis expert helping developers troubleshoot issues.

IMPORTANT: All file operations (read_file, grep, ls, glob, execute) MUST use paths under %s.
Do NOT access files outside this workspace directory.
IMPORTANT: Stay within the workspace directory %s for any file or shell work (your runtime provides the tools).

Your workspace contains pre-processed log data at %s:
- %s/raw.log — the original log file
- %s/summary.txt — log templates discovered by automated parsing, with occurrence counts and samples
- %s/errors.txt — error and warning patterns extracted from logs

Start by reading %s/summary.txt and %s/errors.txt to understand the log patterns.
Then use grep and read_file on %s/raw.log to investigate specific patterns in detail.
You can also use the execute tool to run shell commands (e.g., awk, sort, wc) for deeper analysis.
Then search and read %s/raw.log for specifics (grep, read, or equivalents your environment exposes).
Use shell only when it helps (e.g. awk, sort, wc).

Provide:
1. Key findings from the logs
Expand All @@ -51,8 +52,7 @@ type Config struct {
func BuildWorkspaceSystemPrompt(workDir string) string {
return fmt.Sprintf(`You are a log analysis expert helping developers troubleshoot issues.

IMPORTANT: All file operations (read_file, grep, ls, glob, execute) MUST use paths under %s.
Do NOT access files outside this workspace directory.
IMPORTANT: Stay within the workspace directory %s for any file or shell work (your runtime provides the tools).

Your workspace at %s contains structured log data:
- %s/logs/ — original log files
Expand All @@ -64,8 +64,8 @@ Your workspace at %s contains structured log data:

Start by reading %s/notes/summary.md and %s/notes/errors.md to understand the log patterns.
Then drill into specific patterns under %s/patterns/ for details.
Use grep on %s/logs/ to search for specific terms across all log files.
You can also use the execute tool to run shell commands (e.g., awk, sort, wc) for deeper analysis.
Search %s/logs/ for specific terms across log files.
Use shell only when it helps (e.g., awk, sort, wc).

Provide:
1. Key findings from the logs
Expand Down Expand Up @@ -114,30 +114,28 @@ func RunAgentWithPrompt(ctx context.Context, config Config, workDir, question, s
return "", errors.Errorf("create chat model: %w", err)
}

backend, err := local.NewBackend(ctx, &local.Config{})
if err != nil {
return "", errors.Errorf("create local backend: %w", err)
if systemPrompt == "" {
systemPrompt = buildSystemPrompt(absDir)
}
backendAdapter := newLocalBackendAdapter(backend)

fsHandler, err := fsmw.New(ctx, &fsmw.MiddlewareConfig{
Backend: backendAdapter,
StreamingShell: backendAdapter,
})
tapePath := filepath.Join(absDir, tape.FileName)
tapeStore, err := tape.OpenJSONL(tapePath)
if err != nil {
return "", errors.Errorf("create filesystem middleware: %w", err)
return "", errors.Errorf("open tape store: %w", err)
}
defer func() { _ = tapeStore.Close() }()

if systemPrompt == "" {
systemPrompt = buildSystemPrompt(absDir)
}
tapeHandler := tape.NewEinoHandler(tapeStore, tape.RunMeta{
RunID: uuid.NewString(),
Provider: provider,
Model: config.Model,
})

agent, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Name: "log-analyzer",
Description: "Analyzes log files to find root causes",
Instruction: systemPrompt,
Model: newACPToolCallingModel(chatModel),
Handlers: []adk.ChatModelAgentMiddleware{fsHandler},
MaxIterations: 15,
})
if err != nil {
Expand All @@ -150,7 +148,7 @@ func RunAgentWithPrompt(ctx context.Context, config Config, workDir, question, s
}

runner := adk.NewRunner(ctx, adk.RunnerConfig{Agent: agent})
iter := runner.Query(ctx, userMessage)
iter := runner.Query(ctx, userMessage, adk.WithCallbacks(tracing.NewSlogEinoHandler(nil), tapeHandler))

var result strings.Builder
for {
Expand Down
52 changes: 0 additions & 52 deletions pkg/analyzer/local_backend_adapter.go

This file was deleted.

Loading
Loading