提示词工程-零样本提示词

零样本提示词

什么是零样本提示词

零样本提示词,顾名思义就是在人和大模型之间交互时,提示词中可以不使用任何样例,大模型只根据提示词中的指示以及预训练的固有知识生成内容或回答问题。

零样本提示词可以是一句话,一个问题,或者一条指令,但是如果期望大模型生成准确有效的信息,零样本提示词也需要遵循一定的结构,具体的信息可以参考另一篇文章:高效提示词的基本结构

假设你想使用一个语言模型来判断一段文本的情感是积极、消极还是中性。你可以向模型提出这样的问题:

“请对这段文本进行情感分类:‘我在海滩度过了一个很棒的一天!’它是积极的、消极的还是中性的?”

指令微调已经被证明可以提高零样本学习的效果(Wei et al., 2022)。指令微调本质上是指在通过指令描述的数据集上对模型进行微调。此外,RLHF(从人类反馈中进行强化学习)已被用于扩展指令微调,其中模型被调整以更好地符合人类偏好。这种最近的发展推动了像ChatGPT这样的模型的出现。我们将在接下来的章节中讨论所有这些方法。

当零样本不起作用时,建议在提示中提供演示或示例,这就是所谓的少样本提示。

零样本提示词的优缺点

优点:零样本提示在需要快速回答而无需准备示例时非常有用。它还有助于测试模型的一般语言理解能力。

缺点:与少样本或微调模型相比,零样本提示的准确度可能较低,尤其对于复杂任务或与训练数据差异很大的任务。

参考文章

Zero-shot and Few-shot prompting in LLMs
zeroshot
zeroshot prompting

提示词工程-高效提示词的基本结构

提示词工程-高效提示词的基本结构

自从生成式人工智能技术产生已来,提示词工程成为一种新的职业方向。提示词工程(Prompt Engineering)是一种在生成式人工智能(如GPT模型)中创建和优化提示词以获得期望输出的方法。

所谓的提示词,其实就是人和AI交互时的输入,可以是一个问题,一段描述性的指示,生成式AI可以根据你的指示或者问题,输出你期望的内容。

随着各个领域的工程师不断的实践和评估,实践经验发现,有效的机构化的提示词可以控制大模型输出的准确性和有效性,同时可以控制大模型输出的幻觉等。

提示词的基本结构

根据大家的经验,如果在写提示词时遵循如下的结构,可以提升和AI交互的效率:

  • 角色(role):交互时通过给AI大模型设定某种角色,根据经验可以获得意想不到的效果,可以使大模型的输出更加具有个性化,专业化,同时能够增加模型输出的准确性。比如如下的例子,指明AI大模型为一个通信专家,就是给当前交互的AI上下文设定了角色。

假设你是一名通讯专家。起草一封电子邮件给你的客户,告知他/她由于物流问题导致的交货时间延迟。

  • 指示/指令(task/instruction):指示或者指令,是指示AI大模型需要完成的任务,比如通过何种知识及技能,来完成何种任务。以下例子中指示或者指令为现在为一个黑客马拉松设计一个挑战性任务,涉及加密和解密。

假设你是一名软件工程师,正在设计一个涉及加密技术的黑客马拉松任务。任务设计应适合中高级开发者,且应在4小时内完成。请用五个步骤描述设计这个任务的过程,确保任务能够让参与者理解现代加密算法的核心原理。现在为一个黑客马拉松设计一个挑战性任务,涉及加密和解密。

  • 上下文(context):当你给出与任务相关的背景或细节时,可以帮助AI大模型更好的理解任务的具体情况,使得输出更加精确。以上示例中上下文部分为正在设计一个涉及加密技术的黑客马拉松任务
  • 输入或者问题:需要大模型执行具体的任务,或者具体的问题, 有时候如果指令部分描述的是具体的任务时,则指令部分就是输入。以上示例中指令部分就是输入部分:现在为一个黑客马拉松设计一个挑战性任务,涉及加密和解密
  • 约束:有时候需要限制AI大模型的输出范围,设定一些约束条件,可以对输出进行控制。以上示例中通过约束任务设计应适合中高级开发者,且应在4小时内完成来控制马拉松挑战的难度。
  • 输出格式:输出格式,主要给大模型指明,输出特定内容时的格式,比如输出为Json或者Markdown等。以上示例中请用五个步骤描述设计这个任务的过程指明了输出结构。
  • 例子:提供例子可以贡AI大模型进行参考,可以有效的提高输出的准确度,比如 few-shot prompt 就是提供一个或者多个例子,使得大模型的输出更加确定。

一些范例

以下是基于 角色+指令+上下文+输入问题+约束+例子 格式提供的几个提示词范例:

范例 1: 生成文章

角色: 你是一名资深环境学家。

指令: 我需要撰写一篇关于气候变化影响的文章,你可以作为我的咨询顾问。

上下文: 全球气温正在上升,极端天气事件变得更加频繁。政府和非政府组织正在采取措施应对这一问题。

输入问题: 气候变化如何影响不同地区的生态系统?政府可以采取哪些措施来减轻这些影响?

约束: 文章应包含三个部分:引言、影响分析、建议措施,总字数在500字左右。

例子: 引言可以描述气候变化的现状,然后分析不同地区生态系统的具体变化,最后提出可以采取的政策建议。


范例 2: 生成代码

角色: 你是一名Python开发者。

指令: 编写一个函数来排序学生的成绩。

上下文: 你正在开发一个学生成绩管理系统,需要能够对学生成绩进行排序,以便生成成绩单。

输入问题: 请编写一个Python函数,该函数接受一个包含学生姓名和成绩的字典,并按成绩从高到低排序。

约束: 函数应返回一个排序后的字典,并考虑成绩相同的学生按姓名字母顺序排序。

例子: sort_students({'Alice': 90, 'Bob': 85, 'Charlie': 90}) 应返回 {'Charlie': 90, 'Alice': 90, 'Bob': 85}


范例 3: 生成产品设计思路

角色: 你是一名用户体验设计师。

指令: 根据我的具体问题,帮助我设计一个新的移动应用的用户界面,旨在提高用户的日常习惯养成。

上下文: 现代用户越来越注重健康和效率,他们希望有一个工具可以帮助他们建立和维持良好的习惯。

输入问题: 如何设计一个易于使用且能激励用户的移动应用界面,以帮助他们每天坚持新习惯?

约束: 应用界面应简洁,包含进度追踪功能,且用户在3次点击内能完成主要操作。设计思路应包括界面草图和功能说明。

例子: 设计思路可以包含一个主页,展示用户的日常目标和进度条,点击进度条后弹出完成按钮,点击按钮后弹出激励语。


范例 4: 撰写故事

角色: 你是一名侦探小说作家。

指令: 帮助创作一段紧张的情节,描述侦探发现关键线索的过程。

上下文: 故事发生在一个封闭的庄园内,庄园主神秘失踪,侦探正深入调查此案。

输入问题: 侦探在调查庄园的书房时,意外发现了一个隐藏的密室,里面藏有庄园主失踪前留下的日记。

约束: 描述应突出侦探的推理过程和发现的细节,字数控制在300字以内。

例子: 可以描述侦探如何通过书架上的不寻常痕迹发现密室,并在日记中找到庄园主最后的遗言。


范例 5: 创建商业计划

角色: 你是一名创业顾问。

指令: 帮助制定一份初创企业的商业计划,侧重于市场分析和融资策略。

上下文: 一家专注于环保产品的初创公司希望进入市场并获得第一轮融资。

输入问题: 如何评估目标市场的潜力,并制定有效的融资策略来吸引投资者?

约束: 商业计划应包括市场分析、竞争对手分析、产品定位、融资目标和策略,总字数控制在1500字以内。

例子: 市场分析部分可以包括市场规模、增长率和用户画像,融资策略可以包括股权分配和投资回报预期。


这些范例展示了如何通过角色+指令+上下文+输入问题+约束+例子的结构来创建明确且有效的提示词。每个范例都针对特定的任务和输出需求进行了设计,以确保生成的内容符合预期。

参考文章:

一文读懂LLM参数设置,实现大模型应用的最佳性能

一文读懂LLM参数设置,实现大模型应用的最佳性能

引言

可以预见的是,通过生成模型,和通用模型,AI正在不断重塑整个数字化世界,在2024年我们看到LLM 应用像雨后春笋一样,基于LLM开发大模型应用已经逐渐成为程序员掌握的必备技能。

本文将重点介绍开发LLM 应用时,LLM基本参数的重要性,指导通过LLM 参数的设定可以提高应用的性能。

如何掌握temperature、top_p、max_length参数以实现最佳LLM表现?

在设计和测试LLM 应用的提示时,通常需要通过 API 与 LLM 进行交互。API可以配置一些参数以获得不同的提示结果。调整这些设置对于提高响应的可靠性和可取性非常重要,需要进行一些实验才能找出适合您用例的设置。以下文章内容将对不同LLM 之间常见的设置进行解释,并提供设置指导。

优化LLM性能的关键设置

温度

温度是一个控制模型输出随机性的基本设置。较低的温度通过偏向高概率标记使结果更具确定性,而较高的温度则引入更多变化性和创造性。

示例:
对于基于事实的任务(如回答问题或总结文本),较低的温度(如0.2)可确保简洁准确的回答。相比之下,对于创意写作任务(如诗歌生成或头脑风暴),较高的温度(如0.8)可以产生更多样化的输出。

要点

  • 较低温度 = 更确定性的回答
  • 较高温度 = 更具创意和多样性的回答
  • 根据任务要求调整

Top P(核采样)

Top P通过仅考虑构成生成每个标记的顶级累积概率质量的标记子集来控制回答的多样性。

示例:
较低的Top P值(如0.1)将输出限制为高概率标记,适合生成事实性内容。较高的Top P值(如0.9)允许输出更多样化,适合需要创意的任务。

要点

  • 低Top P = 自信精确的答案
  • 高Top P = 多样化和创意输出
  • 使用温度Top P,但不要同时使用两者

最大长度

最大长度定义了对提示的回应中生成的标记数量。

示例:
设置适当的最大长度可以防止过长或无关的回答,同时控制与标记使用相关的成本。

要点

  • 有效管理回答长度
  • 防止无关或冗长的输出
  • 通过控制标记使用优化成本

停止序列

停止序列是一个信号模型停止生成更多标记的字符串。

示例:
要生成不超过十个项目的列表,使用"11"作为停止序列,在列出十个项目后停止输出。

要点

  • 精确控制回答长度
  • 确保结构化输出
  • 使用针对特定任务定制的停止序列

频率惩罚 & 存在惩罚

两种惩罚都减少重复,但应用方式不同:

  • 频率惩罚:较高的值会抑制回答中重复的单词。
  • 存在惩罚:对所有重复的标记施加相等的惩罚,不考虑频率。

示例:
对于多样化的文本生成,使用较高的惩罚;对于聚焦的内容重复控制,根据需求相应调整。

要点

  • 频率惩罚按比例减少单词重复。
  • 存在惩罚平等地抑制任何重复短语。
  • 根据所需结果一次调整一个惩罚。

参考原文链接https://medium.com/@justmalhar/understanding-llm-settings-for-optimal-performance-83ee29c50392

使用AI智能体重塑企业订单管理系统

基于AI智能体的订单管理系统应用与优势

随着人工智能技术的快速发展,各种行业都在探索如何利用AI来提升效率和改善用户体验。订单管理系统作为企业运营中的关键环节,也不例外。然而,传统订单管理系统存在诸多痛点,而引入AI LLM(大语言模型)Agent可以有效解决这些问题。本文将探讨如何利用AI LLM Agent重塑订单管理系统,提升整体效率和客户满意度,并强调数据隐私保护和本地部署的重要性。

传统订单管理系统的痛点

1. 手动操作繁琐

传统订单管理系统依赖大量的人工操作,包括订单录入、库存检查、发货安排等。这不仅耗时耗力,还容易出错,影响工作效率和客户满意度。

2. 响应速度慢

当客户查询订单状态或请求更改订单时,传统系统往往需要经过多个步骤和层级审批,导致响应速度慢,客户体验不佳。

3. 数据分析能力有限

传统系统的数据分析功能较为有限,难以从大量订单数据中提取有价值的信息,影响企业的决策能力和市场响应速度。

什么是AI LLM Agent?

AI LLM(大语言模型)Agent 是基于大语言模型(如GPT-4、Llama 3、Mixtral 8*7B等)构建的智能系统。它不仅能理解和生成自然语言,还能通过与各种工具和数据库的集成,执行复杂的任务。图中展示了一个典型的AI LLM Agent架构,包括系统提示、上下文记忆管理、用户界面、AI LLM、工具以及外部数据库的整合。

引入AI LLM Agent的优势

1. 提升用户体验

通过集成AI LLM Agent,用户可以通过自然语言与订单管理系统进行交互。例如,客户可以直接询问订单状态、预计交货时间等问题,系统会即时响应,提供准确的信息。这种方式不仅提高了响应速度,还使得用户体验更加人性化。

2. 自动化处理

AI LLM Agent 能够自动处理大量重复性工作,如订单录入、库存管理和发货安排等。这不仅减少了人工操作的错误率,还大大提高了工作效率。例如,当客户下单后,系统会自动检查库存、安排发货,并实时更新订单状态。

3. 数据分析与预测

AI LLM Agent 可以集成统计和查询工具,对历史订单数据进行分析,从中发现趋势和规律。通过与Tickets Info数据库的对接,系统可以生成详细的销售报告,帮助企业优化库存管理和销售策略。此外,AI还可以预测未来的订单需求,帮助企业提前做好准备。

基于数据隐私保护和本地部署的重要性

数据隐私保护

在使用AI LLM Agent时,数据隐私保护至关重要。系统应确保所有客户数据和订单信息在传输和存储过程中都经过加密处理,防止未经授权的访问和泄露。同时,企业应制定严格的数据管理政策,确保数据使用的透明度和合法性。

本地部署的重要性

为了更好地保护数据隐私和提高系统的响应速度,许多企业选择将AI LLM Agent本地部署。本地部署不仅能确保数据不离开企业内部网络,降低数据泄露风险,还能提高系统的响应速度和稳定性,尤其在处理大量订单请求时更为显著。

架构详解

用户界面(UI)

用户界面是用户与AI LLM Agent交互的入口。通过友好的UI设计,用户可以轻松下达指令或查询信息。无论是通过文字输入还是语音命令,UI都会将用户的请求传递给AI LLM。

系统提示(Sys Prompts)

系统提示模块用于初始化和指导AI LLM的行为和响应方式。它包含了一系列预定义的指令和模板,确保AI在处理用户请求时的准确性和一致性。

上下文记忆管理(Context Memory Management)

为了提供连续性和上下文相关的响应,AI LLM Agent 具有上下文记忆管理功能。它能记住用户之前的交互内容,确保在多轮对话中提供一致且相关的回答。

AI LLM 核心

AI LLM是整个系统的核心,它负责理解用户的请求,并调用合适的工具或数据库来完成任务。无论是处理自然语言还是进行复杂的数据查询,AI LLM都能胜任。它可以根据用户需求,灵活调用Llama 3和Mixtral 8*7B等不同的语言模型,确保高效处理各种任务。

工具与数据库集成

AI LLM Agent 集成了多个工具(如统计工具、查询工具)和外部数据库(如Tickets Info数据库)。当用户提出查询请求时,AI LLM 会调用相应的工具从数据库中提取所需信息,并生成易于理解的回复。

结语

通过引入AI LLM Agent,订单管理系统可以实现前所未有的自动化和智能化。它不仅提升了用户体验,还大幅提高了运营效率和数据分析能力。特别是在数据隐私保护和本地部署方面的重视,进一步确保了系统的安全性和稳定性。未来,随着AI技术的进一步发展,我们可以预见更多创新应用的出现,为各行各业带来更多的价值。

思维链提示词工程(Chain-of-Thought Prompting)

思维链提示词工程

扩大大型语言模型(LLMs)的规模在情感分析和机器翻译方面已经显示出了良好的结果,即使没有提供任何示例。然而,它们在解决复杂的多步骤问题,如算术和常识推理方面却表现不佳。为了解决这个问题,LLMs可以针对特定任务进行微调,或者通过少量示例提示(few-shot prompting)来教授。然而,这两种方法都有其局限性。微调在创造高质量推理方面成本较高,而仅使用少量示例提示对于任务来说效果不够好。

思维链(Chain-of-Thought,简称CoT)提示可以同时解决这两个问题。在本文中,我们将探讨CoT提示以及如何实施它来提升您的业务技能。

什么是提示工程?

提示工程是一种编写结构良好、精心设计的提示的实践,这些提示能被生成性AI模型更好地理解。提示告诉大型语言模型(LLM)需要执行什么任务以及生成什么样的输出。它可以包含指令、上下文、输入数据和输出指标。通过使用提示工程,我们可以利用LLM来完成各种任务,从简单的问题回答到复杂的创意文本生成。它基于一种新兴属性——上下文学习,允许LLM从提示中学习。提示工程提高了LLM在手头任务上的表现。它使用了前文提到的零样本、少样本、主动和思维链提示等技术。

