MoreRSS

site iconuptoz | 小小修改

软件开发工程师,INFP,比较喜欢研究数码和软件,想要探究在互联网上的事物是如何被创造和发展。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

uptoz | 小小的 RSS 预览

【CSharp】通过SemaphoreSlim类限制资源访问线程数

2025-02-20 23:20:06

概念

在 C# 中,SemaphoreSlim​ 是 System.Threading​ 命名空间下的轻量级同步原语,用于限制同时访问共享资源的线程数量。

核心特性

  1. 轻量高效
    专为高性能设计,适用于进程内同步,比 Semaphore​ 类更高效。

  2. 异步支持
    提供 WaitAsync()​ 方法,支持异步编程模型,避免线程阻塞。

  3. 并发控制
    通过计数器限制资源访问线程数,初始计数(initialCount​)表示可用资源数,最大计数(maxCount​)为资源上限。

构造函数

SemaphoreSlim(int initialCount);  // 初始计数,最大计数默认为 int.MaxValue
SemaphoreSlim(int initialCount, int maxCount);  // 指定初始和最大计数
  • 说明:initialCount​ 必须 ≥0 且 ≤ maxCount​,否则抛出 ArgumentOutOfRangeException​。

常用方法

方法

说明

​Wait()​

同步等待信号量,计数器减1;若计数为0则阻塞。

WaitAsync()​

异步等待信号量,适用于非阻塞异步场景。

​Release()​

释放信号量,计数器加1(默认释放1次,可指定次数 Release(n)​)。

Dispose()​

释放资源,避免内存泄漏。

使用场景

  1. 资源池管理
    如数据库连接池、文件句柄池等,限制并发访问数量。

  2. 异步任务协调
    控制异步任务并发度,避免资源过载。

  3. 生产者-消费者模型
    同步生产者和消费者的资源访问节奏。

代码示例

示例1:同步控制(限制3个线程)

static SemaphoreSlim semaphore = new SemaphoreSlim(3);

static void AccessResource(string name, int seconds)
{
    semaphore.Wait();
    try
    {
        Console.WriteLine($"{name} 访问资源");
        Thread.Sleep(seconds * 1000);
    }
    finally
    {
        semaphore.Release();
    }
}

示例2:异步控制(异步等待)

static SemaphoreSlim semaphore = new SemaphoreSlim(1,1);

static async Task AccessResourceAsync(string name, int seconds)
{
    await semaphore.WaitAsync();
    try
    {
        Console.WriteLine($"{name} 开始异步操作");
        await Task.Delay(seconds * 1000);
    }
    finally
    {
        semaphore.Release();
    }
}

注意事项

  • 初始计数为0:需手动调用Release(n)​初始化资源池,否则所有线程会被阻塞。

  • 线程安全Dispose()​方法非线程安全,需确保释放时无其他操作。

  • 与Semaphore区别Semaphore​支持跨进程和命名信号量,而SemaphoreSlim​仅限进程内使用但性能更优。

【CSharp】抽象类和接口详解

2025-02-20 10:39:06

抽象类(Abstract Class)

