在软件开发中,字符串处理是永恒的主题。无论是日志分析、代码生成,还是构建动态提示词,我们都需要灵活、高效且智能的工具。本文将介绍一个集成了文本分类、指令式字符串操作和智能提示构建的C#框架。该框架由三个核心命名空间组成:

TextClassification:对输入文本进行自动分类(代码、错误信息、普通文本)。

StringCommandFramework:提供一套类似汇编指令的字符串操作指令集,支持顺序执行和控制流。

SmartPromptBuilder:基于分类结果,将多个输入智能地填入模板,生成高质量的提示词。

这三个模块既可独立使用,也可协同工作,为需要复杂字符串处理的场景提供了一种模块化、可扩展的解决方案。

  1. TextClassification:智能文本分类器
    TextClassifier 类能够将一段文本划分为三种预定义类别:Code(代码片段)、Error(错误/异常信息)和 General(普通文本)。分类基于多种启发式特征,并通过加权评分确定最终类别。

分类特征与权重
错误信息识别
关键词匹配:使用预定义的错误关键词集合(如 “exception”, “stack trace”, “fatal” 等),每个匹配项贡献 0.8 分。

堆栈跟踪模式:通过正则表达式识别典型的堆栈跟踪行(如 at Class.Method() in File:line),命中则加 2 分。

十六进制地址:识别 0x… 格式的内存地址,加 0.5 分。

行号标记:识别 “line 123” 或 “:123” 模式,加 0.5 分。

关键短语:包含 “exception in” 或 “stack trace:” 等短语,加 1 分。

代码片段识别
编程语言关键词:包含 C# 常见关键字(如 if, class, public 等),每个匹配项贡献 0.5 分。

符号密度:统计代码符号(; {} = + 等)的数量,若占总字符比例超过 2%(SymbolRatioThreshold),加 1 分。

分号行比例:若包含分号的行数占总行数比例超过 20%(SemicolonLineRatioThreshold),加 1 分。

大括号对:同时出现 { 和 },加 1 分。

注释标记:包含 // 或 /*,加 1 分。

缩进行比例:以空格或制表符开头的行数占比超过 20%(IndentLineRatioThreshold),加 1 分。

括号对:出现 ()、[] 或 <>,加 0.5 分。

分类逻辑
分类器分别计算错误得分和代码得分,然后进行比较:

如果错误得分 ≥ 代码得分且 > 0 → Error

如果代码得分 > 错误得分且 > 0 → Code

否则 → General

这种设计使得分类结果具有可解释性,且权重可通过常量调整,便于针对不同场景优化。

  1. StringCommandFramework:字符串操作指令集
    该命名空间实现了一种指令模式,允许用户将字符串操作建模为一系列指令,并支持顺序执行、条件跳转和标签。核心接口为 IStringInstruction,所有指令均实现 Execute(ICommandContext) 方法。

指令上下文 ICommandContext
上下文对象保存了当前字符串构建状态(StringBuilder)、指令执行位置(CurrentIndex)以及标签字典(Labels)。这使得指令之间可以共享数据,并支持跳转。

基础指令
AppendInstruction / AppendLineInstruction:追加文本或带换行的文本。

InsertInstruction:在指定索引处插入字符串。

ReplaceInstruction:替换第一个匹配项。

ReplaceAllInstruction:替换所有匹配项。

DeleteInstruction:删除指定范围的字符。

ClearInstruction:清空当前内容。

控制流指令
IfInstruction:接受一个条件委托和一个子指令,条件满足时执行子指令。

GotoInstruction:跳转到指定标签位置。

LabelInstruction:定义标签,本身不执行操作。

