普通视图

发现新文章,点击刷新页面。
昨天以前ipfans

程序员的提示工程手册

2025年6月1日 04:30
Featured image of post 程序员的提示工程手册

原文来自于 Addy Osmani,他是 Google Chrome 的工程主管,在 Google 工作多年。

开发人员越来越依赖 AI 编程助手来加速我们的日常工作流程。这些工具可以自动完成函数、建议修复错误,甚至生成整个模块或 MVP。然而,正如我们许多人所了解的那样,AI 输出的质量很大程度上取决于你提供的提示的质量。换句话说,提示工程已经成为一项必备技能。一个措辞不当的请求可能会产生不相关或笼统的答案,而一个精心设计的提示可以产生周到、准确甚至富有创造性的代码解决方案。本文将实际探讨如何系统地构建适用于常见开发任务的提示。

AI 结对程序员很强大但并非神奇——除了你告诉他们的或包含在上下文中的信息外,对你特定的项目或意图没有先验知识。你提供的信息越多,输出就越好。我们将提炼出开发者反响热烈的提示模式、可重复的框架和难忘的示例。你将看到好与坏提示的并排比较以及实际的 AI 响应,并附有评论以了解为什么一个成功而另一个失败。以下是入门小贴士:

技巧 提示模板 目的
1. 角色提示 “你是一名资深的{语言名}开发者。请审查这个函数以实现{目标}。” 模拟专家级的代码审查、调试或重构
2. 明确上下文设定 “问题如下:{问题说明}。下面是代码。它本应做到{预期行为},但实际上做了 {实际行为}。为什么?” 明确框定问题,避免泛泛而谈的回答
3. 输入/输出示例 “这个函数在输入{输入内容}时应返回{预期输出}。你能编写或修复这段代码吗?” 通过示例引导 AI 理解意图
4. 迭代分步 “首先生成这个组件的骨架。接下来我们添加状态。然后处理 API 调用。” 将大任务拆解为步骤,避免模糊或过载的提示
5. 模拟调试 “逐行走查这个函数。变量值是什么?哪里可能出错?” 引导 AI 模拟运行时行为,发现隐藏的 bug
6. 功能蓝图 “我正在构建{功能特性}。需求如下:{具体说明}。技术栈为:{技术栈说明}。请搭建初始组件并解释你的选择。” 启动 AI 主导的功能规划与脚手架搭建
7. 重构指导 “请重构这段代码以提升{目标},比如(如:可读性、性能、符合语言习惯的风格)。请使用注释解释变更。” 让 AI 的重构符合你的目标,而不是随意更动
8. 请求替代方案 “你能用函数式风格重写这段代码吗?递归版本会是什么样?” 探索多种实现方式,扩展工具箱
9. 橡皮鸭调试 “我觉得这个函数的作用是:{你的解释}。我遗漏了什么吗?有没有 bug?” 让 AI 质疑你的理解并发现潜在问题
10. 约束锚定 “请避免(例如:递归),并遵守(例如:ES6 语法、无外部库)。优化方向为(例如:内存)。这是函数代码:” 防止 AI 过度发挥或引入不兼容的模式

高效代码提示基础

向 AI 编码工具提示与与一个非常有原则、有时会知识渊博的合作者交流有点像。为了获得有用的结果,您需要清楚地设置场景,并指导 AI 您想要什么以及如何实现。

从零学习 Hypothetical Document Embeddings (HyDE) - 1

2024年4月9日 22:15
Featured image of post 从零学习 Hypothetical Document Embeddings (HyDE) - 1

最近看到了一篇新的论文,关于评估 RAG 中的各种技术的评估。这篇《ARAGOG: Advanced RAG Output Grading》提供了一个对比多种 RAG 技术的评估。其中提到 HyDE 和 LLM re-rank 能够很好的提高检索精度。不过似乎 HyDE 其实讨论并不是很多,那么咱就对这块内容添砖加瓦一下,让我们从零开始进行一段 HyDE 的冒险。

什么是 RAG?

大语言模型(LLM)虽然在过去的一段时间中成为大家眼中的香馍馍,但它们也存在一些关键的局限性:

  • 知识局限性: LLM 的知识范围主要局限于其训练数据,难以涵盖所有知识领域,在一些专业或冷门话题上表现不佳。尤其是现在 AI 应用场景需要使用来自于企业内部知识库或者外部知识库中的相关信息,而这些内部知识库往往是不在外部公开的。
  • 可靠性问题: LLM 依赖于概率模型生成文本,结果难免存在事实性错误或缺乏可靠性,在某些关键应用中存在风险。比如之前的加拿大航空赔偿事件,尤其是对敏感的需要正确信息的场景,一旦出现错误的信息往往意味着潜在的损失。