概念

  • 不能实例化:抽象类不能被实例化,它通常作为基类存在,为子类提供一套通用的接口和部分实现。

  • 包含实现:抽象类可以包含具体的方法实现和抽象方法。抽象方法必须在子类中被重写。

  • 单继承:一个类只能继承自一个抽象类(C#中不支持多重继承)。

应用场景

当你希望提供一个通用的基类,该基类定义了一些子类共有的方法实现,并且还有一些方法需要由子类提供具体实现时,使用抽象类是一个不错的选择。

示例

示例1:图形基类

namespace App01
{
    // 抽象的图形基类
    public abstract class Shape
    {
        // 抽象方法:计算面积
        public abstract double Area();

        // 具体实现的方法:显示形状信息
        public void Display()
        {
            Console.WriteLine("This is a shape.");
        }
    }

    // 圆形类,继承自Shape
    publicclass Circle : Shape
    {
        publicdouble Radius { get; set; }

        // 重写抽象方法:计算圆的面积
        public override double Area()
        {
            return Math.PI * Radius * Radius;
        }
    }

    // 矩形类,继承自Shape
    publicclass Rectangle : Shape
    {
        publicdouble Width { get; set; }
        publicdouble Height { get; set; }

        // 重写抽象方法:计算矩形的面积
        public override double Area()
        {
            return Width * Height;
        }
    }
    internal class Program
    {
        static void Main(string[] args)
        {
            Circle circle = new Circle();
            circle.Radius = 5;
            Console.WriteLine("The area of the circle is {0}.", circle.Area());

            Rectangle rectangle = new Rectangle();
            rectangle.Width = 4;
            rectangle.Height = 6;
            Console.WriteLine("The area of the rectangle is {0}.", rectangle.Area());
        }
    }
}

说明:

  • Shape​类是一个抽象类,包含一个抽象方法Area()​和一个具体方法Display()​

  • Circle​Rectangle​类继承自Shape​,并实现了Area()​方法。

示例2:动物基类

namespace App02  
{
    // 抽象的动物基类
    public abstract class Animal
    {
        // 抽象方法:发出叫声
        public abstract void MakeSound();

        // 具体方法:共同的行为
        public void Sleep()
        {
            Console.WriteLine("The animal is sleeping.");
        }
    }

    // 狗类,继承自Animal
    publicclass Dog : Animal
    {
        public override void MakeSound()
        {
            Console.WriteLine("Dog barks: Woof!");
        }
    }

    // 猫类,继承自Animal
    publicclass Cat : Animal
    {
        public override void MakeSound()
        {
            Console.WriteLine("Cat meows: Meow!");
        }
    }
    internal class Program
    {
        static void Main(string[] args)
        {
            // 创建Dog对象
            Dog dog = new Dog();
            // 调用Dog的共同行为
            dog.Sleep();
            // 调用Dog的叫声
            dog.MakeSound();
            // 创建Cat对象
            Cat cat = new Cat();
            // 调用Cat的共同行为
            cat.Sleep();
            // 调用Cat的叫声
            cat.MakeSound();
        }
    }
}

说明:

  • Animal​类定义了共有的行为Sleep()​,并要求子类实现MakeSound()​方法。

接口(Interface)

概念

  • 完全抽象:接口只能包含方法、属性、事件、索引器的声明,不能包含任何实现。

  • 多实现:一个类可以实现多个接口,实现接口即需要实现其所有成员。

  • 成员默认是公共的:接口成员默认是公共的,不能包含访问修饰符。

应用场景

当你希望定义一组不相关类之间的通用行为契约,并且不涉及实现细节时,接口是最好的选择。

示例

示例1:可绘制和可变形

namespace App01
{
    // 可绘制的接口
    public interface IDrawable
    {
        void Draw();
    }

    // 可变形的接口
    public interface ITransformable
    {
        void Rotate(double angle);
        void Scale(double factor);
    }

    // 实现了IDrawable和ITransformable的形状类
    publicclass TransformableShape : IDrawable, ITransformable
    {
        public void Draw()
        {
            Console.WriteLine("Drawing the shape.");
        }

        public void Rotate(double angle)
        {
            Console.WriteLine($"Rotating the shape by {angle} degrees.");
        }

        public void Scale(double factor)
        {
            Console.WriteLine($"Scaling the shape by a factor of {factor}.");
        }
    }
    internal class Program
    {
        static void Main(string[] args)
        {
            TransformableShape shape = new TransformableShape();
            shape.Draw();
            shape.Rotate(45);
            shape.Scale(2.0);
            Console.ReadKey();
        }
    }
}

说明:

  • ​IDrawable​ITransformable​是两个接口,定义了绘制和变形的行为。

  • TransformableShape​类实现了这两个接口,必须提供所有方法的实现。

示例2:数据存储接口

namespace App02
{
    // 数据存储接口
    public interface IDataStore
    {
        void Save(string data);
        string Load();
    }

    // 本地文件存储类
    publicclass FileDataStore : IDataStore
    {
        public void Save(string data)
        {
            Console.WriteLine("Saving data to file.");
            // 实际的文件保存逻辑
        }

        public string Load()
        {
            Console.WriteLine("Loading data from file.");
            // 实际的文件加载逻辑
            return"Data from file";
        }
    }

    // 云端存储类
    publicclass CloudDataStore : IDataStore
    {
        public void Save(string data)
        {
            Console.WriteLine("Saving data to cloud.");
            // 实际的云保存逻辑
        }

        public string Load()
        {
            Console.WriteLine("Loading data from cloud.");
            // 实际的云加载逻辑
            return"Data from cloud";
        }
    }
    internal class Program
    {
        static void Main(string[] args)
        {
            // 选择存储方式
            IDataStore dataStore = new FileDataStore();
            // 保存数据
            dataStore.Save("Some data");
            // 加载数据
            string loadedData = dataStore.Load();
            Console.WriteLine("Loaded data: " + loadedData);
        }
    }
}

抽象类与接口的组合使用

有时,我们可以将抽象类和接口结合起来使用,以充分利用它们的优势。

示例:动物行为

namespace App03
{
    // 抽象的动物类
    public abstract class Animal
    {
        public abstract void Eat();

        public void Breathe()
        {
            Console.WriteLine("Animal breathes.");
        }
    }

    // 可移动的接口
    public interface IMovable
    {
        void Move();
    }

    // 可飞行的接口
    public interface IFlyable
    {
        void Fly();
    }

    // 狗类,继承自Animal并实现IMovable接口
    publicclass Dog : Animal, IMovable
    {
        public override void Eat()
        {
            Console.WriteLine("Dog eats.");
        }

        public void Move()
        {
            Console.WriteLine("Dog runs.");
        }
    }

    // 鸟类,继承自Animal并实现IMovable和IFlyable接口
    publicclass Bird : Animal, IMovable, IFlyable
    {
        public override void Eat()
        {
            Console.WriteLine("Bird eats.");
        }

        public void Move()
        {
            Console.WriteLine("Bird walks.");
        }

        public void Fly()
        {
            Console.WriteLine("Bird flies.");
        }
    }
    internal class Program
    {
        static void Main(string[] args)
        {
            // 创建Dog对象并调用方法
            Dog dog = new Dog();
            dog.Eat();
            dog.Breathe();
            dog.Move();
            // 创建Bird对象并调用方法
            Bird bird = new Bird();
            bird.Eat();
            bird.Breathe();
            bird.Move();
            bird.Fly();
            Console.ReadKey();
        }
    }
}

说明:

  • Animal​是一个抽象类,定义了所有动物的共有行为。

  • IMovable​和IFlyable​是接口,定义了可移动和可飞行的行为。

  • Dog​类继承自Animal​并实现了IMovable​接口。

  • Bird​类继承自Animal​并实现了IMovable​和IFlyable​接口。

总结

抽象类和接口的区别

特性

抽象类

接口

实例化

❌ 不能实例化

❌ 不能实例化

实现内容

✅ 可包含具体方法和抽象方法

❌ 仅声明成员,无实现

继承/实现数量

单继承(一个子类只能继承一个父类)

多实现(一个类可实现多个接口)

成员访问修饰符

支持(如public​、protected​)

成员默认public​,不可显式修饰

字段/属性

✅ 可定义字段、属性

❌ 只能声明属性(无字段)

设计目的

提供代码复用和部分通用逻辑

定义行为契约,实现多态

组合使用场景

  • 结合优势:抽象类提供基础实现,接口扩展额外功能

    • 示例

      • Animal​抽象类定义Eat()​Breathe()​。

      • IMovable​和IFlyable​接口分别定义移动和飞行能力。

      • Dog​继承Animal​并实现IMovable​。

      • Bird​继承Animal​并实现IMovable​和IFlyable​。

何时选择?

  • 抽象类:聚焦于代码复用层次化设计,适合“是什么”(Is-A)关系。

  • 接口:聚焦于行为契约功能扩展,适合“能做什么”(Can-Do)关系。

  • 组合使用:通过抽象类提供基础能力,接口扩展多样化功能,实现灵活设计。

【WPF】单双屏显示切换控制

2025-02-16 22:56:50

前言

近期我完成了一个项目,该项目需求是在连接了双显示器的设备上,对屏幕显示设置的更改进行监听。具体而言,当显示设置调整为 “复制这些显示器” 时,程序要自动隐藏;而当显示设置变为 “扩展这些显示器” 时,程序则需显示在非主显示器的第二显示器上。接下来,我会对此次项目中的业务逻辑进行简单记录,希望能对你有所助益。

解决方案

  1. 启动程序时通过调用ShowInSecondScreen2()​方法,将窗口移动至非主显示器上面。

    private double left = 0;
    
    private Rectangle primaryRect;
    
    private Rectangle secondRect;
    
    private double top = 0;
    
    private void ShowInSecondScreen2()
    {
        this.Dispatcher.Invoke(new Action(() =>
        {
            this.WindowState = WindowState.Normal;
            //获取所有显示器的信息
            Screen[] screens = Screen.AllScreens;
    
            //确定主显示器的分辨率,用于计算副显示器的起始位置
            primaryRect = Screen.PrimaryScreen.Bounds;
    
            //将窗口放置在第一个找到的副显示器上
            bool placed = false;
            foreach (Screen screen in screens)
            {
                if (screen.Primary == false) //找到的是非主显示器
                {
                    Rectangle workingArea = screen.Bounds;
                    //设置窗口位置,使其在副显示器的左上角显示
                    left = this.Left = workingArea.Left;
                    top = this.Top = workingArea.Top;
                    this.Width = workingArea.Width;
                    this.Height = workingArea.Height;
                    secondRect = workingArea;
                    placed = true;
                    break;
                }
            }
    
            if (!placed)
            {
                // 如果没有找到副显示器,就在主显示器上显示
                this.Left = primaryRect.Left;
                this.Top = primaryRect.Top;
                this.Width = secondRect.Width;
                this.Height = secondRect.Height;
            }
            this.Activate();
        }));
    }
    
  2. 注册SystemEvents.DisplaySettingsChanged​事件,用于监听屏幕显示设置的更改。

    
    private void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        this.DataContext = (System.Windows.Application.Current.Resources["Locator"] as ViewModelLocator).Main;
        ShowInSecondScreen2();
        Microsoft.Win32.SystemEvents.DisplaySettingsChanged += SystemEvents_DisplaySettingsChanged;
    }
    
  3. 编写SystemEvents_DisplaySettingsChanged​事件方法,对单/双屏幕设置来控制窗口的显示位置。

    private void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e)
    {
        try
        {
            var screens = System.Windows.Forms.Screen.AllScreens;
            if (screens.Length >= 2)
            {
                this.Show();
                this.Dispatcher.Invoke(new Action(() =>
                {
                    this.Left = left;
                    this.Top = top;
                    this.Width = secondRect.Width;
                    this.Height = secondRect.Height;
                    this.Activate();
                }));
            }
            else
            {
                this.Hide();
            }
        }
        catch (Exception exp)
        {
    
        }
    }

