[C#/COMMON/.NET8] 대용량 CSV 파일 병합하기
■ 대용량 CSV 파일을 병합하는 방법을 보여준다. ※ CSV 파일에 헤더 라인이 있어야 합니다. ※ 병합하는 CSV 파일은 동일한 헤더 라인을 갖고
■ 대용량 CSV 파일을 병합하는 방법을 보여준다. ※ CSV 파일에 헤더 라인이 있어야 합니다. ※ 병합하는 CSV 파일은 동일한 헤더 라인을 갖고
■ 엑셀에서 OLLAMA에게 질문을 하는 사용자 함수를 추가하는 방법을 보여준다. ▶ TestLibrary.csproj
1 2 3 4 5 6 7 8 9 10 11 12 |
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net6.0-windows</TargetFramework> <ImplicitUsings>disable</ImplicitUsings> <Nullable>disable</Nullable> </PropertyGroup> <ItemGroup> <PackageReference Include="ExcelDna.AddIn" Version="1.8.0" /> </ItemGroup> </Project> |
▶ launchSettings.json
1 2 3 4 5 6 7 8 9 10 11 |
{ "profiles": { "Excel": { "commandName": "Executable", "executablePath": "C:\\Program Files\\Microsoft Office\\root\\Office16\\EXCEL.EXE", "commandLineArgs": "/x \"TestLibrary-AddIn64.xll\"" } } } |
▶ OllamaClient.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
using System.Net.Http; using System.Text; using System.Text.Json; namespace TestLibrary; /// <summary> /// OLLAMA 클라이언트 /// </summary> public class OllamaClient { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// 기본 URL /// </summary> private readonly string baseURL; /// <summary> /// 모델명 /// </summary> private readonly string modelName; /// <summary> /// HTTP 클라이언트 /// </summary> private readonly HttpClient client; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - OllamaClient(baseURL, modelName) /// <summary> /// 생성자 /// </summary> /// <param name="baseURL">기본 URL</param> /// <param name="modelName">모델명</param> public OllamaClient(string baseURL = "http://localhost:11434", string modelName = "bnksys/eeve-yanolja-v1:latest") { this.baseURL = baseURL; this.modelName = modelName; this.client = new HttpClient(); } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 질문하기 - Ask(question) /// <summary> /// 질문하기 /// </summary> /// <param name="question">질문</param> /// <returns>답변</returns> public string Ask(string question) { var requestContent = new { model = this.modelName, prompt = question, stream = false, raw = false }; string json = JsonSerializer.Serialize(requestContent); StringContent stringContent = new StringContent(json, Encoding.UTF8, "application/json"); using(HttpResponseMessage httpResponseMessage = client.PostAsync($"{baseURL}/api/generate", stringContent).Result) { httpResponseMessage.EnsureSuccessStatusCode(); string respnseJSON = httpResponseMessage.Content.ReadAsStringAsync().Result; using(JsonDocument jsonElement = JsonDocument.Parse(respnseJSON)) { JsonElement rootJSONElement = jsonElement.RootElement; if(rootJSONElement.TryGetProperty("response", out JsonElement responseElement)) { return responseElement.GetString() ?? string.Empty; } return string.Empty; } } } #endregion } |
▶ CustomFunction.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
using ExcelDna.Integration; namespace TestLibrary; /// <summary> /// 커스텀 함수 /// </summary> public static class CustomFunction { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region OLLAMA 질문하기 - AskOllama(question) /// <summary> /// OLLAMA 질문하기 /// </summary> /// <param name="question">질문</param> /// <returns>답변</returns> [ExcelFunction(Description = "Ollama에게 질문을 합니다.")] public static string AskOllama(string question) { OllamaClient ollamaClient = new OllamaClient(); string answer = ollamaClient.Ask(question); return answer; } #endregion } |
■ ExcelDna.AddIn 누겟을 설치하는 방법을 보여준다. 1. Visual Studio를 실행한다. 2. [도구] / [NuGet 패키지 관리자] / [패키지 관리자 콘솔] 메뉴를 실행한다.
■ HttpClient 클래스를 사용해 OLLAMA 서버와 통신하는 방법을 보여준다. ▶ OllamaRequest.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
using System.Text.Json.Serialization; namespace TestProject; /// <summary> /// OLLAMA 요청 /// </summary> public class OllamaRequest { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 모델 - Model /// <summary> /// 모델 /// </summary> [JsonPropertyName("model")] public string Model { get; set; } #endregion #region 프롬프트 - Prompt /// <summary> /// 프롬프트 /// </summary> [JsonPropertyName("prompt")] public string Prompt { get; set; } #endregion #region 스트림 여부 - Stream /// <summary> /// 스트림 여부 /// </summary> [JsonPropertyName("stream")] public bool Stream { get; set; } #endregion } |
▶ OllamaResponse.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
using System.Text.Json.Serialization; namespace TestProject; /// <summary> /// OLLAMA 응답 /// </summary> public class OllamaResponse { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 모델 - Model /// <summary> /// 모델 /// </summary> [JsonPropertyName("model")] public string Model { get; set; } #endregion #region 응답 - Response /// <summary> /// 응답 /// </summary> [JsonPropertyName("response")] public string Response { get; set; } #endregion #region 완료 여부 - Done /// <summary> /// 완료 여부 /// </summary> [JsonPropertyName("done")] public bool Done { get; set; } #endregion } |
▶ Program.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
using System; using System.Net.Http; using System.Text; using System.Text.Json; using System.Threading.Tasks; namespace TestProject; /// <summary> /// 프로그램 /// </summary> public class Program { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Private #region OLLAMA 요청 보내기 (비동기) - SendOllamaRequestAsync(httpClient, ollamaRequest) /// <summary> /// OLLAMA 요청 보내기 (비동기) /// </summary> /// <param name="httpClient">HTTP 클라이언트</param> /// <param name="ollamaRequest">OLLAMA 요청</param> /// <returns>OLLAMA 응답 태스크</returns> private static async Task<OllamaResponse> SendOllamaRequestAsync(HttpClient httpClient, OllamaRequest ollamaRequest) { string json = JsonSerializer.Serialize ( ollamaRequest, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase } ); StringContent stringContent = new StringContent(json, Encoding.UTF8, "application/json"); HttpResponseMessage response = await httpClient.PostAsync("/api/generate", stringContent); response.EnsureSuccessStatusCode(); string responseBody = await response.Content.ReadAsStringAsync(); return JsonSerializer.Deserialize<OllamaResponse> ( responseBody, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase } ); } #endregion #region 프로그램 시작하기 - Main() /// <summary> /// 프로그램 시작하기 /// </summary> /// <returns>태스크</returns> private static async Task Main() { string baseURL = "http://localhost:11434"; using var httpClient = new HttpClient(); httpClient.BaseAddress = new Uri(baseURL); OllamaRequest ollamaRequest = new() { Model = "bnksys/eeve-yanolja-v1:latest", Prompt = "여기어때와 야놀자의 차이점을 알려주세요.", Stream = false }; try { OllamaResponse ollamaResponse = await SendOllamaRequestAsync(httpClient, ollamaRequest); Console.WriteLine($"질문 : {ollamaRequest.Prompt }"); Console.WriteLine($"응답 : {ollamaResponse.Response}"); } catch(Exception exception) { Console.WriteLine($"오류 발생 : {exception.Message}"); } } #endregion } |
TestProject.zip
■ RegistryKey 클래스를 사용해 CUDA 설치 여부 및 CUDA 버전을 구하는 방법을 보여준다. ▶ CUDAHelper.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
using Microsoft.Win32; namespace TestProject; /// <summary> /// CUDA 헬퍼 /// </summary> public class CUDAHelper { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region CUDA 설치 여부 구하기 - IsCUDAInstalled() /// <summary> /// CUDA 설치 여부 구하기 /// </summary> /// <returns>CUDA 설치 여부</returns> public static bool IsCUDAInstalled() { try { #pragma warning disable CA1416 // 플랫폼 호환성 유효성 검사 using(RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\NVIDIA Corporation\GPU Computing Toolkit\CUDA")) { if(registryKey != null) { string[] subkeyNameArray = registryKey.GetSubKeyNames(); if(subkeyNameArray.Length > 0) { return true; } } } #pragma warning restore CA1416 // 플랫폼 호환성 유효성 검사 return false; } catch { return false; } } #endregion #region CUDA 버전 구하기 - GetGUDAVersion() /// <summary> /// CUDA 버전 구하기 /// </summary> /// <returns>CUDA 버전</returns> public static string GetGUDAVersion() { if(IsCUDAInstalled()) { #pragma warning disable CA1416 // 플랫폼 호환성 유효성 검사 using(RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\NVIDIA Corporation\GPU Computing Toolkit\CUDA")) { if(registryKey != null) { string[] subkeyNameArray = registryKey.GetSubKeyNames(); if(subkeyNameArray.Length > 0) { string version = string.Join(", ", subkeyNameArray); return version; } } } #pragma warning restore CA1416 // 플랫폼 호환성 유효성 검사 return null; } else { return null; } } #endregion } |
▶ Program.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
using System; namespace TestProject; /// <summary> /// 프로그램 /// </summary> class Program { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Private #region 프로그램 시작하기 - Main() /// <summary> /// 프로그램 시작하기 /// </summary> static void Main() { bool isCUDAInstalled = CUDAHelper.IsCUDAInstalled(); if(isCUDAInstalled) { Console.WriteLine("CUDA가 설치되어 있습니다."); string version = CUDAHelper.GetGUDAVersion(); Console.WriteLine($"CUDA 버전 : {version}"); } else { Console.WriteLine("CUDA가 설치되어 있지 않습니다."); } } #endregion } |
TestProject.zip
■ HtmlDocument 클래스를 사용해 HTML 문자열에서 텍스트를 추출하는 방법을 보여준다. ▶ HTMLHelper.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
using System; using System.Linq; using System.Text.RegularExpressions; using HtmlAgilityPack; /// <summary> /// HTML 헬퍼 /// </summary> public class HTMLHelper { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region 텍스트 추출하기 - ExtractText(html) /// <summary> /// 텍스트 추출하기 /// </summary> /// <param name="html">HTML</param> /// <returns>텍스트</returns> public static string ExtractText(string html) { HtmlDocument htmlDocument = new HtmlDocument(); htmlDocument.LoadHtml(html); return htmlDocument.DocumentNode.InnerText; } #endregion #region 문자열 정규화하기 - NormalizeString(sourceString) /// <summary> /// 문자열 정규화하기 /// </summary> /// <param name="sourceString">소스 문자열</param> /// <returns>정규화 문자열</returns> /// <remarks> /// 1. 각 줄의 문자열의 앞뒤 공백을 제거한다. /// 2. 빈줄이 반복되는 경우 1개의 빈줄로 만든다. /// </remarks> public static string NormalizeString(string sourceString) { string[] sourceLineArray = sourceString.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); string[] targetLineArray = sourceLineArray.Select(line => line.Trim()).ToArray(); string joinedString = string.Join(Environment.NewLine, targetLineArray); string targetString = Regex.Replace(joinedString, @"(\r\n|\n){2,}", Environment.NewLine + Environment.NewLine); return targetString; } #endregion } |
※ HtmlAgilityPack 누겟 패키지를 설치한다.
■ String 클래스에서 문자열을 정규화하는 방법을 보여준다. ▶ 예제 코드 (C#)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
using System; using System.Linq; using System.Text.RegularExpressions; #region 문자열 정규화하기 - NormalizeString(sourceString) /// <summary> /// 문자열 정규화하기 /// </summary> /// <param name="sourceString">소스 문자열</param> /// <returns>정규화 문자열</returns> /// <remarks> /// 1. 각 줄의 문자열의 앞뒤 공백을 제거한다. /// 2. 빈줄이 반복되는 경우 1개의 빈줄로 만든다. /// </remarks> public string NormalizeString(string sourceString) { string[] sourceLineArray = sourceString.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); string[] targetLineArray = sourceLineArray.Select(line => line.Trim()).ToArray(); string joinedString = string.Join(Environment.NewLine, targetLineArray); string targetString = Regex.Replace(joinedString, @"(\r\n|\n){2,}", Environment.NewLine + Environment.NewLine); return targetString; } #endregion |
■ String 클래스에서 문자열을 정규화하는 방법을 보여준다. ▶ 예제 코드 (C#)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
using System; using System.Collections.Generic; #region 텍스트 정규화하기 - NormalizeText(sourceString) /// <summary> /// 텍스트 정규화하기 /// </summary> /// <param name="sourceString">소스 문자열</param> /// <returns>정규화 텍스트</returns> /// <remarks> /// 1. 각 줄의 문자열의 앞뒤 공백을 제거한다. /// 2. 빈줄이 반복되면 1개의 빈줄로 만든다. /// 3. 문자열이 있는 줄들 사이에 빈줄을 추가한다. /// </remarks> public string NormalizeText(string sourceString) { string[] sourceLineArray = sourceString.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); List<string> targetList = new List<string>(); bool previousLineWasEmpty = false; foreach(string sourceLine in sourceLineArray) { string targetLine = sourceLine.Trim(); if (string.IsNullOrEmpty(targetLine)) { if(!previousLineWasEmpty) { targetList.Add(string.Empty); previousLineWasEmpty = true; } } else { targetList.Add(targetLine); previousLineWasEmpty = false; } } return string.Join(Environment.NewLine, targetList); } #endregion |
■ FSK (Frequency Shift Keying) 변조를 사용하는 방법을 보여준다. [Encoder 프로젝트] ▶ Program.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 |
using System; using System.IO; using System.Linq; using System.Numerics; using System.Text; using NAudio.Wave; namespace DECODER; /// <summary> /// 디코더 /// </summary> class Decoder { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Private #region 다음 2의 거듭 제곱 구하기 - NextPowerOfTwo(value) /// <summary> /// 다음 2의 거듭 제곱 구하기 /// </summary> /// <param name="value">값</param> /// <returns></returns> /// <remarks>입력값보다 큰 가장 작은 2의 거듭 제곱을 계산한다.</remarks> private static int NextPowerOfTwo(int value) { int power = 1; while(power < value) { power <<= 1; } return power; } #endregion #region 비트 반전하기 - BitReverse(bitValue, bitCount) /// <summary> /// 비트 반전하기 /// </summary> /// <param name="bitValue">비트 값</param> /// <param name="bitCount">비트 수</param> /// <returns>반전 비트 값</returns> private static int BitReverse(int bitValue, int bitCount) { int reversed = 0; for(int i = 0; i < bitCount; i++) { int nextBit = (bitValue >> i) & 1; reversed |= (nextBit << (bitCount - i - 1)); } return reversed; } #endregion #region FFT 처리하기 - FFT(complexArray) /// <summary> /// FFT 처리하기 /// </summary> /// <param name="complexArray">복소수 배열</param> private static void FFT(Complex[] complexArray) { int complexArrayLength = complexArray.Length; if(complexArrayLength <= 1) { return; } // Bit reversal permutation int m1 = (int)Math.Log(complexArrayLength, 2); for(int i = 0; i < complexArrayLength; i++) { int j = BitReverse(i, m1); if(j > i) { Complex t = complexArray[i]; complexArray[i] = complexArray[j]; complexArray[j] = t; } } // Cooley-Tukey FFT for(int s = 1; s <= m1; s++) { int m2 = 1 << s; Complex wm = Complex.Exp(-2.0 * Math.PI * Complex.ImaginaryOne / m2); for(int k = 0; k < complexArrayLength; k += m2) { Complex w = 1; for(int j = 0; j < m2 / 2; j++) { int index1 = k + j; int index2 = k + j + m2 / 2; Complex t = w * complexArray[index2]; Complex u = complexArray[index1]; complexArray[index1] = u + t; complexArray[index2] = u - t; w *= wm; } } } } #endregion #region 프로그램 시작하기 - Main(argumentArray) /// <summary> /// 프로그램 시작하기 /// </summary> /// <param name="argumentArray">인자 배열</param> private static void Main(string[] argumentArray) { if(argumentArray.Length != 2) { Console.WriteLine("사용법 : decoder.exe source.wav target.zip"); return; } string sourceFilePath = argumentArray[0]; string targetFilePath = argumentArray[1]; int samplingFrequency = 44100; // 샘플링 주파수 double durationPerBit = 0.01; // 각 비트의 지속 시간 (초) int sampleCountPerBit = (int)(samplingFrequency * durationPerBit); int frequency0 = 1000; // '0'에 대한 주파수 int frequency1 = 2000; // '1'에 대한 주파수 // 시작 및 종료 마커를 설정한다. string startMarkerBitString = "1111000011110000"; string endMarkerBitString = "0000111100001111"; // WAV 파일을 읽는다. float[] signalArray; using(AudioFileReader audioFileReader = new(sourceFilePath)) { int totalSampleCount = (int)audioFileReader.Length / (audioFileReader.WaveFormat.BitsPerSample / 8); signalArray = new float[totalSampleCount]; int readSampleCount = audioFileReader.Read(signalArray, 0, totalSampleCount); } int bitCount = signalArray.Length / sampleCountPerBit; StringBuilder bitStringBuilder = new StringBuilder(); for(int i = 0; i < bitCount; i++) { int start = i * sampleCountPerBit; int end = start + sampleCountPerBit; if(end > signalArray.Length) { break; } float[] segmentArray = new float[sampleCountPerBit]; Array.Copy(signalArray, start, segmentArray, 0, sampleCountPerBit); // 입력 배열 길이를 가장 가까운 2의 거듭제곱으로 패딩한다. int paddedLength = NextPowerOfTwo(segmentArray.Length); Complex[] fftComplexArray = new Complex[paddedLength]; for(int j = 0; j < segmentArray.Length; j++) { fftComplexArray[j] = new Complex(segmentArray[j], 0); } for(int j = segmentArray.Length; j < paddedLength; j++) { fftComplexArray[j] = Complex.Zero; } // 주파수 분석을 위한 FFT를 수행한다. FFT(fftComplexArray); double[] magnitudeArray = fftComplexArray.Take(paddedLength / 2).Select(c => c.Magnitude).ToArray(); int peakIndex = Array.IndexOf(magnitudeArray, magnitudeArray.Max()); double peakFrequency = (double)peakIndex * samplingFrequency / paddedLength; // 주파수에 따라 비트를 결정한다. if(Math.Abs(peakFrequency - frequency0) < 100) { bitStringBuilder.Append('0'); } else if(Math.Abs(peakFrequency - frequency1) < 100) { bitStringBuilder.Append('1'); } else { bitStringBuilder.Append('?'); } } string bitStream = bitStringBuilder.ToString(); // 비트 스트림에서 시작 및 종료 마커를 찾는다. int startMarkerIndex = bitStream.IndexOf(startMarkerBitString); int endMarkerIndex = bitStream.IndexOf(endMarkerBitString); if(startMarkerIndex == -1 || endMarkerIndex == -1) { Console.WriteLine("시작 또는 종료 마커를 찾을 수 없습니다."); return; } int dataStart = startMarkerIndex + startMarkerBitString.Length; int dataEnd = endMarkerIndex; string dataBitStream = bitStream.Substring(dataStart, dataEnd - dataStart); // 파일 크기를 추출한다. (첫 32비트) if(dataBitStream.Length < 32) { Console.WriteLine("파일 크기 정보를 읽을 수 없습니다."); return; } string fileSizeString = dataBitStream.Substring(0, 32); dataBitStream = dataBitStream.Substring(32); // 파일 크기를 정수로 변환한다. int fileSize = Convert.ToInt32(fileSizeString, 2); // 결정할 수 없는 비트를 제거한다. dataBitStream = dataBitStream.Replace("?", ""); // 비트를 바이트로 변환한다. int byteCount = dataBitStream.Length / 8; byte[] dataByteArray = new byte[byteCount]; for(int i = 0; i < byteCount; i++) { string byteBitString = dataBitStream.Substring(i * 8, 8); dataByteArray[i] = Convert.ToByte(byteBitString, 2); } // 파일 크기에 따라 데이터를 자른다. if(dataByteArray.Length > fileSize) { byte[] tempByteArray = new byte[fileSize]; Array.Copy(dataByteArray, tempByteArray, fileSize); dataByteArray = tempByteArray; } // 복원된 파일을 저장한다. File.WriteAllBytes(targetFilePath, dataByteArray); Console.WriteLine("디코딩 완료 : " + targetFilePath); } #endregion } |
[Decoder 프로젝트] ▶ requirements.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
using System; using System.IO; using System.Linq; using NAudio.Wave; namespace ENCODER; /// <summary> /// 인코더 /// </summary> class Encoder { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Private #region 프로그램 시작하기 - Main(argumentArray) /// <summary> /// 프로그램 시작하기 /// </summary> /// <param name="argumentArray">인자 배열</param> private static void Main(string[] argumentArray) { if(argumentArray.Length != 2) { Console.WriteLine("사용법 : encoder.exe source.zip target.wav"); return; } string sourceFilePath = argumentArray[0]; string targetFilePath = argumentArray[1]; // 소스 파일을 읽는다. byte[] sourceFileByteArray = File.ReadAllBytes(sourceFilePath); int sourceFileSize = sourceFileByteArray.Length; // 바이트를 비트로 변환한다. string dataBitString = string.Join(string.Empty, sourceFileByteArray.Select(sourceByte => Convert.ToString(sourceByte, 2).PadLeft(8, '0'))); // 파일 크기를 헤더에 추가한다. (32비트로 표현) string fileSizeBitString = Convert.ToString(sourceFileSize, 2).PadLeft(32, '0'); // 시작 및 종료 마커 문자열을 설정한다. string startMarkerString = "1111000011110000"; string endMarkerString = "0000111100001111"; // 전체 비트 스트림을 생성한다. string bitStreamString = startMarkerString + fileSizeBitString + dataBitString + endMarkerString; // 파라미터를 설정한다. int samplingFrequency = 44100; // 샘플링 주파수 double durationPerBit = 0.01; // 각 비트의 지속 시간 (초) int sampleCountPerBit = (int)(samplingFrequency * durationPerBit); int frequency0 = 1000; // '0'에 대한 주파수 int frequency1 = 2000; // '1'에 대한 주파수 // 신호를 생성한다. float[] signalArray = new float[bitStreamString.Length * sampleCountPerBit]; int bitStreamStringLength = bitStreamString.Length; for(int i = 0; i < bitStreamStringLength; i++) { char bitCharacter = bitStreamString[i]; int frequency = bitCharacter == '1' ? frequency1 : frequency0; for(int j = 0; j < sampleCountPerBit; j++) { double t = (double)j / samplingFrequency; signalArray[i * sampleCountPerBit + j] = (float)Math.Sin(2 * Math.PI * frequency * t); } } // 신호를 정규화한다. float maximumAmplitude = signalArray.Max(x => Math.Abs(x)); for(int i = 0; i < signalArray.Length; i++) { signalArray[i] = signalArray[i] / maximumAmplitude; } // WAV 파일로 저장한다. using(WaveFileWriter waveFileWriter = new WaveFileWriter(targetFilePath, new WaveFormat(samplingFrequency, 16, 1))) { waveFileWriter.WriteSamples(signalArray, 0, signalArray.Length); } Console.WriteLine("인코딩 완료 : " + targetFilePath); } #endregion } |
TestSolution.zip
■ ManagementObjectSearcher 클래스를 사용해 NVIDIA GPU 설치 여부를 구하는 방법을 보여준다. ▶ Program.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
using System; using System.Management; namespace TestProject; /// <summary> /// 프로그램 /// </summary> class Program { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Private #region NVIDIA GPU 설치 여부 구하기 - IsInstalledNvidiaGPU() /// <summary> /// NVIDIA GPU 설치 여부 구하기 /// </summary> /// <returns>NVIDIA GPU 설치 여부</returns> private static bool IsInstalledNvidiaGPU() { using ManagementObjectSearcher managementObjectSearcher = new("SELECT * FROM Win32_VideoController"); foreach(ManagementObject managementObject in managementObjectSearcher.Get()) { string name = managementObject["Name"] as string; if(!string.IsNullOrEmpty(name) && name.ToLower().Contains("nvidia")) { return true; } } return false; } #endregion #region 프로그램 시작하기 - Main() /// <summary> /// 프로그램 시작하기 /// </summary> private static void Main() { bool isInstalledNvidiaGPU = IsInstalledNvidiaGPU(); Console.WriteLine(isInstalledNvidiaGPU ? "NVIDIA GPU가 설치되어 있습니다." : "NVIDIA GPU가 설치되어 있지 않습니다."); } #endregion } |
TestProject.zip
■ RuntimeInformation 클래스의 IsOSPlatform 정적 메소드를 사용해 윈도우즈 운영 체제가 아닌 경우 실행을 중단하는 방법을 보여준다. ▶ Program.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
using System; using System.Runtime.InteropServices; namespace TestProject; /// <summary> /// 프로그램 /// </summary> class Program { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Private #region 프로그램 시작하기 - Main() /// <summary> /// 프로그램 시작하기 /// </summary> private static void Main() { if(!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { Console.WriteLine("이 프로그램은 Windows에서만 실행할 수 있습니다."); return; } } #endregion } |
TestProject.zip
■ HttpClient 클래스를 사용해 Ollama 연동 파이썬 LLM 서버와 통신하는 방법을 보여준다. ▶ AdditionalKeywordArgument.cs
1 2 3 4 5 6 7 8 9 10 |
namespace TestProject; /// <summary> /// 부가적 키워드 인자 /// </summary> public class AdditionalKeywordArgument { } |
▶ AIMessageChunk.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
using Newtonsoft.Json; namespace TestProject; /// <summary> /// AI 메시지 청크 /// </summary> public class AIMessageChunk { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 컨텐트 - Content /// <summary> /// 컨텐트 /// </summary> [JsonProperty("content")] public string Content { get; set; } #endregion #region 부가적 키워드 인자 - AdditionalKeywordArgument /// <summary> /// 부가적 키워드 인자 /// </summary> [JsonProperty("additional_kwargs")] public AdditionalKeywordArgument AdditionalKeywordArgument { get; set; } #endregion #region 응답 메타 데이터 - ResponseMetadata /// <summary> /// 응답 메타 데이터 /// </summary> [JsonProperty("response_metadata")] public ResponseMetadata ResponseMetadata { get; set; } #endregion #region 타입 - Type /// <summary> /// 타입 /// </summary> [JsonProperty("type")] public string Type { get; set; } #endregion #region 명칭 - Name /// <summary> /// 명칭 /// </summary> [JsonProperty("name")] public string Name { get; set; } #endregion #region ID - ID /// <summary> /// ID /// </summary> [JsonProperty("id")] public string ID { get; set; } #endregion #region 예제 여부 - Example /// <summary> /// 예제 여부 /// </summary> [JsonProperty("example")] public bool Example { get; set; } #endregion #region 도구 호출 리스트 - ToolCallList /// <summary> /// 도구 호출 리스트 /// </summary> [JsonProperty("tool_calls")] public ToolCallList ToolCallList { get; set; } #endregion #region 무효 도구 호출 리스트 - InvalidToolCallList /// <summary> /// 무효 도구 호출 리스트 /// </summary> [JsonProperty("invalid_tool_calls")] public InvalidToolCallList InvalidToolCallList { get; set; } #endregion #region 사용 메타 데이터 - UsageMetadata /// <summary> /// 사용 메타 데이터 /// </summary> [JsonProperty("usage_metadata")] public UsageMetadata UsageMetadata { get; set; } #endregion #region 도구 호출 청크 리스트 - ToolCallChunkList /// <summary> /// 도구 호출 청크 리스트 /// </summary> [JsonProperty("tool_call_chunks")] public ToolCallChunkList ToolCallChunkList { get; set; } #endregion } |
▶ InvalidToolCallList.cs
1 2 3 4 5 6 7 8 9 10 11 12 |
using System.Collections.Generic; namespace TestProject; /// <summary> /// 무효 도구 호출 리스트 /// </summary> public class InvalidToolCallList : List<object> { } |
▶
■ Marshal 클래스의 SizeOf 정적 메소드를 사용해 특정 타입의 크기를 구하는 방법을 보여준다. ▶ DataStruct.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
using System.Runtime.InteropServices; namespace TestProject; /// <summary> /// 데이터 구조체 /// </summary> [StructLayout(LayoutKind.Sequential)] public struct DataStruct { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Public #region Field /// <summary> /// 정수 값 /// </summary> public int IntegerValue; /// <summary> /// 배정도 실수 값 /// </summary> public double DoubleValue; /// <summary> /// 문자 값 /// </summary> public char CharacterValue; #endregion } |
▶ Program.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
using System.Runtime.InteropServices; using System; namespace TestProject; /// <summary> /// 프로그램 /// </summary> class Program { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Private #region 프로그램 시작하기 - Main() /// <summary> /// 프로그램 시작하기 /// </summary> static void Main() { int size = Marshal.SizeOf<DataStruct>(); Console.WriteLine($"DataStruct 크기 : {size} 바이트"); } #endregion } |
TestProject.zip
■ HttpClient 클래스를 사용해 네이버 HyperCLOVA X와 통신하는 방법을 보여준다. (IAsyncEnumerable<T> 객체) ▶ AIFilter.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
using Newtonsoft.Json; namespace TestProject; /// <summary> /// AI 필터 /// </summary> public class AIFilter { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 그룹명 - GroupName /// <summary> /// 그룹명 /// </summary> [JsonProperty("groupName")] public string GroupName { get; set; } #endregion #region 명칭 - Name /// <summary> /// 명칭 /// </summary> [JsonProperty("name")] public string Name { get; set; } #endregion #region 점수 - Score /// <summary> /// 점수 /// </summary> [JsonProperty("score")] public string Score { get; set; } #endregion #region 결과 - Result /// <summary> /// 결과 /// </summary> [JsonProperty("result")] public string Result { get; set; } #endregion } |
▶ Message.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
using Newtonsoft.Json; namespace TestProject; /// <summary> /// 메시지 /// </summary> public class Message { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 역할 - Role /// <summary> /// 역할 /// </summary> [JsonProperty("role")] public string Role { get; set; } #endregion #region 내용 - Content /// <summary> /// 내용 /// </summary> [JsonProperty("content")] public string Content { get; set; } #endregion } |
▶ RequestMessage.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
using Newtonsoft.Json; namespace TestProject; /// <summary> /// 요청 메시지 /// </summary> public class RequestMessage { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 메시지 리스트 - MessageList /// <summary> /// 메시지 리스트 /// </summary> [JsonProperty("messages")] public List<Message> MessageList { get; set; } #endregion #region Top P - TopP /// <summary> /// TopP /// </summary> [JsonProperty("topP")] public double TopP { get; set; } #endregion #region Top K - TopK /// <summary> /// TopK /// </summary> [JsonProperty("topK")] public int TopK { get; set; } #endregion #region 최대 토큰 카운트 - MaximumTokenCount /// <summary> /// 최대 토큰 카운트 /// </summary> [JsonProperty("maxTokens")] public int MaximumTokenCount { get; set; } #endregion #region 온도 - Temperature /// <summary> /// 온도 /// </summary> [JsonProperty("temperature")] public double Temperature { get; set; } #endregion #region 반복 패널티 - RepeatPenalty /// <summary> /// 반복 패널티 /// </summary> [JsonProperty("repeatPenalty")] public double RepeatPenalty { get; set; } #endregion #region Stop Before - StopBefore /// <summary> /// Stop Before /// </summary> [JsonProperty("stopBefore")] public List<string> StopBefore { get; set; } #endregion #region AI 필터 포함 여부 - IncludeAIFilters /// <summary> /// AI 필터 포함 여부 /// </summary> [JsonProperty("includeAiFilters")] public bool IncludeAIFilters { get; set; } #endregion #region 시드 - Seed /// <summary> /// 시드 /// </summary> [JsonProperty("seed")] public int Seed { get; set; } #endregion } |
▶
■ ManagementObjectSearcher 클래스를 사용해 시스템 정보를 구하는 방법을 보여준다. ▶ Program.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
#pragma warning disable CA1416 using System.Management; namespace TestProject; /// <summary> /// 프로그램 /// </summary> class Program { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Private #region 프로그램 시작하기 - Main() /// <summary> /// 프로그램 시작하기 /// </summary> private static void Main() { try { ManagementObjectSearcher searcher; // PC 모델 및 제조업체 정보 searcher = new ManagementObjectSearcher("SELECT * FROM Win32_ComputerSystem"); foreach(ManagementObject mo in searcher.Get()) { Console.WriteLine($"PC 모델 : {mo["Model" ]}"); Console.WriteLine($"제조업체 : {mo["Manufacturer"]}"); } // 제조번호(시리얼 넘버) 정보 searcher = new ManagementObjectSearcher("SELECT * FROM Win32_BIOS"); foreach(ManagementObject mo in searcher.Get()) { Console.WriteLine($"제조번호(시리얼 넘버) : {mo["SerialNumber"]}"); } // CPU 정보 searcher = new ManagementObjectSearcher("SELECT * FROM Win32_Processor"); foreach(ManagementObject mo in searcher.Get()) { Console.WriteLine($"CPU : {mo["Name"]}"); } // 메인보드 정보 searcher = new ManagementObjectSearcher("SELECT * FROM Win32_BaseBoard"); foreach(ManagementObject mo in searcher.Get()) { Console.WriteLine($"메인보드 제조사 : {mo["Manufacturer"]}"); Console.WriteLine($"메인보드 제품 ID : {mo["Product" ]}"); Console.WriteLine($"메인보드 시리얼 번호 : {mo["SerialNumber"]}"); } // 메모리 용량 정보 searcher = new ManagementObjectSearcher("SELECT * FROM Win32_ComputerSystem"); foreach(ManagementObject mo in searcher.Get()) { ulong totalPhysicalMemory = Convert.ToUInt64(mo["TotalPhysicalMemory"]); Console.WriteLine($"메모리 용량 : {(totalPhysicalMemory / (1024 * 1024 * 1024))} GB"); } } catch (Exception exception) { Console.WriteLine("오류 발생 : " + exception.Message); } Console.WriteLine("아무 키나 누르면 종료됩니다..."); Console.ReadKey(false); } #endregion } |
TestProject.zip
■ Segoe UI Emoji 폰트에서 이모지 문자 여부를 구하는 방법을 보여준다. ▶ Segoe UI Emoji 폰트에서 이모지 문자 여부 구하기 예제 (C#)
■ Typeface 클래스를 사용해 이모지 타입 페이스 객체를 만드는 방법을 보여준다. ▶ 예제 코드 (C#)
1 2 3 4 5 6 7 8 9 10 11 12 |
using System.Windows; using System.Windows.Media; Typeface emojiTypeface = new Typeface ( new FontFamily("Segoe UI Emoji"), FontStyles.Normal, FontWeights.Normal, FontStretches.Normal ); |
■ char 구조체의 ConvertToUtf32 정적 메소드를 사용해 이모지 문자에서 UTF-32 문자 코드를 구하는 방법을 보여준다. ▶ 예제 코드 (C#)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
string smileString = char.ConvertFromUtf32(0x1f600); // 반환값: "😀" (웃는 얼굴 이모지) string dragonString = char.ConvertFromUtf32(0x1f409); // 반환값: "🐉" (용 이모지) int smileCode = char.ConvertToUtf32(smileString , 0); int dragonCode = char.ConvertToUtf32(dragonString, 0); Console.WriteLine($"0x{smileCode :x}"); Console.WriteLine($"0x{dragonCode:x}"); /* 0x1f600 0x1f409 */ |
■ char 구조체의 ConvertFromUtf32 정적 메소드를 사용해 이모지 문자를 구하는 방법을 보여준다. ▶ 예제 코드 (C#)
1 2 3 4 |
string smileString = char.ConvertFromUtf32(0x1f600); // 반환값: "😀" (웃는 얼굴 이모지) string dragonString = char.ConvertFromUtf32(0x1f409); // 반환값: "🐉" (용 이모지) |
■ HttpClient 클래스의 GetAsync 메소드를 사용해 파일을 다운로드하는 방법을 보여준다. ▶ 예제 코드 (C#)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
using System.IO; using System.Net.Http; using System.Threading.Tasks; #region 파일 다운로드하기 (비동기) - DownloadFileAsync(url, filePath) /// <summary> /// 파일 다운로드하기 (비동기) /// </summary> /// <param name="url">URL</param> /// <param name="filePath">파일 경로</param> /// <returns>처리 결과</returns> public async Task<bool> DownloadFileAsync(string url, string filePath) { using HttpClient client = new HttpClient(); try { using HttpResponseMessage response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); response.EnsureSuccessStatusCode(); using Stream stream = await response.Content.ReadAsStreamAsync(); using FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None); await stream.CopyToAsync(fileStream); return true; } catch(HttpRequestException) { return false; } } #endregion |
■ Python 프로그램을 자동 설치하는 방법을 보여준다. ▶ Program.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
using System; using System.Diagnostics; using System.IO; using System.Net.Http; using System.Threading.Tasks; namespace TestProject; /// <summary> /// 프로그램 /// </summary> class Program { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Private #region 프로그램 시작하기 - Main() /// <summary> /// 프로그램 시작하기 /// </summary> /// <returns>태스크</returns> static async Task Main() { string installerURL = "https://www.python.org/ftp/python/3.12.4/python-3.12.4-amd64.exe"; string installerFilePath = @"C:\temp\python_installer.exe"; if(!Directory.Exists(@"C:\temp")) { Directory.CreateDirectory(@"C:\temp"); } Console.WriteLine($"[{DateTime.Now.ToString("HH:mm:ss")}] 파이썬 설치 프로그램 다운로드를 시작합니다."); await DownloadFileAsync(installerURL, installerFilePath); Console.WriteLine($"[{DateTime.Now.ToString("HH:mm:ss")}] 파이썬 설치 프로그램 다운로드를 종료합니다."); Console.WriteLine($"[{DateTime.Now.ToString("HH:mm:ss")}] 파이썬 프로그램 설치를 시작합니다."); ProcessStartInfo processStartInfo = new() { FileName = installerFilePath, Arguments = "/quiet InstallAllUsers=1 PrependPath=1", // 자동 설치를 위한 인자 UseShellExecute = false }; Process process = new() { StartInfo = processStartInfo }; process.Start(); process.WaitForExit(); Console.WriteLine($"[{DateTime.Now.ToString("HH:mm:ss")}] 파이썬 프로그램 설치를 종료합니다."); string pythonFilePath = @"C:\Program Files\Python312\python.exe"; if(File.Exists(pythonFilePath)) { Console.WriteLine("Python이 성공적으로 설치되었습니다."); } else { Console.WriteLine("Python 설치에 실패했습니다."); } Console.WriteLine("프로그램을 종료하기 위해 아무 키나 눌러 주세요."); Console.ReadKey(false); } #endregion #region 파일 다운로드하기 (비동기) - DownloadFileAsync(url, filePath) /// <summary> /// 파일 다운로드하기 (비동기) /// </summary> /// <param name="url">URL</param> /// <param name="filePath">파일 경로</param> /// <returns>처리 결과</returns> private static async Task<bool> DownloadFileAsync(string url, string filePath) { using HttpClient client = new HttpClient(); try { using HttpResponseMessage response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); response.EnsureSuccessStatusCode(); using Stream stream = await response.Content.ReadAsStreamAsync(); using FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None); await stream.CopyToAsync(fileStream); return true; } catch(HttpRequestException) { return false; } } #endregion } |
TestProject.zip
■ HttpClient 클래스를 사용해 OLLAMA에게 LLAMA 3 로컬 LLM 통신하는 방법을 보여준다. (스트림) ▶ RequestMessage.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
using Newtonsoft.Json; namespace TestProject; /// <summary> /// 요청 메시지 /// </summary> public class RequestMessage { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 모델 - Model /// <summary> /// 모델 /// </summary> [JsonProperty("model")] public string Model { get; set; } #endregion #region 프롬프트 - Prompt /// <summary> /// 프롬프트 /// </summary> [JsonProperty("prompt")] public string Prompt { get; set; } #endregion } |
▶ ResponseMessage.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
using Newtonsoft.Json; namespace TestProject; /// <summary> /// 응답 메시지 /// </summary> public class ResponseMessage { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 모델 - Model /// <summary> /// 모델 /// </summary> [JsonProperty("model")] public string Model { get; set; } #endregion #region 생성 시간 - CreateTime /// <summary> /// 생성 시간 /// </summary> [JsonProperty("created_at")] public string CreateTime { get; set; } #endregion #region 응답 - Response /// <summary> /// 응답 /// </summary> [JsonProperty("response")] public string Response { get; set; } #endregion #region 완료 여부 - Done /// <summary> /// 완료 여부 /// </summary> [JsonProperty("done")] public bool Done { get; set; } #endregion } |
▶ APIClient.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
using System.Text; using System.Net.Http.Headers; using Newtonsoft.Json; namespace TestProject; /// <summary> /// API 클라이언트 /// </summary> public class APIClient : IDisposable { //////////////////////////////////////////////////////////////////////////////////////////////////// Event ////////////////////////////////////////////////////////////////////////////////////////// Public #region 메시지 도착시 이벤트 - MessageArrived /// <summary> /// 메시지 도착시 이벤트 /// </summary> public event EventHandler<ResponseMessage> MessageArrived; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// 리소스 해제 여부 /// </summary> private bool disposed = false; /// <summary> /// BASE URL /// </summary> private const string BASE_URL = "http://localhost:11434"; /// <summary> /// HTTP 클라이언트 /// </summary> private HttpClient client; /// <summary> /// 품질을 갖는 스트림 미디어 타입 헤더 값 /// </summary> private MediaTypeWithQualityHeaderValue streamMediaTypeWithQualityHeaderValue; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region HTTP 클라이언트 - Client /// <summary> /// HTTP 클라이언트 /// </summary> public HttpClient Client { get { return this.client; } } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region API 클라이언트 - APIClient() /// <summary> /// API 클라이언트 /// </summary> public APIClient() { this.client = new HttpClient(); this.streamMediaTypeWithQualityHeaderValue = new MediaTypeWithQualityHeaderValue("text/event-stream"); } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 채팅 요청하기 (비동기) - RequestChatAsync(requestMessage) /// <summary> /// 채팅 요청하기 (비동기) /// </summary> /// <param name="requestMessage">요청 메시지</param> /// <returns>응답 메시지 태스크</returns> public async Task<List<ResponseMessage>> RequestChatAsync(RequestMessage requestMessage) { if(this.client.DefaultRequestHeaders.Accept.Contains(this.streamMediaTypeWithQualityHeaderValue)) { this.client.DefaultRequestHeaders.Accept.Remove(this.streamMediaTypeWithQualityHeaderValue); } string requestJSON = JsonConvert.SerializeObject(requestMessage); StringContent content = new StringContent(requestJSON, Encoding.UTF8, "application/json"); HttpResponseMessage responseMessage = await this.client.PostAsync($"{BASE_URL}/api/generate", content); responseMessage.EnsureSuccessStatusCode(); string responseString = await responseMessage.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject<List<ResponseMessage>>($"[{responseString}]"); } #endregion #region 채팅 요청하기 (스트림) (비동기) - RequestChatStreamAsync(requestMessage) /// <summary> /// 채팅 요청하기 (스트림) (비동기) /// </summary> /// <param name="requestMessage">요청 메시지</param> /// <returns>스트리밍 메시지 리스트 태스크</returns> public async Task<List<ResponseMessage>> RequestChatStreamAsync(RequestMessage requestMessage) { if(!this.client.DefaultRequestHeaders.Accept.Contains(this.streamMediaTypeWithQualityHeaderValue)) { this.client.DefaultRequestHeaders.Accept.Add(this.streamMediaTypeWithQualityHeaderValue); } string requestJSON = JsonConvert.SerializeObject(requestMessage); StringContent content = new StringContent(requestJSON, Encoding.UTF8, "application/json"); using var request = new HttpRequestMessage(HttpMethod.Post, $"{BASE_URL}/api/generate"); request.Content = content; using HttpResponseMessage httpResponseMessage = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); httpResponseMessage.EnsureSuccessStatusCode(); using Stream stream = await httpResponseMessage.Content.ReadAsStreamAsync(); using StreamReader reader = new StreamReader(stream); List<ResponseMessage> responseMessageList = new List<ResponseMessage>(); while(!reader.EndOfStream) { string line = await reader.ReadLineAsync(); if(string.IsNullOrWhiteSpace(line)) { continue; } ResponseMessage responseMessage = JsonConvert.DeserializeObject<ResponseMessage>(line); responseMessageList.Add(responseMessage); MessageArrived?.Invoke(this, responseMessage); } return responseMessageList; } #endregion #region 리소스 해제하기 - Dispose() /// <summary> /// 리소스 해제하기 /// </summary> public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion ////////////////////////////////////////////////////////////////////////////////////////// Protected #region 리소스 해제하기 - Dispose(disposing) /// <summary> /// 리소스 해제하기 /// </summary> /// <param name="disposing">리소스 해제 여부</param> protected virtual void Dispose(bool disposing) { if(!this.disposed) { if(disposing) { if(this.client != null) { this.client.Dispose(); } this.client = null; this.disposed = true; } } } #endregion } |
■ HttpClient 클래스를 사용해 OLLAMA에게 LLAMA 3 로컬 LLM 통신하는 방법을 보여준다. ▶ RequestMessage.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
using Newtonsoft.Json; namespace TestProject; /// <summary> /// 요청 메시지 /// </summary> public class RequestMessage { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 모델 - Model /// <summary> /// 모델 /// </summary> [JsonProperty("model")] public string Model { get; set; } #endregion #region 프롬프트 - Prompt /// <summary> /// 프롬프트 /// </summary> [JsonProperty("prompt")] public string Prompt { get; set; } #endregion } |
▶ Program.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
using System.Text; using Newtonsoft.Json; namespace TestProject; /// <summary> /// 프로그램 /// </summary> class Program { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Private #region 프로그램 시작하기 - Main() /// <summary> /// 프로그램 시작하기 /// </summary> /// <returns>태스크</returns> static async Task Main() { using HttpClient httpClient = new HttpClient(); httpClient.BaseAddress = new Uri("http://localhost:11434"); RequestMessage requestMessage = new() { Model = "llama3", Prompt = "Hello, how are you?" }; StringContent stringContent = new ( JsonConvert.SerializeObject(requestMessage), Encoding.UTF8, "application/json" ); HttpResponseMessage httpResponseMessage = await httpClient.PostAsync("/api/generate", stringContent); string responseString = await httpResponseMessage.Content.ReadAsStringAsync(); Console.WriteLine(responseString); } #endregion } |
TestProject.zip
■ Markdown 클래스의 Parse 정적 메소드를 사용해 마크다운 문자열을 파싱하는 방법을 보여준다. ▶ Program.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
using Markdig; using Markdig.Syntax; namespace TestProject; /// <summary> /// 프로그램 /// </summary> class Program { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Private #region 프로그램 시작하기 - Main() /// <summary> /// 프로그램 시작하기 /// </summary> static void Main() { #region 마크다운 문자열을 설정한다. string markdownString = @" # 제목 이것은 **Markdown** 문서입니다. - 목록 항목 1 - 목록 항목 2 - 목록 항목 3 ```csharp // 코드 블록 예제 Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); ``` 1. 순서가 있는 항목 1. 순서가 있는 항목 1. 순서가 없는 항목 1. 순서가 없는 항목 1. 순서가 있는 항목 1. 순서가 있는 항목 - 순서가 없는 항목 - 순서가 없는 항목 - 순서가 없는 항목 - 순서가 없는 항목 | 값 | 의미 | 기본값 | |---|:---:|---:| | `static` | 유형(기준) 없음 / 배치 불가능 | `static` | | `relative` | 요소 자신을 기준으로 배치 | | | `absolute` | 위치 상 부모(조상)요소를 기준으로 배치 | | | `fixed` | 브라우저 창을 기준으로 배치 | | | `sticky` | 스크롤 영역 기준으로 배치 | | > 인용문을 작성하세요! >> 중첩된 인용문(nested blockquote)을 만들 수 있습니다. >>> 중중첩 인용문 1 >>> 중중첩 인용문 2 >>> 중중첩 인용문 3 | 값 | 의미 | |---|---| | 버티컬바 출력 | \| | | 인라인 코드 강조 | `\|` | > 인용문 - 남의 말이나 글에서 직접 또는 간접으로 따온 문장. > _(네이버 국어 사전)_ BREAK! ![Prunus](http://www.gstatic.com/webp/gallery/4.jpg) 1. 순서가 있는 항목 1. 순서가 있는 항목 1. 순서가 없는 항목 1. 순서가 없는 항목 1. 순서가 있는 항목 1. 순서가 있는 항목 [샘플 PDF 다운로드](https://drive.google.com/file/d/1P-2-Rui-5lX5v2vIUDtMp_NRr1Fmp3--/view?usp=drive_link) "; #endregion MarkdownPipeline markdownPipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build(); MarkdownDocument markdownDocument = Markdown.Parse(markdownString, markdownPipeline); foreach(Block block in markdownDocument) { MarkdownHelper.EnumerateBlock(block, 0); } } #endregion } |
▶ MarkdownHelper.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 |
using Markdig.Extensions.Tables; using Markdig.Syntax.Inlines; using Markdig.Syntax; public static class MarkdownHelper { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region 블럭 나열하기 - EnumerateBlock(block, level) /// <summary> /// 블럭 나열하기 /// </summary> /// <param name="block">블럭</param> /// <param name="level">레벨</param> public static void EnumerateBlock(Block block, int level) { if(block is Table table) { WriteElementType(block, level, false); Console.Write(" : ["); TableRow firstTableRow = table[0] as TableRow; for(int i = 0; i < firstTableRow.Count; i++) { TableColumnDefinition tableColumnDefinition = table.ColumnDefinitions[i]; if(i > 0) { Console.Write(","); } if(tableColumnDefinition.Alignment.HasValue) { Console.Write($"{tableColumnDefinition.Alignment.Value}"); } else { Console.Write($"{TableColumnAlign.Left}"); } } Console.WriteLine("]"); foreach(Block childBlock in table) { if(childBlock is TableRow tableRow) { EnumerateTableRow(tableRow, level + 1); } else { EnumerateBlock(childBlock, level + 1); } } } else { if(block is LeafBlock) { if(block is ParagraphBlock paragraphBlock) { WriteElementType(block, level, true); EnumerateInline(paragraphBlock.Inline, level + 1); } else if(block is HeadingBlock headingBlock) { WriteElementType(block, level, false); Console.WriteLine($" : {headingBlock.Level}"); EnumerateInline(headingBlock.Inline, level + 1); } else if(block is FencedCodeBlock fencedCodeBlock) { WriteElementType(block, level, false); Console.WriteLine($" : {fencedCodeBlock.Info}"); Console.WriteLine("----------------------------------------------------------------------------------------------------"); foreach(object line in fencedCodeBlock.Lines) { Console.WriteLine(line.ToString()); } Console.WriteLine("----------------------------------------------------------------------------------------------------"); } else { WriteElementType(block, level, true); return; } } else if(block is ContainerBlock containerBlock) { WriteElementType(block, level, true); foreach(Block childBlock in containerBlock) { EnumerateBlock(childBlock, level + 1); } } } } #endregion //////////////////////////////////////////////////////////////////////////////// Private #region 엘리먼트 타입 쓰기 - WriteElementType(inline, level, newLine) /// <summary> /// 엘리먼트 타입 쓰기 /// </summary> /// <param name="inline">인라인</param> /// <param name="level">레벨</param> /// <param name="newLine">개행 여부</param> private static void WriteElementType(Inline inline, int level, bool newLine = true) { int count = level * 4; string padding = count > 0 ? new string(' ', count) : string.Empty; string elementType = $"{padding}{inline.GetType().Name}"; if(newLine) { Console.WriteLine(elementType); } else { Console.Write(elementType); } } #endregion #region 인라인 열거하기 - EnumerateInline(inline, level) /// <summary> /// 인라인 열거하기 /// </summary> /// <param name="inline">인라인</param> /// <param name="level">레벨</param> private static void EnumerateInline(Inline inline, int level) { if(inline is ContainerInline containerInline) { if(containerInline is LinkInline linkInline) { WriteElementType(inline, level, false); string isImage = linkInline.IsImage ? "(이미지)" : "(링크)"; Console.WriteLine($" : {isImage} {linkInline.Url}"); foreach(Inline childInline in containerInline) { EnumerateInline(childInline, level + 1); } } else { WriteElementType(inline, level, true); foreach(Inline childInline in containerInline) { EnumerateInline(childInline, level + 1); } } } else if(inline is LeafInline leafInline) { if(inline is LiteralInline literalInline) { WriteElementType(inline, level, false); Console.WriteLine(" : " + literalInline.Content); } else if(inline is CodeInline codeInline) { WriteElementType(inline, level, false); Console.WriteLine(" : " + codeInline.Content); } else { WriteElementType(inline, level, true); } return; } } #endregion #region 엘리먼트 타입 쓰기 - WriteElementType(block, level, newLine) /// <summary> /// 엘리먼트 타입 쓰기 /// </summary> /// <param name="block">블럭</param> /// <param name="level">레벨</param> /// <param name="newLine">신규 행 여부</param> private static void WriteElementType(Block block, int level, bool newLine = true) { int count = level * 4; string padding = count > 0 ? new string(' ', count) : string.Empty; string elementType = $"{padding}{block.GetType().Name}"; if(newLine) { Console.WriteLine(elementType); } else { Console.Write(elementType); } } #endregion #region 테이블 셀 열거하기 - EnumerateTableCell(tableCell, level) /// <summary> /// 테이블 셀 열거하기 /// </summary> /// <param name="tableCell">테이블 셀</param> /// <param name="level">레벨</param> private static void EnumerateTableCell(TableCell tableCell, int level) { WriteElementType(tableCell, level, true); foreach(Block childBlock in tableCell) { EnumerateBlock(childBlock, level + 1); } } #endregion #region 테이블 행 열거하기 - EnumerateTableRow(tableRow, level) /// <summary> /// 테이블 행 열거하기 /// </summary> /// <param name="tableRow">테이블 행</param> /// <param name="level">레벨</param> private static void EnumerateTableRow(TableRow tableRow, int level) { WriteElementType(tableRow, level, true); foreach(Block childBlock in tableRow) { if(childBlock is TableCell tableCell) { EnumerateTableCell(tableCell, level + 1); } else { EnumerateBlock(childBlock, level + 1); } } } #endregion } |
TestProject.zip
■ MarkdownExtensions 클래스의 UseAdvancedExtensions 확장 메소드를 사용해 고급 확장 기능을 활성화하는 방법을 보여준다. ▶ 예제 코드 (C#)
1 2 3 4 5 |
using Markdig; MarkdownPipelineBuilder markdownPipelineBuilder = new MarkdownPipelineBuilder().UseAdvancedExtensions(); |