要解决这些问题,那么就需要是用 RAG 来帮助解决。那么,什么是 RAG 呢?

Retrieval-Augmented Generation (RAG) 是一种基于检索的生成式模型,它结合了检索式和生成式的优点,可以在生成式模型(Generative Model)中引入外部知识。RAG 模型由两部分组成:一个是检索器,另一个是生成器。检索器负责从知识库中检索相关信息,生成器则负责生成答案。与传统的大语言模型(LLM)仅依靠自身有限的知识进行文本生成不同,RAG 模型能够动态地从海量的外部知识库中检索相关信息,并将这些信息融入到生成过程中,产生更加准确、连贯的输出内容。

换句话说,当用户提出一个查询或者需要生成某种类型的文本时,RAG 模型首先会利用一个信息检索子模块,从知识库中检索出与查询相关的信息。然后,RAG 的生成子模块会结合这些检索到的相关信息,生成最终的输出文本。这种结合检索和生成的方式,使 RAG 模型能够弥补传统 LLM 的局限性,提高生成内容的准确性和可靠性。

简单点来说,RAG 的基本流程如下:

什么是 HyDE?

但是在是用 RAG 的时候,你往往也会遇到一些问题。

让我们设想一个场景:我们有一个知识库,里面存储了大量的文档,我们希望使用 RAG 模型来回答用户的问题。但是,原始的 RAG 只能处理知识库中包含的数据,但是对于知识库中不存在的数据,RAG 无法处理。比较常见的场景包含用户的问答系统,帮助中心,对话系统等。用户通常对你的系统或者专有名词并不了解,他们通常会提出一些根据自己理解或者自己熟悉概念中的名词问题,这些表达方式可能并不在你的知识库中。这时候 RAG 就无法从知识库中检索到相关信息。

使用 Ollama 快速部署本地开源大语言模型

2024年2月29日 01:39
Featured image of post 使用 Ollama 快速部署本地开源大语言模型

如果你是第一次开始研究如何使用开源大语言模型(LLM)测试 GenerativeAI 时,一开始所有的信息一股脑在你的眼前,令人望而生畏。互联网上存在着来自许多不同来源的大量碎片信息,使得快速启动项目变得困难。

这篇文章的目标是提供一篇简易上手的文章,帮助你使用名为 Ollama 的模型的启动程序在本地设置和运行开源 AI 模型,当然,这些步骤也同样可以让你在你的服务器上运行它。

什么是 Ollama

Ollama 是一款帮助我们在本地机器上运行大型语言模型的工具,它让你在本地测试 LLM 变得更加容易。Ollama 提供了本地命令行和 API 调用多种方式进行交互。如果你是想快速测试,那么 CLI 则是一个非常不错的方式;如果你是想开始一个产品,你也可以选择试用/api/chat进行开发一个应用。

Ollama 分为两个部分:一个是运行 LLM 的服务端,另外一个则是可选组件:用于和服务端和 LLM进行交互的 CLI。

安装 Ollama

Ollama 官方提供了安装包用于 MacOS/Linux/Windows 下载。其中 Windows 支持截止到目前(2024/02/27)为止,还是预览支持,可能存在问题。因此这里我们演示使用 MacOS 安装这个应用。

下载安装

下载完成后,解压这个 zip 文件即可得到Ollama 的应用程序,你可以把它拖到系统的应用程序文件夹中,双击打开:

image

如果是第一次打开,会遇到安全提示,选择打开即可。

image

接下来需要安装 Ollama 的命令行工具,这样你就可以在命令行中访问LLM 了。

image

命令行安装

