2025年1月

一、有限状态机(Finite State Machine, FSM)基本概念

有限状态机是由
寄存器组

组合逻辑
构成的硬件时序电路;
其状态只能在同一时钟跳变沿从一个状态转向另一个状态;状态的选择不仅取决于各个输入值,还取决于当前状态,可用于产生在时钟跳变沿时刻开关的复杂的控制逻辑,是数字逻辑的控制核心

1.有限状态机的优势

  • 高效的顺序控制逻辑
    克服了纯硬件数字系统顺序方式控制不灵活的缺点,在其运行方式上类似于控制灵活和方便的CPU,是高速高效控制的首选
  • 容易利用现成的EDA工具进行优化设计
    状态机构建简单,设计方案相对固定,使用HDL综合其可以发挥其强大的优化功能;
    性能良好的综合器都具有许多可控或自动优化状态机的功能
  • 稳定性能
    状态机容易构成良好的同步时序逻辑模块,可用于解决大规模逻辑电路设计中的竞争和冒险现象
  • 高速性能
    在高速通信和高速控制方面,状态机更有其巨大的优势,一个状态机的功能类似于CPU的功能
  • 高可靠性能
    状态机是由纯硬件电路构成,不存在CPU运行软件过程中许多固有的缺陷;
    状态机的设计中能够使用各种容错技术;
    当状态机进入非法状态并从中跳出进入正常状态的时间短暂,对系统的危害不大。

2.有限状态机的分类

一般来说,状态机的基本操作主要有:
状态机的内部状态转换

产生输出信号序列
根据电路的输出信号是否与电路的输入有关,可以将状态机划分为:
Moore型状态机
(输出只与当前电路状态有关,保持输出的稳定性和可预测性);
Mealy型状态机
(输出与当前电路状态和当前电路输入有关,来快速响应输入变化)

3.有限状态机的描述方法

  • 状态转移图
    :紧凑,适合描述较为简单的系统
  • 算法状态机(ASM)图
    算法状态机图更像是流程图,能较好地描述复杂系统中状态的转换和动作

这两种表示方法包含了相同的信息,都包含了状态机的输入、输出、状态和转换。

二、有限状态机的设计

1.状态机的设计步骤

  • 依据具体的设计原则,确定采用Moore型状态机还是Mealy型状态机
  • 分析设计要求,列出状态机的所有状态,并对每一个状态进行状态编码
  • 依据状态转移关系和输出函数,画出状态图
  • 依据所画的状态图,采用硬件描述语言对状态机进行描述

2.状态图的完备性和互斥性检查

  • 完备性:
    对于每一个状态,将所有脱离这一状态的条件表达式进行
    逻辑或运算
    ,如果结果为
    1
    就是完备的,否则不完备,也就是说状态图进入某状态后,却不能跳出该状态;
  • 互斥性:
    对于每一个状态,将所有脱离这一状态的条件表达式找出,然后任意两个表达式进行
    逻辑与
    运算,如果结果为
    0
    就是互斥的。也就是要保证在任何时候不会激活两个脱离状态的转换,即从一个状态跳到两个状态

3.安全状态机设计

  • 状态引导法
    :对于未使用的状态,也给予次态赋值,避免状态机处于未知状态;
  • 状态编码检测法
    :判断被触发的触发器的个数,当数量大于1时,说明出现问题;
  • 借助
    EDA工具
    自动生成安全状态机

三、有限状态机的Verilog HDL描述

考虑如下图所示的状态转移图