注意

以上代码会受到系统缩放百分比影响,建议禁用DPI感知。在AssemblyInfo.cs​中,写入禁用DPI感知代码。

//禁用DPI感知
[assembly: System.Windows.Media.DisableDpiAwareness]

原因分析

  1. DPI感知模式影响数据获取

    • 默认情况下,未声明DPI感知的应用程序会被Windows自动进行位图缩放,导致Screen.Bounds​返回逻辑分辨率而非物理分辨率。

    • 当主副显示器缩放比例不同时,Screen​类可能仅返回主显示器缩放后的逻辑分辨率,而副显示器的物理分辨率无法正确识别。

  2. Screen类的局限性

    • Screen.AllScreens​的Bounds​属性返回的是系统缩放后的逻辑分辨率,而非实际物理分辨率。

    • 在多显示器不同缩放比例场景下,未正确设置DPI感知的应用程序可能无法区分各显示器的实际分辨率。

【WPF】解决在WPF中使用MediaElement控件播放视频卡顿问题

2025-02-14 10:31:58

问题

在近期项目中,我使用了 WPF 原生的 MediaElement​ 媒体控件来播放视频。然而,当我的笔记本连接外接显示屏时,程序中 MediaElement​ 控件播放视频会出现短暂卡顿,尤其是在每次实例化该控件并自动播放视频时。而当我断开外接显示屏后,MediaElement​ 控件则能正常播放视频。经过仔细研究,我发现笔记本在外接显示屏的情况下运行该程序时,会自动调用 GPU 加速。由此我得出结论:

