21 May 2025

IMG-THUMBNAIL

本次我们会尝试通过OpenAI对接大模型,基于现有工具搭建了Gekk头条,可以每天帮我获取最新科技资讯并做总结。

AI Agent 与 LLM 关系

IMG-THUMBNAIL

LLM 扮演了 Agent 的 “大脑”,在 Agent 这个系统中提供推理、规划等能力。——《LLM、Prompt、AI Agent、RAG… 一网打尽大模型热门概念》

一般架构

IMG-THUMBNAIL

AI Agent 中 LLM 是最核心的部分,实际工程应用中,还需要建设好周边工具,提供「作业」、「记忆」等能力,结合 LLM 一套建设完整的智能体。

MCP 中 Tools、Prompts、Resources的区别

Primitive Control Description Example
Prompts User-controlledPrompts
像是提前配置好的,需要指定后执行,不需要每次填写
Interactive templates invoked by user choice Slash commands, menu options
/prompts
/prompt
generate_search_prompt topic=history num_papers=2
Resources Application-controlled应用选择怎么用,如果需要LLM使用还是需要提供Tools Contextual data attached and managed by the client File contents, git history
Tools Model-controlled Functions exposed to the LLM to take actions API POST requests, file writing

流程

IMG-THUMBNAIL

Geek 头条实现

Geek 头条的流程:

  1. 调用tech_news工具,获取最新科技新闻;
  2. 调用tech_news_prompt工具生成提示词;
  3. 调用LLM,生成总结。

OpenAI 对接 LLM

OpenAI除了做模型,也提供了标准API,我们可以直接调用API来调用LLM。

模型申请

我申请的是siliconflow的模型,它支持免费使用 Qwen/Qwen2-7B-Instruct 和 deepseek-ai/DeepSeek-R1-Distill-Qwen-7B 等多种模型。

需要用到的包:

go get github.com/openai/openai-go
初始化 OpenAI
aiClient = openai.NewClient(
  option.WithBaseURL("https://api.siliconflow.cn/v1"),
  option.WithAPIKey("ApiKey"), // defaults to os.LookupEnv("OPENAI_API_KEY")
)
获取新闻
callToolReq := mcp.CallToolRequest{}
callToolReq.Params.Name = "tech_news"
callResp, err := mcpClient.CallTool(ctx, callToolReq)
获取提示词
getPromptReq := mcp.GetPromptRequest{}
getPromptReq.Params.Name = "tech_news_prompt"
getPromptReq.Params.Arguments = map[string]string{
  "news": strings.Join(newsLink, ","),
}

https://github.com/ThinkInAIXYZ/deepchat/releases/tag/v0.1.1,https://tonybai.com/2025/05/20/post-quantum-cryptography-in-go/,https://tonybai.com/2025/05/19/shardedvalue-per-cpu-proposal/ [I] 2025/05/21 21:20:35 new_agent.go:67: https://github.com/ThinkInAIXYZ/deepchat/releases/tag/v0.1.1,https://tonybai.com/2025/05/20/post-quantum-cryptography-in-go/,https://tonybai.com/2025/05/19/shardedvalue-per-cpu-proposal/ 分别总结这些网页,使用网页标题,不需要做对比,并为每个标题附带上文档链接。

调用LLM
params := openai.ChatCompletionNewParams{
  Messages: []openai.ChatCompletionMessageParamUnion{
    openai.UserMessage(promptMsg), // 提示词,UserMessage代表说用户说的话
  },
  Model: "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B", // 选择的模型
  Tools: ts,
  Seed: openai.Int(0),
}
chatCompletion, err := aiClient.Chat.Completions.New(ctx, params)
  • User(用户):代表与 AI 进行对话的人类。是对话的发起者和推动者,通过提出问题、表达需求或提供指令来引导对话的方向。
  • Assistant(助手):代表 AI 本身,是对用户进行回应的角色。根据系统设定和用户输入生成相应的回复,为用户提供信息、回答问题、完成任务或提供建议等。
finish_reason

chatCompletion.Choices[0].FinishReason字段保存了 LLM 停止&返回客户的的原因。

原因标识 含义解释
stop 模型到达自然停止点或遇到提供的停止序列
length 达到请求中指定的最大 token 数量
content_filter 内容因内容过滤器标记被省略
tool_calls 模型调用了工具
function_call(deprecated) 模型调用了函数(已弃用 )
渲染

因为生成的内容是markdown格式,这里用到了github.com/gomarkdown/markdown包来将markdown渲染成html,

func MDToHTML(md []byte) []byte {
	// create markdown parser with extensions
	extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock
	p := parser.NewWithExtensions(extensions)
	doc := p.Parse(md)

	// create HTML renderer with extensions
	htmlFlags := html.CommonFlags | html.HrefTargetBlank
	opts := html.RendererOptions{Flags: htmlFlags}
	renderer := html.NewRenderer(opts)

	return markdown.Render(doc, renderer)
}

默认html只能按照文本展示需要用template.HTML用作安全渲染
```go
res["news"] = template.HTML(string(views))

最后

效果查询:Geek 头条

  1. 为什么是用markdown而不是更结构化的比如json呢?尝试了很多提示词,并不能严格按照期望的格式生成json,经常会有自由发挥的字段,总结的内容也很离谱,还不如用简单的提示词效果好。另外猜测模型本身也是基于markdown训练的,所以对json支持并不友好。
  2. 关于流程,目前我了解到的是也有流程框架LangChainGo,它和MCP之间也有Adaptar来做适配对接,接下来详细学学看。

原文链接:通过OpenAI对接LLM——搭建Geek头条,转载请注明来源!

EOF