零样本提示

在零样本提示中,我们给大型语言模型(LLM)一个描述任务的提示,但这个提示本身并不提供任何任务的示例。然后,我们要求LLM针对这个提示生成一个回答。这种方法增强了LLM的灵活性和泛化能力。它可以用来在多个任务上训练LLM,而无需为每个任务收集训练数据。例如,ChatGPT可以在没有任何写诗示例的情况下,根据提示工程写出一首诗。然而,零样本提示在处理复杂任务时是有限的。

少样本提示

少样本提示可以通过提供示例来引导模型获得更好的表现。这是一种向大型语言模型提供少量期望输出示例的技术,除了主要提示之外还包括这些例子。这些示例帮助模型更好地理解任务,从而生成更准确、更有信息量的回答。我们应该向模型提供丰富多样的例子,而不是多个相似的例子。这样做可以确保模型尽可能多地学习关于任务的知识。标准的少样本提示对许多任务来说是一种不错的技术,但对于复杂的推理任务则不太可靠。因此,我们需要更高级的提示技术,比如思维链、主动提示和微调等。

主动提示

主动提示通过不断给予大型语言模型(LLM)反馈来提高它们在复杂任务上的表现。这种反馈可以帮助LLM从错误中学习,生成更准确、更有价值的回答。
主动提示的过程是这样的:首先给LLM一个提示和一些期望输出的例子。然后LLM生成一个回答。接着,人类评估者会评价这个回答,并就其准确性和信息量给出反馈。LLM再利用这些反馈来改进自己生成回答的能力。这个过程会不断重复,直到LLM能够生成足够准确和有价值的回答,让人类评估者满意为止。
主动提示对于思维链(CoT)提示特别重要,因为它可以:

  • 找出需要重点标注的问题
  • 减少人工标注的工作量
  • 提高CoT提示的准确性和信息量

下图展示了如何通过主动提示配合CoT来提升性能。这是一个包含四个阶段的过程:

  • 通过多次查询LLM来估计一个问题的不确定性
  • 通过排序选出最不确定的问题进行标注
  • 由人类评估者对这些问题进行详细反馈标注
  • 利用LLM生成答案,并使用标注阶段的反馈来提高新问题答案的质量

什么是思维链提示?

思维链提示是一种提示工程技术。通过这种技术,我们能让大型语言模型(LLM)输出一系列中间步骤,最终得出所需的答案。这种方法能提高LLM的推理能力。

思维链提示的好处有:

  • 让模型能专注于一次解决一个步骤,而不是一下子考虑整个问题。
  • 对于那些难以或无法一步解决的复杂问题特别有帮助。
  • 为我们提供了一个可解释的窗口,让我们能看到模型的行为。我们可以通过跟随模型采取的步骤序列,了解它是如何得出答案的。

总的来说,思维链提示让我们能更好地理解和控制LLM的思考过程,从而得到更准确、更可靠的结果。

思维链(CoT)提示可以用于参数量很大(大约1000亿个参数)的大型语言模型,适用于多种推理任务,包括数学应用题、常识推理和符号运算。

举个例子,在PaLM模型中使用CoT提示,而不是标准的少样本提示,显著提高了在GSM8K基准测试中的表现,
从17.9%提升到了58.1%。

有趣的是,对于足够大的语言模型来说,不需要特殊训练或微调,就可以轻松引出CoT推理过程。这个特点使得CoT提示成为一种可扩展且容易使用的技术。

简单来说,CoT提示就像是给模型一个"思考的框架",让它能像人类一样一步步推理,而不是直接给出答案。这种方法特别适合大型模型,因为它们有足够的"智力"来理解和执行这种复杂的思考过程。而且,因为不需要额外训练,任何人只要有权限使用这些大模型,就可以尝试CoT提示技术。

少样本思维链

少样本提示是指给大型语言模型(LLM)提供一个问题和答案。然后,再给LLM几个解决类似问题的例子。这些例子的呈现方式会鼓励LLM对问题进行思考,形成一条通向答案的思维链。

少样本思维链比普通的少样本基准更有效地提高了LLM的推理能力,因为它为LLM提供了类似问题的例子。不过,少样本思维链的实施可能会比普通少样本基准更复杂,因为它需要创建示例提示。

尽管如此,少样本思维链带来的好处还是超过了额外的复杂性。它让LLM不只是简单地回答问题,而是学会了如何思考和推理。这种方法可以让LLM处理更复杂的问题,给出更详细、更准确的答案。

简单来说,少样本思维链就像是给LLM提供了一些"解题思路"的例子,而不仅仅是答案。这样,LLM就能学会如何一步步地思考问题,而不是只会简单地背诵答案。虽然准备这些例子可能会多花些时间,但最终得到的结果会更好,更有价值。

零样本思维链

零样本思维链是指在原始提示中加入"让我们一步步思考"这句话。它通过两个步骤来提取推理过程和答案。

  • 推理提取:
    在这一步,语言模型思考问题,形成一条通向答案的推理链。我们给语言模型一个包含问题和触发句"让我们一步步思考"的提示。然后,语言模型会生成一个解释它如何得出答案的句子。

  • 答案提取:
    在第二步,我们从语言模型的回答中提取最终答案。我们把提示、生成的句子和一个触发句"答案是"连在一起。这告诉语言模型给我们答案。然后,语言模型会生成一个包含问题答案的句子。

相比之下,零样本基准使用"答案是"这样的提示来提取答案。而少样本提示,无论是标准的还是思维链的,通过设计以正确格式结尾的示例答案,避免了使用这种答案提取提示的需要。

研究人员比较了零样本思维链和其他两种评估大型语言模型零样本推理能力的方法。结果发现,在各种推理任务中,零样本思维链的表现优于其他方法。

如果你想找一个经过思维链提示训练的较小模型,可以考虑Flan-T5模型。它可以用于零样本自然语言处理任务,包括文本摘要、自然语言推理、翻译和常识推理。

思维链能力何时出现?

思维链推理是大型语言模型(LLM)的一种涌现能力,可能是由于模型参数超过1000亿而产生的。对于较小的LLM,思维链并不能提高性能,只有在使用这种规模的模型时才能带来性能提升。这有两个原因:

  • 较小的LLM无法生成既流畅又合乎逻辑的长思维链。这导致其表现不如标准提示。
  • 思维链推理对更复杂的问题更有效。它要求LLM能够识别解决问题的关键步骤,然后生成一连串思考过程,最终得出解决方案。较小的LLM可能无法像大型LLM那样有效地做到这一点。

大型LLM出现思维链推理能力的另一个原因可能与它们的预训练数据有关。大型LLM通常在包含逐步推理的海量数据集上训练,这可能有助于它们发展出以思维链方式推理的能力。

有趣的是,遵循指令的能力似乎并不是思维链能力所必需的。研究显示,使用未经过遵循指令微调的LLM,也能展现零样本和少样本思维链推理。不过,遵循指令的能力可能会提高思维链推理的质量。

如何进行思维链提示?

要进行思维链提示,你只需在提示的末尾加上"让我们一步步思考"这句话。这会迫使模型分步思考,将问题分解成更小的部分。下面是一个例子,展示了不使用和使用思维链提示的区别:

你可以看到,使用思维链让大型语言模型(LLM)给出了更好、更复杂且正确的输出。没有分步思考的提示立即导致了错误的答案。

如果你有一个非常严格的问题,你知道只能用特定的推理模式来解决,那就是你应该使用少样本思维链的时候。你可以提供一些针对你特定问题集所需的推理步骤的例子,然后LLM会尝试用类似的步骤来解决给定的问题。或者,你可以用这种技术为你的用户以特定方法解决问题。比如,如果学生要使用你的应用,你可能想用少样本思维链以有趣、简单且易懂的方式解决问题。

这些少样本例子应该展示中间步骤和最终解决方案。一旦你开发了思维链提示和例子,你就可以将它们整合到模型中。最后,测试模型并反复调整思维链提示和例子,直到模型的表现令人满意。

思维链提示的关键方面

在这部分,我们将探讨影响大型语言模型中思维链提示性能和可靠性的重要维度。我们将深入研究敏感性、自一致性、鲁棒性和连贯性如何在塑造思维链提示技术的有效性中发挥关键作用。

自一致性

自一致性是一种提高语言模型在需要多步推理任务上表现的技术。在思维链提示的背景下,自一致性可以通过为同一个问题采样多个不同的思维链来提高模型的性能。然后,模型可以被训练从这些思维链中选择最一致的答案。

自一致性显著提高了思维链提示在许多流行的算术和常识推理基准测试上的表现。例如,在GSM8K基准测试中,自一致性将思维链提示的性能提高了17.9%。在SVAMP基准测试中提高了11.0%,在AQuA基准测试中提高了12.2%。

这是一种完全无监督的技术,可以直接在预训练的语言模型上使用。它不需要额外的人工标注,也避免了任何其他训练、模型或微调。它对采样策略和参数具有鲁棒性。在PaLM-540B上改变温度采样中的T、top-k采样中的k和核采样策略中的p时,自一致性始终能提高性能。

鲁棒性

研究人员进行了三组不同的思维链标注实验,每组由不同的标注者完成。他们发现,无论是哪个标注者,思维链提示的表现始终优于标准基准。这表明思维链提示不依赖于特定的语言风格。

研究人员还进行了使用从GSM8K训练集随机抽样的样本的实验,这是一个独立的来源。他们发现,使用这些样本的思维链提示与使用手动编写的样本的思维链提示表现相当。这表明思维链提示不依赖于使用的具体样本。

研究人员还进行了使用不同数量样本的实验。他们发现思维链提示对不同数量的样本保持稳定。这表明思维链提示不需要大量样本就能有效。

研究人员还使用了各种语言模型进行实验,包括LaMDA 137B。他们发现思维链提示对所有这些语言模型都有效。这表明思维链提示不依赖于使用的特定语言模型。

总的来说,这些实验结果表明,思维链提示是一种可以提高语言模型在各种任务上表现的稳健技术。它不依赖于特定的语言风格、标注者、样本集或语言模型。

敏感性

思维链提示中的敏感性指的是提示的设计对模型性能的影响程度。如果提示设计不好,模型的性能可能会下降。提示应该清晰、简洁,易于模型理解。应避免使用模型可能不熟悉的行话或技术术语。提示应该与模型试图解决的特定任务相匹配。如果提示与任务不匹配,模型可能无法生成正确的答案。任务越复杂,模型对提示设计的敏感性可能越高。

当提示示例问题类型和任务问题类型不匹配时,少样本思维链的性能下降。这表明少样本思维链对提示的设计高度敏感,需要仔细将提示与特定任务匹配才能获得良好的性能。

连贯性

连贯性指的是思维链推理步骤的正确顺序程度。这意味着后面的步骤不应该是前面步骤的先决条件,前面的步骤也不应该基于后面的步骤。例如,在引入"32"或"42"之前出现"32 + 42 = 74"的推理就不具有连贯性。这是因为"32 + 42 = 74"这个等式是一个后面的步骤,依赖于引入数字"32"和"42"的前面步骤。

研究人员设计了一组消融设置,以检验连贯性对类似思维链推理不同组成部分的影响。消融设置是一种测试系统不同部分重要性的方法,通过移除它们并观察对系统性能的影响。研究发现,连贯性对类似思维链推理的所有组成部分都很重要。当移除连贯性时,系统的性能会下降。

研究人员还发现,语言模板的连贯性对思维链提示的性能特别重要。语言模板是用来连接思维链推理不同步骤的短语。如果语言模板不连贯,模型可能无法理解推理过程并生成正确的答案。

思维链提示的类型

在思维链(CoT)提示领域中,有两种显著的变体作为有影响力的策略:多模态CoT和从少到多提示。让我们详细探讨这些技术。

多模态CoT

传统的CoT专注于语言模态,这意味着它只使用文本为模型提供推理背景。多模态CoT将文本和视觉整合到一个两阶段框架中。

第一步涉及基于多模态信息的推理生成。这意味着模型同时接收文本和图像,然后被要求生成一个解释文本和图像如何相关的推理。

框架的第二阶段是答案推断。在这里,模型使用第一步生成的信息丰富的推理来推断出问题的正确答案。

1B多模态CoT的表现超过GPT-3.5 16个百分点(75.17%对91.68%的准确率),并在ScienceQA基准测试中超过了人类表现。在8个问题类别中,我们的模型将带配对图像的问题的表现从67.43%提高到88.80%。

像UnifiedQA和GPT-3.5这样的方法使用图像说明来理解图像显示的内容,然而,使用图像特征更有效。未来的研究可以通过使用更好的图像特征、添加常识知识和过滤掉无关信息来改进CoT推理。

从少到多提示

思维链提示是一种强大的自然语言推理技术,但在处理比提示中显示的例子更难的问题时可能会遇到困难。为了解决这个挑战,我们提出了一种新的提示策略,称为从少到多提示。

从少到多提示的工作原理是将一个复杂问题分解成一系列更简单的子问题,然后按顺序解决它们。每个子问题都由前面子问题的答案促进。

例如,要解决一个数学应用题,我们可能首先询问语言模型将问题分解成子问题,比如"第一件物品的成本是多少?"和"总成本是多少?"然后我们会询问语言模型依次解决子问题,使用前面子问题的答案来指导我们的查询。

从少到多提示在符号操作、组合泛化和数学推理任务上可以推广到更困难的问题。使用从少到多提示的GPT-3 code-davinci-002可以用14个示例解决SCAN,准确率达到99%,而思维链提示只能达到16%的准确率。

下表显示了不同提示方法在GSM8K和DROP基准测试中仅包含数值问题的子集上的准确率。基础语言模型是code-davinci-002。

下表显示了不同提示方法在最后字母连接任务上的准确率。

自动CoT

自动CoT是一种自动创建带有问题和推理链的演示的方法。它使用大型语言模型为每个演示生成推理链,使用"让我们一步步思考"的提示。自动CoT有两个主要步骤:

  1. 将给定数据集中的问题分成几个集群。
  2. 从每个组中选择一个代表性问题,并使用零样本CoT和简单的启发式方法生成推理链。

演示问题的多样性对于减少零样本CoT在推理链中的错误很重要。通过将问题聚类成几个组,自动CoT可以确保每个演示代表不同类型的问题。这有助于减少零样本CoT在推理链中犯错的机会。

自动CoT在10个推理任务上进行了测试,包括算术推理(MultiArith, GSM8K, AQUA-RAT, SVAMP)、常识推理(CSQA, StrategyQA)和符号推理(最后字母连接,硬币翻转)。在GPT-3中,自动CoT的表现始终与手动CoT相当或超过手动CoT。

这里是自动CoT与四种基线方法的比较:零样本、零样本CoT、少样本和手动CoT。

CoT的应用

CoT的应用涉及各种领域,包括算术、常识、符号推理、自然语言推理和问答。CoT提示为大型语言模型提供了解决这些领域复杂问题的能力。

算术推理

当与540B参数的语言模型一起使用时,思维链(CoT)提示在各种任务上的表现与针对特定任务微调的模型相当,包括算术推理。解决数学应用题对语言模型来说是一项具有挑战性的任务。

为了评估大型语言模型解决数学问题的能力,使用了两个基准测试:MultiArith和GSM8K。标准提示显示这些基准测试的扩展曲线相对平坦,这意味着增加模型大小并不会显著提高性能。然而,当使用CoT提示时,增加模型规模显著提高了性能,特别是对于大型模型。

PaLM(一个540B参数的语言模型)结合CoT提示,在GSM8K基准测试中达到了58%的最高性能。自一致性技术进一步提高了CoT提示的性能,在GSM8K上达到74%的准确率。CoT提示在数学应用题解决方面达到了最高水平,超过了微调的GPT-3基线。

常识推理

思维链提示也可以用于常识推理任务。这些任务需要基于一般知识对物理和人类互动进行推理。常识推理对当前的自然语言理解系统来说是具有挑战性的。

CoT提示在常识推理基准测试上进行了评估,如CommonsenseQA、StrategyQA、日期理解和体育理解。这些任务的性能通常随着模型规模的增加而提高。CoT提示比常规提示有小幅改进。CoT提示在提高体育理解任务的性能方面最为有效。

使用CoT的PaLM 540B在体育理解方面以95%对84%的分数超过了未经辅助的体育爱好者,在StrategyQA上以75.6%对69.4%的分数超过了先前的最高水平,在体育理解上以95.4%对84%的分数超过了先前的最高水平。但在CommonsenseQA(CSQA)上看到的改进很小。

符号推理

思维链提示使语言模型能够执行标准提示难以完成的符号推理任务。它还支持长度泛化,允许模型处理比少样本示例中看到的更长的推理时间输入。

在研究过程中,为了测试CoT提示,使用了两个玩具任务进行评估。第一个是最后字母连接,模型将名字中单词的最后字母连接起来。第二个是硬币翻转,模型确定在人们翻转硬币后,硬币是否仍然正面朝上。