在 WPF 中使用 MediaElement​ 控件时,其自动调用 GPU 加速是导致视频卡顿的原因。

解决

1. 禁用硬件加速

在需要禁用硬件加速的页面中,可以在App.xaml.cs​OnStartup​方法中设置RenderOptions.ProcessMode​属性为SoftwareOnly​。这样可以确保MediaElement在停止播放后不再出现卡顿现象。但需要注意的是,禁用硬件加速可能会导致播放时仍然出现卡顿,尤其是在高分辨率(如8K)或复杂场景下,WPF默认的硬件加速可能成为性能瓶颈。可以通过以下代码在特定页面禁用硬件加速:

RenderOptions.ProcessRenderMode = System.Windows.Interop.RenderMode.SoftwareOnly;

并在离开页面后重新启用硬件加速以恢复其他功能的性能。此方法能显著缓解UI卡顿,但可能影响播放时的流畅度。

2. 关闭透明效果

将AllowsTransparency​属性设置为False​,避免透明层叠加带来的额外渲染开销:

<Window AllowsTransparency="False">

【折腾笔记】利用AI大模型构建本地知识库

2025-02-11 18:49:03

前言

维度

传统知识库

具有AI的知识库

数据存储方式

结构化数据为主(表格、文档),依赖手动分类和标签

支持非结构化数据(文本、图片、音视频),利用嵌入技术(Embedding)自动编码为向量存储

检索机制

基于关键词匹配或固定规则(如SQL查询)

支持语义搜索,通过向量相似度匹配理解用户意图(如“性价比高的手机”≈“低价高性能手机”)

