c7a02c3905
5 层 + 1 条 NATS 零拷贝消息总线的 monorepo(Monolith First → Microservices Morph B)。 纵向主干(任务流 + Token 流回流)已真实跑通,横向各层能力为带注释的桩。 已贯通(real code): - sundynix-shared: 共享契约 + JetStream/core NATS 真实收发(bus) + 内嵌 NATS(devnats) + e2e 测试 - sundynix-gateway: Gin 接入 + DSL 解析组装 + NATS Publish + SSE 流式输出 - sundynix-dispatcher: NATS 消费 + Eino Orchestrator 流式回流 + 熔断器 + LLM Pool 占位流式 - 链路: HTTP POST → DSL → sundynix.tasks.* → Dispatcher → Token 经 sundynix.streams.<id> 回流 → SSE - 基础设施: docker-compose(nats/postgres/redis/neo4j/milvus) + Makefile(make demo/e2e) 待填(桩): - Eino 图编排 compose.NewGraph、LLM Pool 接 vLLM/Ollama - Gateway store 换真实 pgx/redis - sundynix-mcp-go: Bleve+Milvus+Neo4j 混合检索 / UniOffice / 外部 API - sundynix-mcp-py: gVisor 沙箱 / MinerU(PaddleOCR) / Docker 解释器 - sundynix-desktop: React Flow 画布 → DSL 导出 → SSE 展示
63 lines
1.7 KiB
Go
63 lines
1.7 KiB
Go
// Package llm 抽象 LLM Pool(vLLM / Ollama 集群)的负载均衡与流式推理。
|
||
package llm
|
||
|
||
import (
|
||
"context"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
// Pool 维护后端 LLM 实例列表与路由策略。
|
||
type Pool struct{ /* backends []Backend */ }
|
||
|
||
func NewPool() *Pool { return &Pool{} }
|
||
|
||
// 占位参数:模拟真实后端的 TTFT(首 token 延迟) 与逐 token 间隔。
|
||
const (
|
||
timeToFirstToken = 700 * time.Millisecond
|
||
interTokenDelay = 60 * time.Millisecond
|
||
)
|
||
|
||
// Stream 选择一个后端进行流式推理,逐 Token 回调 onToken。
|
||
// 当前为占位实现:把对 prompt 的确定性回复按 token 流式返回,
|
||
// 真实接入 vLLM/Ollama 时替换为后端 streaming API 即可(回调签名不变)。
|
||
func (p *Pool) Stream(ctx context.Context, prompt string, onToken func([]byte)) error {
|
||
// TODO: 选路 (least-load / 模型亲和) → 调 vLLM/Ollama streaming API
|
||
reply := buildReply(prompt)
|
||
|
||
select {
|
||
case <-ctx.Done():
|
||
return ctx.Err()
|
||
case <-time.After(timeToFirstToken): // 模拟 TTFT
|
||
}
|
||
|
||
for _, tok := range tokenize(reply) {
|
||
select {
|
||
case <-ctx.Done():
|
||
return ctx.Err()
|
||
default:
|
||
}
|
||
onToken([]byte(tok))
|
||
time.Sleep(interTokenDelay)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// buildReply 占位:真实实现应由 DSL 编排出的对话上下文驱动后端生成。
|
||
func buildReply(prompt string) string {
|
||
p := strings.TrimSpace(prompt)
|
||
if len(p) > 40 {
|
||
p = p[:40] + "…"
|
||
}
|
||
return "已编排执行该 Agent 图,输入摘要: " + p
|
||
}
|
||
|
||
// tokenize 占位分词:按 rune 切,保证多字节中文也能逐字流式。
|
||
func tokenize(s string) []string {
|
||
out := make([]string, 0, len(s))
|
||
for _, r := range s {
|
||
out = append(out, string(r))
|
||
}
|
||
return out
|
||
}
|