使用域内和域外测试集来评估使用思维链提示(CoT)和标准提示的PaLM 540B在这两个任务上的表现。对于域内评估,示例的步骤数与训练/少样本示例相同。对于域外评估,示例的步骤数比示例中的多。

在域内评估中,使用CoT的PaLM 540B几乎达到100%的解决率。标准提示在域内和域外评估中都失败了。CoT提示导致性能提高,但低于域内评估。

问答

CoT提示通过将复杂问题或提示分解成一系列更简单的逻辑步骤来改进问答(QA)。这种方法帮助语言模型理解问题的结构和其组成部分之间的关系。每个步骤都专注于问题的一个特定方面,帮助模型更有效地识别相关信息。

CoT鼓励模型执行多跳推理,在这种推理中,它迭代地从不同来源或文档中收集和组合信息。这使模型能够执行改进的推理,并将单独的知识片段连接起来,得出准确的答案。

通过明确指定推理步骤,CoT提示可以帮助防止语言模型在回答复杂问题时可能引入的常见错误或偏见。此外,CoT提示允许用户理解模型是如何得出特定响应的。

思维链(CoT)与其他方法的比较

在这部分,我们深入比较思维链提示与其他方法,特别是标准提示和思维树提示。评估它们的优缺点可以为选择最适合你的业务应用的方法提供有价值的见解。

思维链vs标准提示

标准提示使用输入-输出对作为例子。这些对以问题和答案的形式呈现。模型根据这些对预测答案。它在有效处理多步推理任务方面有限制,但适合直接的任务,如单轮问答。它需要较少的计算资源。它通常使用单次提示进行训练,对于复杂任务往往需要更多数据来微调。标准提示可能不会随着模型规模的增加而显著提高性能。

而思维链提示涉及生成中间推理步骤。这些步骤在提供最终答案之前进行。它擅长复杂推理,使模型能够逐步思考。它versatile应用于广泛的需要复杂推理的任务。它需要对一系列提示进行训练,并有效利用数据进行多步推理。它在更大的模型上表现更好,因此需要更多的计算能力。它在复杂推理基准测试和需要多步问题解决的任务中表现出色。

MAWPS基准测试的比较:

长度泛化任务的比较:

对于直接的任务,你可以选择标准提示;对于需要深入、多步推理和可解释性的应用,思维链提示是更好的选择。GitHub上有一个与思维链推理相关的开源数据和工具库。它有各种任务的数据集,如数学问题和常识推理。它还有一个社区论坛供讨论。

思维链vs思维树提示

思维链遵循线性方法,每个新词或想法直接与前一个相连,形成一个链条。它代表了一种顺序的思维组织。

另一方面,思维树(ToT)采用层次方法。想法组织成树状结构,每个想法分支成多个相关想法。它代表了一种更复杂和分支的思维组织。

像GPT-3这样的思维链模型通常擅长在短跨度内生成连贯和与上下文相关的文本。像Transformer模型这样的思维树模型通常更擅长在更长的文本中保持连贯性,并且可以同时跟踪多个相关想法。

由于思维树的层次性,思维链模型在结构上更简单,计算强度也较低。此外,思维树引入了通过强化学习(RL)训练的"ToT控制器"概念。这个控制器可以潜在地从新数据或自我对弈中学习,允许思维树系统在固定语言模型的情况下进化和获取新知识。

思维链-SC(带有思维链的自一致性)使用简单的提示技术。它没有明确提到使用搜索算法。思维树采用广度优先搜索(BFS)和深度优先搜索(DFS)等搜索算法,以实现系统的思维探索。它将这些算法与树结构结合用于问题解决。因此,思维树的表现显著优于其他方法。

对于简单、较短的文本,你可以选择思维链;对于复杂、较长的文本和问题解决任务,思维树可能更合适。

本文翻译自该原文:
https://www.mercity.ai/blog-post/guide-to-chain-of-thought-prompting

提示词工程技术图谱-prompt engineering

提示词工程技术栈

提示词工程技术的不同方法和分类

无需大量训练的新任务(New Tasks Without Extensive Training)

  • 零样本提示(Zero-shot Prompting)
  • 少样本提示(Few-shot Prompting)
  • 连续链提示(Chain-of-Thought (CoT) Prompting)
  • 自动化连续链提示(Automatic Chain-of-Thought (Auto-CoT))
  • 自我一致性(Self-Consistency)
  • 逻辑链提示(Logical CoT (LogiCoT) Prompting)
  • 符号链提示(Chain-of-Symbol (CoS) Prompting)
  • 思维树提示(Tree-of-Thoughts (ToT) Prompting)
  • 思维图提示(Graph-of-Thought (GoT) Prompting)
  • 系统2注意力提示(System 2 Attention Prompting)
  • 思维线程提示(Thread of Thought (ThoT) Prompting)
  • 表格链提示(Chain of Table Prompting)

推理与逻辑(Reasoning and Logic)

  • 检索增强生成(Retrieval Augmented Generation (RAG))
  • 反应式提示(ReAct Prompting)
  • 验证链提示(Chain-of-Verification (CoVe) Prompting)
  • 注释链提示(Chain-of-Note (CoN) Prompting)
  • 知识链提示(Chain-of-Knowledge (CoK) Prompting)

减少幻觉(Reduce Hallucination)

  • 主动提示(Active-Prompt)

用户交互(User Interaction)

  • 自动提示工程师(Automatic Prompt Engineer (APE))
  • 自动化推理和工具使用(Automatic Reasoning and Tool-use (ART))

微调和优化(Fine-Tuning and Optimization)

– 对比连续链提示(Contrastive Chain-of-Thought Prompting (CCoT))

基于知识的推理和生成(Knowledge-Based Reasoning and Generation)

  • 情绪提示(Emotion Prompting)

提高一致性和连贯性(Improving Consistency and Coherence)

  • 抓取提示(Scratchpad Prompting)
  • 思维程序提示(Program of Thought (PoT) Prompting)

情绪和语气管理(Managing Emotions and Tone)

  • 程序化连续链提示(Structured Chain-of-Thought Prompting (SCoT))

代码生成和执行(Code Generation and Execution)

  • 代码链提示(Chain of Code (CoCo) Prompting)

优化和效率(Optimization and Efficiency)

  • 提示优化(Optimization by Prompting)

理解用户意图(Understanding User Intent)

  • 重述和响应提示(Rephrase and Respond Prompting)

元认知和自我反思(Metacognition and Self-Reflection)

  • 退一步思考提示(Take a Step Back Prompting)

如何使用Gemini的Golang API

如何使用Gemini的Golang API

在本文中,我们将讨论如何使用Gemini的Golang API来生成内容。我们会提供一个示例代码并详细解释每个部分的功能。

使用Gemini API前提条件

在开始之前,请确保您已经设置了Gemini API密钥,并将其作为环境变量GEMINI_API_KEY

Gemini API示例代码

以下是一个使用Gemini API的完整Golang示例:

package gemini

import (
    "context"
    "fmt"
    "os"

    "nuwa-engineer/pkg/llms"

    "github.com/google/generative-ai-go/genai"
    "google.golang.org/api/option"
)

// Gemini是Gemini API的包装器。
type Gemini struct {
    Client *genai.Client
    Model  *genai.GenerativeModel
}

// NewGemini返回一个新的Gemini客户端。
func NewGemini(ctx context.Context, modelName string) (llms.Model, error) {
    // 从环境变量中获取API密钥
    client, err := genai.NewClient(ctx, option.WithAPIKey(os.Getenv("GEMINI_API_KEY")))
    if err != nil {
        return nil, fmt.Errorf("创建Gemini客户端失败: %w", err)
    }

    model := client.GenerativeModel(modelName)

    return &Gemini{
        Client: client,
        Model:  model,
    }, nil
}

// ContentToString将内容转换为字符串
func (g *Gemini) ContentToString(content *genai.Content) string {
    var str string
    for _, part := range content.Pparts {
        // 检查part是否是Text类型
        if _, ok := part.(genai.Text); ok {
            str += string(part.(genai.Text))
        }
    }
    return str
}

// GenerateContent根据提示生成内容。
func (g *Gemini) GenerateContent(ctx context.Context, prompt string) (string, error) {
    resp, err := g.Model.GenerateContent(ctx, genai.Text(prompt))
    if err != nil {
        return "", fmt.Errorf("生成内容失败: %w", err)
    }

    // 将响应转换为字符串
    return g.ContentToString(resp.Candidates[0].Content), nil
}

// 关闭客户端。
func (g *Gemini) CloseBackend() error {
    return g.Client.Close()
}

详细解释

包和依赖导入

首先,我们导入了需要的包和依赖:

import (
    "context"
    "fmt"
    "os"

    "nuwa-engineer/pkg/llms"

    "github.com/google/generative-ai-go/genai"
    "google.golang.org/api/option"
)

定义Gemini结构体

定义一个Gemini结构体来封装Gemini API客户端和模型:

type Gemini struct {
    Client *genai.Client
    Model  *genai.GenerativeModel
}

创建新的Gemini客户端

NewGemini函数用于创建一个新的Gemini客户端:

func NewGemini(ctx context.Context, modelName string) (llms.Model, error) {
    client, err := genai.NewClient(ctx, option.WithAPIKey(os.Getenv("GEMINI_API_KEY")))
    if err != nil {
        return nil, fmt.Errorf("创建Gemini客户端失败: %w", err)
    }

    model := client.GenerativeModel(modelName)

    return &Gemini{
        Client: client,
        Model:  model,
    }, nil
}

将内容转换为字符串

ContentToString方法将genai.Content转换为字符串:

func (g *Gemini) ContentToString(content *genai.Content) string {
    var str string
    for _, part := range content.Parts {
        if _, ok := part.(genai.Text); ok {
            str += string(part.(genai.Text))
        }
    }
    return str
}

根据提示生成内容

GenerateContent方法根据给定的提示生成内容:

func (g *Gemini) GenerateContent(ctx context.Context, prompt string) (string, error) {
    resp, err := g.Model.GenerateContent(ctx, genai.Text(prompt))
    if err != nil {
        return "", fmt.Errorf("生成内容失败: %w", err)
    }

    return g.ContentToString(resp.Candidates[0].Content), nil
}

关闭客户端

CloseBackend方法用于关闭Gemini客户端:

func (g *Gemini) CloseBackend() error {
    return g.Client.Close()
}

Main 函数

package main

import (
    "context"
    "fmt"
    "log"
    "os"
    "your/package/path/gemini" // 替换为您的实际包路径
)

func main() {
    // 设置上下文
    ctx := context.Background()

    // 定义模型名称(替换为您实际使用的模型名称)
    modelName := "your-model-name"

    // 创建Gemini客户端
    client, err := gemini.NewGemini(ctx, modelName)
    if err != nil {
        log.Fatalf("创建Gemini客户端失败: %v", err)
    }
    defer func() {
        if err := client.CloseBackend(); err != nil {
            log.Fatalf("关闭Gemini客户端失败: %v", err)
        }
    }()

    // 定义提示
    prompt := "请生成一段关于人工智能的文章。"

    // 生成内容
    content, err := client.GenerateContent(ctx, prompt)
    if err != nil {
        log.Fatalf("生成内容失败: %v", err)
    }

    // 打印生成的内容
    fmt.Println("生成的内容:")
    fmt.Println(content)
}

Coze 如何自定义插件-获取公众号文章列表

Coze 如何自定义插件-获取公众号文章列表

关于Coze

毫无疑问,2023年是大模型元年,那么2024年,将会是LLM Agent应用爆发的一年。通过LLM agent 模式,AI 大模型将会释放自己的能力,助力更多的应用智能化。

Coze 平台是字节跳动开发的一款能够低代码创建AI agent的平台,无论你是否有编程经验,Coze 平台都能让你轻松创建各种聊天机器人,并将它们部署到不同的社交平台和消息应用中。该平台通过提供友好的界面和强大的功能,使得用户可以快速上手,开发出具有个性化和智能交互功能的机器人。简而言之,Coze 平台为聊天机器人的开发和应用提供了一个简单、高效、多功能的环境。

关于如何使用Coze,及了解更多的信息,可以阅读Coze 操作文档

本篇文章将主要聚焦于如何自定义Coze的工具(插件)。

创建Coze的插件

进入 Coze 主页

  • 1 选择个人空间
  • 2 选择插件
  • 3 点击创建插件按钮,进入创建插件界面。

根据上图:

  • 填写插件命名,可以使用中文。
  • 填写插件描述,简单描述插件的用途。
  • 选择在Coze IDE 中创建
  • 有两种编程语言可选,Node.js 和 Python3, 本实例使用python,因此选择Python3
  • 点击 确认

点击按钮 在IDE中创建工具

命名工具,必须以字母数字下划线格式命名,跟函数命名一致。
描写工具的用途。点击 确认

此时,你可以通过编程实现handler函数,来实现你的插件功能。
如果该工具需要自定义输入输出参数,则可通过元数据标签页,添加输入参数和输出参数。

分别使用输入参数和输出参数的编辑按钮,编辑输入参数和输出参数。
由于获取公众号文章时,公众号API需要使用公众号的appid和secret来获取访问资源的token,因此在此定义输入参数为appid和secret。
同时输出为公众号文章的列表,因此输出是一个数组,并且数组的每个item是一个对象,该对象具有如下字段:title,author,digest,url,如下图所示。

编辑完成后记得点击保存。然后切换到代码标签页。
由于公众号的API依赖于requests, 因此需要使用添加依赖,添加依赖的库。
添加依赖以后,就可以编程实现相应的功能,以下是获取公众号文章的代码。


from runtime import Args
from typings.GetArticleList.GetArticleList import Input, Output

import json
import requests

def get_stable_access_token(appid, secret, force_refresh=False):
    """
    获取稳定的访问令牌

    参数:
    appid (str):微信小程序的 appid
    secret (str):微信小程序的 secret
    force_refresh (bool):是否强制刷新令牌,默认为 False

    返回:
    dict:访问令牌的 JSON 数据
    """
    url = "https://api.weixin.qq.com/cgi-bin/stable_token"
    payload = {
        "grant_type": "client_credential",
        "appid": appid,
        "secret": secret,
        "force_refresh": force_refresh
    }
    headers = {'Content-Type': 'application/json;charset=utf-8'}
    response = requests.post(url, headers=headers, data=bytes(json.dumps(payload), encoding='utf-8'))
    print(response)
    if response.status_code == 200:
        return response.json()
    else:
        response.raise_for_status()

def extract_info(articles):
    """
    提取文章列表中所有文章的信息

    Args:
        articles (dict):包含文章列表的 JSON 数据

    Returns:
        list:包含所有文章信息的列表,每个元素是一个字典
    """

    result = []
    if "item" in articles:
        for item in articles["item"]:
            if "content" in item and "news_item" in item["content"]:
                for news_item in item["content"]["news_item"]:
                    # 获取文章的标题、作者、摘要和 URL,如果没有则为空字符串
                    title = news_item.get("title", "")
                    author = news_item.get("author", "")
                    digest = news_item.get("digest", "")
                    url = news_item.get("url", "")
                    # 创建一个新的 JSON 对象来存储提取的信息
                    article = {
                        "title": title,
                        "author": author,
                        "digest": digest,
                        "url": url
                    }
                    result.append(article)
    return result

def get_published_articles(access_token, offset, count, no_content):
    """
    获取已发布的文章列表

    参数:
        access_token (str):调用接口的 access_token
        offset (int):要获取的文章列表的起始位置
        count (int):要获取的文章数量
        no_content (bool):是否返回文章内容

    返回:
        str:返回的文章列表的 JSON 字符串
    """
    url = f"https://api.weixin.qq.com/cgi-bin/freepublish/batchget?access_token={access_token}"
    payload = {
        "offset": offset,
        "count": count,
        "no_content": no_content
    }
    headers = {'Content-Type': 'application/json;charset=utf-8'}
    response = requests.post(url, headers=headers, data=json.dumps(payload))
    text = response.content.decode('utf-8')

    if response.status_code == 200:
        return text
    else:
        response.raise_for_status()

"""
Each file needs to export a function named <code>handler</code>. This function is the entrance to the Tool.

Parameters:
args: parameters of the entry function.
args.input - input parameters, you can get test input value by args.input.xxx.
args.logger - logger instance used to print logs, injected by runtime.

Remember to fill in input/output in Metadata, it helps LLM to recognize and use tool.

Return:
The return data of the function, which should match the declared output parameters.
"""
def handler(args: Args[Input])->Output:
    offset = 0
    count = 100
    no_content = 0

    appid = args.input.appid
    appsecret = args.input.appsecret

    access_token = get_stable_access_token(appid, appsecret)
    token = access_token['access_token']
    content = get_published_articles(token, offset, count, no_content)
    articles = json.loads(content)
    result = extract_info(articles)
    return result

运行测试时,输入以下参数,运行可以查看结果。

{
    "appid":"your appid",
    "appsecret":"your appsecret"
}

测试通过即可将你的插件进行发布

图解云计算概念,数字时代基石

2024-06-23 14:09:00 星期日

什么是云计算?

