工业离线 AI 助手程序总结

这是一个基于C# .NET开发、集成通义千问 Qwen2.5-7B 开源大模型的本地离线工业 AI 助手控制台程序,核心实现了无网络环境下的工业场景智能问答,结合本地知识库提供精准、安全的专属 AI 服务。net8.0  winform

程序核心架构分为模型加载、知识库管理、对话交互、推理问答四大模块:启动时自动加载 GGUF 量化格式的 4 比特模型,纯 CPU 运行仅占用约 6GB 内存,无需依赖显卡,大幅降低部署门槛;自动读取指定knowledge文件夹内的所有文件构建本地知识库,支持展示已学习文件,通过关键词匹配技术快速检索与用户问题相关的参考资料。

对话功能具备完整的交互体验,支持连续对话记忆、一键清空历史、数字快捷追问上一轮问题,内置退出、清屏等实用指令。推理环节采用通义千问专属对话模板,结合系统提示词限定 AI 以工业专业助手身份作答,将检索到的知识库内容融入 prompt,保证回答精准贴合工业场景,同时限制输出长度,提升响应速度。

整体方案解决了工业场景数据保密、无网络环境的核心痛点,模型轻量化、部署简单、交互便捷,既能独立完成通用问答,又能依托本地知识库实现专业工业问题解答,是适配工业现场的轻量化离线 AI 解决方案,兼顾了实用性、安全性和易用性。

总结

  1. 这是纯离线、本地化的工业专用 AI 助手,无数据外泄风险,适配无网工业环境;
  2. 基于 Qwen2.5-7B 量化模型,低资源占用,纯 CPU 即可流畅运行;
  3. 核心能力:本地知识库检索 + 智能问答,支持对话记忆、快捷追问,交互友好;
  4. 部署简单、操作便捷,完美匹配工业场景的安全、专业、轻量化需求。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using LLama;
using LLama.Common;
using LLama.Sampling;

namespace QwenConsole
{
    class Program
    {
        private static LLamaWeights _model;
        private static LLamaContext _context;
        private static InteractiveExecutor _executor;
        private static Dictionary<string, string> _knowledge;

        // 内部记忆(不显示)
        private static List<ChatHistoryItem> _chatHistory = new();
        private static int _historyId = 1;

        private static readonly InferenceParams _infParams = new InferenceParams
        {
            MaxTokens = 500,
            AntiPrompts = new[] { "<|im_end|>", "User:", "用户:", "Assistant:", "助手:" },
            SamplingPipeline = new DefaultSamplingPipeline { Temperature = 0.3f }
        };

        private static string _modelPath = "qwen2.5-7b-instruct-1m-q4_k_m.gguf";
        private static string _knowledgeFolder = "knowledge"; // 知识库文件夹

        public class ChatHistoryItem
        {
            public int Id { get; set; }
            public string Question { get; set; }
            public string Answer { get; set; }
        }

        static async Task Main(string[] args)
        {
            Console.OutputEncoding = Encoding.UTF8;
            Console.WriteLine("正在加载模型...");

            using (var nullWriter = new StringWriter())
            {
                Console.SetOut(nullWriter);
                var parameters = new ModelParams(_modelPath)
                {
                    ContextSize = 8192,
                    GpuLayerCount = 99,
                    Threads = Environment.ProcessorCount,
                };

                _model = LLamaWeights.LoadFromFile(parameters);
                _context = new LLamaContext(_model, parameters);
                _executor = new InteractiveExecutor(_context);
                _knowledge = FileHelper.LoadAllKnowledgeFiles();
            }

            Console.SetOut(new StreamWriter(Console.OpenStandardOutput()) { AutoFlush = true });

            Console.WriteLine("=================================================="); Console.WriteLine("==================================================");
            Console.WriteLine("             工业离线AI助手");
            Console.WriteLine("   模型:Qwen2.5-7B-Instruct 通义千问开源大模型");
            Console.WriteLine("   量化:4-bit Q4_K_M(高精度小体积)");
            Console.WriteLine("   占用:内存 ~ 6GB ± 显存 0GB(纯CPU运行)");
            Console.WriteLine($"   已加载知识库文件夹:{_knowledgeFolder}");
            Console.WriteLine($"         学习文件总数:{_knowledge.Count} 个"); 
            ShowLoadedFiles(); // 显示学习了哪些文件
            Console.WriteLine("  输入数字 = 追问上一轮 | 直接输入 = 新问题");
            Console.WriteLine("        exit 退出 | clear 清空记忆");
            Console.WriteLine("==================================================\n");

            while (true)
            {
                Console.Write("你:");
                string input = Console.ReadLine()?.Trim();
                if (string.IsNullOrEmpty(input)) continue;

                if (input.ToLower() == "exit") break;
                if (input.ToLower() == "clear")
                {
                    _chatHistory.Clear();
                    _historyId = 1;
                    Console.WriteLine("✅ 记忆已清空\n");
                    continue;
                }

                string userQuestion = input;

                // 输入数字 = 追问上一轮
                if (int.TryParse(input, out int num) && num == _historyId - 1 && _chatHistory.Count > 0)
                {
                    var last = _chatHistory.Last();
                    userQuestion = $"请详细解释:{last.Question}";
                    Console.WriteLine($"▶ 继续追问:{last.Question}");
                }

                string know = QueryKnowledge(userQuestion);
                string answer = await InferAnswer(userQuestion, know);

                _chatHistory.Add(new ChatHistoryItem
                {
                    Id = _historyId++,
                    Question = userQuestion,
                    Answer = answer
                });

                Console.WriteLine("\n");
            }

            _context?.Dispose();
            _model?.Dispose();
        }