交互方式

用户需输入精确关键词,返回静态结果

支持自然语言对话(如提问“如何解决打印机卡纸?”),生成动态答案或分步骤指导

知识更新

依赖人工录入和定期维护,更新滞后

支持自动学习新数据(如爬取最新行业报告),部分系统可结合RAG(检索增强生成)实时整合外部知识

推理能力

无自主推理能力,仅提供已有信息

通过LLM(大语言模型)进行逻辑推理、总结归纳(如分析故障原因并推荐解决方案)

应用场景

企业文档管理、FAQ库等静态场景

智能客服、个性化推荐、自动化报告生成等动态场景

相比传统的知识库,AI知识库具有更高的智能化程度。它不仅能够理解用户的查询意图,还能根据用户的历史行为和偏好进行个性化推荐。此外,AI大模型知识库还具备知识推理、问答生成等高级功能,能够为用户提供更加智能、个性化的知识服务。这使得AI大模型知识库在教育、医疗、金融、客服等多个领域具有广泛的应用前景。

云端AI知识库可能涉及敏感数据泄露风险,因此企业更倾向本地化部署以保障数据安全。为帮助用户实现这一目标,本教程将基于Windows系统,通过Ollama(本地模型管理) + DeepSeek(开源中文模型) + AnythingLLM(私有知识库框架) 的组合方案,逐步演示如何构建安全可控的本地AI知识库。​​

简介

Ollama(本地模型管理)

Ollama 是一个开源的本地大语言模型运行框架。

基本概念

  • 核心功能:Ollama 专注于在本地机器上便捷部署和运行大型语言模型(LLM),支持多种操作系统,包括 macOS、Windows、Linux 以及通过 Docker 容器运行。

  • 主要特点:它提供对模型量化的支持,可以显著降低显存要求,使得在普通家用计算机上运行大型模型成为可能。

主要特点

  • 多种预训练语言模型支持:Ollama 提供了多种开箱即用的预训练模型,包括常见的 GPT、BERT 等大型语言模型,用户可以轻松加载并使用这些模型进行文本生成、情感分析、问答等任务。

  • 易于集成和使用:Ollama 提供了命令行工具(CLI)和 Python SDK,简化了与其他项目和服务的集成,开发者无需担心复杂的依赖或配置,可以快速将 Ollama 集成到现有的应用中。

  • 本地部署与离线使用:Ollama 允许开发者在本地计算环境中运行模型,这意味着可以脱离对外部服务器的依赖,保证数据隐私,并且对于高并发的请求,离线部署能提供更低的延迟和更高的可控性。

  • 支持模型微调与自定义:用户不仅可以使用 Ollama 提供的预训练模型,还可以在此基础上进行模型微调,根据自己的特定需求,开发者可以使用自己收集的数据对模型进行再训练,从而优化模型的性能和准确度。

  • 性能优化:Ollama 关注性能,提供了高效的推理机制,支持批量处理,能够有效管理内存和计算资源,这让它在处理大规模数据时依然保持高效。

  • 跨平台支持:Ollama 支持在多个操作系统上运行,包括 Windows、macOS 和 Linux,这样无论是开发者在本地环境调试,还是企业在生产环境部署,都能得到一致的体验。

  • 开放源码与社区支持:Ollama 是一个开源项目,这意味着开发者可以查看源代码,进行修改和优化,也可以参与到项目的贡献中,此外,Ollama 有一个活跃的社区,开发者可以从中获取帮助并与其他人交流经验。

功能

  • 本地模型管理:Ollama 支持从官方模型库或自定义模型库拉取预训练模型,并在本地保存和加载,它支持各种流行的模型格式(如 ONNX、PyTorch、TensorFlow)。

  • 高效推理:通过 GPU/CPU 的加速,Ollama 提供高效的模型推理,适合本地化应用或需要控制数据隐私的场景。

  • 多种接口访问:Ollama 支持命令行(CLI)、HTTP 接口访问推理服务,并通过 OpenAI 客户端实现更广泛的集成。

  • 环境变量配置:通过灵活的环境变量,用户可以自定义推理设备(GPU/CPU)、缓存路径、并发数、日志级别等。


DeepSeek(开源中文模型)

DeepSeek 是一款开源的大语言模型。