云计算是一种基于互联网的计算方式,它提供共享的计算资源和数据,根据需求向计算机和其他设备提供服务。它是一种模式,可以实现对可配置计算资源(如计算机网络、服务器、存储、应用程序和服务)的无处不在、按需访问,这些资源可以快速进行配置和释放,管理工作量最小化。云计算和存储解决方案为用户和企业提供了在第三方数据中心存储和处理数据的各种能力,这些数据中心可能位于用户远距离的地方,从城市内到世界各地都有。云计算依赖于资源共享以实现一致性和规模经济,类似于电网覆盖电力网络的效果。 —维基百科。

传统计算到云计算的转变

从传统计算到云计算的转变是信息技术行业的一个重大变革,主要体现在以下几个方面:

  • 计算资源的交付模式发生转变
    传统计算模式下,组织需要购买服务器、存储设备等硬件设施,自行建设数据中心。而云计算允许用户按需获取所需的计算资源,并按使用量付费,无需购置昂贵的硬件设备。
  • 资源利用效率提高
    传统模式下,组织往往需要购买超出实际需求的硬件资源,以应对将来业务增长。而云计算可实现资源池化,通过虚拟化技术共享底层硬件资源,提高资源利用率。
  • 运维模式改变
    传统模式下,IT人员需要花费大量精力维护硬件设施、操作系统和应用程序。而云计算将大部分维护工作交给云服务提供商,用户可专注于自身业务创新。
  • 业务敏捷性增强
    传统架构下,组织扩展IT资源需要经历复杂的采购、部署流程。而云计算能够实现快速伸缩计算资源,显著提高业务响应速度。
  • 成本结构改变
    传统模式下,大部分IT支出为资本支出。而云计算则转变为按需付费的运营支出模式,降低前期投资压力。

云计算服务模型

云计算服务通常分为三种主要的服务模型:

  • 基础设施即服务(Infrastructure as a Service, IaaS)
    IaaS提供基础的IT资源,如虚拟化的计算、存储和网络资源。用户可自行部署操作系统、中间件和应用程序。典型的IaaS服务包括亚马逊AWS的EC2、Microsoft Azure的虚拟机等。
  • 平台即服务(Platform as a Service, PaaS)
    PaaS为开发者提供完整的应用程序开发生命周期管理,包括编程环境、数据库、Web服务器等。开发者只需关注应用程序本身的开发,而无需管理底层的基础架构。典型的PaaS有AWS Elastic Beanstalk、Microsoft Azure App Service等。
  • 软件即服务(Software as a Service, SaaS)
    SaaS是云计算中最高级别的服务,提供通过互联网访问的完整应用程序,无需在本地安装和运行。软件由服务提供商完全管理,用户只需通过Web浏览器或客户端访问。常见的SaaS应用包括Office 365、Salesforce、Google G Suite等。

云计算的基本分层架构

云计算的基本分层架构通常包括以下几个层次:

  • 硬件资源层(Hardware Resource Layer)
    这是云计算架构的基础设施层,由大量的物理服务器、存储设备和网络设备组成。这些硬件资源通过虚拟化技术可以被划分为多个资源池共享使用。
  • 虚拟化层(Virtualization Layer)
    通过虚拟化技术如虚拟机(VM)或容器将底层硬件资源虚拟化,形成可供灵活调度的资源池。虚拟化层对资源进行抽象,屏蔽掉底层硬件的物理特征。
  • 资源管理层(Resource Management Layer)
    负责对资源池中的虚拟化资源进行统一调度和管理,实现资源的动态分配和弹性伸缩。通常采用自动化的资源编排工具实现。
  • 平台层(Platform Layer)
    在虚拟资源之上,为开发者提供应用运行的平台环境,如操作系统、中间件、开发框架等。开发者可在此基础上部署和运行应用程序。
  • 应用层(Application Layer)
    最顶层是为用户提供的各种云应用和服务,可以是SaaS形式直接交付,也可以是PaaS或IaaS模式下的自建应用。

    – 管理层(Management Layer)

    贯穿整个分层架构的管理层,负责实现云资源的调配、访问控制、监控、计费等管理职能。

云计算的关键技术

云计算的实现依赖于几项关键技术,主要包括:

  • 虚拟化技术
    虚拟化是云计算的基础,它能将计算、存储、网络等物理资源对象化、池化,以软件定义的方式灵活分配和管理。虚拟机、容器是两种主要的虚拟化技术。
  • 资源调度与自动化
    要高效整合海量的虚拟化资源,需要自动化的资源调度与编排技术,实现资源按需分配、弹性伸缩、负载均衡等管理。常用的工具如Kubernetes、Mesos等。
  • 分布式存储技术
    为应对大规模数据存储需求,需要分布式存储系统将数据分散存储在多个节点上,提供高可靠、高扩展、高性能的存储服务,如HDFS、Ceph等。
  • 分布式计算技术
    针对大数据、高并发等计算需求,利用分布式计算框架如Hadoop、Spark在数据中心规模内并行处理任务,提高计算效率。
  • 网络虚拟化技术
    通过软件定义网络(SDN)、网络功能虚拟化(NFV)等技术,在同构硬件之上构建弹性灵活、可编程的虚拟网络架构。
  • 安全技术
    涉及身份认证、访问控制、数据加密、隔离安全等技术,确保多租户场景下的资源、数据、隐私安全。
  • DevOps
    融合开发(Dev)和运维(Ops)理念的一种文化和实践,通过自动化、持续集成等手段,加快应用交付和部署效率。

什么是AI智能体?

LLM 智能体

考虑一个旨在帮助金融分析师回答关于公司绩效的问题的大型语言模型(LLM)应用程序。通过一个设计良好的检索增强生成(RAG)管道,分析师可以回答类似于:“X公司2022财年的总收入是多少?”这样的问题。这些信息可以由经验丰富的分析师轻松地从财务报表中提取。

现在考虑一个问题,比如,“从2023财年第二季度的盈利电话会议中得出的三个要点是什么?重点关注公司正在构建的技术壁垒”。这是金融分析师想要回答以纳入其报告中的类型问题,但需要投入时间来回答。

我们如何开发一个解决方案来回答类似上述问题?很明显,这种信息需要更多的工作,而不仅仅是从盈利电话会议中查找。这种查询需要计划、定制焦点、记忆、使用不同工具,并将一个复杂问题分解为更简单的子部分。这些概念组合在一起基本上就是我们所谓的LLM智能体。

在这篇文章中,我介绍了由LLM提供动力的智能体,并讨论了智能体是什么以及企业应用的一些用例。有关更多信息,请参阅构建您的第一个智能体应用程序。在那篇文章中,我提供了一个生态系统概述,涵盖了构建AI智能体的可用框架以及一个入门指南,供任何尝试使用问答(Q&A)智能体的人使用。

什么是LLM智能体

虽然没有一个被广泛接受的LLM智能体的定义,但它们可以被描述为一个系统,可以使用LLM来推理问题,创建解决问题的计划,并在一组工具的帮助下执行计划。

简而言之,智能体是一个具有复杂推理能力、记忆和执行任务手段的系统。

这种能力最初是在项目中观察到的,比如AutoGPT或BabyAGI,在这些项目中,复杂的问题得到了解决,几乎没有干预。为了更详细地描述智能体,这里是一个LLM智能体应用程序的一般架构示意图(图1)。

一个智能体由以下关键组件组成(稍后会详细介绍):

  • 智能体核心
  • 记忆模块
  • 工具
  • 规划模块

智能体核心模块

智能体核心是管理智能体的核心逻辑和行为特征的中央协调模块。可以将其视为智能体的“关键决策模块”。在这里我们也定义了:

  • 智能体的总体目标:包含智能体的总体目标和目标。
  • 执行工具:基本上是智能体可以访问的所有工具的简要列表或“用户手册”。
  • 如何利用不同的规划模块:关于不同规划模块的效用以及在何种情况下使用哪个的详细说明。
  • 相关记忆:这是一个动态部分,在推理时填充与用户过去对话中最相关的记忆项。 “相关性”是根据用户提出的问题确定的。
  • 智能体的角色(可选):此角色描述通常用于偏好使用某些类型的工具或在智能体的最终响应中赋予典型的特殊性。

智能体的记忆模块

记忆模块在AI智能体中扮演着至关重要的角色。记忆模块基本上可以被看作是智能体的内部日志以及与用户的互动的存储库。

记忆模块有两种类型:

  • 短期记忆:智能体经历的行动和思考的记录,试图回答用户的单个问题:智能体的“思维线索”。
  • 长期记忆:关于用户和智能体之间发生的事件的行动和思考的记录。它是一个日志簿,包含了跨越数周或数月的对话历史。

记忆不仅需要基于语义相似性的检索。通常,复合分数由语义相似性、重要性、最近性和其他特定于应用程序的指标组成。它用于检索特定信息。

工具集

工具是经过明确定义的可执行工作流程,智能体可以使用它们来执行任务。通常情况下,它们可以被看作是专门的第三方API。

例如,智能体可以使用RAG管道生成上下文感知答案,使用代码解释器解决复杂的编程任务,使用API在互联网上搜索信息,甚至可以使用任何简单的API服务,比如天气API或即时消息应用程序的API。

规划模块

复杂问题,比如分析一组财务报告以回答一个分层业务问题,通常需要细致入微的方法。借助LLM动力智能体,可以通过以下两种技术的组合来处理这种复杂性:

  • 任务和问题分解
  • 反思或评论

问题分解

复合问题或推断信息需要某种形式的分解。举个例子,问题是:“NVIDIA最近的盈利电话会议中有哪三个要点?”

回答这个问题所需的信息不能直接从一个小时的会议记录中提取出来。然而,这个问题可以分解成多个问题主题:

  • “哪些技术转变被讨论得最多?”
  • “是否存在任何业务阻力?”
  • “财务结果如何?”

每个问题都可以进一步分解成子部分。也就是说,一个专业的AI智能体必须引导这种分解过程。

反思和评论

诸如ReAct、Reflexion、Chain of Thought和Graph of Thought之类的技术已经被用作基于评论或证据的提示框架。它们被广泛应用于改进LLM的推理能力和响应。这些技术也可以用于优化智能体生成的执行计划。

企业应用智能体

虽然智能体的应用几乎是无限的,但以下是一些可能对许多企业产生巨大影响的有趣案例:

  • “与您的数据交流”的智能体
  • 智能体群
  • 推荐和体验设计智能体
  • 定制的AI作者智能体
  • 多模式智能体

“与您的数据交流”的智能体

“与您的数据交流”并不是一个简单的问题。有很多挑战是一个直接的RAG管道无法解决的:

  • 源文档的语义相似性
  • 复杂的数据结构,比如表格
  • 缺乏明显的上下文(并非每个块都包含其来源的标记)
  • 用户提出的问题的复杂性

…等等

例如,回到之前的盈利电话会议记录示例(2023年第三季度 | 2024年第一季度)。你如何回答这个问题:“数据中心收入在2023年第三季度和2024年第一季度之间增长了多少?”为了回答这个问题,你基本上必须分别回答三个问题(即,我们需要一个规划模块):

  • 2023年第三季度数据中心收入是多少?
  • 2024年第一季度数据中心收入是多少?
  • 这两者之间有什么区别?

在这种情况下,你需要一个智能体,该智能体可以访问一个进行问题分解的规划模块(生成子问题并搜索答案,直到解决更大的问题),一个RAG管道(用作工具)来检索特定信息,以及记忆模块来准确处理子问题。在“LLM动力智能体:构建您的第一个智能体应用程序”一文中,我详细介绍了这种类型的案例。

智能体群

一群智能体可以被理解为一组智能体共同努力在单一环境中共存,并能相互合作解决问题。分散式的智能体生态系统非常类似于多个“智能”微服务协同解决问题。

像生成式智能体和ChatDev这样的多智能体环境在社区中非常受欢迎(图3)。为什么呢?像ChatDev这样的框架使您能够建立一个工程师、设计师、产品管理、首席执行官和智能体的团队,以低成本构建基本软件。像Brick Breaker或Flappy Bird这样的热门游戏甚至可以以50美分的低价进行原型设计!

通过一群智能体,您可以为数字公司、社区甚至整个城镇创建人口,用于行为模拟经济研究、企业营销活动、物理基础设施的用户体验等应用。

这些应用目前无法在没有大型语言模型的情况下进行模拟,并且在现实世界中运行非常昂贵。

推荐和体验设计的智能体

互联网运作依赖于推荐。由智能体驱动的对话推荐系统可用于打造个性化体验。

例如,考虑一个在电子商务网站上的人工智能智能体,它可以帮助您比较产品,并根据您的一般请求和选择提供建议。还可以构建完整的礼宾式体验,多个智能体协助最终用户在数字商店中导航。选择观看哪部电影或预订哪间酒店房间等体验可以构建为对话形式,而不仅仅是一系列决策树式的对话!

定制的AI作者智能体

另一个强大的工具是拥有一个个人AI作者,可以帮助您处理诸如共同撰写电子邮件或为您准备时间紧迫的会议和演示等任务。常规创作工具的问题在于不同类型的材料必须根据不同的受众进行定制。例如,投资者演讲必须与团队演示有所不同。

智能体可以利用您以前的工作。然后,您可以让智能体根据您的个人风格塑造智能体生成的演讲,并根据您的具体用例和需求定制工作。这个过程对于普通的LLM微调来说通常过于微妙。

多模态智能体

仅使用文本作为输入,您无法真正地“与数据交流”。通过构建能够处理各种输入的多模态智能体,例如图像和音频文件,可以增强所有提到的用例。

这只是解决企业挑战的几个方向的一些例子。数据整理、社交图和领域专业知识的智能体都是开发社区正在积极探索的企业应用领域。

请进一步学习

由LLM驱动的智能体与典型的聊天机器人应用有所不同,因为它们具有复杂的推理能力。智能体由一个智能体核心、记忆模块、工具集和规划模块组成,可以在各种企业环境中生成高度个性化的答案和内容,从数据整理到高级电子商务推荐系统。

要了解有关智能体周围技术生态系统的概述,如实现框架、必读论文、帖子和相关主题,请参阅《构建您的第一个智能体应用》。对问答智能体的无框架实现进行的步骤说明将帮助您更好地与您的数据交流。

要深入了解其他类型的LLM智能体,请参阅《构建一个LLM驱动的API智能体以执行任务》和《构建一个LLM驱动的数据智能体以进行数据分析》。

本位翻译自,感兴趣的话阅读原文:
https://developer.nvidia.com/blog/introduction-to-llm-agents/

现代技术文档的常用类型

现代技术文档的常用类型

快速指南

快速指南是一种指南或教程类型,为用户提供了开始使用特定软件、产品或服务所需的基本步骤。快速入门通常设计简单易懂,让用户能够迅速完成设置,并且在尽可能少的努力或先前知识的情况下开始使用软件或服务。

快速指南通常涵盖安装、配置和基本使用等主题,并可能包含屏幕截图或视频以帮助说明步骤。它们通常包含在产品文档中,或者可以在公司的网站或在线知识库上找到。

快速指南对于对软件或服务不熟悉的新用户特别有帮助,或者对于需要快速为特定用例或项目设置软件的用户也很有帮助。通过提供快速简便的开始方式,快速入门可以帮助用户节省时间,减少沮丧,同时提高他们对软件或服务的整体体验。

用户指南

用户指南,也称为用户手册,是一种提供有关如何使用特定产品或服务的指导和说明的文档类型。用户指南通常由产品制造商或服务提供商创建,并旨在帮助用户理解和有效使用产品或服务。

用户指南可能包括逐步说明、屏幕截图、图表和其他视觉元素,以帮助说明关键特性和功能。它们还可能包括对技术术语或概念的解释,以及故障排除技巧和其他资源,以帮助用户解决问题或困难。

用户指南通常按照产品或服务的不同方面,如安装、配置和使用,分成不同的部分或章节。它们也可能以不同的格式提供,如印刷小册子、PDF或在线帮助系统。

总的来说,用户指南是产品文档的重要组成部分,在帮助用户充分利用产品或服务的同时,也在提高用户的整体体验和满意度方面发挥着重要作用。

API 文档

API文档是一种技术文档,提供有关如何使用特定应用程序编程接口(API)的信息和指导。API是一组用于构建软件应用程序的协议、例程和工具,API文档解释了如何与API进行交互以访问其功能。

API文档通常包括有关API的可用函数、方法和参数的信息,以及有关如何对API进行身份验证和授权访问的详细信息。它还可能包括示例代码、错误处理指南和其他资源,以帮助开发人员理解并有效使用API。

API文档对API提供者和使用者都很重要。对于API提供者,清晰和全面的文档可以帮助吸引开发人员,并促进API的采用。对于API使用者,良好的文档可以减少学习曲线,使其更容易将API集成到自己的软件应用程序中。

Open API

