让DeepSeek模仿曹操,果然好玩!

上回说到,在《新三国》中荀彧对曹操说的那句名言,但相比荀彧而言,我觉得曹操的名言会更多,我一想,若能用AI重现这位乱世奸雄曹操,会得到怎样的体验?

于是这篇文章我们将以Go语言为例,展示如何通过LangChain框架调用DeepSeek大模型,重现一代枭雄曹操的独特对话风格。

工具介绍

LangChain 是一个专为构建大语言模型应用设计的开发框架,其核心使命是打通语言模型与真实世界的连接通道。它通过模块化设计将数据处理、记忆管理、工具调用等能力封装为标准化组件,开发者可像搭积木般将这些模块组装成智能应用链。经过一段时间的发展,LangChain不仅支持Python生态快速实现原型验证,也提供Go语言实现满足高并发生产需求。

在Go项目中安装:

shell 复制代码
go get -u github.com/tmc/langchaingo

使用LangChain接入DeepSeek

现在我们写一个最简单的LangChain程序,主要分为以下几个步骤:

1)函数定义和初始化OpenAI客户端

2)创建聊天消息

3)生成内容并流式输出

4)输出推理过程和最终答案

下面是代码:

go 复制代码
func Simple() {
    // 函数定义和初始化OpenAI客户端
    llm, err := openai.New(
        openai.WithBaseURL("https://api.deepseek.com"),
        openai.WithModel("deepseek-chat"),
        openai.WithToken("xxx"), // 填写自己的API Key
    )
    if err != nil {
        log.Fatal(err)
    }
    
    // 创建聊天消息
    content := []llms.MessageContent{
        llms.TextParts(llms.ChatMessageTypeSystem, "你现在模仿曹操,以曹操的口吻和风格回答问题,要展现出曹操的霸气与谋略"),
        llms.TextParts(llms.ChatMessageTypeHuman, "赤壁之战打输了怎么办?"),
    }

    // 生成内容并流式输出
    fmt.Print("曹孟德:")
    completion, err := llm.GenerateContent(
        context.Background(),
        content,
        llms.WithMaxTokens(2000),
        llms.WithTemperature(0.7),
        llms.WithStreamingReasoningFunc(func(ctx context.Context, reasoningChunk []byte, chunk []byte) error {
            contentColor := color.New(color.FgCyan).Add(color.Bold)
            if len(chunk) > 0 {
                _, err := contentColor.Printf("%s", string(chunk))
                if err != nil {
                    return err
                }
            }
            return nil
        }),
    )
    if err != nil {
        log.Fatal(err)
    }

    // 输出推理过程和最终答案
    if len(completion.Choices) > 0 {
        choice := completion.Choices[0]
        fmt.Printf("\nFinal Answer:\n%s\n", choice.Content)
    }
}

当然,如果我们想通过控制台和大模型多轮对话的话可以基于现有程序进行改造:

go 复制代码
func Input() {
    llm, err := openai.New(
        openai.WithBaseURL("https://api.deepseek.com"),
        openai.WithModel("deepseek-chat"),
        openai.WithToken("xxx"),
    )
    if err != nil {
        log.Fatal(err)
    }

    // 初始系统消息
    systemMessage := llms.TextParts(llms.ChatMessageTypeSystem, "你现在模仿曹操,以曹操的口吻和风格回答问题,要展现出曹操的霸气与谋略。")
    content := []llms.MessageContent{systemMessage}

    scanner := bufio.NewScanner(os.Stdin)
    for {
        fmt.Print("闫同学:")
        scanner.Scan()
        question := scanner.Text()

        if question == "exit" {
            break
        }

        // 添加新的用户问题
        userMessage := llms.TextParts(llms.ChatMessageTypeHuman, question)
        content = append(content, userMessage)

        fmt.Print("曹孟德:")
        // Generate content with streaming to see both reasoning and final answer in real-time
        completion, err := llm.GenerateContent(
            context.Background(),
            content,
            llms.WithMaxTokens(2000),
            llms.WithTemperature(0.7),
            llms.WithStreamingReasoningFunc(func(ctx context.Context, reasoningChunk []byte, chunk []byte) error {
                contentColor := color.New(color.FgCyan).Add(color.Bold)
                if len(chunk) > 0 {
                    _, err := contentColor.Printf("%s", string(chunk))
                    if err != nil {
                        return err
                    }
                }
                return nil
            }),
        )
        if err != nil {
            log.Fatal(err)
        }
        fmt.Println()
        // 将回复添加到历史消息中
        if len(completion.Choices) > 0 {
            choice := completion.Choices[0]
            assistantMessage := llms.TextParts(llms.ChatMessageTypeHuman, choice.Content)
            content = append(content, assistantMessage)
        }
    }
}

现在我们来启动调试一下:

重点步骤说明

其实纵观上面的整段代码,我认为在打造自己Agent中,最重要的一步莫过于在与AI对话前的消息组合部分,我们到底该怎样与AI对话才能得到自己想要的结果。

首先是content代码段的作用

go 复制代码
content := []llms.MessageContent{
    llms.TextParts(llms.ChatMessageTypeSystem, "你现在模仿曹操,以曹操的口吻和风格回答问题,要展现出曹操的霸气与谋略"),
    llms.TextParts(llms.ChatMessageTypeHuman, "赤壁之战打输了怎么办?"),
}

