大模型会上网

2026年4月15日 · 556 字 · 3 分钟

如何让大模型具备联网能力,搜索并整合互联网信息

为什么大模型需要联网

大模型虽然知识渊博,但存在一个明显的局限性:知识截止日期。当用户询问最近发生的事情时,大模型往往无能为力。

例如,当我们问大模型"985大学最差食堂前三名是哪些"这样的问题时,大模型可能无法给出准确的答案,因为它需要最新的网络信息。

这时候,我们就需要给大模型装上"翅膀"——联网搜索能力

架构设计

要实现一个能上网的AI Agent,我们需要两个核心工具:

  1. 搜索工具(SearchTool): 通过搜索引擎搜索网络内容
  2. 爬取工具(CrawlTool): 爬取指定URL的网页内容

整体流程如下:

用户提问 → AI Agent分析问题 → 调用搜索工具 → 获取搜索结果 → 调用爬取工具 → 整合信息 → 返回答案

代码实现

1. 搜索工具实现

我们使用 openserp 库来实现Google搜索功能:

package tools

import (
	"context"
	"encoding/json"
	"fmt"
	"sync"

	"github.com/cloudwego/eino/components/tool"
	"github.com/cloudwego/eino/components/tool/utils"
	"github.com/karust/openserp/core"
	"github.com/karust/openserp/google"
)

// 搜索参数结构体
type Search struct {
	Text  string `json:"text" jsonschema:"required" jsonschema_description:"搜索的文本"`
	Limit int    `json:"limit" jsonschema:"default=3" jsonschema_description:"返回的结果数量"`
}

var browserOnce sync.Once
var browser *core.Browser

// 打开浏览器
func OpenBrowser() *core.Browser {
	browserOnce.Do(func() {
		var err error
		// 会打开电脑上的浏览器,通过浏览器去发起请求
		browser, err = core.NewBrowser(core.BrowserOpts{})
		if err != nil {
			panic(err)
		}
	})
	return browser
}

// 关闭浏览器
func CloseBrowser() {
	if browser != nil {
		err := browser.Close()
		if err != nil {
			fmt.Printf("close browser failed: %v", err)
		}
	}
}

// 搜索网页内容
func SearchWeb(ctx context.Context, search Search) (string, error) {
	browser = OpenBrowser()
	googleSearch := google.New(
		*browser,
		core.SearchEngineOptions{},
	)

	// 限制搜索结果数量,最多3个
	if search.Limit <= 0 {
		search.Limit = 3
	} else if search.Limit > 3 {
		search.Limit = 3
	}

	// 执行搜索
	results, err := googleSearch.Search(core.Query{
		Text:  search.Text,
		Limit: search.Limit,
	})
	if err != nil {
		return "", fmt.Errorf("search failed: %w", err)
	}

	// 将结果序列化为JSON
	jsonResults, err := json.Marshal(results)
	if err != nil {
		return "", fmt.Errorf("marshal search results failed: %w", err)
	}
	return string(jsonResults), nil
}

// 创建搜索工具
func SearchTool() tool.InvokableTool {
	tool, err := utils.InferTool("SearchTool",
		`通过这个工具可以搜索网络内容,最多返回3个结果。`,
		SearchWeb)
	if err != nil {
		panic(err)
	}
	return tool
}

代码解析:

  1. OpenBrowser(): 使用单例模式打开浏览器实例,避免重复打开
  2. SearchWeb(): 执行Google搜索,返回JSON格式的搜索结果
  3. SearchTool(): 使用eino框架的工具推断功能,自动生成工具描述

2. 爬取工具实现

搜索工具只能返回搜索结果的标题和链接,要获取详细内容,还需要爬取工具:

// URL参数结构体
type Url struct {
	Url string `json:"url" jsonschema:"required" jsonschema_description:"要爬取的URL"`
}

// 爬取网页内容
func Crawl(ctx context.Context, url *Url) (string, error) {
	if url.Url == "" {
		return "", fmt.Errorf("url is empty")
	}

	// 打开网页
	page, _ := OpenBrowser().Navigate(url.Url)

	// 提取标题
	element, _ := page.Element("title")
	title, _ := element.Text()

	// 提取正文内容
	bodyElement, _ := page.Element("body")
	body, _ := bodyElement.Text()

	return fmt.Sprintf("title=%s,body=%s", title, body), nil
}

