目前大部分AI应用基本都是python开发,因为python的语言优势和学习成本,使其成为计算科学、深度学习的主流编程语言。
因为公司项目的需求,所以最近调研了下golang有什么比较好的AI框架,现在github有几个比较火的框架,比如 langchaingo和 eino还有最近的 adk-go
此文主要介绍下langchaingo和eino,因为adk-go才推出不久,我还没有用过
Langchaingo @
核心能力 @
对于现代的AI框架来说,模型、编排、记忆、数据成为核心功能,langchain就是最典型的ai框架之一。
langchaingo的核心能力主要包括以下部分:
-
模型集成,langchaingo提供几个厂商的llm client的封装,包括
openai、anthropic、googleai、ollama、huggingface等 -
链式编排,链是 LangChainGo 的核心编排能力,用于将多个组件串联成复杂的工作流。
- 基础链:如
LLMChain(提示词 + LLM)、ConversationChain(对话管理)。 - 组合链:如
SequentialChain(顺序执行多个链)、RouterChain(根据输入动态选择执行的链)。 - 自定义链:支持开发者通过实现
Chain接口创建业务专属的链逻辑。
- 基础链:如
-
数据连接,负责与外部数据源交互,让 LLM 能够访问和处理私有数据。
- 文档加载器:支持加载多种格式的文档,如文本文件、Markdown、PDF、HTML 等。
- 文本分割器:提供多种策略(如按字符、按句子、递归字符分割)将长文档切分为适合模型处理的小块。
- 向量存储集成:支持主流向量数据库,如 Pinecone、Chroma、Weaviate、本地文件存储等,用于文档的嵌入存储与检索。
- 检索增强生成(RAG) :通过
RetrievalQA链快速构建 RAG 应用,实现基于私有数据的问答。
-
智能体,赋予 LLM 自主决策和调用工具的能力,是构建复杂自主应用的核心。
- 工具调用:内置多种工具(如
SerpAPI 搜索引擎、Calculator计算器),也支持自定义工具。 - 智能体类型:实现了
ReAct、ZeroShotAgent等经典智能体模式,支持 LLM 根据任务规划步骤、选择工具、执行操作、验证结果。 - 记忆机制:智能体可结合记忆组件,保留对话或任务上下文。
- 工具调用:内置多种工具(如
-
记忆,用于在对话或多轮任务中保留上下文信息。
- 多种记忆类型:如
ConversationBufferMemory(简单对话缓存)、ConversationSummaryMemory(对话摘要记忆)、VectorStoreRetrieverMemory(基于向量的长期记忆)。 - 状态管理:将记忆无缝集成到链或智能体中,实现多轮对话的上下文连贯。
- 多种记忆类型:如
入门级用法 @
如果需要调用deepseek可以使用openai的协议,因为deepseek也兼容openai的调用方式,初始化llm代码如下
llm, err := openai.New(
openai.WithModel("deepseek-chat"),
openai.WithBaseURL("https://api.deepseek.com"),
openai.WithToken("xxxxxxxxxooooooooo"),
)
langchaingo提示词的类型主要是以下几类
const (
// ChatMessageTypeAI is a message sent by an AI.
ChatMessageTypeAI ChatMessageType = "ai"
// ChatMessageTypeHuman is a message sent by a human.
ChatMessageTypeHuman ChatMessageType = "human"
// ChatMessageTypeSystem is a message sent by the system.
ChatMessageTypeSystem ChatMessageType = "system"
// ChatMessageTypeGeneric is a message sent by a generic user.
ChatMessageTypeGeneric ChatMessageType = "generic"
// ChatMessageTypeFunction is a message sent by a function.
ChatMessageTypeFunction ChatMessageType = "function"
// ChatMessageTypeTool is a message sent by a tool.
ChatMessageTypeTool ChatMessageType = "tool"
)
实际常用的就是human和system了
以下demo是我使用AI将原有的python写的poc插件转为nuclei规则的提示词
Nuclei 是一款现代化的高性能漏洞扫描器,它利用基于 YAML 的简单模板。能够设计模拟真实世界条件的自定义漏洞检测场景,从而实现零误报。
content := []llms.MessageContent{
llms.TextParts(llms.ChatMessageTypeSystem,
`你是一个资深的安全行业从业者,擅长编写基于nuclei的yaml规则。
要求如下:
1. 输入:python代码
2. 输出:nuclei的yaml规则
4. 只能使用request协议或network协议
5. 不要做其他任何说明,请直接输出纯文本,不要包含任何格式标记
6. 请进行自检查,确认多个matcher之间的条件关系或者matcher内多个匹配中间的条件关系是否正确,可以使用condition: and 或者 matchers-condition: and 进行修正
7. 如果存在多个请求之间的逻辑关系,可以使用flow语法,但仅限于使用&&或者||等运算符,例如:flow: requests(1) && requests(2) && requests(3) || requests(4) && requests(5) && requests(6)
`),
llms.TextParts(llms.ChatMessageTypeHuman, pycode),
}
如果只是调用llm来生成回答,那接下来可以直接使用llm.GenerateContent方法
下例是我将内容直接输入到文件的例子,这样可以直接帮我生成nuclei的yaml规则
completion, err := llm.GenerateContent(
ctx,
content,
llms.WithMaxTokens(2000),
llms.WithTemperature(0.7),
llms.WithStreamingReasoningFunc(func(ctx context.Context, reasoningChunk []byte, chunk []byte) error {
file, err := os.OpenFile("test1.yaml", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer file.Close()
writer := bufio.NewWriter(file)
defer writer.Flush()
if _, err := writer.Write(chunk); err != nil {
return err
}
if writer.Buffered() > 4096 {
return writer.Flush()
}
if len(chunk) > 0 {
fmt.Printf("Streaming Content: %s\n", string(chunk))
}
return nil
}),
)
llms.WithStreamingReasoningFunc是流式输出的方法,可以接受一个callback函数将流的内容自定义处理,比如上例我通过一个callback的回调将其内容直接输出到文本中,如果只是简单输出,那下例即可
ctx := context.Background()
userprompt := "你能帮我实现愿望吗?"
content := []llms.MessageContent{
llms.TextParts(llms.ChatMessageTypeSystem,`你是一个小仙女,能帮别人实现所有愿望`),
llms.TextParts(llms.ChatMessageTypeHuman,userprompt),
}
completion, err := llms.GenerateFromSinglePrompt(
ctx,
llm,
content,
llms.WithTemperature(0.8),
llms.WithStreamingFunc(func(ctx context.Context, chunk []byte) error {
fmt.Print(string(chunk))
return nil
}),
)
langchaingo基本是继承自其python的langchain框架,核心的能力自然是其链式的编排能力,比如可以写一个简单的链式编排
第一个链负责为主题生成一段介绍,第二个链负责对一链的内容进行翻译
template := "请用{style}风格,为{topic}写一段简短的介绍(不超过50字):"
prompt := prompts.NewPromptTemplate(
[]string{"style", "topic"}, // 输入变量
template, // 模板字符串
)
llmChain := chains.NewLLMChain(llm, prompt)
input := map[string]any{
"style": "幽默风趣",
"topic": "程序员",
}
template2 := "请将下面的内容翻译成英文:\n{content}"
prompt2 := prompts.NewPromptTemplate(
[]string{"content"},
template2,
)
llmChain2 := chains.NewLLMChain(llm, prompt2)
sequentialChain := chains.NewSequentialChain(
[]chains.Chain{llmChain, llmChain2},
[]string{"style", "topic"}, // 输入变量
[]string{"content"}, // 中间变量(第一个Chain的输出作为第二个Chain的输入)
)
seqResult, err := chains.Run(ctx, sequentialChain, input)
if err != nil {
log.Fatalf("运行Sequential Chain失败: %v", err)
}
fmt.Println("翻译结果:")
fmt.Println(seqResult)
总体来说,langchaingo的功能对于大部分业务开发都基本够用,但作为目前的ai框架趋势,我更推荐下面介绍的eino框架。
Eino @
eino是字节推出的开源ai agent开发框架,刚推出的时候我就关注了这个项目,因为其比langChain更加强大,如果你用过python的 langGraph框架,那eino可以说是将langChain和langGraph能力集成在一起开发了一个更加全面的框架。
Eino核心能力 @
-
模型集成,包括
qwen、qianfan、OpenAI、Ollama、gemini、deepseek、claude、ark等 -
编排,支持Chain、Graph、Workflow编排
-
核心组件,提供了构建一个高效AI Agent的全部能力
- ChatModel: 聊天模型抽象,支持文本生成和流式输出
- Tool: 工具调用抽象,支持函数调用和外部API集成
- ChatTemplate: 聊天模板抽象,支持动态提示词生成
- Retriever: 检索器抽象,支持向量检索和文档搜索
- DocumentLoader: 文档加载器,支持多种数据源
- Embedding: 嵌入模型抽象,支持文本向量化
- Indexer: 索引器抽象,支持向量数据库操作
其模型集成主要通过chatModel来实现,例如
// init chat model
cfg := &openai.ChatModelConfig{
APIKey: "",
BaseURL: "",
Model: "",
Temperature: 0.7,
MaxTokens: 2048,
}
chatModel, err := openai.NewChatModel(ctx, cfg)
if err != nil {
return nil, fmt.Errorf("failed to create chat model: %w", err)
}
eino回调能力 @
Eino 中的 Component 和 Graph 等实体,在固定的时机 (Callback Timing),回调用户提供的 function (Callback Handler),并把自己是谁 (RunInfo),以及当时发生了什么 (Callback Input & Output) 传出去
type Handler interface {
OnStart(ctx context.Context, info *RunInfo, input CallbackInput) context.Context
OnEnd(ctx context.Context, info *RunInfo, output CallbackOutput) context.Context
OnError(ctx context.Context, info *RunInfo, err error) context.Context
OnStartWithStreamInput(ctx context.Context, info *RunInfo,
input *schema.StreamReader[CallbackInput]) context.Context
OnEndWithStreamOutput(ctx context.Context, info *RunInfo,
output *schema.StreamReader[CallbackOutput]) context.Context
}
例如通过compose.WithCallbacks 在 graph 运行时注入 Handler,这些 Handler 会在 graph 的本次运行整体上生效go
react agent的编排逻辑 @
react agent 底层使用 compose.Graph 作为编排方案,有 2 个节点: ChatModel、Tools,运行过程的所有历史消息都会放入 state 中。
在将所有历史消息传递给 ChatModel 之前,会 copy 消息交由 MessageModifier 进行处理,MessageModifier 会在每次把所有历史消息传递给 ChatModel 之前执行,处理的结果再传递给 ChatModel。
直到 ChatModel 返回的消息中不再有 tool call,则返回最终消息。
对于部分不支持toolCalls传递的模型,需要手动解析toolCalls参数,然后调用tool,并将结果添加进上下文(例如deepseek 就不支持)
eino框架主要role分为
- assistant: agent回复问题的message
- user:用户发起问题的message
- system: 系统设定的prompt message
- tool: 工具调用message
如果前一条消息的role为assistant 并且 toolCalls 大于0, 则后续一条消息的role必须为tool, 并且toolCallsId 与前一条消息的toolCalls关联
可以通过以下代码快速创建一个react agent
// init agent
agent, err := react.NewAgent(ctx, &react.AgentConfig{
ToolsConfig: compose.ToolsNodeConfig{Tools: tools}, // 可以提供mcp工具
ToolCallingModel: chatModel, // 之前创建的chatModel
MaxStep: 20,
MessageModifier: func(ctx context.Context, input []*schema.Message) []*schema.Message { // 模型被调用前的回调
return input
},
})
if err != nil {
return nil, fmt.Errorf("failed to create agent: %w", err)
}
llm返回的内容可以通过流式和非流式接收
// 非流式
response, err := agent.Generate(ctx, messages)
if err != nil {
return nil, fmt.Errorf("failed to generate initial response: %w", err)
}
// 流式
response, err := s.agent.Stream(ctx, messages)
if err != nil {
return fmt.Errorf("failed to generate initial response: %w", err)
}
for {
defer response.Close()
content, rerr := response.Recv()
if rerr == io.EOF {
break
}
if rerr != nil {
return fmt.Errorf("failed to receive response: %w", rerr)
}
if len(content.Content) > 0 {
fmt.print(content.Content)
}
}
tools工具集成 @
直接集成 @
eino框架可以直接集成tools,方便llm调用,接口如下
type InvokableRun func(ctx context.Context, arguments string, opts ...Option) (content string, err error)
type StreamableRun func(ctx context.Context, arguments string, opts ...Option) (content *schema.StreamReader[string], err error)
type BaseTool interface {
Info() *schema.ToolInfo
}
// InvokableTool the tool for ChatModel intent recognition and ToolsNode execution.
type InvokableTool interface {
BaseTool
Run() InvokableRun
}
// StreamableTool the stream tool for ChatModel intent recognition and ToolsNode execution.
type StreamableTool interface {
BaseTool
Run() StreamableRun
}
调用mcp-server @
可以使用mcp—go编写的mcp-server来实现tools的调用
在github.com/cloudwego/eino-ext/components/tool/mcp提供了GetTools的方法,便于将mcp-go的tools结构转换为eino的tool结构
// 初始化mcp Client连接到mcp server
mcpClient, err := client.NewStdioMCPClient(
config.MCP.ServerPath,
config.MCP.ServerArgs,
)
if err != nil {
return nil, fmt.Errorf("failed to create MCP client: %w", err)
}
initRequest := mcp.InitializeRequest{}
initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
initRequest.Params.ClientInfo = mcp.Implementation{
Name: "demo-mcp-client",
Version: "1.0.0",
}
_, err = mcpClient.Initialize(ctx, initRequest)
if err != nil {
return nil, fmt.Errorf("failed to initialize MCP client: %w", err)
}
// 获取mcp server的tools返回符合eino框架的tools结构
tools, err := mcp.GetTools(ctx, &mcp.Config{Cli: mcpClient})
if err != nil {
return nil, fmt.Errorf("failed to get tools: %w", err)
}
文档索引 @
目前eino提供了相关接口,可以自己实现,也有封装好的组件,目前可以接入ES8、Milvus、Redis、volc VikingDB,对于retriever还多提供了dify的支持
Embedding服务目前提供Ollama、dashscope、qianfan(百度)、tencentcloud(腾讯混元)、火山引擎Ark、OpenAI
indexer indexer.Indexer // rag文本向量索引器
retriever retriever.Retriever // rag文本向量检索器
embedder embedding.Embedder // rag文本向量嵌入器
简单的可以使用redis存储文本数据
client := redis.NewClient(&redis.Options{
Addr: redisAddr,
Protocol: 2, // 强制使用Redis协议2
Password: password,
})
// 测试Redis连接
if err := client.Ping(ctx).Err(); err != nil {
return nil, fmt.Errorf("redis连接失败: %v", err.Error())
}
// 初始化Embedder组件, 这个是我自己实现的,只需要实现其接口即可,也可以使用模型厂商提供的服务
// type Embedder interface {
// EmbedStrings(ctx context.Context, texts []string, opts ...Option) ([][]float64, error) // invoke
// }
embedder := NewSimpleEmbedder(1536)
idx, err := rindexer.NewIndexer(ctx, &rindexer.IndexerConfig{
Client: client,
KeyPrefix: prefix + ":messages",
Embedding: embedder,
})
if err != nil {
return nil, fmt.Errorf("初始化索引器失败: %w", err)
}
// 创建检索器
ret, err := redisret.NewRetriever(ctx, &redisret.RetrieverConfig{
Client: client,
Index: prefix + ":messages",
VectorField: "vector",
TopK: 10,
ReturnFields: []string{"content", "conversation_id", "role", "user_id", "timestamp"},
Embedding: embedder,
DocumentConverter: func(ctx context.Context, doc redis.Document) (*schema.Document, error) {
return &schema.Document{
ID: doc.ID,
Content: doc.Fields["content"],
MetaData: map[string]any{
"conversation_id": doc.Fields["conversation_id"],
"role": string(doc.Fields["role"]),
"user_id": doc.Fields["user_id"],
"timestamp": doc.Fields["timestamp"],
},
}, nil
},
})
if err != nil {
return nil, fmt.Errorf("初始化检索器失败: %w", err)
}
除此之外,目前eino也支持amazon s3、本地文件、网络URL 、HTML网页、PDF文件等文档加载,还提供了Bingsearch、Googlesearch、DuckDuckGoSearch等工具进行联网检索,由于文章篇幅有限,就不一一介绍了,感兴趣的朋友可以调研下