        // 显示已学习的所有文件
        static void ShowLoadedFiles()
        {
            if (_knowledge.Count == 0)
            {
                Console.WriteLine("   未学习任何文件");
                return;
            }
            Console.WriteLine("   已学习文件列表:");
            int i = 1;
            foreach (var file in _knowledge.Keys)
            {
                Console.WriteLine($"     {i++}. {file}");
            }
        }

        static async Task<string> InferAnswer(string question, string know)
        {
            string extra = string.IsNullOrEmpty(know) ? "" : $"\n【参考资料】\n{know}\n";
            string system = $"你是专业工业AI助手,根据参考资料精准简洁回答。{extra}";

            StringBuilder historyPrompt = new();
            foreach (var h in _chatHistory)
            {
                historyPrompt.AppendLine($"<|im_start|>user\n{h.Question}<|im_end|>");
                historyPrompt.AppendLine($"<|im_start|>assistant\n{h.Answer}<|im_end|>");
            }

            string prompt = $"<|im_start|>system\n{system}<|im_end|>\n"
                          + historyPrompt
                          + $"<|im_start|>user\n{question}<|im_end|>\n"
                          + $"<|im_start|>assistant\n";

            Console.Write("AI:");
            StringBuilder answer = new();
            int count = 0;
            const int LIMIT = 500;

            await foreach (var token in _executor.InferAsync(prompt, _infParams))
            {
                if (count++ > LIMIT || token.Contains("<|im_end|>") || token.Contains("User:"))
                    break;
                Console.Write(token);
                answer.Append(token);
            }
            return answer.ToString().Trim();
        }

        private static string QueryKnowledge(string question)
        {
            if (_knowledge.Count == 0) return "";

            var words = question.ToLower()
                .Replace("?", " ").Replace("?", "")
                .Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

            var matches = _knowledge
                .Select(k => new
                {
                    k.Key,
                    k.Value,
                    Score = words.Count(w => k.Value.ToLower().Contains(w))
                })
                .Where(x => x.Score > 0)
                .OrderByDescending(x => x.Score)
                .Take(3)
                .Select(x => $"【{x.Key}】\n{x.Value.Substring(0, Math.Min(1200, x.Value.Length))}");

            return string.Join("\n\n", matches);
        }
    }
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using ExcelDataReader;
using System.Data;
using iTextSharp.text.pdf;
using iTextSharp.text.pdf.parser;
using DocumentFormat.OpenXml.Packaging;
using Path = System.IO.Path;

public static class FileHelper
{
    /// <summary>
    /// 读取【资料】文件夹下所有支持的文档
    /// 支持:txt / pdf / docx / xlsx / xls
    /// </summary>
    public static Dictionary<string, string> LoadAllKnowledgeFiles()
    {
        var knowledge = new Dictionary<string, string>();
        string baseDir = AppDomain.CurrentDomain.BaseDirectory;
        string learnDir = Path.Combine(baseDir, "资料");

        if (!Directory.Exists(learnDir))
        {
            Directory.CreateDirectory(learnDir);
            return knowledge;
        }

        string[] exts = { ".txt", ".pdf", ".docx", ".xlsx", ".xls" };
        var files = Directory.GetFiles(learnDir, "*.*", SearchOption.TopDirectoryOnly);

        foreach (var file in files)
        {
            string ext = Path.GetExtension(file).ToLower();
            if (!Array.Exists(exts, e => e == ext)) continue;

            try
            {
                string content = ext switch
                {
                    ".txt" => ReadText(file),
                    ".pdf" => ReadPdf(file),
                    ".docx" => ReadDocx(file),
                    ".xlsx" or ".xls" => ReadExcel(file),
                    _ => ""
                };

                if (!string.IsNullOrWhiteSpace(content))
                    knowledge[Path.GetFileName(file)] = content;
            }
            catch { }
        }

        return knowledge;
    }

    public static string ReadText(string path) => File.ReadAllText(path, Encoding.UTF8);

    public static string ReadPdf(string path)
    {
        try
        {
            var sb = new StringBuilder();
            using var reader = new PdfReader(path);
            for (int i = 1; i <= reader.NumberOfPages; i++)
                sb.AppendLine(PdfTextExtractor.GetTextFromPage(reader, i));
            return sb.ToString();
        }
        catch { return ""; }
    }

    public static string ReadDocx(string path)
    {
        try
        {
            var sb = new StringBuilder();
            using var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
            using var doc = WordprocessingDocument.Open(fs, false);
            if (doc.MainDocumentPart?.Document?.Body == null) return "";

            foreach (var p in doc.MainDocumentPart.Document.Body.Elements<DocumentFormat.OpenXml.Wordprocessing.Paragraph>())
            {
                string text = p.InnerText?.Trim();
                if (!string.IsNullOrEmpty(text)) sb.AppendLine(text);
            }
            return sb.ToString();
        }
        catch { return ""; }
    }

    public static string ReadExcel(string path)
    {
        try
        {
            Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
            using var stream = File.OpenRead(path);
            using var reader = ExcelReaderFactory.CreateReader(stream);
            var ds = reader.AsDataSet();
            var sb = new StringBuilder();

            foreach (DataTable table in ds.Tables)
            {
                sb.AppendLine("=== " + table.TableName + " ===");
                foreach (DataRow row in table.Rows)
                {
                    foreach (DataColumn col in table.Columns)
                        sb.Append(row[col] + "\t");
                    sb.AppendLine();
                }
            }
            return sb.ToString();
        }
        catch { return ""; }
    }
}

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