指令解析器 StringCommandParser
解析器将文本命令(如 APPEND “Hello”)转换为对应的指令对象。它支持转义字符(\ 和 ")和引号包裹的参数,使得命令格式友好。例如:

text
APPEND Hello
APPENDLINE World!
INSERT 5 , beautiful
REPLACE World Universe
IF CONTAINS Universe THEN REPLACEALL Universe Cosmos
解析器还处理了 IF 语句的复杂语法,支持 CONTAINS、LENGTH 和 EQUALS 三种条件,并嵌套执行 THEN 后的指令。

指令执行器 CommandExecutor
执行器负责执行指令列表,并在执行前收集所有标签的位置。它维护当前指令索引,支持 Goto 跳转,最终返回构建完成的字符串。

批量处理 StringCommandProcessor
提供了 ProcessList 方法,可以对一组输入字符串应用相同的指令集,返回处理后的结果列表。这在批量格式化文本时非常有用。

  1. SmartPromptFramework:智能提示构建器
    该模块旨在解决一个常见需求:根据多个输入文本的类型,自动填充模板中的占位符,生成最终提示词。例如,在AI提示工程中,可能需要将错误堆栈和代码片段分别填入模板的不同部分。

核心类 SmartPromptBuilder
构造函数接受一个模板字符串和一个可选的占位符到类别的映射字典。默认映射为:

{{CODE}} → TextCategory.Code

{{ERROR}} → TextCategory.Error

{{GENERAL}} → TextCategory.General

构建流程
分类输入:遍历所有输入字符串,使用 TextClassifier 为每个输入确定类别。

分组:按类别将输入分组。

替换占位符:使用正则表达式查找模板中所有 {{NAME}} 形式的占位符。对于每个占位符,根据映射找到对应类别,若该类别下有输入,则用 “\n\n” 连接所有字符串进行替换;若没有输入且 throwIfMissing 为 true,则抛出异常,否则替换为空字符串。

返回结果:返回替换后的完整提示词。

便捷静态类 PromptBuilder
提供了多个重载的 Build 和 BuildFromList 方法,简化使用。例如:

Build(string template, params string[] inputs):自动分类输入并填充模板。

BuildFromList(params string[] items):自动将最后一个包含占位符的字符串视为模板,其余视为输入。

这种设计使得在运行时可以灵活处理不同来源的输入,非常适合构建动态提示。

  1. 综合应用示例
    假设我们需要构建一个AI调试助手,要求将错误信息和相关代码片段分别填入提示模板的不同位置。模板如下:

text
请帮我分析以下错误:
{{ERROR}}
相关代码:
{{CODE}}
我们有两个输入:

错误堆栈:System.NullReferenceException: Object reference not set to an instance of an object. at Program.Main() in Program.cs:line 10

代码片段:if (obj == null) throw new Exception();

使用 SmartPromptBuilder 可以轻松生成最终提示:

csharp
var inputs = new[] {
“System.NullReferenceException: …”,
“if (obj == null) throw new Exception();”
};
string template = “请帮我分析以下错误:\n{{ERROR}}\n相关代码:\n{{CODE}}”;
string prompt = PromptBuilder.Build(template, inputs);
Console.WriteLine(prompt);
分类器会自动识别第一个输入为 Error,第二个为 Code,并正确填充占位符。

更进一步,如果需要对代码片段进行预处理(例如缩进或删除注释),可以使用 StringCommandFramework 定义指令集:

csharp
var parser = new StringCommandParser();
var instructions = new List
{
parser.Parse(“REPLACEALL “throw new Exception();” “// exception thrown””),
parser.Parse(“APPENDLINE “// processed””)
};
var processedCode = StringCommandProcessor.ProcessList(new List { codeSnippet }, instructions).First();
然后将处理后的代码作为输入传递给 SmartPromptBuilder。

  1. 总结
    本文介绍的三个命名空间共同构成了一个功能丰富、设计清晰的字符串处理工具集:

TextClassification 提供基于规则的文本分类能力,为后续智能处理奠定基础。

StringCommandFramework 实现了一种可编程的字符串操作指令集,支持顺序执行和跳转,适合构建可重用的字符串转换流程。

SmartPromptBuilder 将分类与模板结合,实现了智能化的提示词构建,尤其适用于AI Prompt工程。

这三个模块既可以独立使用,也可以无缝集成,为开发者提供了从文本分类、指令式处理到最终生成的完整解决方案。其模块化设计和可扩展性使得它能够适应各种复杂场景,是C#项目中处理字符串的得力助手。

希望本文能帮助您快速理解并使用该框架。您可以根据实际需求调整分类权重、添加新的指令类型,甚至扩展占位符映射规则,打造属于自己的智能字符串处理流水线。

   namespace TextClassification
   {
       public enum TextCategory { Code, Error, General }

       public class TextClassifier
       {
           private const double ErrorKeywordWeight = 0.8;
           private const double CodeKeywordWeight = 0.5;
           private const double SymbolRatioThreshold = 0.02;
           private const double IndentLineRatioThreshold = 0.2;
           private const double SemicolonLineRatioThreshold = 0.2;

           private static readonly HashSet<string> CodeKeywords = new HashSet<string>
       {
           "if", "else", "for", "while", "switch", "case", "break", "continue",
           "return", "class", "struct", "interface", "enum", "using", "namespace",
           "public", "private", "protected", "internal", "static", "void", "int",
           "string", "var", "const", "readonly", "new", "this", "base", "try",
           "catch", "finally", "throw", "delegate", "event", "override", "virtual",
           "abstract", "sealed", "partial", "async", "await", "lock", "unsafe"
       };

           private static readonly HashSet<string> ErrorKeywords = new HashSet<string>
       {
           "error", "exception", "fail", "failed", "failure", "unhandled",
           "stack trace", "at", "line", "crash", "abort", "fatal", "warning",
           "syntax error", "runtime error", "nullreference", "indexoutofrange",
           "argumentnullexception", "invalidoperation"
       };

           private static readonly Regex StackTraceRegex = new Regex(
               @"\s*at\s+[\w\.]+\(.*\)\s+in\s+\S+:\d+",
               RegexOptions.Compiled | RegexOptions.IgnoreCase);

           private static readonly Regex HexAddressRegex = new Regex(
               @"\b0x[0-9a-fA-F]+\b",
               RegexOptions.Compiled);

           private static readonly Regex LineNumberRegex = new Regex(
               @"(?:line|:)\s*\d+",
               RegexOptions.Compiled | RegexOptions.IgnoreCase);

           private static readonly HashSet<char> CodeSymbols = new HashSet<char>
       { ';', '{', '}', '(', ')', '=', '+', '-', '*', '/', '<', '>', '&', '|', '!', '?' };

           private static readonly Regex CodeKeywordsRegex = new Regex(
               string.Join("|", CodeKeywords.Select(k => @"(?<!\w)" + Regex.Escape(k) + @"(?!\w)")),
               RegexOptions.Compiled | RegexOptions.IgnoreCase);

           private static readonly Regex ErrorKeywordsRegex = new Regex(
               string.Join("|", ErrorKeywords.Select(Regex.Escape)),
               RegexOptions.Compiled | RegexOptions.IgnoreCase);

           public TextCategory Categorize(string text)
           {
               if (string.IsNullOrWhiteSpace(text))
                   return TextCategory.General;

               double errorScore = CalculateErrorScore(text);
               double codeScore = CalculateCodeScore(text);

               if (errorScore >= codeScore && errorScore > 0)
                   return TextCategory.Error;
               if (codeScore > errorScore && codeScore > 0)
                   return TextCategory.Code;
               return TextCategory.General;
           }

           private double CalculateErrorScore(string text)
           {
               double score = 0;

               score += ErrorKeywordsRegex.Matches(text).Count * ErrorKeywordWeight;

               if (StackTraceRegex.IsMatch(text))
                   score += 2;

               if (HexAddressRegex.IsMatch(text))
                   score += 0.5;

               if (LineNumberRegex.IsMatch(text))
                   score += 0.5;

               if (text.IndexOf("exception in", StringComparison.OrdinalIgnoreCase) >= 0 ||
                   text.IndexOf("stack trace:", StringComparison.OrdinalIgnoreCase) >= 0)
                   score += 1;

               return score;
           }

           private double CalculateCodeScore(string text)
           {
               double score = 0;
               var lines = text.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
               int totalLines = lines.Length;

               score += CodeKeywordsRegex.Matches(text).Count * CodeKeywordWeight;

               if (text.Count(c => CodeSymbols.Contains(c)) > text.Length * SymbolRatioThreshold)
                   score += 1;

               if (totalLines > 0 && (double)lines.Count(l => l.Contains(';')) / totalLines >= SemicolonLineRatioThreshold)
                   score += 1;

               if (text.Contains('{') && text.Contains('}'))
                   score += 1;

               if (text.Contains("//") || text.Contains("/*"))
                   score += 1;

               if (totalLines > 0 && (double)lines.Count(l => l.Length > 0 && (l[0] == ' ' || l[0] == '\t')) / totalLines >= IndentLineRatioThreshold)
                   score += 1;

               if (text.Contains("()") || text.Contains("[]") || text.Contains("<>"))
                   score += 0.5;

               return score;
           }
       }
   }

   namespace StringCommandFramework
   {
       public interface IStringInstruction
       {
           void Execute(ICommandContext context);
       }

       public interface ICommandContext
       {
           StringBuilder Builder { get; }
           int CurrentIndex { get; set; }
           Dictionary<string, int> Labels { get; }
       }

       public class CommandContext : ICommandContext
       {
           public StringBuilder Builder { get; } = new StringBuilder();
           public int CurrentIndex { get; set; }
           public Dictionary<string, int> Labels { get; } = new Dictionary<string, int>();
       }

       #region 基础指令
       public class AppendInstruction : IStringInstruction
       {
           private readonly string _text;
           public AppendInstruction(string text) => _text = text;
           public void Execute(ICommandContext context) => context.Builder.Append(_text);
       }

       public class AppendLineInstruction : IStringInstruction
       {
           private readonly string _text;
           public AppendLineInstruction(string text = null) => _text = text;
           public void Execute(ICommandContext context) => context.Builder.AppendLine(_text);
       }

       public class InsertInstruction : IStringInstruction
       {
           private readonly int _index;
           private readonly string _text;
           public InsertInstruction(int index, string text) => (_index, _text) = (index, text);
           public void Execute(ICommandContext context)
           {
               if (_index < 0 || _index > context.Builder.Length)
                   throw new ArgumentOutOfRangeException(nameof(_index), $"Insert index {_index} out of range (length={context.Builder.Length}).");
               context.Builder.Insert(_index, _text);
           }
       }

       public class ReplaceInstruction : IStringInstruction
       {
           private readonly string _oldValue, _newValue;
           public ReplaceInstruction(string oldValue, string newValue)
           {
               if (string.IsNullOrEmpty(oldValue))
                   throw new ArgumentException("Old value cannot be null or empty.", nameof(oldValue));
               (_oldValue, _newValue) = (oldValue, newValue);
           }
           public void Execute(ICommandContext context)
           {
               string current = context.Builder.ToString();
               int index = current.IndexOf(_oldValue);
               if (index >= 0)
               {
                   context.Builder.Remove(index, _oldValue.Length);
                   context.Builder.Insert(index, _newValue);
               }
           }
       }

       public class ReplaceAllInstruction : IStringInstruction
       {
           private readonly string _oldValue, _newValue;
           public ReplaceAllInstruction(string oldValue, string newValue)
           {
               if (string.IsNullOrEmpty(oldValue))
                   throw new ArgumentException("Old value cannot be null or empty.", nameof(oldValue));
               (_oldValue, _newValue) = (oldValue, newValue);
           }
           public void Execute(ICommandContext context) => context.Builder.Replace(_oldValue, _newValue);
       }

       public class DeleteInstruction : IStringInstruction
       {
           private readonly int _start, _end;
           public DeleteInstruction(int start, int end)
           {
               if (start < 0) throw new ArgumentOutOfRangeException(nameof(start), "Start index cannot be negative.");
               if (end < 0) throw new ArgumentOutOfRangeException(nameof(end), "End index cannot be negative.");
               if (start > end)
                   throw new ArgumentException($"Start index {start} cannot be greater than end index {end}.");
               (_start, _end) = (start, end);
           }
           public void Execute(ICommandContext context)
           {
               int length = _end - _start;
               if (_start < 0 || _start + length > context.Builder.Length)
                   throw new ArgumentOutOfRangeException($"Delete range [{_start},{_end}) out of range (length={context.Builder.Length}).");
               context.Builder.Remove(_start, length);
           }
       }

       public class ClearInstruction : IStringInstruction
       {
           public void Execute(ICommandContext context) => context.Builder.Clear();
       }
       #endregion

       #region 控制流指令
       public class IfInstruction : IStringInstruction
       {
           private readonly Func<ICommandContext, bool> _condition;
           private readonly IStringInstruction _thenInstruction;
           public IfInstruction(Func<ICommandContext, bool> condition, IStringInstruction thenInstruction) =>
               (_condition, _thenInstruction) = (condition, thenInstruction);
           public void Execute(ICommandContext context)
           {
               if (_condition(context))
                   _thenInstruction.Execute(context);
           }
       }

       public class GotoInstruction : IStringInstruction
       {
           private readonly string _label;
           public GotoInstruction(string label) => _label = label;
           public void Execute(ICommandContext context)
           {
               if (!context.Labels.TryGetValue(_label, out int targetIndex))
                   throw new InvalidOperationException($"Label '{_label}' not found.");
               context.CurrentIndex = targetIndex - 1;
           }
       }

       public class LabelInstruction : IStringInstruction
       {
           public string Label { get; }
           public LabelInstruction(string label) => Label = label;
           public void Execute(ICommandContext context) { }
       }
       #endregion

       public class StringCommandParser
       {
           private static string[] ParseArguments(string commandLine)
           {
               var args = new List<string>();
               var current = new StringBuilder();
               bool inQuotes = false;

               for (int i = 0; i < commandLine.Length; i++)
               {
                   char c = commandLine[i];
                   if (c == '\\' && i + 1 < commandLine.Length)
                   {
                       char next = commandLine[i + 1];
                       if (next == '\\')
                       {
                           current.Append('\\');
                           i++;
                       }
                       else if (next == '"')
                       {
                           current.Append('"');
                           i++;
                       }
                       else
                       {
                           current.Append(c);
                       }
                   }
                   else if (c == '"')
                   {
                       inQuotes = !inQuotes;
                   }
                   else if (c == ' ' && !inQuotes)
                   {
                       if (current.Length > 0)
                       {
                           args.Add(current.ToString());
                           current.Clear();
                       }
                   }
                   else
                   {
                       current.Append(c);
                   }
               }

               if (current.Length > 0)
                   args.Add(current.ToString());

               return args.ToArray();
           }

           private static string JoinArgs(string[] args, int startIndex = 0)
           {
               if (startIndex >= args.Length)
                   return "";
               return string.Join(" ", args, startIndex, args.Length - startIndex);
           }

           public IStringInstruction Parse(string commandLine)
           {
               if (string.IsNullOrWhiteSpace(commandLine))
                   throw new ArgumentException("Command cannot be empty.");

               var parts = ParseArguments(commandLine);
               string cmd = parts[0].ToUpperInvariant();
               string[] args = parts.Length > 1 ? parts[1..] : Array.Empty<string>();

               return cmd switch
               {
                   "APPEND" => new AppendInstruction(JoinArgs(args)),
                   "APPENDLINE" => new AppendLineInstruction(args.Length > 0 ? JoinArgs(args) : null),
                   "INSERT" => ParseInsert(args),
                   "REPLACE" => ParseReplace(args),
                   "REPLACEALL" => ParseReplaceAll(args),
                   "DELETE" => ParseDelete(args),
                   "CLEAR" => new ClearInstruction(),
                   "IF" => ParseIf(commandLine, parts, args),
                   "GOTO" => ParseGoto(args),
                   "LABEL" => ParseLabel(args),
                   _ => throw new NotSupportedException($"Unknown command '{cmd}'.")
               };
           }

           private IStringInstruction ParseInsert(string[] args)
           {
               if (args.Length < 2) throw new FormatException("INSERT requires position and text.");
               if (!int.TryParse(args[0], out var pos)) throw new FormatException("INSERT position must be an integer.");
               return new InsertInstruction(pos, JoinArgs(args, 1));
           }

           private IStringInstruction ParseReplace(string[] args)
           {
               if (args.Length < 2) throw new FormatException("REPLACE requires old and new values.");
               return new ReplaceInstruction(args[0], JoinArgs(args, 1));
           }

           private IStringInstruction ParseReplaceAll(string[] args)
           {
               if (args.Length < 2) throw new FormatException("REPLACEALL requires old and new values.");
               return new ReplaceAllInstruction(args[0], JoinArgs(args, 1));
           }

           private IStringInstruction ParseDelete(string[] args)
           {
               if (args.Length < 2) throw new FormatException("DELETE requires start and end.");
               if (!int.TryParse(args[0], out var start) || !int.TryParse(args[1], out var end))
                   throw new FormatException("DELETE start and end must be integers.");
               return new DeleteInstruction(start, end);
           }

           private IStringInstruction ParseGoto(string[] args)
           {
               if (args.Length == 0) throw new FormatException("GOTO requires label.");
               return new GotoInstruction(args[0]);
           }

           private IStringInstruction ParseLabel(string[] args)
           {
               if (args.Length == 0) throw new FormatException("LABEL requires label name.");
               return new LabelInstruction(args[0]);
           }

           private IStringInstruction ParseIf(string fullCommand, string[] parts, string[] args)
           {
               int thenIndex = Array.FindIndex(parts, p => string.Equals(p, "THEN", StringComparison.OrdinalIgnoreCase));
               if (thenIndex == -1 || thenIndex == 0)
                   throw new FormatException("IF requires THEN keyword.");

               string[] condParts = parts[1..thenIndex];
               if (condParts.Length == 0)
                   throw new FormatException("IF condition cannot be empty.");

               string conditionType = condParts[0].ToUpperInvariant();
               Func<ICommandContext, bool> condition = conditionType switch
               {
                   "CONTAINS" => BuildContainsCondition(condParts[1..]),
                   "LENGTH" => BuildLengthCondition(condParts[1..]),
                   "EQUALS" => BuildEqualsCondition(condParts[1..]),
                   _ => throw new FormatException($"Unknown condition type '{conditionType}'.")
               };

               string thenCommand = string.Join(" ", parts, thenIndex + 1, parts.Length - thenIndex - 1);
               var thenInstruction = Parse(thenCommand);
               return new IfInstruction(condition, thenInstruction);
           }

           private Func<ICommandContext, bool> BuildContainsCondition(string[] condArgs)
           {
               string text = JoinArgs(condArgs);
               return ctx => ctx.Builder.ToString().Contains(text);
           }

           private Func<ICommandContext, bool> BuildLengthCondition(string[] condArgs)
           {
               if (condArgs.Length != 2)
                   throw new FormatException("LENGTH requires exactly two arguments: operator and number.");
               if (!int.TryParse(condArgs[1], out var targetLen))
                   throw new FormatException("LENGTH number must be an integer.");

               return condArgs[0] switch
               {
                   "<" => ctx => ctx.Builder.Length < targetLen,
                   ">" => ctx => ctx.Builder.Length > targetLen,
                   "=" => ctx => ctx.Builder.Length == targetLen,
                   "<=" => ctx => ctx.Builder.Length <= targetLen,
                   ">=" => ctx => ctx.Builder.Length >= targetLen,
                   _ => throw new FormatException($"Unknown operator '{condArgs[0]}' for LENGTH.")
               };
           }

           private Func<ICommandContext, bool> BuildEqualsCondition(string[] condArgs)
           {
               string text = JoinArgs(condArgs);
               return ctx => ctx.Builder.ToString() == text;
           }
       }

       public class CommandExecutor
       {
           private readonly List<IStringInstruction> _instructions;
           private readonly ICommandContext _context;

           public CommandExecutor(List<IStringInstruction> instructions, ICommandContext context = null)
           {
               _instructions = instructions;
               _context = context ?? new CommandContext();

               for (int i = 0; i < _instructions.Count; i++)
               {
                   if (_instructions[i] is LabelInstruction label)
                   {
                       if (_context.Labels.ContainsKey(label.Label))
                           throw new InvalidOperationException($"Duplicate label '{label.Label}' found at index {i}.");
                       _context.Labels[label.Label] = i;
                   }
               }
           }

           public string Execute()
           {
               _context.CurrentIndex = -1;
               while (++_context.CurrentIndex < _instructions.Count)
               {
                   _instructions[_context.CurrentIndex].Execute(_context);
               }
               return _context.Builder.ToString();
           }
       }

       public static class StringCommandProcessor
       {
           public static List<string> ProcessList(List<string> inputStrings, List<IStringInstruction> instructions)
           {
               if (inputStrings == null) throw new ArgumentNullException(nameof(inputStrings));
               if (instructions == null) throw new ArgumentNullException(nameof(instructions));

               var results = new List<string>(inputStrings.Count);
               foreach (var str in inputStrings)
               {
                   var fullInstructions = new List<IStringInstruction>(instructions.Count + 1) { new AppendInstruction(str) };
                   fullInstructions.AddRange(instructions);
                   results.Add(new CommandExecutor(fullInstructions).Execute());
               }
               return results;
           }
       }
   }

   namespace SmartPromptFramework
   {
       public class SmartPromptBuilder
       {
           private readonly string _template;
           private static readonly TextClassifier _classifier = new TextClassifier();
           private readonly IReadOnlyDictionary<string, TextCategory> _placeholderToCategory;
           private readonly bool _throwIfMissing;

           public SmartPromptBuilder(
               string template,
               Dictionary<string, TextCategory> mapping = null,
               bool throwIfMissing = false)
           {
               _template = template ?? throw new ArgumentNullException(nameof(template));
               _placeholderToCategory = mapping ?? new Dictionary<string, TextCategory>(StringComparer.OrdinalIgnoreCase)
               {
                   ["CODE"] = TextCategory.Code,
                   ["ERROR"] = TextCategory.Error,
                   ["GENERAL"] = TextCategory.General
               };
               _throwIfMissing = throwIfMissing;
           }

           public string BuildPrompt(IEnumerable<string> inputs)
           {
               var categorized = new Dictionary<TextCategory, List<string>>();
               foreach (var input in inputs)
               {
                   if (string.IsNullOrWhiteSpace(input)) continue;
                   var cat = _classifier.Categorize(input);
                   if (!categorized.ContainsKey(cat))
                       categorized[cat] = new List<string>();
                   categorized[cat].Add(input);
               }

               var regex = new Regex(@"\{\{(\w+)\}\}", RegexOptions.Compiled);
               var matches = regex.Matches(_template);
               var replacementMap = new Dictionary<string, string>();

               foreach (Match m in matches)
               {
                   string full = m.Value;
                   string name = m.Groups[1].Value;
                   if (_placeholderToCategory.TryGetValue(name, out var category))
                   {
                       if (categorized.TryGetValue(category, out var strings) && strings.Count > 0)
                       {
                           replacementMap[full] = string.Join("\n\n", strings);
                       }
                       else
                       {
                           if (_throwIfMissing)
                               throw new InvalidOperationException($"Missing input for placeholder '{full}' (category {category}).");
                           replacementMap[full] = "";
                       }
                   }
               }

               string result = _template;
               result = regex.Replace(result, match =>
               {
                   string fullMatch = match.Value;
                   return replacementMap.TryGetValue(fullMatch, out string value) ? value : fullMatch;
               });
               return result;
           }
       }

       public static class PromptBuilder
       {
           private static readonly Regex _placeholderRegex = new Regex(@"\{\{\w+\}\}", RegexOptions.Compiled);

           private static (string template, List<string> inputs) ExtractTemplateAndInputs(IEnumerable<string> items)
           {
               if (items == null || !items.Any())
                   throw new ArgumentException("At least one input string is required.");

               string template = null;
               var inputs = new List<string>();

               foreach (var item in items)
               {
                   if (_placeholderRegex.IsMatch(item))
                   {
                       if (template == null)
                           template = item;
                       else
                           throw new ArgumentException("Multiple strings containing placeholders found. Only one template is allowed.");
                   }
                   else
                   {
                       inputs.Add(item);
                   }
               }

               if (template == null)
                   throw new ArgumentException("No template found. The template must contain at least one placeholder like {{NAME}}.");

               return (template, inputs);
           }

           public static string Build(string template, params string[] inputs)
           {
               var builder = new SmartPromptBuilder(template);
               return builder.BuildPrompt(inputs);
           }

           public static string Build(string template, Dictionary<string, TextCategory> mapping, params string[] inputs)
           {
               var builder = new SmartPromptBuilder(template, mapping);
               return builder.BuildPrompt(inputs);
           }

           public static string BuildFromList(params string[] items)
           {
               var (template, inputs) = ExtractTemplateAndInputs(items);
               var builder = new SmartPromptBuilder(template);
               return builder.BuildPrompt(inputs);
           }

           public static string BuildFromList(Dictionary<string, TextCategory> mapping, params string[] items)
           {
               var (template, inputs) = ExtractTemplateAndInputs(items);
               var builder = new SmartPromptBuilder(template, mapping);
               return builder.BuildPrompt(inputs);
           }

           public static string BuildFromList(List<string> items)
           {
               var (template, inputs) = ExtractTemplateAndInputs(items);
               var builder = new SmartPromptBuilder(template);
               return builder.BuildPrompt(inputs);
           }

           public static string BuildFromList(Dictionary<string, TextCategory> mapping, List<string> items)
           {
               var (template, inputs) = ExtractTemplateAndInputs(items);
               var builder = new SmartPromptBuilder(template, mapping);
               return builder.BuildPrompt(inputs);
           }
       }
   }
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