content 是一个 []llms.MessageContent 类型的切片,用于存储一系列的聊天消息内容。

llms.TextParts是 langchaingo 库中用于创建文本消息内容的函数。它接受两个参数:消息类型和消息内容。

llms.ChatMessageTypeSystem表示系统消息类型。系统消息通常用于给 AI 提供一些额外的指令或上下文信息。在这个例子中,系统消息告知 AI 要模仿曹操的口吻和风格进行回答。

llms.ChatMessageTypeHuman表示人类用户发送的消息类型。这里的消息内容是用户提出的问题"赤壁之战打输了怎么办?"。

ChatMessageType有哪些常量?我们来看下源码:

go 复制代码
// 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"

解释下这些常量分别代表什么:

1)ChatMessageTypeAI:表示由 AI 生成并发送的消息。当 AI 对用户的问题进行回答时,生成的回复消息就属于这种类型。

2)ChatMessageTypeHuman:代表人类用户发送的消息。例如,用户在聊天界面输入的问题、评论等都属于人类消息。

3)ChatMessageTypeSystem:是系统发送的消息,用于设置 AI 的行为、提供指令或者上下文信息。系统消息可以帮助 AI 更好地理解任务和要求。

4)ChatMessageTypeGeneric:表示由通用用户发送的消息。这里的"通用用户"可以是除了明确的人类用户和 AI 之外的其他类型的用户。

5)ChatMessageTypeFunction:表示由函数调用产生的消息。在一些复杂的聊天系统中,AI 可能会调用外部函数来完成某些任务,函数执行的结果会以这种类型的消息返回。

6)ChatMessageTypeTool:表示由工具调用产生的消息。类似于函数调用,工具调用可以帮助 AI 完成更复杂的任务,工具执行的结果会以这种类型的消息呈现。

这些常量的定义有助于在代码中清晰地区分不同类型的聊天消息,方便对消息进行处理和管理。

接入DeepSeek-R1支持深度思考

本篇文章关于DeepSeek的相关文档主要参考deepseek官方文档,这篇文档里我们可以看到DeepSeek的V3模型和R1模型是两个不同的模型标识,即:

model='deepseek-chat' 即可调用 DeepSeek-V3。

model='deepseek-reasoner',即可调用 DeepSeek-R1。

因此在调用R1模型时我们需要改变初始化client的策略,然后在处理回答的时候也需要额外处理思考部分的回答,具体改动的地方如下:

1)初始化使用deepseek-reasoner:

go 复制代码
llm, err := openai.New(
    openai.WithBaseURL("https://api.deepseek.com"),
    openai.WithModel("deepseek-reasoner"),
    openai.WithToken("xxx"),
 )

2)函数处理思考部分

go 复制代码
completion, err := llm.GenerateContent(
    ctx,
    content,
    llms.WithMaxTokens(2000),
    llms.WithTemperature(0.7),
    llms.WithStreamingReasoningFunc(func(ctx context.Context, reasoningChunk []byte, chunk []byte) error {
        contentColor := color.New(color.FgCyan).Add(color.Bold)
        reasoningColor := color.New(color.FgYellow).Add(color.Bold)

        if !isPrint {
            isPrint = true
            fmt.Print("[思考中]")
        }

        // 思考部分
        if len(reasoningChunk) > 0 {
            _, err := reasoningColor.Printf("%s", string(reasoningChunk))
            if err != nil {
                return err
            }
        }
        
        // 回答部分
        if len(chunk) > 0 {
            _, err := contentColor.Printf("%s", string(chunk))
            if err != nil {
                return err
            }
        }
        return nil
    }),
)

基于上面这些改动我们就能使用R1模型进行接入了。

小总结

这篇文章可以说展示了LangChain对接大模型的最基本功能,也是搭建我们自己Agent的第一步,如果真的想要搭建一个完整的AI Agent,那么还需要有很多地方进行补充和优化,比如:

  • 上下文记忆:添加会话历史管理
  • 风格校验:构建古汉语词库验证
  • 多模态扩展:结合人物画像生成

本篇文章到这里就结束啦~

相关推荐
程序员鱼皮2 分钟前
超全!2025 年小米 Java 面经汇总,大厂面经总结+答案整理
java·后端·面试
星星电灯猴20 分钟前
android打包工具
后端
楽码38 分钟前
一文看懂!编程语言访问变量指针和复制值
后端·go·编程语言
逝水年华QAQ44 分钟前
告别混乱与重装烦恼,一款Windows 软件管理神器!
windows·后端
lcf_zhangxing44 分钟前
提升 Odoo 开发效率:Ubuntu 22.04 + Makefile 的最佳实践
后端
Weison1 小时前
Apache Doris 性能优化补丁:近期版本更新概览
后端
fliter1 小时前
性能比拼: Actix (Rust) vs Zap (Zig) vs Zig
后端
都叫我大帅哥1 小时前
代码界的「跨界婚姻」:桥接模式的鹊桥艺术
java·后端·设计模式
来杯咖啡1 小时前
Golang 事务消息队列:基于 SQLite 的轻量级消息队列解决方案
后端·go
LaoZhangAI1 小时前
2025最新Trae配置Claude完全指南:超强AI开发体验【无限免费使用】
前端·后端