Целевая схема для другого проекта: в go.mod только github.com/magomedcoder/gen, процесс gen-runner поднимается отдельно.
gRPC (llmrunner.proto)
gen / или ваш сервис ---------------------------> gen-runner (LLM)
| SendMessage, Embed, ...
|
|
| tools/MCP/RAG/сессии - в коде на gen/pkg/*
|
---> gen-mcp-servers (опционально, отдельные процессы)
module testtest
go 1.26
require (
github.com/magomedcoder/gen v0.x.x
github.com/magomedcoder/gen/llm-runner v0.x.x
)Локальная разработка в монорепо
replace (
github.com/magomedcoder/gen => ../gen
github.com/magomedcoder/gen/llm-runner => ../gen-runner
)import (
"context"
"github.com/magomedcoder/gen/pkg/domain"
"github.com/magomedcoder/gen/pkg/runnerconnect"
"github.com/magomedcoder/gen/pkg/toolloop"
)
func main() {
ctx := context.Background()
conn, err := runnerconnect.FromAddresses(ctx, []string{"127.0.0.1:50052"}, true)
if err != nil {
panic(err)
}
defer conn.Pool.Close()
llm := conn.LLM // domain.LLMRepository
if err := conn.Pool.WarmModelOnRunner(ctx, "127.0.0.1:50052", "my-model"); err != nil {
panic(err)
}
msgs := []*domain.Message{
domain.NewMessage(1, "Вы - полезный помощник.", domain.MessageRoleSystem),
domain.NewMessage(1, "Привет", domain.MessageRoleUser),
}
gp := &domain.GenerationParams{}
ch, err := llm.SendMessageOnRunner(ctx, "127.0.0.1:50052", msgs, nil, 120, gp)
if err != nil {
panic(err)
}
var full string
for c := range ch {
full += c.Content
}
if blob := toolloop.ExtractToolActionBlob(full); blob != "" {
// tool-loop parse -> execute -> снова llm.SendMessageOnRunner
_ = blob
}
}package main
import (
"context"
"flag"
"fmt"
"os"
"github.com/magomedcoder/gen/pkg/agent"
"github.com/magomedcoder/gen/pkg/domain"
"github.com/magomedcoder/gen/pkg/runnerconnect"
"github.com/magomedcoder/gen/pkg/runnerprompt"
"github.com/magomedcoder/gen/pkg/toolloop"
)
type memStore struct {
msgs []*domain.Message
id int64
}
func (m *memStore) Create(_ context.Context, msg *domain.Message) error {
m.id++
msg.Id = m.id
m.msgs = append(m.msgs, msg)
return nil
}
type echoExecutor struct{}
func (echoExecutor) Execute(_ context.Context, call toolloop.ExecutableToolCall, _ *domain.GenerationParams, _ *toolloop.LoopEnv) (string, error) {
return fmt.Sprintf(`{"tool":%q,"ok":true}`, call.ResolvedName), nil
}
// go run ./examples/minimal -runner 127.0.0.1:50052 -model <alias>
func main() {
runnerAddr := flag.String("runner", "127.0.0.1:50052", "gen-runner gRPC address")
model := flag.String("model", "", "model alias для LoadModel (обязательно)")
ctx := context.Background()
conn, err := runnerconnect.FromAddresses(ctx, []string{*runnerAddr}, true)
if err != nil {
fmt.Fprintf(os.Stderr, "runner: %v\n", err)
os.Exit(1)
}
defer conn.Pool.Close()
if err := conn.Pool.WarmModelOnRunner(ctx, *runnerAddr, *model); err != nil {
fmt.Fprintf(os.Stderr, "load model: %v\n", err)
os.Exit(1)
}
sessionID := int64(1)
tools := []domain.Tool{
{
Name: "echo_tool",
Description: "Возвращает echo JSON",
ParametersJSON: `{"type":"object"}`,
},
}
sys := domain.NewMessage(sessionID, "Вы очень полезны.", domain.MessageRoleSystem)
runnerprompt.EnrichSystemMessage(sys, runnerprompt.SystemToolsOptions{
Tools: tools,
})
user := domain.NewMessage(sessionID, "Вызови echo_tool с пустыми parameters, затем ответь пользователю кратко.", domain.MessageRoleUser)
gp := &domain.GenerationParams{
Tools: tools,
}
store := &memStore{}
ch, err := agent.Run(ctx, agent.Config{
SessionID: sessionID,
RunnerAddr: *runnerAddr,
SelectedModel: *model,
LLM: conn.LLM,
InitialHistory: []*domain.Message{sys, user},
TimeoutSeconds: 120,
GenParams: gp,
MaxRounds: 6,
Executor: echoExecutor{},
Store: store,
})
if err != nil {
fmt.Fprintf(os.Stderr, "агент: %v\n", err)
os.Exit(1)
}
for c := range ch {
if c.Text != "" {
fmt.Print(c.Text)
}
}
fmt.Println()
fmt.Fprintf(os.Stderr, "сообщение: %d\n", len(store.msgs))
}