OpenAPI文档,也称为OpenAPI规范,是使用OpenAPI标准描述API功能和结构的可机器读取文件。OpenAPI文档提供了API的详细、结构化的概述,包括以下信息:

  • 端点和操作:
    API支持的URL路径,以及用于与它们交互的HTTP方法(GET、POST、PUT、DELETE等)。
  • 请求和响应格式:
    请求和响应有效载荷的预期格式,包括数据类型、头部和媒体类型。
  • 认证和授权:
    验证和授权API请求的方法和要求。
  • 错误处理:
    在使用API时可能发生的错误类型,以及它们对应的HTTP状态代码和错误消息。
  • 示例和使用场景:
    示例请求和响应,说明如何在实践中使用API。

OpenAPI文档通常以YAML或JSON格式编写,可用于生成交互式API文档、客户端库和服务器代码。它们还可用于验证API请求和响应,以及测试API实现是否符合OpenAPI规范。

安装文档

安装指南是一种技术文档,提供了逐步说明如何安装和设置软件应用程序的说明。安装指南的目的是帮助用户快速高效地启动和运行应用程序,避免遇到任何错误或问题。
安装指南通常包括以下信息:

  • 系统要求:
    关于运行应用程序所需的硬件、软件和操作系统的信息。
  • 预安装步骤:
    安装前需要完成的任何必要准备工作或前提条件。
  • 安装说明:
    详细说明安装应用程序的步骤,包括任何必要的配置或自定义选项。
  • 安装后步骤:
    安装完成后可能需要执行的其他步骤,例如注册软件或配置安全设置。
  • 故障排除技巧:
    安装过程中可能出现的常见问题和解决方案,以及获取进一步帮助的资源。

配置文档

配置指南是一种技术文档,提供了配置和定制软件应用程序的说明。配置指南的目的是帮助用户根据其特定的需求和偏好调整应用程序,并在其环境中优化其性能。

配置指南通常包括以下信息:

  • 概述:
    对应用程序及其关键特性的简要描述。
  • 配置选项:
    各种可定制的设置和选项的概述,以及它们对应用程序行为的影响。
  • 配置说明:
    逐步说明如何配置应用程序,包括对设置或配置文件的任何必要更改。
  • 最佳实践:
    配置应用程序以实现最佳性能和功能的技巧和建议。
  • 故障排除技巧:
    配置过程中可能出现的常见问题和解决方案,以及获取进一步帮助的资源。

架构文档

架构文档是一种技术文档,提供了软件应用程序或系统整体设计和结构的概述。架构文档的目的是帮助利益相关者(如开发人员、架构师和项目经理)了解应用程序或系统的整体情况,以及其组件如何共同实现其目标。

架构文档通常包括以下信息:

  • 概述:
    对应用程序或系统的高层描述,包括其目的和目标。
  • 架构概述:
    应用程序或系统组件及其关系的视觉表示,如图表或流程图。
  • 组件描述:
    对应用程序或系统的每个组件的详细描述,包括其目的、功能和与其他组件的交互。
  • 数据流:
    描述数据如何在应用程序或系统中流动,包括输入、输出和数据存储。
  • 技术规格:
    有关应用程序或系统中使用的技术和平台的技术细节,如编程语言、数据库和API。

注:如果此部分适用于某个RCP组件或服务,则架构文档的范围仅限于该组件或服务,而不是整个RCP架构。RCP组件架构应描述上游和下游组件之间的关系,以及子组件的结构,如上述描述。例如,API网关的架构文档应重点关注WebEM、HTTPD、API网关和RCP用户管理服务之间的关系。

设计文档

设计文档是一种技术文档,提供了对软件应用程序或系统的设计和功能的详细描述。设计文档的目的是引导开发人员和其他利益相关者完成开发过程,帮助他们理解应用程序或系统的需求、设计决策和实现细节。

设计文档通常包括以下信息:

  • 用户需求:
    对应用程序或系统的用户需求和期望的描述。
  • 用例:
    应用程序或系统在实际世界中的使用场景或示例,包括输入和输出数据。
  • 数据设计:
    对应用程序或系统中使用的数据结构和格式的描述,包括数据库和文件格式。
  • 系统设计:
    对应用程序或系统的整体架构和组件的描述,包括图表和流程图。
  • 界面设计:
    对应用程序或系统的用户界面的描述,包括线框图和模型。
  • 算法和代码:
    应用程序或系统中使用的算法和代码的详细描述。

故障排除指南

故障排除指南是一种技术文档,提供了诊断和解决软件应用程序或系统中问题或错误的逐步过程。故障排除指南的目的是帮助用户或支持团队尽快、高效地识别和解决问题。

故障排除指南通常包括以下信息:

  • 症状:
    用户正在经历的问题或错误的描述。
  • 原因:
    问题或错误发生的可能原因,包括常见问题和错误。
  • 解决方案:
    解决问题或错误的逐步过程,包括任何必要的配置更改或软件更新。
  • 技巧和诀窍:
    有关故障排除问题的额外信息和最佳实践,包括常见解决方法和预防措施。

安全规范

安全规范是一种技术文档,描述了软件应用程序或系统的安全要求、指南和程序。安全规范的目的是确保应用程序或系统在设计和实现时考虑了安全性,以防止未经授权的访问、数据泄露和其他安全风险。

安全规范通常包括以下信息:

  • 威胁建模:
    识别应用程序或系统中潜在的安全威胁和漏洞的过程。
  • 安全要求:
    应用程序或系统的安全要求列表,包括身份验证和授权、数据加密和安全通信协议。
  • 安全指南:
    确保应用程序或系统安全性的最佳实践和指南,包括安全编码实践和定期安全审计。
  • 事件响应程序:
    检测、响应和缓解安全事件的程序,包括事件报告和升级程序。
  • 灾难恢复和业务连续性程序:
    从安全事件中恢复并确保在灾难事件发生时业务连续性的程序。

技术指导(Technical tutorials)

技术教程是一种教育性内容,提供了关于如何使用技术或软件完成特定任务或实现特定目标的逐步说明。技术教程可以涵盖广泛的主题,包括编程语言、软件框架、工具和技术以及最佳实践。

技术教程的目的是为读者提供实用的知识和技能,使他们能够解决现实世界中的问题,并提高在特定技术或软件方面的熟练程度。技术教程通常包括示例和代码片段,以帮助读者理解讨论的主题,并可能包括屏幕截图、图表和其他视觉辅助工具。

软件开发指南

软件开发指南是一份全面的文件,提供了关于软件开发整个过程的指导,从最初的规划和需求收集到部署和维护。

软件开发指南的目的是为软件开发团队提供一个清晰而一致的框架,确保在整个软件开发生命周期中遵循最佳实践。软件开发指南通常包括项目管理方法论、软件开发方法论、编码标准、测试实践、部署和发布管理以及维护和支持等信息。

软件开发指南通常由经验丰富的软件开发专业人士创建,包括项目经理、软件架构师和开发人员。它们被软件开发团队使用,以确保项目中的所有人都遵循相同的指导方针和程序,并在整个软件开发生命周期中保持一致性和质量。

软件开发指南可以根据特定组织或项目的具体需求进行定制,并可以针对不同的开发方法论和技术进行调整。它们是任何开发软件并希望确保其软件开发流程高效、有效和高质量的组织的必备资源。

功能列表

功能列表是软件产品文档中的一个部分,列出了产品中所有可用的功能。功能列表的目的是提供对产品功能的全面概述,并帮助用户了解产品的能力和限制。

注:这里的一个功能指的是一个RCP组件或服务的一个能力或功能。颗粒大小相对较小,这里的功能列表不是RCP CB功能列表。例如,RCP用户管理的功能列表如下:

  • 用户管理
  • 查询用户
  • 添加用户
  • 删除用户
  • 修改用户信息
  • 角色管理
  • 查询角色
  • 添加角色
  • 修改角色信息
  • 删除角色

功能列表通常包括对每个功能的简要描述,以及使用该功能所需的任何先决条件或依赖关系。它还可能包括有关如何访问或使用每个功能的信息,以及适用的任何限制或限制。

功能列表通常包含在产品文档中,如用户指南或手册中,并且通常可以在软件供应商的网站上找到。它们是用户了解产品功能的重要资源,可以帮助他们决定某个产品是否符合他们的需求。对于需要跟踪特定产品中包含哪些功能的产品经理和开发人员来说,它们也很有用,并确保所有功能都得到适当的文档支持。

发布说明

发布说明是软件产品文档中的一个部分,提供关于最新版本软件的信息。发布说明的目的是向用户介绍最新版本软件中包含的任何更改、错误修复、新功能和已知问题。

发布说明通常包括对最新版本中所做更改和改进的简要摘要,以及安装或升级到新版本的详细信息。它们还可能包括尚未修复的已知问题或错误列表,以及任何可用的解决方法或解决方案。

发布说明对于升级到软件产品的最新版本的用户来说是重要的资源,因为它们提供了有关更改内容和新版本预期的清晰概述。对于需要向客户或利益相关者传达更改和改进的产品经理和开发人员来说,它们也很有用。

发布说明可以包含在软件产品的文档中,也可以单独发布在软件供应商的网站或其他在线平台上。它们通常会随着软件产品的每个新版本而更新。

现代技术文档范式-文档即代码

documentation as code

什么是 documentation as code 方法

这种方法背后的理念是将文档视为软件源代码的固有部分,因此应使用与开发人员在日常编码相关活动中使用的相同工具和流程来编写和更新文档。

程序员的日常工具包括:

  • 问题跟踪器
  • 版本控制 (Git)
  • 编码 IDE(VS Code、Eclipse、Vim……)
  • 纯文本标记语言(Markdown、reStructuredText、Asciidoc)
  • 代码审查
  • CI/CD

documentation as code 是如何工作的

使用纯文本来写文档

Documentation as Code 背后最重要的思想是文档是用纯文本编写的,纯文本格式的一个优点是它们与开发人员使用的所有 IDE 兼容,因此开发人员可以直接从他们喜欢的 IDE 读取和编辑此类文档与编码方式相同。

这种文件的另一个重要优点是它可以帮助开发人员只关注技术内容,这意味着负责编写文档的团队不必担心它的布局。

由于文档旨在供人类阅读,因此建议采用允许使用基本格式的纯文本格式。技术论文所需的主要格式功能包括:

  • 文本突出显示(粗体、斜体和下划线)
  • 超文本链接
  • 项目符号和编号列表
  • 代码块和片段
  • 标题

利用这些简单的特性,内容的可读性将得到显着提升,同时书写体验也不会过重。

文档版本的控制和管理

如果文档是用纯文本编写的,那么开发人员可以很容易地使用相同的版本控制系统(VCS)来管理文档,这与编码的方式相同,因为我们所有的代码都是用纯文本格式编写的。

使用此类 VCS 工具,开发人员可以轻松跟踪所有文档更改,了解这些更改由谁创建以及何时以他们熟悉的方式进行。

由于它的相对易用性和开源的特性,Git 获得了巨大的财富和广泛的传播。事实上,全球最知名和使用最多的平台,即 GitHub 和 GitLab,都是基于 Git 的。

这些工具提供的一大优势是可以与多个人异步处理同一个文件。感谢分支,任何人都可以在他们的设备上创建文件的一个版本,进行更改,然后创建一个拉/合并请求来实施更改。所有这些都不会直接影响原始文件或正在处理同一文件的其他版本的其他人。这些平台还提供了一个界面来轻松报告和解决冲突(即对文件的同一部分进行两个不同的竞争性编辑)。

可以按照你想要的格式发布文档

文档编写、版本控制和保存后,就可以发布了。然后你会问什么是对用户好的格式?在这里,您可能一定认为将具有基本格式的纯文本文档直接发布给用户不是一个好主意。

是的,确实不是一个好主意,但是我们有工具,使用静态站点生成器 (SSG) 可以轻松地将纯文本文件转换为 HTML,允许您添加 CSS 样式表以增强和品牌外观和感觉,如果需要, 还允许您添加动态 JavaScript 部分:然后所有内容都将在服务器端呈现,为客户端提供加载速度非常快的静态页面。

多亏了 SSG,您可以轻松地将内容与其最终的图形表示解耦:开发团队将只负责技术内容,而视觉方面的责任可能会交给另一个团队。这样,开发人员不必担心选择正确的字体、格式化内容或任何其他此类活动:他们只关心将基本格式直接添加到纯文本文件中。此外,通过这种方式,任何图形更改都可以级联到所有页面,只需编辑一个样式表。

大多数 SSG 还提供了一些附加功能,这些功能极大地增强了内容的可用性,但不一定需要开发团队的手动干预。这些附加功能的示例包括:

  • 带有子部分链接的主页;
  • 侧边栏导航菜单;
  • 搜索引擎;
  • 锚链接到标题;
  • 每页的目录;
  • 发行说明管理;
  • 管理不同语言的内容;
  • 显示以前软件版本的文档。

这些功能本身就非常简单,但它们一起使查找和消费内容变得容易,帮助用户快速找到他们正在寻找的内容。
您甚至可以使用其他工具将纯文本文件转换为其他格式,如 pdf、word 等。

自动化及CI/CD

文档即代码方法的另一个好处是,您可以以与代码相同的方式利用 CI/CD(持续集成和持续交付)系统的自动化。

关于 CI,您可以创建自动化测试,每次在主分支上发出 Pull/Merge 请求时检查内容:此类测试可以检测技术错误(即无效链接、无效代码片段等)或形式错误(即 、拼写错误、缺少标点符号等)。 如果测试失败,团队会收到错误通知并采取措施解决; 如果测试通过,CD 阶段就可以开始了。

作为如何利用 CD 的示例,您将能够创建自动化检测主分支中的所有更改,并自动发布新内容。 这样,您就不必在发布过程上花费时间,而传统上这需要花费大量时间。

使用 documentation as code 维护和发布软件文档的优点

  • 更容易学习纯文本文档

纯文本框架提供了标题、超文本链接、文本突出显示和列表等基本语法。这些语法非常容易使用,同时像 reStructuredText 或 Markdown 这样的纯文本框架为基本语法提供了很好的指导,用户可以轻松学习从例子。

  • 开发人员更快速高效地编写文档

使用他们每天用来编写代码的相同工具,开发人员将能够只专注于内容,从而更快地生成内容。

  • 更好、更准确的文档

通过在编写阶段更快,开发人员将生成更准确的文档,因为编写阶段将在他们想要记录的功能开发后不久发生。另外,如果软件文档和代码在同一个仓库,那么软件变更和文档变更可以在同一个合并请求中,这样审阅者就更容易发现软件和文档之间的不匹配并及时给出评论。

  • 发布文件的格式更灵活

文档即代码方法将内容和图形解耦,开发人员只负责内容的准确性,他们不需要关心最终的文档形式,有很多工具可以用来将纯文本转换为不同的漂亮的脸格式像 SSG 一样,这些工具可以很容易地集成到 CI/CD 系统中,以利用文档发布的自动化。

  • 完整的版本控制

使用文档即代码方法,您将能够轻松跟踪对文档的每一次更改,并且在出现错误时您将能够轻松回滚。此外,版本控制还允许您检查文档是否是最新的。

  • 更轻松的协作

使用文档即代码方法,这些使用的工具旨在鼓励协作,并且通过对它们非常熟悉,开发人员可以更轻松地进行协作,并且他们将更倾向于提出更改和改进的建议。

documentation as code 的工具链

纯文本文档工具

Markdown

Markdown是一种轻量级标记语言,您可以使用它来给纯文本文档添加格式化元素。由John Gruber于2004年创建,Markdown现在是世界上最流行的标记语言之一。

使用Markdown与使用所见即所得(WYSIWYG)编辑器不同。在像Microsoft Word这样的应用程序中,您点击按钮来格式化单词和短语,更改会立即显示出来。但Markdown不是这样的。当您创建一个Markdown格式的文件时,您需要在文本中添加Markdown语法来指示哪些单词和短语应该呈现不同的样式。