// 创建爬取工具
func CrawlTool() tool.InvokableTool {
	tool, err := utils.InferTool("CrawlTool",
		`通过这个工具爬取url对应的网页内容`,
		Crawl)
	if err != nil {
		panic(err)
	}
	return tool
}

代码解析:

  1. Crawl(): 使用浏览器打开指定URL,提取页面标题和正文内容
  2. CrawlTool(): 创建爬取工具,供AI Agent调用

3. 创建联网Agent

现在我们将搜索和爬取工具组合起来,创建一个能上网的AI Agent:

package agent

import (
	"context"
	"eino_study/config"
	"eino_study/model"
	"eino_study/tools"

	"github.com/cloudwego/eino/adk"
	"github.com/cloudwego/eino/components/tool"
	"github.com/cloudwego/eino/compose"
)

func NewSearchAgent() adk.Agent {
	ctx := context.Background()
	model := model.NewChatModel(config.NewConfig("../config/config.json"))

	// 创建agent
	a, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
		Name:        "WebGetAgent",
		Description: "互联网信息整合大师",
		Instruction: `请你充分利用搜索引擎和爬虫技术,整合互联网上的信息来回答我的问题,每个问题爬取的网页数目不超过3个`,
		Model:       model.OpenaiChatModel,
		ToolsConfig: adk.ToolsConfig{
			ToolsNodeConfig: compose.ToolsNodeConfig{
				Tools: []tool.BaseTool{
					tools.SearchTool(),
					tools.CrawlTool(),
				},
			},
		},
	})
	if err != nil {
		panic(err)
	}
	return a
}

关键配置说明:

  1. Name: Agent名称为"WebGetAgent"
  2. Description: Agent描述为"互联网信息整合大师"
  3. Instruction: 系统提示词,指导Agent如何使用工具
  4. Tools: 注册了搜索和爬取两个工具

4. Agent运行器

创建一个运行器来执行Agent:

func SARunner(ctx context.Context, messages []adk.Message, callback func(msg string)) adk.Message {
	agent := NewSearchAgent()

	// 创建runner
	runner := adk.NewRunner(ctx, adk.RunnerConfig{
		Agent: agent,
	})

	// 运行agent
	iter := runner.Run(ctx, messages)

	var lastmsg adk.Message

	// 迭代处理事件
	for {
		event, ok := iter.Next()
		if !ok {
			break
		}

		// 处理消息输出
		if event.Output.MessageOutput != nil {
			msg, err := event.Output.MessageOutput.GetMessage()
			if err != nil {
				panic(err)
			}
			lastmsg = msg
		}
	}
	return lastmsg
}

实际测试

让我们测试一下这个能上网的AI Agent:

测试案例1: 查询985大学最差食堂

func TestSearchAgent(t *testing.T) {
	ctx := context.Background()

	messages := []adk.Message{
		{
			Role:    schema.User,
			Content: "985大学最差食堂前三名是哪些?请搜索并整理相关信息。",
		},
	}

	result := SARunner(ctx, messages, func(msg string) {
		fmt.Printf("回调消息: %s\n", msg)
	})

	fmt.Println("最终结果:", result.Content)
}

运行结果:

Agent会自动执行以下步骤:

  1. 分析用户问题,识别需要搜索网络信息
  2. 调用SearchTool搜索"985大学最差食堂"
  3. 从搜索结果中选择相关链接
  4. 调用CrawlTool爬取网页内容
  5. 整合多个来源的信息
  6. 生成结构化的回答

测试案例2: 查询人物信息

func TestSearchAgentWithFollowUp(t *testing.T) {
	ctx := context.Background()

	messages := []adk.Message{
		{
			Role:    schema.User,
			Content: "特朗普是谁,并告诉我来源网站",
		},
	}

	result := SARunner(ctx, messages, func(msg string) {
		fmt.Printf("%s\n", msg)
	})
	fmt.Println("回答:", result.Content)
}