核心优势

  • 智能化:DeepSeek 能够理解复杂的问题,并提供精准的解决方案。它通过深度学习和自然语言处理技术,能够理解用户的需求并提供个性化的建议。

  • 多功能性:DeepSeek 在多个领域都有广泛的应用,包括学习、工作和生活。它可以用作学习助手、编程助手、写作助手、生活助手和翻译助手等,满足用户在不同场景下的需求。

  • 易用性:DeepSeek 通过自然语言交互,用户无需学习复杂的操作即可与模型进行对话。这种交互方式使得用户能够轻松地获取所需的信息和服务。

  • 低成本:DeepSeek 的训练和推理成本较低,打破了传统 N 卡垄断,降低了大模型的使用门槛。这使得更多的企业和个人能够使用高性能的 AI 服务。

  • 高效率:DeepSeek 在推理能力和响应速度上表现出色,能够快速处理复杂的查询和任务,提供准确的答案和解决方案。

  • 开源生态:DeepSeek 采用了开源策略,吸引了大量开发者和研究人员的参与,推动了 AI 技术的发展和应用。

技术特点

  • 深度学习:DeepSeek 通过大量的数据训练,学会了如何理解和处理复杂的问题,提供个性化的建议和解决方案。

  • 自然语言处理(NLP):DeepSeek 能够理解人类的语言,无论是中文、英文还是其他语言,支持自然方式的对话。

  • 知识图谱:DeepSeek 存储了大量的结构化知识,能够快速找到相关信息,提供精准的答案。

  • 混合专家模型(MoE):DeepSeek 采用了 MoE 框架,通过训练多个专家模型,并根据输入数据的特征动态选择最合适的专家模型进行处理,从而实现对复杂任务的高效处理。

  • 多头潜在注意力机制(MLA):DeepSeek 的 MLA 技术显著降低了模型推理成本,通过减少对 KV 矩阵的重复计算,提高了模型的运行效率。

  • 大规模强化学习:DeepSeek 通过大规模强化学习技术,增强了模型的推理能力和泛化能力,能够在多个领域中表现出色。


AnythingLLM(私有知识库框架)

AnythingLLM 是一个全栈应用程序,允许用户使用商业现成的 LLM(大语言模型)或流行的开源 LLM 以及向量数据库解决方案,构建一个无需妥协的本地 ChatGPT。用户可以通过它与提供给它的任何文档进行智能交流,新颖的设计使得用户能够选择想要使用的 LLM 或向量数据库,并支持多用户管理和权限设置。

主要特点

  • 多用户支持和权限管理:允许多个用户同时使用,并可设置不同的权限。

  • 支持多种文档类型:包括 PDF、TXT、DOCX 等。

  • 简易的文档管理界面:通过用户界面管理向量数据库中的文档。

  • 两种聊天模式:对话模式保留之前的问题和回答,查询模式则是简单的针对文档的问答。

  • 聊天中的引用标注:链接到原始文档源和文本。

  • 简单的技术栈:便于快速迭代。

  • 100% 云部署就绪:适合云部署。

  • “自带 LLM”模式:可以选择使用商业或开源的 LLM。

  • 高效的成本节约措施:对于大型文档,只需嵌入一次,比其他文档聊天机器人解决方案节省 90% 的成本。

  • 完整的开发者 API:支持自定义集成。

功能

  • 自定义 AI 代理:用户可以根据需求创建自己的 AI 代理,使应用更具个性化。

  • 支持多模态:不仅支持闭源 LLM,还兼容开源 LLM,拓展了应用的灵活性。

  • 工作区内的代理:支持在工作区内浏览网页、运行代码等操作。

  • 自定义可嵌入聊天小部件:可以嵌入到用户的网站。