1.描述状态机中各个状态的名称,并指定状态编码

  • 状态定义/声明与编码
    • 状态必须是常量
      parameter
      或者
      `define
    IDLE=2'b00;
    START=2'b01;
    STOP=2'b10;
    CLEAR=2'B11;
  • 状态寄存器
    (位宽必须与parameter变量一致)


    • cur_state:存储现态
    • next_state:存储次态
    • reg [1:0] state:定义状态寄存器
  • 状态编码


module fsm
	#(parameter IDLE=2'b00,
	  parameter START=2'b01,
	  parameter STOP=2'b10,
	  parameter CLEAR=2'b11)
	( input clk, rstn, a,
	  output reg k1, k2);

	reg [1:0] cur_state, next_state;//定义状态寄存器

2.状态机设计

2.1 设计步骤
  • 用时序的
    always块
    描述状态触发器实现的状态存储;
  • 使用敏感表和case语句(或if-else等价语句)描述的状态转换逻辑
  • 描述状态机的输出逻辑
2.2 状态机的三种RTL描述方法
  • 一段式
    :现态、次态与输出逻辑在同一个always块中
  • 两段式
    :现态在一个always块中,次态与输出逻辑在一个always块中
  • 三段式
    :现态、次态与输出逻辑分别在一个always块中
//一段式
always @(posedge clk)	begin
	if(!rstn)	begin
		cur_state<=IDLE;
		k2<=1'b0;
		k1<=1'b0;
	end
	else	
		case(cur_state)
			IDLE:
				if(a) begin
					cur_state<=START;
					k1<=1'b0;
				end
				else
					cur_state<=IDLE;
			START: 
				if(!a)
					cur_state <= STOP;
				else
					cur_state <=START;
			STOP:
				if(a)	begin
					cur_state <= CLEAR;
					k2<=1'b1;
				end
				else 
					cur_state <= STOP;
			CLEAR:
				if(!a)	begin
					cur_state <= IDLE;
					k2<=1'b0; 
					k1<=1'b1;
				end
				else	
					cur_state <= CLEAR;
		endcase
	end
//两段式
always @(posedge clk)	begin
	if(!rstn)
		cur_state <= IDLE;
	else 
		cur_state <= next_state;
end

always @(cur_state, a)	begin
	case(cur_state)	
		IDLE:
			if(a) begin
				next_state=START;
				k1=1'b0;
			end
			else
				next_state=IDLE;
		START: 
			if(!a)
				next_state = STOP;
			else
				next_state =START;
		STOP:
			if(a)	begin
				next_state = CLEAR;
				k2=1'b1;
			end
			else 
				next_state = STOP;
		CLEAR:
			if(!a)	begin
				next_state = IDLE;
				k2=1'b0; 
				k1=1'b1;
			end
			else	
				next_state = CLEAR;
	endcase
end
//三段式
always@(posedge clk)	begin
	if(!rstn)	
		cur_state <= IDLE;
	else
		cur_state <= next_state;
end

always@(cur_state, a)	begin
	case(cur_state)
		IDLE:
			if(a)	next_state = START;
			else	next_state = IDLE;
		START:
			if(!a)	next_state = STOP;
			else	next_state = START;
		STOP:
			if(a)	next_state = CLEAR;
			else	next_state = STOP;
		CLEAR:
			if(!a)	next_state = IDLE;
			else	next_state = CLEAR;
	endcase
end

always@(cur_state, a)	begin
	k2=1'b0;
	k1=1'b0;
	if(!rstn)	begin
		k2=1'b0;
		k1=1'b0;
	end
	else	begin
		if(cur_state == CLEAR && !a)
			k1=1'b1;
		else
			k1=1'b0;
		if(cur_state == STOP && a)
			k2=1'b1;
		else
			k2=1'b0;
	end
end

四、有限状态机设计实例

实例1:Moore型序列检测器:

要求描述:
序列检测器可用于检测一组或多组由二进制码组成的脉冲序列信号,当序列检测器连续收到一组串行二进制码后,如果这组码与检测器中预先设置的码相同,则输出1,否则输出0
设计一个“1101”的序列检测器,设din为数字码流输入,sout为检出标记输出,高电平表示发现指定序列,低电平表示没有发现指令序列。

Step1.状态定义
  • s0
    :未检测到“1”
  • s1
    :检测到输入序列“1”
  • s2
    :检测到输入序列“11”
  • s3
    :检测到输入序列“110”
  • s4
    :检测到输入序列“1101”
    共五个状态,需要声明位宽为3的状态寄存器
    reg [2:0] cur_state, next_state;
Step2.状态转移表和状态转移图

无可简化状态,对应的Moore型状态转移图如下:

Step3.HDL语言描述状态转换
//实现代码
module seqdet#(
	parameter s0=3'b000,
	parameter s1=3'b001,
	parameter s2=3'b010,
	parameter s3=3'b011,
	parameter s4=3'b100)(
	input clk, rstn, din,
	output reg sout);

	reg [2:0] cur_state, next_state;

	always @(posedge clk)	begin
		if(!rstn)	cur_state <= s0;
		else		cur_state <= next_state;
	end

	always @(cur_state, din)	begin
		sout=0;
		case(cur_state)
			s0:	begin
				if(din == 1)	next_state=s1;
				else		next_state=s0;
			end
			s1:	begin
				if(din == 1)	next_state=s2;
				else		next_state=s0;
			end
			s2:	begin
				if(din == 0)	next_state=s3;
				else		next_state=s2;
			end
			s3:	begin
				if(din == 1)	next_state=s4;
				else		next_state=s0;
			end
			s4:	begin
				if(din == 0)	begin	next_state=s0; sout=1;	end
				else		begin	next_state=s1; sout=1;	end
			end
			default	next_state=s0;
		endcase
	end
endmodule

//仿真代码
module seqdet_tb();
	reg clk, rstn, din;
	wire sout;

	seqdet U1(clk, rstn, din, sout);
	always #1 clk=~clk;

	initial begin
		clk=0; rstn=0; din=0;
		#5 rstn=1;
		#2 din=1;
		#2 din=1;
		#2 din=0;
		#2 din=1;
		#2 din=1;
		#2 din=0;
		#2 din=0;
		#3 rstn=0;
		#5 $finish;
	end
endmodule

仿真得到的波形图如下:

综合出的电路结构图如下:

电路状态转移图如下:

实例2:Mealy型序列检测器:

Step2.状态转移表和状态转换图

状态定义与实例1相同,可以得到状态转移表如下:

对应的Mealy型状态转换图如下:

//实现代码
module seqdet_mealy#(
	parameter s0=3'b000,
	parameter s1=3'b001,
	parameter s2=3'b010,
	parameter s3=3'b011,
	parameter s4=3'b100)(
	input clk, rstn, din,
	output reg sout);

	reg [2:0] cur_state, next_state;
	
	always @(posedge clk)	begin
		if(!rstn)	cur_state <= s0;
		else		cur_state <= next_state;
	end

	always @(cur_state, din)	begin
		sout = 1'b0;
		case(cur_state)
			s0:	if(din==1)	next_state=s1;
				else		next_state=s0;
			s1:	if(din==1)	next_state=s2;
				else		next_state=s0;
			s2:	if(din==0)	next_state=s3;
				else		next_state=s2;
			s3:	if(din==1)	begin	next_state=s4; sout=1;	end
				else		next_state=s0;
			s4:	if(din==1)	next_state=s2;
				else		next_state=s0;
			default:		next_state=s0;
		endcase
	end
endmodule

//仿真代码
module seqdet_mealy_tb();
	reg clk, rstn, din;
	wire sout;

	seqdet_mealy U1(clk, rstn, din, sout);
	always #1 clk=~clk;

	initial begin
		clk=0; rstn=0; din=0;
		#5 rstn=1;
		#2 din=1;
		#2 din=1;
		#2 din=0;
		#2 din=1;
		#2 din=1;
		#2 din=0;
		#2 din=0;
		#3 rstn=0;
		#5 $finish;
	end
endmodule

仿真得到的波形图如下:

综合出的电路结构图如下:

电路状态转移图如下:


title: 特殊数据类型的深度分析:JSON、数组和 HSTORE 的实用价值
date: 2025/1/4
updated: 2025/1/4
author:
cmdragon

excerpt:
随着数据管理需求的多样化,许多现代数据库系统开始支持特殊数据类型,以满足更多复杂应用场景的需求。在 PostgreSQL 中,JSON、数组和 HSTORE 类型为开发者提供了灵活的数据存储和操作方式,使得结构化和非结构化数据的处理愈加高效。智的选择。

categories:

  • 前端开发

tags:

  • PostgreSQL
  • JSON
  • 数组
  • HSTORE
  • 数据类型
  • 数据存储
  • 数据管理


image
image

扫描
二维码
关注或者微信搜一搜:
编程智域 前端至全栈交流与成长

随着数据管理需求的多样化,许多现代数据库系统开始支持特殊数据类型,以满足更多复杂应用场景的需求。在 PostgreSQL 中,JSON、数组和 HSTORE 类型为开发者提供了灵活的数据存储和操作方式,使得结构化和非结构化数据的处理愈加高效。智的选择。

1. 引言

在现代数据库领域,传统的关系模型已不能满足某些应用场景下对数据的灵活需求。尤其是在处理结构化与非结构化数据的同时,开发者和数据科学家面临许多挑战。为了应对这些挑战,PostgreSQL 引入了诸如 JSON、数组和 HSTORE 等特殊数据类型,旨在为开发者提供更加灵活和高效的方式来处理复杂数据结构。

2. JSON 数据类型

2.1 JSON 的定义

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,常用于存储和传输结构化数据。它以键值对的形式存储数据,易于人类阅读与编写,同时也易于机器解析和生成。在 PostgreSQL 中,JSON 数据类型主要有
JSON

JSONB
两种形式。

  • JSON
    :以文本形式存储,支持存储原始 JSON 数据。虽然可以进行某些操作,但其解析速度相对较慢。

  • JSONB
    :为二进制格式的 JSON,支持对数据进行索引,解析速度更快,适合进行复杂数据查询和操作。

2.2 PostgreSQL 中 JSON 类型的实现

在 PostgreSQL 中使用 JSON 类型时,可执行以下操作:

  • 数据插入

    CREATE TABLE products (
        id SERIAL PRIMARY KEY,
        name VARCHAR(100),
        attributes JSONB
    );
    
    INSERT INTO products (name, attributes) VALUES ('Laptop', '{"brand": "Dell", "memory": "16GB", "storage": "512GB SSD"}');
    
  • 数据查询

    SELECT * FROM products WHERE attributes->>'brand' = 'Dell';
    
  • 数据更新

    UPDATE products SET attributes = jsonb_set(attributes, '{memory}', '"32GB"') WHERE id = 1;
    

2.3 JSON 的优势与局限

JSON 数据类型的优势包括:

  • 灵活性:可以轻松存储不同结构的数据,提高了数据模型的灵活性。
  • 可扩展性:实现简单的扩展,不需要对数据库结构进行重大变更。

然而,JSON 和 JSONB 也有一些局限性:

  • 存储效率:虽然 JSONB 的存储效率较高,但仍然比传统关系型数据占用更多存储空间。
  • 复杂性:数据查询和更新语句会变得更复杂,尤其在嵌套结构较深时。

3. 数组数据类型

3.1 数组的定义

PostgreSQL 支持将任何基本数据类型存储为数组,这为存储多值字段提供了便利。例如,可以在同一列中保存多个标签、多个评论等。

3.2 PostgreSQL 中数组类型的实现

使用数组类型时,开发者可以在创建表时指定数组。例如:

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    favorite_colors TEXT[]
);

3.3 数组的使用

  • 数据插入

    INSERT INTO users (name, favorite_colors) VALUES ('Alice', ARRAY['Red', 'Green', 'Blue']);
    
  • 数据查询

    SELECT * FROM users WHERE 'Red' = ANY(favorite_colors);
    
  • 数据更新

    UPDATE users SET favorite_colors = array_append(favorite_colors, 'Yellow') WHERE id = 1;
    

3.4 数组的优势与局限

数组的优势包括:

  • 简洁性:可以轻松表示多值属性,而不必创建多个表。
  • 查找效率:在数组中查找元素相对直接,可以通过函数进行快速查询。

然而,数组也有一定的局限性:

  • 数据规范性:数组数据的结构不如关系型表明确,可能导致数据规范性较差。
  • 操作复杂性:在执行复杂查询、更新或关联操作时,可能会导致性能和可读性问题。

4. HSTORE 数据类型

4.1 HSTORE 的定义

HSTORE 是 PostgreSQL 中一种键值对存储的数据类型,适合存储稀疏的数据结构,或是具有动态字段的数据模型。特别适用于处理动态属性或不确定字段的场景。

4.2 PostgreSQL 中 HSTORE 类型的实现

在 PostgreSQL 中,可以通过以下方式使用 HSTORE:

  • 创建表

    CREATE TABLE products (
        id SERIAL PRIMARY KEY,
        name VARCHAR(100),
        parameters HSTORE
    );
    
  • 数据插入

    INSERT INTO products (name, parameters) VALUES ('Phone', 'brand => "Apple", features => "waterproof, wireless charging"');
    

4.3 HSTORE 的使用

  • 数据查询

    SELECT * FROM products WHERE parameters -> 'brand' = 'Apple';
    
  • 数据更新

    UPDATE products SET parameters = parameters || 'color => "black"'
    WHERE name = 'Phone';
    

4.4 HSTORE 的优势与局限

HSTORE 的优势有:

  • 灵活性:以键值对形式存储数据,适应数据结构变化。
  • 有效性:适合于稀疏数据存储,节省空间。

但其局限性包括:

  • 限制性:数据类型不如 JSON 丰富,支持的操作较少。
  • 查询复杂度:复杂查询时,HSTORE 的性能可能逊色于其他特殊类型。

5. 特殊数据类型的应用场景

5.1 JSON 的应用场景

  • 配置存储
    :将应用配置存储为 JSON 格式,实现灵活配置管理。
  • API 响应
    :从 REST API 接收的数据通常为 JSON 格式,存储到数据库时可以保留原格式。
  • 分析数据
    :适合用于保留原始数据,以便后期处理和分析。

5.2 数组的应用场景

  • 多值属性
    :如用户的多个兴趣爱好、商品的标签等,简单表示多值数据。
  • 快速查找
    :经常用于数据集中的快速过滤,例如条件检索。

5.3 HSTORE 的应用场景

  • 动态字段
    :用于存储不固定字段的模型,例如用户自定义表单。
  • 稀疏数据
    :表中字段较多但多数为空的情况下适合使用 HSTORE。

6. 最佳实践

6.1 数据建模

当需要选择使用特殊数据类型时,应首先考虑用例的具体需求分析。如果数据结构变化频繁且不确定,JSON 和 HSTORE 可以提供更高的灵活性。如果数据较为固定,那么传统关系表可能更适合。

6.2 查询优化

对于 JSONB 和 HSTORE,确保在经常查询的字段上创建合适的索引,以提升查询性能。

  • 创建指标

    CREATE INDEX idx_on_parameters ON products USING GIN (parameters);
    

6.3 数据验证

在应用层增加 JSON 结构验证,确保存储的 JSON 数据符合结构要求,防止后期数据解析错误。

7. 特殊数据类型的性能考量

7.1 存储效率

在使用 JSON、数组和 HSTORE 时特别注意其存储效率,避免不必要的嵌套和冗余,以降低存储成本。

7.2 查询性能

测试查询性能,使用 EXPLAIN 分析查询计划,确保查询方式高效。例如,对于复杂查询,采用合适的索引。

8. 特殊数据类型的迁移与转换

在数据库迁移过程中,处理 JSON、数组和 HSTORE 数据类型需要特别关注。

8.1 数据导入导出

使用 PostgreSQL 提供的工具进行 JSON 数据的导入导出;导出为 JSON 文件时,确保结构保持一致。

8.2 跨数据库平台

如果考虑迁移到其他数据库平台,需确保 JSON 和数组的数据可迁移。在某些数据库中可能不支持直接迁移。

9. 特殊数据类型在不同数据库中的比较

虽然许多现代数据库系统都支持 JSON 数据类型,但在实现和性能上存在差异。

  • MySQL
    :支持 JSON 数据类型,操作方式相似,但在复杂查询上不及 PostgreSQL 优化。
  • MongoDB
    :作为 NoSQL 数据库,原生支持 JSON 风格的数据,使用上更为灵活。

10. 结论

特殊数据类型(如 JSON、数组和 HSTORE)为 PostgreSQL 提供了强大的数据建模能力,增加了处理结构化和非结构化数据的灵活性。开发者在设计数据模型时,合理选择特殊数据类型可以显著增强应用的可扩展性和灵活性。

参考

  1. PostgreSQL Documentation:
    PostgreSQL Data Types
  2. PostgreSQL: Up and Running - Regis Beauduin
  3. PostgreSQL 实战 - 曾云
  4. The Nature of Code - Daniel Shiffman
  5. NoSQL 数据管理 - Dan Sullivan

余下文章内容请点击跳转至 个人博客页面 或者 扫码关注或者微信搜一搜:
编程智域 前端至全栈交流与成长
,阅读完整的文章:
特殊数据类型的深度分析:JSON、数组和 HSTORE 的实用价值 | cmdragon's Blog

往期文章归档:

前言

上篇文章介绍了使用Semantic Kernel Chat Completion Agent实现的版本。

使用C#构建一个论文总结AI Agent

今天来介绍一下使用Microsoft.Extensions.AI的版本。

Microsoft.Extensions.AI介绍

Microsoft.Extensions.AI 是微软为 .NET 生态系统推出的一组核心库,旨在为开发者提供统一的 C# 抽象层,简化与 AI 服务的集成。它通过与 .NET 生态系统的深度协作(包括与 Semantic Kernel 团队的合作),为开发者提供了一种标准化的方式来与各种 AI 服务(如大型语言模型、嵌入生成、工具调用等)进行交互。

GitHub地址:
https://github.com/dotnet/extensions/tree/main/src/Libraries/Microsoft.Extensions.AI

实践

新建一个C#控制台项目。

安装包:

image-20250104145553743

创建插件:

internal sealed class PaperAssistantPlugin
{
    public PaperAssistantPlugin()
    {
        var envVars = DotEnv.Read();
        ApiKeyCredential apiKeyCredential = new ApiKeyCredential(envVars["PaperSummaryApiKey"]);

        OpenAIClientOptions openAIClientOptions = new OpenAIClientOptions();
        openAIClientOptions.Endpoint = new Uri($"{envVars["PaperSummaryEndpoint"]}");

        IChatClient openaiClient =
        new OpenAIClient(apiKeyCredential, openAIClientOptions)
            .AsChatClient(envVars["PaperSummaryModelId"]);

        Client = new ChatClientBuilder(openaiClient)
                     .UseFunctionInvocation()
                     .Build();
    }

    internal IChatClient Client { get; set; }

    [Description("读取指定路径的PDF文档内容")]
    [return: Description("PDF文档内容")]
    public string ExtractPDFContent(string filePath)
    {
        Console.WriteLine($"执行函数ExtractPDFContent,参数{filePath}");

        StringBuilder text = new StringBuilder();
        // 读取PDF内容
        using (PdfDocument document = PdfDocument.Open(filePath))
        {
            foreach (var page in document.GetPages())
            {
                text.Append(page.Text);
            }
        }
        return text.ToString();
    }

    [Description("根据文件路径与笔记内容创建一个md格式的文件")]
    public void SaveMDNotes([Description("保存笔记的路径")] string filePath, [Description("笔记的md格式内容")] string mdContent)
    {
        try
        {
            Console.WriteLine($"执行函数SaveMDNotes,参数1:{filePath},参数2:{mdContent}");

            // 检查文件是否存在,如果不存在则创建
            if (!File.Exists(filePath))
            {
                // 创建文件并写入内容
                File.WriteAllText(filePath, mdContent);
            }
            else
            {
                // 如果文件已存在,覆盖写入内容
                File.WriteAllText(filePath, mdContent);
            }
        }
        catch (Exception ex)
        {
            // 处理异常
            Console.WriteLine($"An error occurred: {ex.Message}");
        }
    }

    [Description("总结论文内容生成一个md格式的笔记,并将笔记保存到指定路径")]
    public async void GeneratePaperSummary(string filePath1, string filePath2)
    {
        Console.WriteLine($"执行函数GeneratePaperSummary,参数1:{filePath1},参数2:{filePath2}");

        StringBuilder text = new StringBuilder();
        // 读取PDF内容
        using (PdfDocument document = PdfDocument.Open(filePath1))
        {
            foreach (var page in document.GetPages())
            {
                text.Append(page.Text);
            }
        }

        // 生成md格式的笔记
        string skPrompt = """
                            请使用md格式总结论文的摘要、前言、文献综述、主要论点、研究方法、结果和结论。
                            论文标题为《[论文标题]》,作者为[作者姓名],发表于[发表年份]。请确保总结包含以下内容:
                            论文摘要
                            论文前言
                            论文文献综诉
                            主要研究问题和背景
                            使用的研究方法和技术
                            主要结果和发现
                            论文的结论和未来研究方向
                            """;
        List<ChatMessage> history = [];
        history.Add(new ChatMessage(ChatRole.System, skPrompt));
        history.Add(new ChatMessage(ChatRole.User, text.ToString()));

        var result = await Client.CompleteAsync(history);

        try
        {
            // 检查文件是否存在,如果不存在则创建
            if (!File.Exists(filePath2))
            {
                // 创建文件并写入内容
                File.WriteAllText(filePath2, result.ToString());
                Console.WriteLine($"生成笔记成功,笔记路径:{filePath2}");
            }
            else
            {
                // 如果文件已存在,覆盖写入内容
                File.WriteAllText(filePath2, result.ToString());
            }
        }
        catch (Exception ex)
        {
            // 处理异常
            Console.WriteLine($"An error occurred: {ex.Message}");
        }
    }
}

创建好了插件之后,我们需要创建一个IChatClient,由于国内大模型提供商大部分都已经兼容了OpenAI格式,所以安装Microsoft.Extensions.AI.OpenAI就可以用了。

在Microsoft.Extensions.AI.OpenAI中使用国内大语言模型的方式如下所示:

 var envVars = DotEnv.Read();

 ApiKeyCredential apiKeyCredential = new ApiKeyCredential(envVars["ToolUseApiKey"]);

 OpenAIClientOptions openAIClientOptions = new OpenAIClientOptions();
 openAIClientOptions.Endpoint = new Uri($"{envVars["ToolUseEndpoint"]}");

 IChatClient openaiClient =
 new OpenAIClient(apiKeyCredential, openAIClientOptions)
 .AsChatClient(envVars["ToolUseModelId"]);

 IChatClient client = new ChatClientBuilder(openaiClient)
 .UseFunctionInvocation()
 .Build();

在ChatOptions中导入工具:

 ChatOptions chatOptions = new()
 {
     Tools = [AIFunctionFactory.Create(paperAssistantPlugin.ExtractPDFContent),
              AIFunctionFactory.Create(paperAssistantPlugin.SaveMDNotes),
              AIFunctionFactory.Create(paperAssistantPlugin.GeneratePaperSummary)]
 };

总结论文并将笔记保存至指定路径:

image-20250104150522404

image-20250104150643237

提问论文相关问题:

image-20250104150933860

将笔记保存至指定路径:

image-20250104151253266

image-20250104151334522

总结

只是一个非常简单的示例,希望对大家使用Microsoft.Extensions.AI实现自己的应用有所帮助。代码已上传至GitHub,地址:
https://github.com/Ming-jiayou/PaperAssistant。

记录一下Python学习过程中的一些问题:


1. 在JupyterLab中查询当前文件的地址

import os
print(os.getcwd())
#查询该文件的地址


2. 新建cell

在 JupyterLab 中新建一个单元格(cell)的方法有多种,以下是一些常用的方法:

  1. 使用快捷键

    • B
      :在当前单元格下方新建一个单元格。
    • A
      :在当前单元格上方新建一个单元格。

实测有效,虽然看起来挺神奇的,确实方便

  1. 使用工具栏按钮


    • 在工具栏上,你可以找到用于添加单元格的按钮。通常,这些按钮位于工具栏的中间部分,看起来像是一个带有加号的单元格图标。
  2. 使用右键菜单


    • 在工作区的空白处或者在已有单元格的边缘右键点击,然后选择“Insert Cell Above”或“Insert Cell Below”来在相应位置插入新单元格。
  3. 使用命令面板


    • 点击工具栏上的 "Commands" 图标(或者使用快捷键
      Ctrl+Shift+P
      ),在命令面板中搜索 "Insert Cell Below" 或 "Insert Cell Above" 并选择它。
  4. 拖拽单元格


    • 你可以将一个单元格拖拽到工作区的空白处,然后释放鼠标,这将在该位置创建一个新的单元格。
  5. 使用菜单


    • 在 JupyterLab 的菜单栏中,点击 "Insert",然后选择 "Insert Cell Below" 或 "Insert Cell Above"。
  6. 使用侧边栏


    • 在 JupyterLab 的侧边栏中,点击 "+" 图标,然后选择 "Code" 或 "Markdown" 来创建一个新的单元格。
  7. 使用拖拽文件


    • 你也可以将一个
      .py

      .ipynb
      文件拖拽到 JupyterLab 的工作区中,这将创建一个新的单元格或打开一个新笔记本。

选择适合你的方法来新建单元格,然后你就可以在新单元格中输入代码或 Markdown 文本了。

3. 集合、列表、元祖的区别

在 Python 中,集合(set)、列表(list)和元组(tuple)是三种不同的数据结构,它们各自有不同的特性和用途:

  1. 列表(List)


    • 列表是可变的,这意味着你可以在创建后修改它们(添加、删除或更改元素)。
    • 列表可以包含重复的元素。
    • 列表是有序的,即它们保持元素插入的顺序。
    • 列表可以包含不同类型的元素。
    • 列表支持索引和切片操作。
    • 列表的典型用法包括创建一个有序的元素集合,或者当你需要修改集合中的元素时。
  2. 元组(Tuple)


    • 元组是不可变的,一旦创建就不能修改(不能添加、删除或更改元素)。
    • 元组可以包含重复的元素。
    • 元组也是有序的,并且支持索引和切片操作。
    • 元组可以包含不同类型的元素。
    • 元组通常用于存储不应该改变的数据。
    • 元组是创建不可变集合的推荐方式,它们还可以用作字典的键(因为字典的键必须是不可变的)。
  3. 集合(Set)


    • 集合是无序的,这意味着集合中的元素没有固定的顺序,并且可能会在每次迭代时改变。
    • 集合是可变的,你可以添加或删除元素。
    • 集合不能包含重复的元素,每个元素都是唯一的。
    • 集合不支持索引操作,因为它们是无序的。
    • 集合的典型用法包括消除重复元素、进行集合运算(如并集、交集、差集)。
    • 集合是不可哈希的,因此不能用作字典的键。

以下是一些示例:

# 列表
my_list = [1, 2, 3, 4, 5]
my_list.append(6)  # 添加元素
my_list[1] = 20    # 修改元素

# 元组
my_tuple = (1, 2, 3, 4, 5)
# my_tuple[1] = 20  # 这会引发 TypeError,因为元组是不可变的

# 集合
my_set = {1, 2, 3, 4, 5}
my_set.add(6)  # 添加元素
# my_set[1]  # 这会引发 TypeError,因为集合不支持索引

选择使用哪种数据结构取决于你的具体需求,比如是否需要有序性、是否允许重复元素、是否需要修改集合等。


4. 张量的二维表达是矩阵

张量(Tensor)和矩阵(Matrix)是线性代数中的两个概念,它们有一定的联系,但在维度和应用上有所不同。

  1. 矩阵


    • 矩阵是一个二维数组,由行和列组成,可以看作是特殊的张量。
    • 矩阵中的元素通常用两个索引来访问,如 ( a_{ij} ),其中 ( i ) 表示行索引,( j ) 表示列索引。
    • 矩阵在数学、物理学和工程学中有广泛的应用,特别是在线性变换、系统方程和图形变换等领域。
  2. 张量


    • 张量是多维数组的推广,可以有任意数量的维度。例如,0维张量是一个标量,1维张量是一个向量,2维张量是一个矩阵。
    • 张量中的元素可以用多个索引来访问,如 ( a_{ijk} ),其中 ( i )、( j ) 和 ( k ) 分别表示不同维度的索引。
    • 张量在物理学中用于描述具有多个分量的物理量,如应力、应变等。在深度学习中,张量用于表示数据和权重,可以是多维的,以适应不同的网络结构和计算需求。

在深度学习和计算机科学中,张量的概念被广泛使用,尤其是在以下领域:

  • 神经网络
    :张量用于表示网络的输入、输出和权重。
  • 图像处理
    :图像可以表示为三维张量(高度、宽度、颜色通道)。
  • 自然语言处理
    :文本数据可以表示为张量,用于模型的输入和处理。

在 PyTorch 和 TensorFlow 等深度学习框架中,张量是核心数据结构,它们提供了对多维数组的高效操作,并且可以利用 GPU 加速计算。这些框架中的张量操作通常包括基本的数学运算、矩阵乘法、卷积、池化等。

我们在项目开发中,为了保证系统功能完整、准确性,我们都需要模拟真实数据进行测试。

今天推荐一个开源库,方便我们制造假数据测试。

01 项目简介

Bogus 是一个开源的 .NET 库,它提供了一个强大的工具集,用于生成虚假(mock)数据。方便项目用于测试、填充数据库、创建模拟数据集或生成示例数据,以便开发、测试、演示使用。

02 项目特点

1、简单易用:提供了一个简单直观的 API,使得生成各种类型的数据变得非常容易。

2、定制性强:用户可以定制生成的假数据,包括自定义格式、规则和数据类型。

3、扩展性:允许开发人员创建自己的数据生成器和处理程序,以支持特定的数据格式或结构。

4、丰富的数据类型:支持生成多种类型的数据,包括但不限于名字、地址、日期、电话号码、电子邮件地址等。

5、本地化支持:支持不同地区和文化的本地化数据生成,这使得它能够生成特定语言和地区的假数据。

6、可配置的随机性:可以配置随机种子,以便在需要可重复结果的测试场景中使用。

7、复合类型支持:能够生成复合对象的假数据,包括对象图和复杂类型的数据。

8、日期和时间生成:可以生成随机的日期和时间数据,并且可以指定范围。

9、可与其他库集成:可以轻松地与 Entity Framework 等 ORM 工具集成,用于数据库的种子数据生成。

03 使用方法

1、安装依赖库

Install-Package Bogus

2、示例代码

using Bogus;

// 创建一个 Faker 实例
var faker = new Faker();

//示例1:生成一个随机的名字
string name = faker.Person.FullName;
Console.WriteLine(name);

//示例2:生成一个随机的地址
string address = faker.Address.FullAddress();
Console.WriteLine(address);

//示例3:生成一个随机的日期
DateTime date = faker.Date.Past();
Console.WriteLine(date);

//示例4
// 创建一个自定义的数据生成器
var customGenerator = new Faker<Address>()
    .RuleFor(a => a.Street, f => f.Address.StreetName())
    .RuleFor(a => a.City, f => f.Address.City());

// 使用自定义生成器生成一个地址对象
Address generatedAddress = customGenerator.Generate();
Console.WriteLine($"Street: {generatedAddress.Street}, City: {generatedAddress.City}");

public class Address
{
    public string Street { get; set; }
    public string City { get; set; }
}

3、效果

图片

04 项目地址

https://github.com/bchavez/Bogus

- End -

更多开源项目:
https://github.com/bianchenglequ/NetCodeTop

推荐阅读

.NET日志库:Serilog、NLog、Log4Net等十大开源日志库大盘点!

ImageSharp:高性能跨平台.NET开源图形库

DateTimeExtensions:一个轻量C#的开源DateTime扩展方法库

一个C#开源工具库,集成了超过1000个扩展方法

Plotly.NET:一个强大的、漂亮的.NET开源交互式图表库