当然,如果你安装了[homebrew](https://brew.sh/)包管理工具,也可以使用下面的方式快速安装 Ollama:

brew install ollama

命令行方式安装时,需要额外注意我们无法像图形界面进行后台服务启动。你需要施工执行下面的命令启动Ollama 服务端:

ollama serve

试用 Ollama

现在你已经安装完成 Ollama 了,接下来让我们运行一个基础的模型试用一下。Ollama 支持了很多的开源模型,包括 Google 最新开源的 Gemma。不过这里我推荐使用Mistral 7B 模型。我们可以使用下面的命令快速安装对应模型:

使用子解释器运行Python并行应用

2023年11月23日 17:39
Featured image of post 使用子解释器运行Python并行应用

译者注:最近 Python 3.12 引入了子解释器概念,非常火热,更好的消息是已经在FastAPI应用成功了,虽然是很简单的那种。因此顺腾摸瓜,找到了作者的博客,翻译分享给大家。

Python 3.12 引入了一个新的 API 用于“子解释器”(sub interpreters),这是 Python 的一种不同的并行执行模型,提供了真正并行处理和多进程处理之间的良好折中,且具有更快的启动时间。在这篇文章中,我将解释什么是子解释器,为什么它对 Python 中的并行代码执行很重要,以及它与其他方法的比较。

什么是子解释器?

Python 的系统架构大致由三部分组成:

  • 一个包含一个或多个解释器的 Python 进程
  • 一个包含锁(GIL)和一个或多个 Python 线程的解释器
  • 一个包含当前执行代码信息的线程。

interpreter states

要了解更多关于这方面的信息,你可以阅读我的书《CPython 内部实现》中的“并行性和并发性”章节。

自 Python 1.5 以来,就有一个 C-API 可以支持多个解释器,但这个功能由于 GIL 的限制而受到严重限制,没有真正实现真正的并行性。因此,运行并行代码最常用的技术(不使用第三方库)是使用 multiprocessing 模块

2017 年,CPython 核心开发人员提出改变解释器结构的提议,使它们更好地与拥有它们的 Python 进程隔离,并能够并行操作。实现这一目标的工作相当巨大(6 年后仍未完成),并分为两个 PEP。PEP684 将 GIL 在各个解释器独立开,PEP554 提供了一个创建解释器和在它们之间共享数据的 API。

GIL 是“全局解释器锁”,是 Python 进程中的一个锁,意味着在任何时间点 Python 进程中只能执行一条指令,即使它有多个线程。这实际上意味着,即使你在拥有 4 核 CPU 的电脑上同时启动 4 个 Python 线程,也只有一个线程会在任何时候运行。

一些实用工具列表

2021年11月30日 01:48
Featured image of post 一些实用工具列表

一些在工作中经常使用的一些工具。如果有什么推荐的,也欢迎在评论中提供。这个列表后续会持续更新

HTTP工具

  • curlie - httpie-like 工具,底层是curl
  • lego - Let’s Encrypt证书工具
  • mkcert - 方便导入本地证书
  • paw.cloud - 原生的macOS HTTP调试工具,现在每年都会免费送,有兴趣关注一下

编译工具

  • go-task - 我用来替代Makefile,并无什么特殊必要,主要是不想写Makefile

代码质量

  • golangci-lint - 感觉无需介绍了,集成了很多实用工具,重复的就不列举了
  • pre-commit - 提交前检查代码质量,比如代码风格,缩进,空格等等
  • dcd - 查找代码中的重复代码

代码统计

  • scc - 高性能统计代码行数

图表工具

  • go-diagrams - 使用Go语言描述系统架构图
  • ndiag - 如果不想用Go描述,也可以选择用YAML描述系统架构
  • draft - 另外一个用YAML描述的工具,风格不一样
  • k8sviz - 你也可以从现成的K8s环境中生成系统架构图
  • archview - 通过代码中注释生成应用内部分层结构
  • go-plantuml - 根据Go代码生成结构体的PlantUML图
  • goplantuml - 另外一种生成PlantUML的工具
  • go-erd - 不想用PlantUML也可以换这种风格
  • asciiflow - 可以画ASCII图,ASCII图好处是可以放在代码里,如果你愿意的话
  • sequence - 嫌弃asciiflow比较原始,做时序图的时候可以用这个
  • mermaid-js - 方便集成在网页中,也可以导出成图片
  • kroki - 上面没提到的图类型的生成?看看这个

IaC

  • pulumi - Terraform业界比较常用,不过要学习HCL比较蛋疼,我个人比较喜欢pulumi,可以选择自己的习惯的语言,tf-cdk目前还比较初级。

Go 1.17 泛型尝鲜

2021年8月18日 01:48
Featured image of post Go 1.17 泛型尝鲜

今天,Go的1.17版本终于正式发布,除了带来各种优化和新功能外,1.17正式在程序中提供了尝鲜的泛型支持,这一功能也是为1.18版本泛型正式实装做铺垫。意味着在6个月后,我们就可以正式使用泛型开发了。那在Go 1.18正式实装之前,我们在1.17版本中先尝鲜一下泛型的支持吧。

泛型有什么作用?

在使用Go没有泛型之前我们怎么实现针对多类型的逻辑实现的呢?有很多方法,比如说使用interface{}作为变量类型参数,在内部通过类型判断进入对应的处理逻辑;将类型转化为特定表现的鸭子类型,通过接口定义的方法实现逻辑整合;还有人专门编写了Go的函数代码生成工具,通过批量生成不同类型的相同实现函数代替手工实现等等。这些方法多多少少存在一些问题:使用了interface{}作为参数意味着放弃了编译时检查,作为强类型语言的一个优势就被抹掉了。同样,无论使用代码生成还是手工书写,一旦出现问题,意味着这些方法都需要重复生成或者进行批量修改,工作量反而变得更多了。

在Go中引入泛型会给程序开发带来很多好处:通过泛型,可以针对多种类型编写一次代码,大大节省了编码时间。你可以充分应用编译器的编译检查,保证程序变量类型的可靠性。借助泛型,你可以减少代码的重复度,也不会出现一处出现问题需要修改多处地方的尴尬问题。这也让很多测试工作变得更简单,借助类型安全,你甚至可以少考虑很多的边缘情况。

Go语言官方有详细的泛型提案文档可以在这里这里查看详情。

如何使用泛型

前面理论我们仅仅只做介绍,这次尝鲜还是以实践为主。让我们先从一个小例子开始。

从简单的例子开始

让我们先从一个最简单的例子开始:

package main

import (
 "fmt"
)

type Addable interface {
 type int, int8, int16, int32, int64,
 uint, uint8, uint16, uint32, uint64, uintptr,
 float32, float64, complex64, complex128,
 string
}

func add[T Addable](a, b T) T {
 return a + b
}

func main() {
 fmt.Println(add(1,2))
 fmt.Println(add("1", "2"))
}

这个函数可以实现任何需要使用+符号进行运算的类型,我们通过定义Addable类型,枚举了所有可能可以使用add方法的所有的类型。比如我们在main函数中就使用了intstring两种不同类型。

但是如果这时我们使用简单的go run命令运行,会发现提示语法错误:

$ go version
go version go1.17 darwin/arm64
$ go run ~/main.go
# command-line-arguments
../main.go:8:2: syntax error: unexpected type, expecting method or interface name
../main.go:15:6: missing function body
../main.go:15:9: syntax error: unexpected [, expecting (

因为在Go 1.17中,泛型并未默认开启,你需要定义gcflags方式启用泛型:

构建属于你自己的dapr服务发现

2021年5月9日 02:38
Featured image of post 构建属于你自己的dapr服务发现

写在最前: 这篇文章其实算是马后炮了,因为一直拖延症的问题,顺带过了一个五一假期,结果发现已经有社区贡献者提供了Consul的服务发现实现,于是本来写了一半的文章只能进行调整了。拖延症害人啊!几个草稿的文章看来要尽快赶出来了🤦‍♂️

上一篇文章中,我其实遗留了一个问题:如何定义dapr的服务发现呢?其实在后面阅读dapr的源码之后也前一篇文章的评论中提到了答案:目前dapr提供了内置两种服务发现模式:K8s模式和用于独立部署的mDNS模式。mDNS模式在某些网络环境下可能存在问题(比如跨机房),不过没有关系,dapr同时提供了可扩展能力,可以通过定义自主的服务发现能力扩展dapr的边界。

从 NameResolution 到 Resolver 接口

pkg/components/nameresolution/registry.go 文件中,dapr定义了一个 NameResolution 结构体用于服务注册和发现:

type (
 // NameResolution is a name resolution component definition.
 NameResolution struct {
 Name string
 FactoryMethod func() nr.Resolver
 }

 // Registry handles registering and creating name resolution components.
 Registry interface {
 Register(components ...NameResolution)
 Create(name, version string) (nr.Resolver, error)
 }

 nameResolutionRegistry struct {
 resolvers map[string]func() nr.Resolver
 }
)

其中真正的服务解析则是依靠 components-contrib 中实现了 Resolver 接口的具体实现执行。

// Resolver is the interface of name resolver.
type Resolver interface {
 // Init initializes name resolver.
 Init(metadata Metadata) error
 // ResolveID resolves name to address.
 ResolveID(req ResolveRequest) (string, error)
}

其中 Init 会在 Runtime 初始化时被调用,而 ResolveID 则会在服务查询时调用。比如在 pkg/messaging/direct_messaging.go 的方法 getRemoteApp 中进行服务的解析:

从 Go 语言的依赖库讲起(2)监控、分布式追踪和日志

2020年2月6日 02:30

我们通常会遇到线上甚至测试中代码出现问题,这些问题可能来自于我们开发过程中的引入的BUG,有些来自于我们的功能未得到理想结果的,甚至有一些问题来自于运行环境的。很多事情可能未必能够足够可控,尤其是上线之后才发现出现了问题。除了我们前面一篇文章中介绍了一些测试相关的内容,虽然可以解决一部分问题,但是这些并不能完全杜绝所有问题在线上一定不会出现任何问题。因此我们需要建立对发布/预发环境一套相对完善的监控、诊断机制,保证我们可以尽快进行故障的分析和溯源。

❌
❌