部署

  1. 前往 Ollama 官方网站(https://ollama.com/download)下载安装包。

  2. 直接通过安装包安装Ollama会直接安装在C盘,如果需要自定义安装路径,需要通过命令行指定安装路径,启动安装程序,点击 Install 后,Ollama 就会安装到指定的目录了。

    OllamaSetup.exe /DIR=E:\MySoftware\Ollama

  3. 打开一个新的命令行,输入ollama​回车执行,如果返回以下内容,则表示安装成功。

    在浏览器中输入127.0.0.1:11434​,显示Ollama is running​表示Ollama已经成功运行。

  4. 更改模型存储位置。大模型资源包默认下载到 C 盘,可以手动创建大模型存储目录,然后在用户账户中设置环境变量OLLAMA_MODELS​,将其设置为希望存储模型的路径。

    需要重启Ollama才能生效!

  5. 拉取模型到本地。在命令行输入ollama pull deepseek-r1:32b​。

    根据本机配置和业务需求进行选择模型参数。

    Models

    Configuration

    Command

    DeepSeek-R1-1.5B

    CPU :最低 4 核,推荐 Intel/AMD 多核处理器。
    内存 :8GB +。
    硬盘 :3GB + 存储空间,模型文件约 1.5-2GB。
    显卡 :非必需,纯 CPU 推理即可,若 GPU 加速可选 4GB + 显存,如 GTX 1650。

    ollama pull deepseek-r1:1.5b

    DeepSeek-R1-7B

    CPU :8 核以上,推荐现代多核 CPU。
    内存 :16GB +。
    硬盘 :8GB +,模型文件约 4-5GB。
    显卡 :推荐 8GB + 显存,如 RTX 3070/4060。

    ollama pull deepseek-r1:7b

    DeepSeek-R1-8B

    CPU :8 核以上,推荐现代多核 CPU。
    内存 :16GB +。
    硬盘 :8GB +,模型文件约 4-5GB。
    显卡 :推荐 8GB + 显存,如 RTX 3070/4060。

    ollama pull deepseek-r1:8b

    DeepSeek-R1-14B

    CPU :12 核以上。
    内存 :32GB +。
    硬盘 :15GB +。
    显卡 :16GB + 显存,如 RTX 4090 或 A5000。

    ollama pull deepseek-r1:14b

    DeepSeek-R1-32B

    CPU :16 核以上,如 AMD Ryzen 9 或 Intel i9。
    内存 :64GB +。
    硬盘 :30GB +。
    显卡 :24GB + 显存,如 A100 40GB 或双卡 RTX 3090。

    ollama pull deepseek-r1:32b

    DeepSeek-R1-70B

    CPU :32 核以上,服务器级 CPU。
    内存 :128GB +。
    硬盘 :70GB +。
    显卡 :多卡并行,如 2x A100 80GB 或 4x RTX 4090。

    ollama pull deepseek-r1:70b

    DeepSeek-R1-671B

    CPU :64 核以上,服务器集群。
    内存 :512GB +。
    硬盘 :300GB +。
    显卡 :多节点分布式训练,如 8x A100/H100。

    ollama pull deepseek-r1:671b

    等待模型拉取到本地。

  6. 拉取嵌入模型nomic-embed-text​

    ollama pull nomic-embed-text

  7. 前往 AnythingLLM 官方网站(https://anythingllm.com/desktop)下载安装包。

  8. 配置LLM首选项。平台选择Ollama​,模型选择我们刚拉取的模型deepseek-r1:32b​,其他保持默认,然后点击“Save changes”进行保存。

  9. 配置嵌入模型。平台选择Ollama​,模型选择nomic-embed-text:latest​,其他的保持默认,然后点击“Save changes”进行保存。

  10. 如果对英文不友好,可以在 Customization 中将 Display Language 设置成Chinese​。

使用

  1. 创建工作区。给工作区设置一个名称,然后保存。

  2. 上传知识库文件。点击工作区名称旁边的上传图标,进入文件上传管理。

  3. 选择知识库文件上传。

  4. 将知识库文件移动到当前工作区。

  5. 保存并嵌入知识库文件。

  6. 在工作区中与大模型进行对话。

总结

在使用 DeepSeek-R1 模型的过程中,我发现当参数在 32B 及以下时,模型在处理复杂推理任务时的表现似乎不太理想,感觉有些力不从心。具体来说,它会出现中英文混合输出的情况,这在一定程度上影响了结果的准确性和可读性。

另外,就 AnythingLLM 框架而言,其检索能力也存在一些不足之处。有时候我提出的问题,明明在知识库中是存在相关知识的,但系统却提示没有找到相关知识,这说明框架的检索功能还有待进一步优化和提升,以便能更精准地定位和提供知识库中的有效信息。

【Docker】搭建一个功能强大的自托管虚拟浏览器 - n.eko

2025-01-18 22:46:06

前言

本教程基于群晖的NAS设备DS423+docker功能进行搭建,DSM版本为 DSM 7.2.2-72806 Update 2。

n.eko 支持多种类型浏览器在其虚拟环境中运行,本次教程使用 Chromium​ 浏览器镜像进行演示,支持访问内网设备和公网地址。

简介

n.eko 是一款基于 Docker 的自托管虚拟浏览器,利用 WebRTC 技术实现实时音视频传输和多人协作功能。它允许用户在虚拟环境中运行功能齐全的浏览器(如 Firefox、Chrome 等),并支持多人同时访问和操作,适用于远程协作、观看派对、互动演示等场景。

​​

核心功能

  1. 虚拟浏览器

    • 支持多种浏览器内核(如 Firefox、Chrome、Opera 等),用户可以在虚拟环境中浏览网页、运行应用程序。

    • 所有操作都在 Docker 容器中完成,确保安全性和隐私性。

  2. 多人协作

    • 支持多用户同时访问,用户可以共享浏览器画面并实时互动,适合团队协作、远程教学或家庭娱乐。

    • 提供聊天功能、文件传输和剪贴板同步,增强协作体验。

  3. 实时音视频传输

    • 基于 WebRTC 技术,实现低延迟的音视频传输,支持屏幕共享和远程控制。

    • 支持 RTMP 推流,可将内容广播到 Twitch 或 YouTube 等平台。

  4. 隐私与安全

    • 所有操作都在隔离的 Docker 容器中进行,避免数据泄露。

    • 支持管理员权限控制,如踢出用户、锁定房间等。

  5. 灵活部署

    • 通过 Docker 部署,支持多种操作系统(Windows、Linux、macOS)。

    • 提供丰富的配置选项,如分辨率、密码设置、文件传输路径等。

适用场景

  • 远程协作:团队成员可以共同浏览网页、调试代码或进行头脑风暴。

  • 观看派对:与朋友或家人一起观看视频、动漫,并实时聊天互动。

  • 教育培训:用于远程教学或演示,支持多人同时操作和互动。

  • 隐私浏览:在隔离环境中访问敏感网站,避免留下痕迹。

部署

  1. 在群晖NAS上面的“File Station”中新建一个docker映射文件,用于映射docker中neko-chromium的数据。

  2. 打开“Container Manager”,在“项目”中,点击“新增”。填写项目名称,路径选择创建好的映射文件夹,文件选择“创建 docker-compose.yml”,然后将以下配置代码复制粘贴进去。

    version: "3.8"
    
    services:
      neko:
        image: "m1k1o/neko:chromium"
        container_name: "neko-chromium"
        restart: "unless-stopped"
        shm_size: "3gb" # 设置共享内存大小为 3GB,此设置为必须。
        ports:
          - "19800:8080"
          - "52000-52100:52000-52100/udp"
        cap_add:
          - SYS_ADMIN # 使用 Chromium 内核时需添加,以获取必要的系统管理权限。
        volumes:
          - ./chromium/data:/home/neko/.config/chromium # 策略文件,重启依然能保留浏览器数据。
        environment:
          NEKO_SCREEN: 1280x720@30 # 自定义浏览器窗口分辨率。
          NEKO_PASSWORD: neko # 普通用户的登录密码。
          NEKO_PASSWORD_ADMIN: admin # 管理员(admin)用户的登录密码。  
          NEKO_EPR: 52000-52100 # 设置 WebRTC 的 UDP 端口范围,用于P2P连接。
          NEKO_ICELITE: true # 启用 Ice Lite 协议以优化连接性能,可选。
          NEKO_CONTROL_PROTECTION: true # 控制保护意味着,只有当至少有一个管理员在房间里时,用户才能获得控制权。
          NEKO_NAT1TO1: 192.168.1.111 # 局域网使用时设置为服务器本地 IP,公网则自动获取公网 IP,可选。

    最后点击“下一步”,等待镜像拉取和容器创建完成。

使用

  1. 输入IP:Port​访问,使用管理员登录,用户名为登录后显示的名称,可自定义,密码根据之前配置进行填写。

  2. 设置中文语言。默认是英文,点击左下角en​,选择cn​切换至中文。

  3. 获取浏览器控制权。点击正下方的键盘图标,获取控制权。

  4. 调整屏幕尺寸。点击右上角显示器图标,进行分辨率切换。

  5. 对普通用户进行操作(需要管理员账号)。选中对应头像,鼠标右键单击,可对其进行选择“给予控制”或“踢出”等操作。

  6. 聊天室。点击右上角侧栏图标,然后点击聊天。

  7. 粘贴板共享。在右下角有个粘贴板图标,如果需要将文字内容复制进浏览器,需要将内容粘贴至粘贴板内。

    仅支持纯文本。

  8. 使用自动加入链接。

    示例:http(s)://[URL:Port](URL:Port)/?pwd=neko&usr=guest&cast=1​

    • 添加?pwd=<password>​将预填充密码。

    • 添加?usr=<display-name>​将预填充用户名。

    • 添加?cast=1​将隐藏所有控件,只显示视频。

    • 添加?embed=1​将隐藏大多数附加组件,仅显示视频

    • 添加?volume=<0-1>​将音量设置为给定值。

    • 添加?lang=<language>​将语言设置为给定值。

    • 添加?show_side=1​将在启动时显示侧边栏。

    • 添加?mute_chat=1​将在启动时静音聊天。

  9. 更多使用教程,请参考官方文档 n.keo Doc(https://neko.m1k1o.net/#/getting-started/