例如,要表示一个标题,您在前面加上一个井号(例如,# 标题一)。或者要使短语加粗,您在前后各加两个星号(例如,这段文字加粗)。如果您习惯于使用所见即所得的应用程序,可能需要一些时间来适应在文本中看到Markdown语法。下面的截图显示了在Visual Studio Code文本编辑器中显示的Markdown文件。

您可以使用文本编辑器应用程序向纯文本文件添加Markdown格式化元素。或者,您可以使用适用于macOS、Windows、Linux、iOS和Android操作系统的众多Markdown应用程序。还有一些专为Markdown编写而设计的基于Web的应用程序。

根据您使用的应用程序,您可能无法实时预览格式化的文档,但这没关系。根据Gruber的说法,Markdown语法设计得易读且不显眼,因此即使未呈现,Markdown文件中的文本也是可读的。

Markdonw tools
  • Visual Studio Code
  • GitBook
  • Gitlab Pages
  • GitHub Pages
  • Jekyll
  • MkDocs
  • Docusaurus

Restructure Text

reStructuredText是一种易于阅读、所见即所得的纯文本标记语法和解析系统。它适用于内联程序文档(如Python docstrings)、快速创建简单网页和独立文档。reStructuredText旨在为特定应用领域提供可扩展性。reStructuredText解析器是Docutils的组成部分。reStructuredText是StructuredText和Setext轻量级标记系统的修订和重新解释。

reStructuredText的主要目标是定义和实现一种用于Python docstrings和其他文档领域的标记语法,既易读又简单,同时又足够强大以应对非平凡的使用场景。标记的预期目的是将reStructuredText文档转换为有用的结构化数据格式。

Restructure Text tools
  • Sphinx
  • Read the Docs
  • JEdit
  • ReText
  • Visual Studio Code

MarkDown vs Restructure Text

Diagram as code

图表即代码(Diagram as code)是指使用代码或声明性配置文件来创建图表,而不是传统的图形工具。它涉及使用编程语言或特定的语法来定义图表的结构和元素。

在图表即代码中,您编写描述图表组件、关系和布局的代码。然后,该代码会被工具或框架处理,根据提供的指令生成图表的可视化表示。

使用图表即代码的优点包括:

  • 版本控制和协作:由于图表被表示为代码,可以将其存储在像 Git 这样的版本控制系统中,从而允许多个团队成员协作、跟踪更改和管理修订。

  • 自动化和一致性:可以根据底层代码或配置文件的更改自动生成或更新图表。这确保图表始终是最新的,消除了手动错误或不一致性的风险。

  • 可重用性:以代码方式创建的图表可以在不同项目或环境中轻松重用。可以创建图表组件的模板或库,从而在创建类似图表时更容易维护一致性并节省时间。

  • 可扩展性:由于图表被表示为代码,可以以编程方式生成它们,从而更有效地处理大型或复杂的图表。可以利用循环、条件语句和其他编程结构来动态生成图表。

  • 集成:以代码方式创建的图表可以集成到持续集成/持续交付(CI/CD)流水线或其他自动化工作流中。这使得图表能够与相关的基础设施或应用代码一起自动生成和部署。

PlantUML

PlantUML是一个基于文本的开源工具,用于创建各种类型的图表,例如UML图、流程图、时序图、类图、用例图等。它使用简单的文本语言来描述图表的元素和关系,然后通过渲染引擎生成相应的图表图像。

使用PlantUML,您可以使用简洁的语法描述图表的结构和特征。例如,您可以使用PlantUML创建一个简单的类图如下:

@startuml
class Car {
  + String make
  + String model
  + void start()
  + void accelerate()
  + void brake()
}

class Engine {
  + void start()
  + void stop()
}

Car --> Engine
@enduml

上述代码使用PlantUML的语法描述了一个Car类和一个Engine类之间的关系,通过箭头表示Car类依赖于Engine类。生成的图表将显示两个类及其之间的关系。

PlantUML提供了丰富的语法和标记,使您能够创建复杂的图表。它支持各种图表类型和元素,允许您通过简单的文本描述快速生成专业的图表。您可以将PlantUML集成到文档、代码注释或其他工作流程中,从而方便地创建和维护图表,并保持其与代码同步更新。

此外,PlantUML还支持与其他工具和平台的集成,如IDE编辑器、文档生成器(如Markdown、HTML)、版本控制系统等。它的开放性和可扩展性使其成为软件开发、系统设计和文档编写的有用工具。

Mermaid

Mermaid和PlantUML非常相似,是一个基于JavaScript的绘图和图表工具,它使用受Markdown启发的文本定义和渲染器来创建和修改复杂的图表。Mermaid的主要目的是帮助文档与开发保持同步。

Graphviz

Graphviz是一个开源的图形可视化工具,它使用简单的文本语言来描述图表的结构和特征。它提供了一组命令行工具,可以将文本文件转换为图表图像。Graphviz支持各种图表类型,包括流程图、组织结构图、时序图、类图、用例图等。

API 文档

Doxygen

Doxygen是生成从注释的C++源代码中生成文档的事实上的标准工具,但它也支持其他流行的编程语言,如C、Objective-C、C#、PHP、Java、Python、IDL(Corba、Microsoft和UNO/OpenOffice版本)、Fortran和在一定程度上的D。Doxygen还支持硬件描述语言VHDL。

Doxygen可以通过以下三种方式帮助您:

  • 它可以从一组有注释的源文件中生成在线文档浏览器(HTML)和/或离线参考手册。还支持生成RTF(MS-Word)、PostScript、超链接PDF、压缩HTML和Unix man页的输出。文档直接从源代码中提取,这使得与源代码保持一致的文档更容易。

  • 您可以配置Doxygen从未有注释的源文件中提取代码结构。这对于在大型源代码分发中快速定位非常有用。Doxygen还可以通过包括依赖关系图、继承图和协作图来可视化各个元素之间的关系,这些图都是自动生成的。

  • 您还可以使用Doxygen创建常规文档(就像我为Doxygen用户手册和网站所做的那样)。
    Doxygen在Mac OS X和Linux下开发,但也具有高度可移植性。因此,它也可以在大多数其他Unix版本上运行。此外,还提供了Windows的可执行文件。

程序员的修行之路-保持良好的心态

保持良好的心态

其实,在不同的领域或行业,都会面临来自各方面的压力,那些能保持良好心态的人,总是能够走的最远。但是,如何能够保持良好的心态呢?面对物欲横流,充满着各种诱惑的世界,你是否能够保持清醒的头脑?马云抛起的互联网创业浪潮,让无数IT程序员们蠢蠢欲动,你是否能够保持冷静?IT和互联网总是不断有新的技术涌现,你是能否保持开放,不断的学习和探索新的技术?还有,有些公司虽然给你良好的福利,但是加班加到吐血,你是否愿意付出并坚持呢?

刚跳槽到华为工作的时候,遇到的是全新的领域,感觉工作步态上总是必别人慢半拍,那时候技术视野还不宽,看到的总是自己工作上的点东西,工作上没有得到领导的认可,心里未免有些急躁,再加上加班,感觉工作非常枯燥,也想过放弃。在计算机领域,我不是科班出身,所以在技术上总是保持着虚心学习的心态,每次PL找我沟通的时候,聊起我这方面要提升,那方面要提升,我总是虚心接受的,但是不免让人很有挫折感,我做的好的一面,总没有被看到。曾经看到过一篇文章,讲到了人生如行路,如果你走弯路,你就会看到更多的风景,相反你选择走直路,虽然更快的到达了终点,却错过了沿途的美景。因此我明白一点,不管你成长的快慢,只要你认真做一件事情,不管你遇到多少挫折,都会从中学到很多,自己认可自己非常重要。

其实良好心态最大的敌人就是你的功利心,一定程度的功利心是需要的,这可以不断的鞭策自己进步提升和完善,但是人如果过度的陷入到比较和追逐,就会丧失平衡,从而变的浮躁。要注重自己点滴的积累,看到进步,才能进一步清醒的认识自己。另外,我觉得保持身心健康非常重要,现在整个IT及互联网领域都是急功近利的,日夜的加班损耗了太多人工作热情,所以不断有人抑郁。这个是大的环境,很难一时间改变,但是作为个人,需要清醒的了解,不要迫于压力,就无限制的消耗自己,做到最大的平衡工作和生活,我个人觉得,人的一生不能只是工作和奔波,要适当的慢下来,读读书,听听音乐,参与聚会或者和家人看看电视,喝喝茶,这些都是生活的一部分,毕竟生命是短暂的。程序员要整天对这电脑,是非常不健康的,要多运动,保持健康的身心。

程序员的修行之路-培养工作兴趣

培养工作兴趣

你是为钱而工作还是为理想而工作?看中国好声音的时候,里面导师问的最多的问题就是“你的理想是什么?”,歌手们千篇一律的回答,无非就是坚持音乐,拥有更大的舞台,拥有更多的粉丝。可是我想问,我们真的能为理想而活。考大学的时候,我的理想是学物理,可偏偏被数学录取,考研的时候,觉得自己还有点天赋,希望在数学方面继续深造,可阴差阳错被分到了算法(模式识别图形处理)方向,到了毕业找工作,觉得我这个方向以后会有不错的发展,而且自己刚刚上手,结果大失所望,这个领域很难找到饭吃,最终做个普普通通的程序员。是不是觉得相当的苦逼,其实这不只是我的经历,也许有很多人跟我一样,有着相似的命运,这就是现实和理想的冲突,面对现实我们其实没有多余的选择而已。但是,反过来想,理想总是太高远,有时候飘忽不定,很多时候人很难对自己有准确的定位,随着不断的成长,你会对生活又有了新的想法,最初的理想早已被抛在九霄云外,所以,回到我最初的问题,你是为钱而工作还是为理想而工作?我想大多数人会选择最务实的答案,经济社会本来就是务实的。但是我如果将问题稍微修改一下,你是想为钱而工作还是想为兴趣而工作?你会有什么样的答案呢。

社会太务实,我们谈不了理想,只能退而求其次,谈谈兴趣咯。想想看,如果没有找到自己理想的工作,工作中又没有自己的兴趣点,那这样的工作会变的何等的乏味枯燥。因此在工作中,要注意培养自己的兴趣,带着兴趣工作,你会发现工作不再枯燥乏味,反而会增加你工作的动力,同时也会拓展你的视野,学习到新的东西。IT领域日新月异,更新换代很快,不断有新的技术涌现,程序员只有不断的学习,补充新知识,才能紧跟科技潮流的步伐。因此,程序员必须培养自己对技术的热衷和好奇心,学会不断的探索,才能不断的完善自己,增强技能和战斗力。

如何培养自己在技术方面的兴趣?我可以结合我自己的经历来讲述。记得读研的时候,我一个纯数学方向的本科生转到了模式识别图像处理算法方向读研,对于编程,除了考计算机二级时的那点皮毛,真的是一点基础都没有,虽然因为有比较好的数学背景,对于理论的学习相对比较容易,但是对于算法实现,感觉自己非常吃力。为了补计算机及编程基础,除了自学C及C++编程基础的书来读以外,就是每周都会选几节本科生的计算机课去听,一学期下来,总算有一些收获,感触最深的是,对C++的学习,C还是一知半解的情况下,学C++真的是非常吃力,c++primer我至少读了两三遍。学了这么多,最重要的其实还是实践,一直觉得自己对理论的理解是比较快的,但是动手编程的能力比较差,后来在学习机器学习的时候,专门选了几个典型的算法,在网络上找了很多参考的例子,自己动手实现了,影响最深的是使用前馈神经网络识别阿拉伯数字,最终识别率只能达到89%左右(我那个时候好的算法能达到95%左右),虽然不算高,但是对我来说已经算非常兴奋的事情了,就这件事情本身来说,让我对编写程序,产生了极大的兴趣,这就是成就感所产生的化学作用。

刚工作的一年多,感觉自己在工作上的压力非常大,不断有新的东西要学习,同时又经常被项目经理劈头盖脸的批评搞的很受挫,那段时间曾经怀疑过自己,是否真的不适合这个行业。不过还是坚持了下来,一年多的时间,不断的在项目上得到认可,同时在软件的分析和设计上有了很快的成长,最终我在离开我第一家公司之前,从设计到开发,由我单独负责一个子系统。有时候人需要在压力下,不断挖掘自己的潜力,你才能对自己有新的发现。后来跳槽进了华为,原因当然是为了钱啊,当初给我开的工资是当初的两三倍,不来才傻缺呢,不过还有个根本的原因就是,我在第一家公司能够学到的东西感觉已经就那么多了,再呆下去,我能看到自己还是干着类似的活,做类似的产品,进步的空间太有限。来华为后,我从windows 界面开发,转向了linux云计算,对我来说完全是全新的领域,而且也颠覆了我对软件开发的认识,在华为的几年时间里,我做过很多项目,从开发到维护,同时也拿到过专利,抛开工作的艰辛外,我想我的收获是满满的,尤其是逐渐培养了我高效的工作方法,和分析问题的能力,同时让我接触到了开源软件,可以在开源软件里找到自己感兴趣的项目进行学习。从我个人的经历来说,如果你想进步,首先需要得到自己的认可,并且能够从自己工作的项目中找到成就感,同时拓展自己的兴趣视野,这样才能不断的激发自己的工作热情。因此我觉得如果你要热爱程序员这个行业,最重要的就是不断培养自己的兴趣和好奇心。

目录 上一节 下一节

云计算技术栈

云计算技术栈

介绍

本文将会通过思维导图列出云计算相关的技术栈,以便于大家了解云计算的相关技术。但是不会深入的介绍,后续的文章会详细涉猎。

云计算(Cloud Computing)的核心概念可以概括为:

  • 按需使用 云计算允许用户根据实际需求按需获取计算资源,包括CPU、内存、存储、带宽等,无需预先采购硬件设备。用户可根据业务发展实时扩缩资源使用量。
  • 资源虚拟化 通过虚拟化技术,将物理资源进行抽象和整合,形成资源池,实现资源可计量、可编目、可调度和快速重新分配。
  • 资源池共享 云计算将大量计算资源集中在资源池中,用户可以按需共享资源池中的资源,降低空闲资源浪费。
  • 通过网络交付服 资源和服务都通过互联网以服务的方式提供给用户,用户可以通过网络随时随地访问所需的资源和服务。
  • 按使用量付费 用户只需为实际使用的资源和服务付费,付费模式灵活,无需预先采购硬件设备或软件。

云计算的核心思想是将计算资源进行池化和虚拟化,通过网络以服务的形式进行按需交付和按量付费,提高资源利用率,降低IT成本。

云计算关键技术:

  • 虚拟化:将物理资源(如服务器、存储、网络)抽象化为逻辑资源,并以服务的方式提供给用户。
  • 分布式计算:将计算任务分配到多个计算节点上执行,以提高计算能力和可靠性。
  • 云存储:将数据存储在分布式的存储系统中,并以服务的方式提供给用户。
  • 云网络:将传统的网络资源虚拟化为逻辑网络,并以服务的方式提供给用户。

云计算服务模式:

  • 基础设施即服务(IaaS):为用户提供虚拟化的计算、存储和网络资源。
  • 平台即服务(PaaS):为用户提供开发、部署和运行应用程序的平台。
  • 软件即服务(SaaS):为用户提供应用程序服务,用户无需安装和维护软件。

部署模式:

  • 公有云:云资源由云服务提供商提供,并向所有用户开放。
  • 私有云:云资源部署在用户的专用网络中,仅供用户内部使用。
  • 混合云:将公有云和私有云结合起来使用,以满足用户的不同需求。

优势:

  • 弹性性:用户可以根据需求,随时扩展或缩减IT资源。
  • 按需付费:用户仅需为实际使用的资源付费。
  • 可靠性:云服务提供商通常拥有强大的基础设施,可以确保服务的可靠性。
  • 易用性:用户可以轻松地使用云服务,无需具备专业的IT知识。

应用场景:

  • Web应用程序:云计算是开发和部署Web应用程序的理想平台。
  • 大数据分析:云计算可以提供强大的计算能力和存储空间,以满足大数据分析的需求。
  • 移动应用:云计算可以为移动应用提供后端服务和数据存储。
  • 物联网:云计算可以连接和管理大量的物联网设备。

云计算技术图谱(开发人员)

  • 云计算
    • IaaS
    • 基础设施
      • 虚拟化
      • 半虚拟化
        • kvm
        • xen
      • 全虚拟化
        • QEMU
      • 计算虚拟化
        • CPU虚拟化
        • 内存虚拟化
      • 存储虚拟化
        • 文件虚拟化
        • 块虚拟化
        • 对象虚拟化
        • 存储网关
        • 存储卷
        • 存储池
        • 存储快照
        • 存储克隆
        • 存储迁移
      • 网络虚拟化
        • Linux bridge
        • Open vSwitch
        • DPDK
        • SR-IOV
        • VXLAN
        • GRE
        • IPsec
        • OpenFlow
        • NFV
        • SDN
        • iptables
        • Tools
        • ip
        • ifconfig
        • brctl
        • ovs-vsctl
        • ovs-ofctl
        • ethtool
        • tcpdump
        • wireshark
        • nmap
        • iptables
        • iperf
        • netstat
        • sar
      • 容器化
      • docker
        • container
        • image
        • registry
        • compose
        • network
        • storage
        • security
      • LXC
      • rkt
      • OCI
      • CRI
      • CNI
      • containerd
      • runc
      • k8s
    • PaaS
    • OpenStack
    • CloudStack
    • Kubernetes
      • 框架
      • etcd
      • api-server
      • controller-manager
      • scheduler
      • kubelet
      • kube-proxy
      • cni
      • cri
      • containerd
      • runc
      • Resources
        • pod
        • service
        • ingress
        • volume
        • configmap
        • secret
        • networkpolicy
        • statefulset
        • daemonset
        • job
        • cronjob
        • autoscale
        • security
        • logging
    • Docker Swarm
    • SaaS
    • 软件即服务

Mind Map

云计算知识图

该博客会持续更新,请关注!

软件匠艺之良好单元测试的必要性

单元测试

什么是单元测试

你可以在互联网上找到众多的定义,以下我引用维基百科的定义和《单元测试艺术》的定义。

单元测试是一段测试代码,它调用软件代码的某个工作单元,并检查该工作单元的一个特定功能和最终执行结果。 如果期望的结果和最终执行的结果不一致,则单元测试就会失败。 一个单元测试的范围可以小到一个方法,也可以大到多个类。 –《单元测试艺术》

单元测试(英语:Unit Testing)又称为模块测试,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。 — 维基百科

如果在没有单元测试的情况下,程序员通常在写完代码时,需要手动运行程序或者通过调试工具来检验代码功能是否满足期望,引入单元测试后,就是将这部分工作完全自动化,使用代码来测试代码。因此程序员是编写单元测试的第一责任人,因为程序员在编写软件代码时,需要保证代码交付的质量。执行单元测试,就是为了证明这段代码的行为和我们期望的一致。单元测试是对软件设计的最小单位进行正确性检查的测试工作,其测试目的在于发现模块内部存在的各种错误。单元测试的要点是进行单元模块所有数据项的正确性、完善性测试,主要关注模块的算法细节和模块接口间流动的数据。

为什么需要单元测试

想想看我在A公司经历的情况,整个软件系统没有任何自动化测试用例支撑,只是通过部分手动测试验证功能的可用性是远远不够的,这样的软件系统其实是维护人员的噩梦,因为深藏在代码里的bug就像一颗颗定时炸弹,随时都有可能让你的系统崩溃。
通过使用单元测试,软件质量可以达到一下目标:

  • 软件得到充分的测试

    通过单元测试的定义可以看出,单元测试的对象其实是函数或者类,属于软件系统的最小功能单元。由于只关注软件的最小部件,因此测试目标和测试用例比较容易建立,不容易遗漏,测试覆盖会更加充分。

  • 软件的异常分支得到良好的覆盖

    手动测试相比单元测试最大的弱点是无法充分的覆盖异常情况,手动测试更多的只能验证软件的功能,有些异常很难模拟。但是单元测试可以通过使用测试框架,对软件单元外界的异常进行模拟,可以对异常分支进行充分的验证。

  • 测试的自动化保证了软件系统能够得到持续系统验证

    单元测试用例的集合和代码一样,都是软件生命周期中的重要组成部分,当软件系统在不断演进的过程中,单元测试用例的集合可以保证系统功能按照期望的方式运行,新增的代码对原有系统功能没有影响。单元测试用例的集合可以帮助程序员快速的发现新功能对原有功能的冲击和影响。

  • 单元测试帮助程序员尽快的发现问题,而不是隐藏问题

    传统的开发模式下,程序员只关注软件需求和功能的实现,因此软件代码都是一气呵成,聪明的程序员写代码的热情一旦被激发,就会一发不可收拾,当软件主要功能的代码实现后,他们才会去考虑功能及可靠性的测试。在这种开发模式下,一个很大的缺陷是程序员的手动测试用例很难充分覆盖,因此时常会出现漏测,和考虑不周的测试,很多隐藏的异常问题很难被发现。同时,当程序员手动测试发现问题时,调查和解决问题的效率很低,由于新增和修改的代码太多,并且很多代码已经是几天或者数周前写的,此时程序员早已对旧代码的逻辑变得陌生,很难通过阅读代码快速发现问题,必须借助一些调试工具(GDB 等)进行调试。我就有类似的经历,职业之初时常因为一个简单的逻辑和拼写错误耗费数小时更甚于数天的时间来定位。
    TODO: picture for 传统的开发调试模式
    如果程序员采用TDD(即测试驱动开发)开发模式的话,单元测试用例是在开发业务代码之前编写,因此每当你完成部分业务代码,通过运行测试用例,程序员可以快速得到反馈,由于开发测试用例和业务代码几乎是同时进行的,因此当用例失败时,程序员需要定位的代码片段非常小,并且这些代码几乎是在十几分钟之前开发的,程序员清楚的知道自己修改了什么,很多时候只需要通过快速的阅读代码就可以解决问题。
    单元测试的另外一个好处是,当程序员对已有软件系统进行修改时,通过运行单元测试可以得到快速反馈,如果受到影响,则可以快速发现问题,不用推迟到集成测试阶段。并且单元测试发现的问题要比集成测试发现的问题更容易调查和调试。

写出良好的单元测试的必要性

通过我在B公司X团队经历证明,通过覆盖充分的测试用例并不一定能在软件生命周期中带来正向反馈,相反,那些随着时间推移逐渐腐臭的测试用例代码,反而成了程序员的噩梦,这样的用例无法在保证产品质量上带来任何好处。实践证明,通过覆盖率单纯的来衡量单元测试质量会适得其反,聪明的程序员们常常会投机取巧,为了满足覆盖率而做一些对质量毫无意义的事情,恰恰是这样的作法,使得单元测试的质量大打折扣,随着时间的推移变成了累赘。

单元测试诚然是保证代码质量的一项关键技术,但是程序员首先要认识到,单元测试代码的质量和产品代码的质量同样重要。其次需要掌握生产良好单元测试代码的相关技术。

我可以列举一些糟糕单元测试的例子,以及他们的副作用:

  • 糟糕的可读性
  • 单个测试用例测试多个场景
  • 用例执行慢
  • 测试用例依赖于外部环境
  • 过多的重复代码

良好的单元测试具有以下一些属性:

  • 能够完全自动化;
  • 完全控制所有正在运行的部分(在需要时使用模拟或存根来实现这种隔离);
  • 如果是许多其他测试的一部分,可以按任何顺序运行;
  • 在内存中运行(例如,无 DB 或文件访问);
  • 始终返回相同的结果(例如,您始终运行相同的测试,因此没有随机数。保存那些用于集成或范围测试);
  • 运行快(单个用例执行只需要几毫秒);
  • 测试系统中的单个逻辑概念;
  • 良好的可读性;
  • 良好的可维护性;
  • 值得信赖(当你看到它的结果时,你不需要为了确定而调试代码);

如何使用timerfd-超时timer,定时timer和不定时timer

如何使用timerfd-超时timer,定时timer和不定时timer

timerfd 是Linux为用户程序提供的一个定时器接口。这个接口基于文件描述符,通过文件描述符的可读事件进行超时通知,所以能够被用于select/poll的应用场景,本文将通过使用epoll接口实现超时timer,定时timer和不定时timer。

首先对timerfd的相关接口进行说明:

头文件


#include <sys/timerfd.h>

创建timerfd的文件描述符:


int timerfd_create(int clockid, int flags);

参数clockid用来设置timer相关的时钟类型,有一下类型:


CLOCK_REALTIME: 可设置的系统范围的实时时钟
CLOCK_MONOTONIC: 不可设置的单调递增时钟
          从过去某个未指定的点开始的时间
           系统启动后更改。

CLOCK_BOOTTIME(从Linux 3.15开始): 
                  像CLOCK_MONOTONIC一样,这是单调递增的
          时钟。但是,尽管CLOCK_MONOTONIC时钟不
          测量系统挂起的时间,
          CLOCK_BOOTTIME时钟确实包括
          系统已挂起。这对于以下应用程序很有用
          需要保持挂起状态。 CLOCK_REALTIME不适合
          这类应用,因为该时钟受
          系统时钟的不连续更改。
CLOCK_REALTIME_ALARM(从Linux 3.11开始):
          该时钟类似于CLOCK_REALTIME,但是如果以下情况将唤醒系统
          它被暂停了。呼叫者必须具有CAP_WAKE_ALARM
          以便针对此时钟设置计时器。
CLOCK_BOOTTIME_ALARM(从Linux 3.11开始):
          该时钟类似于CLOCK_BOOTTIME,但是如果以下情况将唤醒系统
          它被暂停了。呼叫者必须具有CAP_WAKE_ALARM
          以便针对此时钟设置计时器。

设置超时时间及间隔:


int timerfd_settime(int fd, int flags,
                           const struct itimerspec *new_value,
                           struct itimerspec *old_value);
// timerfd_settime 用来启动或者停止timerfd描述符的计时器,参数new_value为以下结构:
struct timespec {
    time_t tv_sec; / *秒* /
    long tv_nsec; / *纳秒* /
};
struct itimerspec {
    struct timespec it_interval; / *定期计时器的间隔* /
    struct timespec it_value; / *初始到期时间* /
};

// new_value.it_value 指定计时器的初始到期时间,两个字段秒和纳秒。将new_value.it_value的任何字段设置为非零值将使计时器计时。将new_value.it_value两个字段设置为零将使计时器撤销。
// 将new_value.it_interval的一个或两个字段设置为非零值指定重复计时器的周期(以秒和纳秒为单位)初始到期后到期。如果两个字段都new_value.it_interval为零,计时器仅过期一次,此时由new_value.it_value指定超时时间。

获取timerfd的计时器设置:


timerfd_gettime(int fd, struct itimerspec *curr_value);

改函数返回当前描述符所对应的计时器设置。

使用timerfd和epoll实现超时单次的timer


#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/timerfd.h>
#include <time.h>

static char *itimerspec_dump(struct itimerspec *ts);

int randomf(int min, int max) {
    srand(time(NULL));
    return min + rand() / (RAND_MAX / (max - min + 1) + 1);
}

int main()
{
    int tfd, epfd, ret;
    struct epoll_event ev;
    struct itimerspec ts;
    int sec = 10; //设置timer10秒后超时
    uint64_t res;

    printf("testcase start\n");

    tfd = timerfd_create(CLOCK_MONOTONIC, 0); //创建定时器的文件描述符
    if (tfd == -1) {
        printf("timerfd_create() failed: errno=%d\n", errno);
        return EXIT_FAILURE;
    }
    printf("created timerfd %d\n", tfd);

    ts.it_interval.tv_sec = 0;
    ts.it_interval.tv_nsec = 0; //设置interval的两项都为0时,定时器只超时一次
    ts.it_value.tv_sec = sec; //定时器启动10秒后超时
    ts.it_value.tv_nsec = 0;

    epfd = epoll_create(1); //创建epoll对象
    if (epfd == -1) {
        printf("epoll_create() failed: errno=%d\n", errno);
        close(tfd);
        return EXIT_FAILURE;
    }
    printf("created epollfd %d\n", epfd);

    ev.events = EPOLLIN;
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, tfd, &ev) == -1) { //向Epoll上注册timerfd的 EPOLLIN事件
        printf("epoll_ctl(ADD) failed: errno=%d\n", errno);
        close(epfd);
        close(tfd);
        return EXIT_FAILURE;
    }
    printf("added timerfd to epoll set\n");

    if (timerfd_settime(tfd, 0, &ts, NULL) < 0) { //设置超时时间,并启动定时器
        printf("timerfd_settime() failed: errno=%d\n", errno);
        close(tfd);
        return EXIT_FAILURE;
    }
    printf("set timerfd time=%s\n", itimerspec_dump(&ts));

    sleep(1);

    while(1){
        memset(&ev, 0, sizeof(ev));
        ret = epoll_wait(epfd, &ev, 1, 500); //等待epoll时间
        if (ret < 0) {
            printf("epoll_wait() failed: errno=%d\n", errno);
            close(epfd);
            close(tfd);
            return EXIT_FAILURE;
        }
        printf("waited on epoll, ret=%d\n", ret); //timer 超时后会打印改条记录

        ret = read(tfd, &res, sizeof(res)); //超时后必须读取timer 文件描述符上的数据,否则timerfd无法正常运行
        printf("read() returned %d, res=%" PRIu64 "\n", ret, res);

    }

    if (close(epfd) == -1) { //使用完毕,关闭epoll 文件描述符
        printf("failed to close epollfd: errno=%d\n", errno);
        return EXIT_FAILURE;
    }

    if (close(tfd) == -1) {//使用完毕后关闭timer 的文件描述符
        printf("failed to close timerfd: errno=%d\n", errno);
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

static char *
itimerspec_dump(struct itimerspec *ts)
{
    static char buf[1024];

    snprintf(buf, sizeof(buf),
            "itimer: [ interval=%lu s %lu ns, next expire=%lu s %lu ns ]",
            ts->it_interval.tv_sec,
            ts->it_interval.tv_nsec,
            ts->it_value.tv_sec,
            ts->it_value.tv_nsec
           );

    return (buf);
}

使用timerfd和epoll实现重复的定时的timer


#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/timerfd.h>
#include <time.h>

static char *itimerspec_dump(struct itimerspec *ts);

int randomf(int min, int max) {
    srand(time(NULL));
    return min + rand() / (RAND_MAX / (max - min + 1) + 1);
}

int main()
{
    int tfd, epfd, ret;
    struct epoll_event ev;
    struct itimerspec ts;
    int sec = 10; //设置timer10秒后超时
    uint64_t res;

    printf("testcase start\n");

    tfd = timerfd_create(CLOCK_MONOTONIC, 0); //创建定时器的文件描述符
    if (tfd == -1) {
        printf("timerfd_create() failed: errno=%d\n", errno);
        return EXIT_FAILURE;
    }
    printf("created timerfd %d\n", tfd);

    ts.it_interval.tv_sec = sec; //定时器第一次超时后,将重复每10秒超时一次
    ts.it_interval.tv_nsec = 0;
    ts.it_value.tv_sec = sec; //定时器启动后10秒后超时
    ts.it_value.tv_nsec = 0;

    epfd = epoll_create(1); //创建epoll对象
    if (epfd == -1) {
        printf("epoll_create() failed: errno=%d\n", errno);
        close(tfd);
        return EXIT_FAILURE;
    }
    printf("created epollfd %d\n", epfd);

    ev.events = EPOLLIN;
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, tfd, &ev) == -1) { //向Epoll上注册timerfd的 EPOLLIN事件
        printf("epoll_ctl(ADD) failed: errno=%d\n", errno);
        close(epfd);
        close(tfd);
        return EXIT_FAILURE;
    }
    printf("added timerfd to epoll set\n");

    if (timerfd_settime(tfd, 0, &ts, NULL) < 0) { //设置超时时间,并启动定时器
        printf("timerfd_settime() failed: errno=%d\n", errno);
        close(tfd);
        return EXIT_FAILURE;
    }
    printf("set timerfd time=%s\n", itimerspec_dump(&ts));

    sleep(1);

    while(1){
        memset(&ev, 0, sizeof(ev));
        ret = epoll_wait(epfd, &ev, 1, 500); //等待epoll时间
        if (ret < 0) {
            printf("epoll_wait() failed: errno=%d\n", errno);
            close(epfd);
            close(tfd);
            return EXIT_FAILURE;
        }
        printf("waited on epoll, ret=%d\n", ret); //timer 超时后会打印改条记录

        ret = read(tfd, &res, sizeof(res)); //超时后必须读取timer 文件描述符上的数据,否则timerfd无法正常运行
        printf("read() returned %d, res=%" PRIu64 "\n", ret, res);

    }

    if (close(epfd) == -1) { //使用完毕,关闭epoll 文件描述符
        printf("failed to close epollfd: errno=%d\n", errno);
        return EXIT_FAILURE;
    }

    if (close(tfd) == -1) {//使用完毕后关闭timer 的文件描述符
        printf("failed to close timerfd: errno=%d\n", errno);
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

static char *
itimerspec_dump(struct itimerspec *ts)
{
    static char buf[1024];

    snprintf(buf, sizeof(buf),
            "itimer: [ interval=%lu s %lu ns, next expire=%lu s %lu ns ]",
            ts->it_interval.tv_sec,
            ts->it_interval.tv_nsec,
            ts->it_value.tv_sec,
            ts->it_value.tv_nsec
           );

    return (buf);
}

使用timerfd和epoll实现时间间隔不定时的timer

有些情况下,会有一些特殊的需求,timer的间隔时间不是定时的,此处将以间隔为1~10的随机值为例子。


#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/timerfd.h>
#include <time.h>

static char *itimerspec_dump(struct itimerspec *ts);

int randomf(int min, int max) {
    srand(time(NULL));
    return min + rand() / (RAND_MAX / (max - min + 1) + 1);
}

int main()
{
    int tfd, epfd, ret;
    struct epoll_event ev;
    struct itimerspec ts;
    int sec = 10; //设置timer10秒后超时
    uint64_t res;

    printf("testcase start\n");

    tfd = timerfd_create(CLOCK_MONOTONIC, 0); //创建定时器的文件描述符
    if (tfd == -1) {
        printf("timerfd_create() failed: errno=%d\n", errno);
        return EXIT_FAILURE;
    }
    printf("created timerfd %d\n", tfd);

    ts.it_interval.tv_sec = 0;
    ts.it_interval.tv_nsec = 0; //设置interval的两项都为0时,定时器只超时一次
    ts.it_value.tv_sec = sec; //定时器启动10秒后超时
    ts.it_value.tv_nsec = 0;

    epfd = epoll_create(1); //创建epoll对象
    if (epfd == -1) {
        printf("epoll_create() failed: errno=%d\n", errno);
        close(tfd);
        return EXIT_FAILURE;
    }
    printf("created epollfd %d\n", epfd);

    ev.events = EPOLLIN;
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, tfd, &ev) == -1) { //向Epoll上注册timerfd的 EPOLLIN事件
        printf("epoll_ctl(ADD) failed: errno=%d\n", errno);
        close(epfd);
        close(tfd);
        return EXIT_FAILURE;
    }
    printf("added timerfd to epoll set\n");

    if (timerfd_settime(tfd, 0, &ts, NULL) < 0) { //设置超时时间,并启动定时器
        printf("timerfd_settime() failed: errno=%d\n", errno);
        close(tfd);
        return EXIT_FAILURE;
    }
    printf("set timerfd time=%s\n", itimerspec_dump(&ts));

    sleep(1);

    while(1){
        memset(&ev, 0, sizeof(ev));
        ret = epoll_wait(epfd, &ev, 1, 500); //等待epoll时间
        if (ret < 0) {
            printf("epoll_wait() failed: errno=%d\n", errno);
            close(epfd);
            close(tfd);
            return EXIT_FAILURE;
        }
        printf("waited on epoll, ret=%d\n", ret); //timer 超时后会打印改条记录

        ret = read(tfd, &res, sizeof(res)); //超时后必须读取timer 文件描述符上的数据,否则timerfd无法正常运行
        printf("read() returned %d, res=%" PRIu64 "\n", ret, res);

        ts.it_interval.tv_sec = 0;
        ts.it_interval.tv_nsec = 0;
        ts.it_value.tv_sec = randomf(1, 10); //重新设置定时器的初次超时时间为1~10的随机数,
                                             //相当于再每次超时后重新启动timer计时器
        ts.it_value.tv_nsec = 0;

        if (timerfd_settime(tfd, 0, &ts, NULL) < 0) {
            printf("timerfd_settime() failed: errno=%d\n", errno);
            close(tfd);
            return EXIT_FAILURE;
        }
        printf("set timerfd time=%s\n", itimerspec_dump(&ts));

    }

    if (close(epfd) == -1) { //使用完毕,关闭epoll 文件描述符
        printf("failed to close epollfd: errno=%d\n", errno);
        return EXIT_FAILURE;
    }

    if (close(tfd) == -1) {//使用完毕后关闭timer 的文件描述符
        printf("failed to close timerfd: errno=%d\n", errno);
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

static char *
itimerspec_dump(struct itimerspec *ts)
{
    static char buf[1024];

    snprintf(buf, sizeof(buf),
            "itimer: [ interval=%lu s %lu ns, next expire=%lu s %lu ns ]",
            ts->it_interval.tv_sec,
            ts->it_interval.tv_nsec,
            ts->it_value.tv_sec,
            ts->it_value.tv_nsec
           );

    return (buf);
}

质量在于充分完备的测试,尽早发现问题

质量在于充分完备的测试,尽早发现问题

  任何软件产品,在交付给客户或者用户之前,必须经过严格并且充分的测试,由此才能保证软件产品功能可用,易用,并且能够可靠持续的提供服务。道理很简单,只有稳定可靠的软件服务才能在用户或者客户群里赢得良好的声誉,从而赚取更多的利润。相反随时崩溃的程序带给用户的只有极其差劲的体验,我想说只有傻瓜才愿意为这样的软件服务买单。如果你的软件面对的不是直接用户,而是中间的供应商,那么你就更应该保证软件的质量,在这种情况下,任何软件的bug都可能给你的客户带来极大的损失,你的公司也会因此丢失这样的客户,同时使你在同样的客户群中失去信誉,因此作为软件提供商,必须清醒的知道,应该用高质量的软件来成就客户才能赢得双赢的局面。

  软件测试如果按照测试阶段分类的话,主要分为单元测试、集成测试、系统测试和验收测试。其中单元测试按照测试方式的话,属于白盒测试,是程序员在写代码的过程中必须完成的测试,距离编码最近,是能够最早发现程序问题的一种测试手段。由于单元测试能够帮助程序员在编码的早期阶段发现问题,程序员可以快速定位到问题的根因,因此解决问题的成本几乎可以忽略不记。

  单元测试是保证程序质量的第一步,因此程序员对此必须要有清醒的认知,业务代码和单元测试的代码具有相同的重要性。

  在这里,我想用我的个人职业生涯的经历来说明,怎么做才是提高软件质量的正确做法。

  我硕士毕业后的第一份工作是在A公司,正是在A公司,奠定了我十多年软件职业的基础,但是在A公司工作的三年里,我甚至没有听过关于单元测试的任何讨论,我个人甚至没有接触单元测试的概念,在A公司,程序员们按照需求编写相应的软件功能,然后直接交给测试同事,由于编写的软件是windows界面,测试的同事们也不了解什么是自动化测试,以我现在的目光来看,在A公司,测试同事们使用最原始的方式进行测试,不借助任何工具。而程序员们只关注编码,测试并不是自己的责任。当你看到这里,也许你很好奇,以这样的方式开发的软件是否真的可靠?我的直观感受是,我们整个团队,其实对软件的质量并没有十足的自信,因为每次客户在使用我们的软件系统时,都要求我们有足够的技术团队的现场支持,我甚至好几次碰到过在客户现场,团队惊慌失措的解决临时问题,也许这问题就是个低级错误。

  我刚进A公司的时候,我曾经作为技术支持接手过一套软硬件系统,原作者在我进公司的不久前离职了,他是大家公认的牛人,写这套软件的时候据说是起早贪黑在很短的时间内快速完成了需求任务。提到这位同事的时候,公司的领导和其他同事都会眼前一亮,说他在写程序方面非常厉害,佩服的五体投地。而我一个新手可就没那么容易了,当我阅读他写的代码时,满篇的代码都是简写,简短的命名,没有任何注释,深度的嵌套,以及错综的分支,这样的代码很难说具有可读性。为了读懂和理解代码逻辑,我不得不找到硬件的开发文档,同时找了一台设备,自己手动测试加单步调试。我不得不说,这是一套复杂而脆弱的系统,因为几乎没有任何测试就交付给客户使用了。很多次我都是在现场解决一些奇怪的问题同时被一些客户破口大骂。对这样的程序员,我曾有过一丝的崇拜,可是当我在软件工程这条道路上逐渐成熟的时候,我意识到应该对聪明的程序员和专业的程序员加以区分,我觉得我的这位前辈只能归为聪明的程序员,而我更喜欢后者,坚持让自己逐渐成长为专业的程序员。

  当我意识到在A公司成长有限时,我毅然决定离开,选择了远在其他城市的B公司,B公司是国内比较大通信及软件供应商,具有完整的质量管理体系,领导层也具有远见卓识,是国内数一数二的技术型企业,正是在B公司工作的很多年里,让我深入接触了Linux 平台,在软件开发领域打开新的眼界,我的技术能力也得到了快速的发展。正是在B公司,我开始接触到单元测试和TDD的概念,同时我也开始听到了周围一些同事对单元测试的讨论。可是遗憾的是,我现在看来,我们当时的团队X在采用TDD开发模式上是失败的。团队在一开始软件开发时,就设定了单元测试覆盖率需要达到95%以上,但是由于团队成员资历尚浅,缺乏资深专家的正确引导,并没有严格的按照TDD 所谓的先写测试,后写业务代码的流程做开发,大部分的人还是先写业务逻辑的代码,然后再考虑UT覆盖,甚至很多人只是为了满足覆盖率,让用例蒙混过关。我身边所有的同事都认为,单元测试的代码是用来测试的,所以单元测试的代码质量并不重要,这导致团队开发人员对于如何写好测试用例根本没有认知。最后导致的结果是,单元测试没有帮助程序员有效的发现问题,反而成了项目维护的累赘。最典型的问题是当后期代码有任何变动的时候,导致很多用例失败,程序员在维护测试用例上花了很多成本。

  有趣的是,X团队里,起初大家认可单元测试的有效性,可是到后期就开始蔓延单元测试无用论。

  当我从B公司的X团队转到Y团队时,由于Y团队基于开源软件做开发,因为开源软件本身没有引入单元测试框架,因此大家写代码的时候索性都裸奔了,只是在程序员交付之前必须有手动的验收测试报告,程序员在设计软件过程中,需要设计自己的验收测试用例,然后手动执行,交付功能给测试人员时,需要保证验收测试用例全部通过。测试人员会再此基础上再做更加完备的测试。这套流程看似是严谨的,可是常常出现的情况是,程序员的验收测试用例时常设计的比较简单,为了应付交付的压力,验收测试用例基本上只保证了功能的可用,对于异常,性能和稳定性上缺乏足够的测试。同时还存在一个巨大漏洞是,当软件发生维护和变动时,因为缺乏单元测试的覆盖,对于那些解决老问题,引入的新问题很难快速发现,常常漏到集成测试,或者产品验证阶段才发现,这样导致的结果是问题不能尽早发现,因此解决问题的成本会非常高。

  B公司的一大特点是崇尚加班文化,996几乎成了程序员职业的代名词,我当时所在的Y团队成员几乎每天晚上要工作到10点,想想看,高强度的工作再加代码裸奔,软件的质量该如何保证?在我看来,这其实导致一种恶性循环,程序员在疲惫的情况下交付不可靠的软件->测试不充分->疲惫的测试人员漏测->下游或者客户发现问题->程序员加班解决问题。虽然在B公司,我在技术能力上有了很大的突破,可是在我看来,程序员几乎被绑在了公司运转的车轮上,生活空间被工作挤压,同时没有业余的时间研究新技术,学习新的软件工程方法,日复一日使用老旧的技术开发。团队里所谓的技术大牛,在我看来其实都是些聪明的程序员,他们在如何提高软件质量方面,眼界永远越不过自己的鼻尖。因此基层的程序员缺乏专业程序员的引导,他们的知识体系很难得到更新,很多人其实在如何保证软件质量方面没有任何思考,日复一日的拷贝粘贴,或者写出那些看似高明的复杂代码。为什么国内把程序员叫码农,是因为程序员几乎成了一种体力劳动者。

  我所经历的这种情况几乎成了国内技术公司普遍存在的现象。

构建GNU Autotools项目

构建GNU Autotools项目

Autoconf和Automake提供了一个有效的构建系统来维护你的软件,通常在别人的系统上。Automake检查源文件,确定它们如何相互依赖,并生成一个Makefile,以便可以按正确的顺序编译这些文件。Autoconf允许自动配置软件安装,处理大量系统怪癖以增加可移植性。Libtool(这里未讨论)是编译器和链接器的命令行界面,可以轻松生成静态库和共享库。

基本文件

最小的项目要求您只提供两个文件:

  • Makefile.am – automake的输入文件,它指定了项目的构建要求:需要构建的内容以及安装后的位置。
  • configure.in – autoconf的输入文件,提供autoconf用于构建配置 脚本的宏调用和shell代码片段。

GNU Autotools将生成构建项目所需的其余文件。

目录结构

在为新项目编写任何代码之前,您需要确定项目将使用的目录结构。

  • 顶级目录用于配置文件,如configure.in,和其他杂项文件,如更新日志,COPY(项目执照副本),并自述。
  • 任何独特的库都应该有自己的子目录, 其中包含所有的头文件和源文件,Makefile.am文件以及任何其他库特定的文件。
  • 主应用程序的头文件和源文件应位于另一个子目录中,通常称为src。
  • 其他目录可以包括:配置为中间文件,文档项目文档和测试 该项目自测试套件。

以下步骤将带您完成创建和构建HelloWorld项目。HelloWorld的顶级目录是<tests / project>。您可以在src子目录中找到项目的标题和来源。有三个文件:helloworld.cc,helloworld.h和main.cc.

Makfile.am

您必须为源代码树中的每个目录提供一个Makefile.am文件。Makefile.am用于顶层目录很简单。在<tests / project>目录下创建一个名为Makefile.am的新文本文件。将以下行添加到该文件并保存它:

SUBDIRS = src

SUBDIRS变量用于列出必须构建的子目录。

接下来,在<tests / project / src>子目录中创建另一个名为Makefile.am的文本文件。将以下行添加到文件并保存它:

bin_PROGRAMS = helloworld AM_CXXFLAGS = $(INTI_CFLAGS) helloworld_SOURCES = main.cc helloworld.cc helloworld.h helloworld_LDADD = $(INTI_LIBS)

bin_PROGRAMS变量指定当make install运行时,我们需要构建一个名为helloworld的程序并将其安装在bin目录中。

AM_CXXFLAGS宏设置编译器标志。您不应该在Makefile.am中使用CXXFLAGS,因为它不安全。CXXFLAGS是用户期望能够覆盖的用户变量。

helloworld_SOURCES变量指定用于构建helloworld目标的源文件。请注意,目标的SOURCES变量以目标名称作为前缀,在本例中为helloworld。

最后一个变量helloworld_LDADD指定了必须传递给链接器来构建目标的库。该变量仅供程序和库使用。请注意,LDADD使用与SOURCES变量相同的命名规则。

configure.in

configure.in文件必须位于项目的顶层目录中。切换到<tests / project>目录并创建一个名为configure.in的文本文件。将以下行添加到文件并保存它:

AC_INIT(src/main.cc) PACKAGE=helloworld VERSION=0.1.0 AM_INIT_AUTOMAKE($PACKAGE, $VERSION) INTI_REQUIRED_VERSION=1.0.7 PKG_CHECK_MODULES(INTI, inti-1.0 >= $INTI_REQUIRED_VERSION) AC_SUBST(INTI_CFLAGS) AC_SUBST(INTI_LIBS) AC_PROG_CXX AC_OUTPUT(Makefile src/Makefile)

AC_INIT宏对生成的配置脚本执行基本初始化。它将源目录中的文件名作为参数,以确保源目录已被正确指定。

PACKAGE和VERSION变量分别声明包的名称和版本。

AM_INIT_AUTOMAKE宏执行Automake所需的所有标准初始化,并且接受两个参数,即包名称和版本号。

INTI_REQUIRED_VERSION变量指定所需的最小Inti版本,在本例中为1.0.7。

PKG_CHECK_MODULES宏会检查Inti库的指定版本,如果找到,则必须在$(INTI_CFLAGS)中放置必要的包含标志,并将库与$(INTI_LIBS)链接。如果找不到正确的版本,配置会报告错误。

AC_PROG_CXX检查C ++编译器并设置变量CXX,GXX和CXXFLAGS。

必须在configure.in的最后调用最后一个宏AC_OUTPUT来创建Makefiles。

生成输出文件

现在我们需要从两个输入文件configure.inMakefile.am中生成所需的输出文件。首先,我们需要收集configure.in中的所有宏调用,Autoconf将需要构建配置脚本。这是通过以下命令完成的:

$ aclocal

这会生成文件aclocal.m4并将其添加到当前目录。

接下来,运行autoconf:

$ autoconf

运行autoconf后,您将在当前目录中找到配置脚本。首先运行aclocal非常重要,因为automake依赖configure.in和aclocal.m4中的内容。

GNU标准所说的一些文件必须出现在顶层目录中,如果没有找到,Automake会报告一个错误。输入以下命令来创建这些文件:

$ touch AUTHORS NEWS README ChangeLog

现在我们可以运行automake来创建Makefile.in。–add-missing参数将Automake安装中的一些样板文件复制到当前目录中。

$ automake --add-missing

到目前为止,<tests / project>目录的内容应该看起来很像您以前可能安装的GNU软件包的顶级目录:

aclocal.m4 autom4te.cache config.h.in configure.in depcomp install-sh  Makefile.in mkinstalldirs README AUTHORS   ChangeLog    configure  COPYING    INSTALL Makefile.am missing   NEWS      src

建立和安装项目

此时,您应该能够将源代码树打包到tarball中,并将其提供给其他用户以安装在他们自己的系统上。用户只需解压缩tarball并运行以下命令:

$ ./configure --prefix=some_directory $ make $ make install

如果你运行上面的命令并查看你的bin目录,你会发现helloworld。看看可执行文件的大小。哇!它的588千字节。这是因为它包含调试程序所需的所有调试和编译器符号。

现在运行以下命令:

$ make install-strip

如果你现在看helloworld的大小,它会小很多,只有35.7千字节。命令make install-strip去除所有调试符号。由此产生的可执行文件更小更快,但您将无法调试程序。通常情况下,只有在稳定时才应该剥离程序。

维护输入文件

每次编辑软件包中的任何GNU Autotools输入文件时,都必须重新生成输出文件。如果您将新的源文件添加到Makefile.am中的helloworld_SOURCES变量,则必须重新生成Makefile.in。如果你正在构建你的软件包,你将需要重新运行configure来重新生成Makefile文件。许多项目维护人员将必要的命令放入名为autogen.sh的脚本中,并在需要重新生成输出文件时运行此脚本。

在顶层目录中创建一个名为autogen.sh的文本文件,并确保更改其文件模式以使其可执行。将以下命令添加到该文件并保存:

#! /bin/sh aclocal \ && automake –add-missing \ && autoconf

 `现在,您可以轻松运行以下命令来更新项目的输出文件,并重建项目:
` 
$./autogen.sh $ ./configure --prefix=/some_directory $ make $ make install 

一些有用的链接

本教程应该让你开始使用GNU Autotools,这应该足够了,一段时间。最终,您需要了解更多信息,例如如何构建共享库或应将哪些宏添加到configure.in中。我发现以下链接非常有用:

注:本文翻译自

Building a GNU Autotools Project

程序员的修行之路

程序员的修行之路

目录

img微信公众号

本文系本站原创文章,著作版权属于作者,未经允许不得转载,如需转载或引用请注明出处或者联系作者。