Skip to content

Commit

Permalink
[from now] 2023/07/04 11:50:05
Browse files Browse the repository at this point in the history
diff --git a/.cache/kvs-node-localstorage/proofdict b/.cache/kvs-node-localstorage/proofdict
index 277aaf0..867b165 100644
--- a/.cache/kvs-node-localstorage/proofdict
+++ b/.cache/kvs-node-localstorage/proofdict
@@ -1 +1 @@
-[{"id":"01BQ92YWWMY2YZNGG7Q70EZTFE","description":"","expected":"たびたび","patterns":["度々"],"specs":[],"tags":["opinion","表記統一"]},{"id":"01BQ92YYSHHRNMZPY2VG4991B7","description":"","expected":"Travis CI","patterns":["/TravisCI/i"],"specs":[{"from":"TravisCI is a service","to":"Travis CI is a service"}],"tags":["noun"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01EKJAAAXCZR005PDSTRVT24QZ","description":"Jamstackに統一された https://github.com/jamstack/jamstack.org/issues/279","expected":"Jamstack","patterns":["/\\bJAMStack\\b/i"],"allows":[],"specs":[{"from":"Jamstack","to":"Jamstack"},{"from":"JAMStack is old word","to":"Jamstack is old word"},{"from":"jamjasmstack","to":"jamjasmstack"}],"tags":["noun"]},{"id":"01BQ92YWJF8WXN0M9FE0S4QYW5","description":"","expected":"$1発売","patterns":["/(\\d+年\\d+月\\d+日)\\s発売/"],"specs":[],"tags":["opinion","jser.info"]},{"id":"01BQ92YXWVT5PGRGQCH1ZZ6S7V","description":"","expected":"operator","patterns":["oeprator"],"specs":[],"tags":["typo"]},{"id":"01BQ92YYBGNT2ZA7SCBGZP4XW8","description":"Reference https://www.w3.org/TR/webstorage/","expected":"sessionStorage","patterns":["sessionstorage","session storage"],"specs":[{"from":"sessionstorage is a parts of webstorage","to":"sessionStorage is a parts of webstorage"},{"from":"session storage is a parts of webstorage","to":"sessionStorage is a parts of webstorage"}],"tags":["noun","JavaScript"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YZ6QD58PVYY3FE7QD2S5","description":"Reference http://www.seleniumhq.org/projects/webdriver/","expected":"WebDriver","patterns":["/WebDriver/i","/Web Driver/i"],"specs":[{"from":"Web Driver is designed in a simpler and more concise programming interface","to":"WebDriver is designed in a simpler and more concise programming interface"},{"from":"webdriver is designed in a simpler and more concise programming interface","to":"WebDriver is designed in a simpler and more concise programming interface"},{"from":"WebdriverIO is OK.","to":"WebdriverIO is OK."}],"tags":["noun"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BW0P1BD0VKQQMZ4E947WVMJ1","description":"See https://developer.mozilla.org/en-US/Add-ons/WebExtensions/What_are_WebExtensions","expected":"WebExtension$1","patterns":["/Web Extension(s)?/"],"specs":[{"from":"Web Extension","to":"WebExtension"},{"from":"Extensions for Firefox are built using WebExtensions APIs","to":"Extensions for Firefox are built using WebExtensions APIs"},{"from":"WebExtension APIs are designed to be compatible with Chrome and Opera extensions","to":"WebExtension APIs are designed to be compatible with Chrome and Opera extensions"}],"tags":["noun"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YYBJTDA6WF56ZE9PKP8F","description":"Reference http://underscorejs.org/","expected":"Underscore.js","patterns":["/underscore.js/i"],"specs":[],"tags":["noun","JavaScript"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YXWVJ7HG4TDWA45RH297","description":"","expected":"webpack","patterns":["/\\bwebpac(?!k)\\b/"],"specs":[{"from":"webpacのビルド速度改善についてのスライド","to":"webpackのビルド速度改善についてのスライド"}],"tags":["typo"]},{"id":"01BQ92YYBH2EZP1E4KVNDMXYV9","description":"Pointer Eventsに統一","expected":"Pointer Events","patterns":["/pointer event/i","/pointer events/i"],"specs":[{"from":"Pointer Events","to":"Pointer Events"},{"from":"MSEdgeの対応、pointer eventsなど対応、","to":"MSEdgeの対応、Pointer Eventsなど対応、"},{"from":"MSEdgeの対応、pointer eventなど対応、","to":"MSEdgeの対応、Pointer Eventsなど対応、"},{"from":"pointer-events","to":"pointer-events"}],"tags":["JavaScript","noun"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YYBHGYFTTDANV4RNC3CJ","description":"Should have a space between words. Reference https://github.com/w3c/ServiceWorker","expected":"$1 $2","patterns":["/(service)(worker)/i"],"specs":[{"from":"This is serviceworker?","to":"This is service worker?"},{"from":"ServiceWorker","to":"Service Worker"}],"tags":["noun","JavaScript"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YYBGWJZC59XKSNA53X2C","description":"Reference https://www.w3.org/TR/webstorage/","expected":"localStorage","patterns":["localstorage","local storage"],"specs":[{"from":"localstorage is a parts of webstorage","to":"localStorage is a parts of webstorage"},{"from":"local storage is a parts of webstorage","to":"localStorage is a parts of webstorage"}],"tags":["noun","JavaScript"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YYBJCY50GECA6CQH874F","description":"Reference https://www.emberjs.com/","expected":"Ember.js","patterns":["/Ember\\.js/i"],"specs":[{"from":"ember.js","to":"Ember.js"}],"tags":["noun","JavaScript"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YXWQDKGCA9W74SKW7VST","description":"","expected":"Arrow Function","patterns":["Array Function"],"specs":[],"tags":["typo"]},{"id":"01BQ92YYBEFBXEHH8T8HC8RCRD","description":"Reference https://www.ecma-international.org/publications/standards/Ecma-262.htm","expected":"ECMAScript $1","patterns":["/ECMAScript([0-9]+)/i","/ECMA Script([0-9]+)/i"],"specs":[{"from":"ECMASCRIPT5","to":"ECMAScript 5"},{"from":"ecmascript2015","to":"ECMAScript 2015"}],"tags":["noun","JavaScript"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YYBGQBRYXEZK5ZRRK1WW","description":"","expected":"Ecma標準","patterns":["ECMA標準"],"specs":[],"tags":["JavaScript"]},{"id":"01BQ92YYBGSXM66TN1ZSJH177M","description":"","expected":"ES6 Classes","patterns":["/\\bES6 classes\\b/i"],"specs":[{"from":"ES6 classes","to":"ES6 Classes"}],"tags":["JavaScript"]},{"id":"01BQ92YXWVMAMKQ3VK2DNYNCCP","description":"Customのtypoです","expected":"Custom","patterns":["/Custome\\b/i"],"specs":[{"from":"WebVRをCustome Elementのタグで書く","to":"WebVRをCustom Elementのタグで書く"}],"tags":["typo"]},{"id":"01BQ92YYBH4NNWB305Y1TD7B6N","description":"Reference https://www.w3.org/TR/webaudio/","expected":"Web Audio API","patterns":["/WebAudio API/i","/Web AudioAPI/i","/Web Audio API/i"],"specs":[{"from":"All scheduled times in the WebAudio API are relative to the value of currentTime.","to":"All scheduled times in the Web Audio API are relative to the value of currentTime."}],"tags":["noun","JavaScript"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YYBJK6WW9Y09W7TF1YTT","description":"See http://todomvc.com/","expected":"TodoMVC","patterns":["/todomvc/i"],"specs":[{"from":"todomvc is todo app.","to":"TodoMVC is todo app."}],"tags":["noun","JavaScript"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQMSTDZCWH8P81E7PBG9BMQS","description":"importのtypoです","expected":"Import","patterns":["Impport"],"specs":[{"from":"Dynamic Impport","to":"Dynamic Import"},{"from":"Impporter","to":"Importer"}],"tags":["typo"]},{"id":"01F4VMKJAEKHMANZVFFR9D6DJA","description":"AWS CloudFront https://aws.amazon.com/jp/cloudfront/","expected":"CloudFront","patterns":["/\\bCloudFront\\b/i"],"allows":["{{COMBINATION_WORD}}"],"specs":[{"from":"Cloudront","to":"Cloudront"},{"from":"Cloudfront","to":"CloudFront"},{"from":"awscloudfront","to":"awscloudfront"}],"tags":["noun"]},{"id":"01CF6NR1Q7XWC035B9XJXNB852","description":"typo","expected":"$1nimation","patterns":["/(a)nimation/i"],"specs":[{"from":"Animation","to":"Animation"},{"from":"CSS Animation","to":"CSS Animation"},{"from":"CSS animations","to":"CSS animations"}],"tags":["noun"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01FB0ZGJEK2GEKX4AM7KCT1E8Y","description":"WHAWG typo","expected":"WHATWG","patterns":["/\\bWHAWG\\b/","/\\bWHATWGT\\b/"],"allows":[],"specs":[{"from":"WHATWG","to":"WHATWG"},{"from":"WHAWG","to":"WHATWG"},{"from":"WHATWG","to":"WHATWG"}],"tags":["noun"]},{"id":"01DSDJ2ZNSB644ZB0SD53C62X5","description":"","expected":"Archive","patterns":["Archieve"],"allows":[],"specs":[{"from":"HTTP Archieve","to":"HTTP Archive"}],"tags":["noun"]},{"id":"01BQ92YYBJXRCA80R6BCA4SS7K","description":"Angular(2.0~)","expected":"Angular","patterns":["/angular/i"],"specs":[{"from":"angular","to":"Angular"},{"from":"angulardart","to":"angulardart"}],"tags":["noun","JavaScript"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YWWH8TVT2N8FT61TTDNH","description":"ひらがなで書かず、漢字で書くと読みやすくなります","expected":"$1中","patterns":["/(その)なか/","/(世界|日)じゅう/"],"specs":[],"tags":["opinion","表記統一"]},{"id":"01BQ92YYBHA4FM6A82V2ZT0V91","description":"Reference https://www.w3.org/TR/html-imports/","expected":"HTML Imports","patterns":["/HTML Import/i"],"specs":[{"from":"HTML import","to":"HTML Imports"}],"tags":["noun","JavaScript"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01EKP3RFQ18K47EYH1ZKB5E4R5","description":"https://github.com/GoogleChrome/lighthouse","expected":"Lighthouse","patterns":["/\\bLighthouse\\b/i"],"allows":["{{COMBINATION_WORD}}"],"specs":[{"from":" lighthouse","to":" Lighthouse"},{"from":"lighthouse-cli","to":"lighthouse-cli"},{"from":"Lighthouse analyzes web apps","to":"Lighthouse analyzes web apps"}],"tags":["noun"]},{"id":"01C1VCJNDQW7RN25S24KNP9V4M","description":"A typo Chromium","expected":"$1hromium","patterns":["/(C)hronium/i"],"specs":[{"from":"Chrome is Chronium","to":"Chrome is Chromium"},{"from":"Chrome is chronium","to":"Chrome is chromium"}],"tags":["noun"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YYBJP9EDR7KS993CMQAR","description":"","expected":"Virtual DOM","patterns":["/VirtualDOM/i"],"specs":[{"from":"VirtualDOM is virtual dom object","to":"Virtual DOM is virtual dom object"}],"tags":["noun","opinion","JavaScript"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YYBGZPBHMB3DMMXMJ0CJ","description":"","expected":"ES.next","patterns":["ES next"],"specs":[{"from":"ES nextでは","to":"ES.nextでは"}],"tags":["JavaScript"]},{"id":"01BQ92YW70P4BSGE4ZBMKECATV","description":"","expected":"PostCSS","patterns":["/PostCSS/i"],"specs":[{"from":"postcss","to":"PostCSS"}],"tags":["noun","CSS"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YZ6QCTPXGPWWJ5NWT6NC","description":"","expected":"Virtual Machine","patterns":["Virtual machine"],"specs":[{"from":"MSEdgeが使えるWindows 10のVirtual machine(VM)が公開された","to":"MSEdgeが使えるWindows 10のVirtual Machine(VM)が公開された"}],"tags":["noun","opinion"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YYSHHTGFD78FF7NB4RX9","description":"","expected":"HTML5 Rocks","patterns":["/HTML5Rocks/i","/HTML5Rock/i"],"specs":[{"from":"HTML5Rockに現在公開されている翻訳記事のまとめ","to":"HTML5 Rocksに現在公開されている翻訳記事のまとめ"}],"tags":["noun"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01DTET96387SQ3VYTXHYSX5BYS","description":"WebAssembly (abbreviated Wasm) https://webassembly.org/","expected":"Wasm","patterns":["/wasm/i"],"allows":[],"specs":[{"from":"wasm","to":"Wasm"},{"from":"WAsm","to":"Wasm"},{"from":"WebAssembly (abbreviated Wasm) ","to":"WebAssembly (abbreviated Wasm) "}],"tags":["noun"]},{"id":"01BQ92YYSD9K713DCXTFGNDENS","description":"Reference https://leanpub.com/","expected":"Leanpub","patterns":["/leanpub/i"],"specs":[{"from":"leanpub","to":"Leanpub"}],"tags":["noun"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YZ6QR8RJKA5Y8W2F9NMY","description":"Reference https://webkit.org/","expected":"WebKit","patterns":["/webkit/i"],"specs":[{"from":"これはwebkitです","to":"これはWebKitです"},{"from":"XXXwebkit","to":"XXXwebkit"},{"from":"node-webkit","to":"node-webkit"}],"tags":["noun"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YXWVY5EFQNE4ZV8K0HS5","description":"","expected":"package.json","patterns":["packge.json"],"specs":[],"tags":["typo"]},{"id":"01EF8QSDGRB63DFJ54B68AKN7J","description":"webpack is lower-case https://webpack.js.org/","expected":"webpack","patterns":["/\\bwebpack\\b/i"],"allows":[],"specs":[{"from":"webpack","to":"webpack"},{"from":"WebPack","to":"webpack"},{"from":"BuildWebPack is OK","to":"BuildWebPack is OK"}],"tags":["noun"]},{"id":"01BQ92YZ6R35HCYAZGN18RB35E","description":"Reference https://www.microsoft.com/windows","expected":"Windows $1","patterns":["/Windows (\\d+)/i"],"specs":[{"from":"OK windows","to":"OK windows"},{"from":"windows 10","to":"Windows 10"}],"tags":["noun"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YYBGNAKFHY1TFA9Z3KTC","description":"Reference https://w3c.github.io/webcomponents/explainer/","expected":"Web Components","patterns":["/WebComponents/i","/web components/i"],"specs":[{"from":"WebComponentsについてのWeeklyメールマガジン","to":"Web ComponentsについてのWeeklyメールマガジン"}],"tags":["noun","JavaScript"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01D1SNYB7AA69NTAEBT1X651RB","description":"Brotliはbr","expected":"$1rotli","patterns":["/\\b(b)rottli\\b/i"],"allows":[],"specs":[{"from":"brottli compression","to":"brotli compression"},{"from":"brottli圧縮","to":"brotli圧縮"},{"from":"BrotliはbrotliでOK","to":"BrotliはbrotliでOK"}],"tags":["noun"]},{"id":"01BQ92YXWFVPNRGMBZ3RZNN1RD","description":"","expected":"Foundation","patterns":["Fundation"],"specs":[{"from":"Fundation","to":"Foundation"}],"tags":["typo"]},{"id":"01BQ92YXWTV260J4A7W8TSWEXH","description":"","expected":"Workaround","patterns":["Workaound"],"specs":[{"from":"iOS7と8のquerySelectorバグの対処(Workaound)","to":"iOS7と8のquerySelectorバグの対処(Workaround)"}],"tags":["typo"]},{"id":"01BQ92YYBJBBNRPR2WYHMDYPPG","description":"Reference http://source-map.github.io/","expected":"Source Map","patterns":["/Source Map/i","/SourceMap/i"],"specs":[{"from":"多段SourceMapに対応","to":"多段Source Mapに対応"},{"from":"SourceMap","to":"Source Map"}],"tags":["noun","JavaScript"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YXWQ02R8SE1ZZHCG89P5","description":"","expected":"Property","patterns":["/propety/i"],"specs":[{"from":"propety","to":"Property"}],"tags":["typo"]},{"id":"01F0W5YS57VWFW3DAP3EC92D6Y","description":"https://github.com/sveltejs/kit","expected":"SvelteKit","patterns":["/SveleteKit/i"],"allows":[],"specs":[{"from":"SveleteKit","to":"SvelteKit"},{"from":"SvelteKit","to":"SvelteKit"}],"tags":["noun"]},{"id":"01BQ92YXWR8T1QBP2JSKFH74RK","description":"","expected":"CoffeeScript","patterns":["CoffeScript"],"specs":[{"from":"CoffeScriptからES6","to":"CoffeeScriptからES6"}],"tags":["typo"]},{"id":"01BQ92YYSHMN57CX27SAJQT2W0","description":"","expected":"Sauce Labs","patterns":["SauceLabs"],"specs":[],"tags":["noun"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YYBJENZB6M480KCJ0J99","description":"Reference http://jquery.com/","expected":"jQuery","patterns":["/jquery/i"],"specs":[{"from":"Jquery","to":"jQuery"},{"from":"jquery","to":"jQuery"},{"from":"jquery-ui","to":"jquery-ui"}],"tags":["noun","JavaScript"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YW6Y5KA9YRC4HRPWZTVV","description":"","expected":"Flexbox","patterns":["Fluxbox"],"specs":[{"from":"CSS Fluxboxでは","to":"CSS Flexboxでは"}],"tags":["noun","CSS"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YWJG32Z679PF9CHJJD5R","description":"","expected":"$1デフォルトで有効$2","patterns":["/(?:(が)デフォルト有効)|(?:デフォルト有効(に))/"],"specs":[],"tags":["opinion","jser.info"]},{"id":"01BQ92YXWTM3A3CG10MT3FCD55","description":"","expected":"Performance","patterns":["/peformance/i"],"specs":[{"from":"ブラウザ上でどうやって計測するかやPeformance Timelineなどの仕様について","to":"ブラウザ上でどうやって計測するかやPerformance Timelineなどの仕様について"}],"tags":["typo"]},{"id":"01BQ92YYBHB83ZNFWK5WBA58B7","description":"JavaScript is not Java Script","expected":"JavaScript","patterns":["/javascript/i","/Java Script/i"],"specs":[{"from":"javascript","to":"JavaScript"},{"from":"java script","to":"JavaScript"},{"from":"Java script","to":"JavaScript"}],"tags":["noun","JavaScript"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YYBJDYYAR7DFH7M57G1P","description":"AngularJS(1.x)はJSを付けます","expected":"AngularJS","patterns":["/Angular\\.js/i","/angularjs/i"],"specs":[{"from":"Angular.js","to":"AngularJS"}],"tags":["JavaScript"]},{"id":"01F0W5YS57VWFW3DAP3EC92D6Z","description":"https://github.com/sveltejs","expected":"$1velte","patterns":["/(s)velete/i"],"allows":[],"specs":[{"from":"svelete","to":"svelte"},{"from":"Svelete","to":"Svelte"}],"tags":["noun"]},{"id":"01BQ92YYBH2H42AT29Y4BP84FA","description":"Reference https://www.w3.org/TR/touch-events/","expected":"Touch Events","patterns":["/Touch Event/i","/Touch Events/i"],"specs":[{"from":"Touch Event、Fullscreen APIなどのサポートなど","to":"Touch Events、Fullscreen APIなどのサポートなど"}],"tags":["noun","JavaScript"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YZ6Q0Y22BVKAVFC4VV1N","description":"Reference http://ocaml.org/","expected":"OCaml","patterns":["/OCaml/i"],"specs":[{"from":"ocaml","to":"OCaml"}],"tags":["noun"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YZ6NT1NACB4X0DC8XHFH","description":"","expected":"$1 EAP","patterns":["/([0-9\\.]+)EAP/"],"specs":[],"tags":["noun"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BSADQQDT7DEBJT25555YCQ14","description":"Reference https://www.mysql.com/","expected":"MySQL","patterns":["/mysql/i"],"specs":[{"from":"mysql is a database.","to":"MySQL is a database."},{"from":"Oracle mysql","to":"Oracle MySQL"}],"tags":["noun"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YYSF66CHGRP4MX5X6P0Q","description":"O'reilly is OK. But oreilly is not OK","expected":"Oreilly","patterns":["/Oreilly/i"],"specs":[{"from":"oreilly","to":"Oreilly"}],"tags":["noun"],"allows":["{{COMBINATION_WORD}}"]}]
\ No newline at end of file
+[{"id":"01BQ92YWWNN391X0F0PW38761G","description":"","expected":"または","patterns":["又は"],"specs":[],"tags":["opinion","表記統一"]},{"id":"01BQ92YYBJTDA6WF56ZE9PKP8F","description":"Reference http://underscorejs.org/","expected":"Underscore.js","patterns":["/underscore.js/i"],"specs":[],"tags":["noun","JavaScript"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01EKJAAAXCZR005PDSTRVT24QZ","description":"Jamstackに統一された https://github.com/jamstack/jamstack.org/issues/279","expected":"Jamstack","patterns":["/\\bJAMStack\\b/i"],"allows":[],"specs":[{"from":"Jamstack","to":"Jamstack"},{"from":"JAMStack is old word","to":"Jamstack is old word"},{"from":"jamjasmstack","to":"jamjasmstack"}],"tags":["noun"]},{"id":"01BQ92YZ6R35HCYAZGN18RB35E","description":"Reference https://www.microsoft.com/windows","expected":"Windows $1","patterns":["/Windows (\\d+)/i"],"specs":[{"from":"OK windows","to":"OK windows"},{"from":"windows 10","to":"Windows 10"}],"tags":["noun"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YXWTM3A3CG10MT3FCD55","description":"","expected":"Performance","patterns":["/peformance/i"],"specs":[{"from":"ブラウザ上でどうやって計測するかやPeformance Timelineなどの仕様について","to":"ブラウザ上でどうやって計測するかやPerformance Timelineなどの仕様について"}],"tags":["typo"]},{"id":"01BQ92YWWRZAQH7M4QN2T8W7Y7","description":"","expected":"$1中","patterns":["/(世界|日)じゅう/"],"specs":[],"tags":["opinion","表記統一"]},{"id":"01BQ92YYBGNAKFHY1TFA9Z3KTC","description":"Reference https://w3c.github.io/webcomponents/explainer/","expected":"Web Components","patterns":["/WebComponents/i","/web components/i"],"specs":[{"from":"WebComponentsについてのWeeklyメールマガジン","to":"Web ComponentsについてのWeeklyメールマガジン"}],"tags":["noun","JavaScript"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YYBGZPBHMB3DMMXMJ0CJ","description":"","expected":"ES.next","patterns":["ES next"],"specs":[{"from":"ES nextでは","to":"ES.nextでは"}],"tags":["JavaScript"]},{"id":"01C1VCJNDQW7RN25S24KNP9V4M","description":"A typo Chromium","expected":"$1hromium","patterns":["/(C)hronium/i"],"specs":[{"from":"Chrome is Chronium","to":"Chrome is Chromium"},{"from":"Chrome is chronium","to":"Chrome is chromium"}],"tags":["noun"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YWWV2VVD5WZ1B8TFTBQP","description":"","expected":"$1とおり","patterns":["/(思った|以下の)通り/"],"specs":[],"tags":["opinion","表記統一"]},{"id":"01BQ92YYSD9K713DCXTFGNDENS","description":"Reference https://leanpub.com/","expected":"Leanpub","patterns":["/leanpub/i"],"specs":[{"from":"leanpub","to":"Leanpub"}],"tags":["noun"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YYBEFBXEHH8T8HC8RCRD","description":"Reference https://www.ecma-international.org/publications/standards/Ecma-262.htm","expected":"ECMAScript $1","patterns":["/ECMAScript([0-9]+)/i","/ECMA Script([0-9]+)/i"],"specs":[{"from":"ECMASCRIPT5","to":"ECMAScript 5"},{"from":"ecmascript2015","to":"ECMAScript 2015"}],"tags":["noun","JavaScript"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YXWVY5EFQNE4ZV8K0HS5","description":"","expected":"package.json","patterns":["packge.json"],"specs":[],"tags":["typo"]},{"id":"01BQ92YXWVJ7HG4TDWA45RH297","description":"","expected":"webpack","patterns":["/\\bwebpac(?!k)\\b/"],"specs":[{"from":"webpacのビルド速度改善についてのスライド","to":"webpackのビルド速度改善についてのスライド"}],"tags":["typo"]},{"id":"01EKP3RFQ18K47EYH1ZKB5E4R5","description":"https://github.com/GoogleChrome/lighthouse","expected":"Lighthouse","patterns":["/\\bLighthouse\\b/i"],"allows":["{{COMBINATION_WORD}}"],"specs":[{"from":" lighthouse","to":" Lighthouse"},{"from":"lighthouse-cli","to":"lighthouse-cli"},{"from":"Lighthouse analyzes web apps","to":"Lighthouse analyzes web apps"}],"tags":["noun"]},{"id":"01DTET96387SQ3VYTXHYSX5BYS","description":"WebAssembly (abbreviated Wasm) https://webassembly.org/","expected":"Wasm","patterns":["/wasm/i"],"allows":[],"specs":[{"from":"wasm","to":"Wasm"},{"from":"WAsm","to":"Wasm"},{"from":"WebAssembly (abbreviated Wasm) ","to":"WebAssembly (abbreviated Wasm) "}],"tags":["noun"]},{"id":"01F0W5YS57VWFW3DAP3EC92D6Z","description":"https://github.com/sveltejs","expected":"$1velte","patterns":["/(s)velete/i"],"allows":[],"specs":[{"from":"svelete","to":"svelte"},{"from":"Svelete","to":"Svelte"}],"tags":["noun"]},{"id":"01BQ92YYSHHRNMZPY2VG4991B7","description":"","expected":"Travis CI","patterns":["/TravisCI/i"],"specs":[{"from":"TravisCI is a service","to":"Travis CI is a service"}],"tags":["noun"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YYBGNT2ZA7SCBGZP4XW8","description":"Reference https://www.w3.org/TR/webstorage/","expected":"sessionStorage","patterns":["sessionstorage","session storage"],"specs":[{"from":"sessionstorage is a parts of webstorage","to":"sessionStorage is a parts of webstorage"},{"from":"session storage is a parts of webstorage","to":"sessionStorage is a parts of webstorage"}],"tags":["noun","JavaScript"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BW0P1BD0VKQQMZ4E947WVMJ1","description":"See https://developer.mozilla.org/en-US/Add-ons/WebExtensions/What_are_WebExtensions","expected":"WebExtension$1","patterns":["/Web Extension(s)?/"],"specs":[{"from":"Web Extension","to":"WebExtension"},{"from":"Extensions for Firefox are built using WebExtensions APIs","to":"Extensions for Firefox are built using WebExtensions APIs"},{"from":"WebExtension APIs are designed to be compatible with Chrome and Opera extensions","to":"WebExtension APIs are designed to be compatible with Chrome and Opera extensions"}],"tags":["noun"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YXWQ02R8SE1ZZHCG89P5","description":"","expected":"Property","patterns":["/propety/i"],"specs":[{"from":"propety","to":"Property"}],"tags":["typo"]},{"id":"01FB0ZGJEK2GEKX4AM7KCT1E8Y","description":"WHAWG typo","expected":"WHATWG","patterns":["/\\bWHAWG\\b/","/\\bWHATWGT\\b/"],"allows":[],"specs":[{"from":"WHATWG","to":"WHATWG"},{"from":"WHAWG","to":"WHATWG"},{"from":"WHATWG","to":"WHATWG"}],"tags":["noun"]},{"id":"01BQ92YYBGQBRYXEZK5ZRRK1WW","description":"","expected":"Ecma標準","patterns":["ECMA標準"],"specs":[],"tags":["JavaScript"]},{"id":"01DSDJ2ZNSB644ZB0SD53C62X5","description":"","expected":"Archive","patterns":["Archieve"],"allows":[],"specs":[{"from":"HTTP Archieve","to":"HTTP Archive"}],"tags":["noun"]},{"id":"01BQ92YXWTV260J4A7W8TSWEXH","description":"","expected":"Workaround","patterns":["Workaound"],"specs":[{"from":"iOS7と8のquerySelectorバグの対処(Workaound)","to":"iOS7と8のquerySelectorバグの対処(Workaround)"}],"tags":["typo"]},{"id":"01G8GDRNBSXRWWTB493W353VFV","description":"NestJS https://nestjs.com/","expected":"NestJS","patterns":["/Nest\\.js/i","/Nestjs/i"],"allows":[],"specs":[{"from":"NestJS","to":"NestJS"},{"from":"Nest.js","to":"NestJS"}],"tags":[]},{"id":"01BQ92YWJG32Z679PF9CHJJD5R","description":"","expected":"$1デフォルトで有効$2","patterns":["/(?:(が)デフォルト有効)|(?:デフォルト有効(に))/"],"specs":[],"tags":["opinion","jser.info"]},{"id":"01BQMSTDZCWH8P81E7PBG9BMQS","description":"importのtypoです","expected":"Import","patterns":["Impport"],"specs":[{"from":"Dynamic Impport","to":"Dynamic Import"},{"from":"Impporter","to":"Importer"}],"tags":["typo"]},{"id":"01CF6NR1Q7XWC035B9XJXNB852","description":"typo","expected":"$1nimation","patterns":["/(a)nimation/i"],"specs":[{"from":"Animation","to":"Animation"},{"from":"CSS Animation","to":"CSS Animation"},{"from":"CSS animations","to":"CSS animations"}],"tags":["noun"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YZ6QCTPXGPWWJ5NWT6NC","description":"","expected":"Virtual Machine","patterns":["Virtual machine"],"specs":[{"from":"MSEdgeが使えるWindows 10のVirtual machine(VM)が公開された","to":"MSEdgeが使えるWindows 10のVirtual Machine(VM)が公開された"}],"tags":["noun","opinion"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YW70P4BSGE4ZBMKECATV","description":"","expected":"PostCSS","patterns":["/PostCSS/i"],"specs":[{"from":"postcss","to":"PostCSS"}],"tags":["noun","CSS"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YZ6QR8RJKA5Y8W2F9NMY","description":"Reference https://webkit.org/","expected":"WebKit","patterns":["/webkit/i"],"specs":[{"from":"これはwebkitです","to":"これはWebKitです"},{"from":"XXXwebkit","to":"XXXwebkit"},{"from":"node-webkit","to":"node-webkit"}],"tags":["noun"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01F4VMKJAEKHMANZVFFR9D6DJA","description":"AWS CloudFront https://aws.amazon.com/jp/cloudfront/","expected":"CloudFront","patterns":["/\\bCloudFront\\b/i"],"allows":["{{COMBINATION_WORD}}"],"specs":[{"from":"Cloudront","to":"Cloudront"},{"from":"Cloudfront","to":"CloudFront"},{"from":"awscloudfront","to":"awscloudfront"}],"tags":["noun"]},{"id":"01BQ92YYSF66CHGRP4MX5X6P0Q","description":"O'reilly is OK. But oreilly is not OK","expected":"Oreilly","patterns":["/Oreilly/i"],"specs":[{"from":"oreilly","to":"Oreilly"}],"tags":["noun"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01D1SNYB7AA69NTAEBT1X651RB","description":"Brotliはbr","expected":"$1rotli","patterns":["/\\b(b)rottli\\b/i"],"allows":[],"specs":[{"from":"brottli compression","to":"brotli compression"},{"from":"brottli圧縮","to":"brotli圧縮"},{"from":"BrotliはbrotliでOK","to":"BrotliはbrotliでOK"}],"tags":["noun"]},{"id":"01BQ92YYBH2EZP1E4KVNDMXYV9","description":"Pointer Eventsに統一","expected":"Pointer Events","patterns":["/pointer event/i","/pointer events/i"],"specs":[{"from":"Pointer Events","to":"Pointer Events"},{"from":"MSEdgeの対応、pointer eventsなど対応、","to":"MSEdgeの対応、Pointer Eventsなど対応、"},{"from":"MSEdgeの対応、pointer eventなど対応、","to":"MSEdgeの対応、Pointer Eventsなど対応、"},{"from":"pointer-events","to":"pointer-events"}],"tags":["JavaScript","noun"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YZ6QD58PVYY3FE7QD2S5","description":"Reference http://www.seleniumhq.org/projects/webdriver/","expected":"WebDriver","patterns":["/WebDriver/i","/Web Driver/i"],"specs":[{"from":"Web Driver is designed in a simpler and more concise programming interface","to":"WebDriver is designed in a simpler and more concise programming interface"},{"from":"webdriver is designed in a simpler and more concise programming interface","to":"WebDriver is designed in a simpler and more concise programming interface"},{"from":"WebdriverIO is OK.","to":"WebdriverIO is OK."}],"tags":["noun"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YXWFVPNRGMBZ3RZNN1RD","description":"","expected":"Foundation","patterns":["Fundation"],"specs":[{"from":"Fundation","to":"Foundation"}],"tags":["typo"]},{"id":"01BQ92YXWQDKGCA9W74SKW7VST","description":"","expected":"Arrow Function","patterns":["Array Function"],"specs":[],"tags":["typo"]},{"id":"01BQ92YYBHB83ZNFWK5WBA58B7","description":"JavaScript is not Java Script","expected":"JavaScript","patterns":["/javascript/i","/Java Script/i"],"specs":[{"from":"javascript","to":"JavaScript"},{"from":"java script","to":"JavaScript"},{"from":"Java script","to":"JavaScript"}],"tags":["noun","JavaScript"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YYBHA4FM6A82V2ZT0V91","description":"Reference https://www.w3.org/TR/html-imports/","expected":"HTML Imports","patterns":["/HTML Import/i"],"specs":[{"from":"HTML import","to":"HTML Imports"}],"tags":["noun","JavaScript"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01GJWEJCX5NN3HE149AXN1ZGGS","description":"","expected":"1Password","patterns":["/1\\s?Password/i"],"allows":[],"specs":[{"from":"1Password","to":"1Password"},{"from":"1password","to":"1Password"},{"from":"1 Password","to":"1Password"}],"tags":["noun"]},{"id":"01BQ92YZ6Q0Y22BVKAVFC4VV1N","description":"Reference http://ocaml.org/","expected":"OCaml","patterns":["/OCaml/i"],"specs":[{"from":"ocaml","to":"OCaml"}],"tags":["noun"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YYBGSXM66TN1ZSJH177M","description":"","expected":"ES6 Classes","patterns":["/\\bES6 classes\\b/i"],"specs":[{"from":"ES6 classes","to":"ES6 Classes"}],"tags":["JavaScript"]},{"id":"01BQ92YYBJXRCA80R6BCA4SS7K","description":"Angular(2.0~)","expected":"Angular","patterns":["/angular/i"],"specs":[{"from":"angular","to":"Angular"},{"from":"angulardart","to":"angulardart"}],"tags":["noun","JavaScript"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YYBJCY50GECA6CQH874F","description":"Reference https://www.emberjs.com/","expected":"Ember.js","patterns":["/Ember\\.js/i"],"specs":[{"from":"ember.js","to":"Ember.js"}],"tags":["noun","JavaScript"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YYBJK6WW9Y09W7TF1YTT","description":"See http://todomvc.com/","expected":"TodoMVC","patterns":["/todomvc/i"],"specs":[{"from":"todomvc is todo app.","to":"TodoMVC is todo app."}],"tags":["noun","JavaScript"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YYBGWJZC59XKSNA53X2C","description":"Reference https://www.w3.org/TR/webstorage/","expected":"localStorage","patterns":["localstorage","local storage"],"specs":[{"from":"localstorage is a parts of webstorage","to":"localStorage is a parts of webstorage"},{"from":"local storage is a parts of webstorage","to":"localStorage is a parts of webstorage"}],"tags":["noun","JavaScript"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01FXD5E4MNMPYWCJPV2KG5Y5QZ","description":"typo","expected":"Microsoft","patterns":["/Microsfot/i"],"allows":[],"specs":[{"from":"MicrosfotV","to":"MicrosfotV"},{"from":"MS","to":"MS"},{"from":"MicrosoftVisio","to":"MicrosoftVisio"}],"tags":["noun"]},{"id":"01BQ92YYSHMN57CX27SAJQT2W0","description":"","expected":"Sauce Labs","patterns":["SauceLabs"],"specs":[],"tags":["noun"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YYBHGYFTTDANV4RNC3CJ","description":"Should have a space between words. Reference https://github.com/w3c/ServiceWorker","expected":"$1 $2","patterns":["/(service)(worker)/i"],"specs":[{"from":"This is serviceworker?","to":"This is service worker?"},{"from":"ServiceWorker","to":"Service Worker"}],"tags":["noun","JavaScript"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YW6Y5KA9YRC4HRPWZTVV","description":"","expected":"Flexbox","patterns":["Fluxbox"],"specs":[{"from":"CSS Fluxboxでは","to":"CSS Flexboxでは"}],"tags":["noun","CSS"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YYBH2H42AT29Y4BP84FA","description":"Reference https://www.w3.org/TR/touch-events/","expected":"Touch Events","patterns":["/Touch Event/i","/Touch Events/i"],"specs":[{"from":"Touch Event、Fullscreen APIなどのサポートなど","to":"Touch Events、Fullscreen APIなどのサポートなど"}],"tags":["noun","JavaScript"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YZ6NT1NACB4X0DC8XHFH","description":"","expected":"$1 EAP","patterns":["/([0-9\\.]+)EAP/"],"specs":[],"tags":["noun"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01EF8QSDGRB63DFJ54B68AKN7J","description":"webpack is lower-case https://webpack.js.org/","expected":"webpack","patterns":["/\\bwebpack\\b/i"],"allows":[],"specs":[{"from":"webpack","to":"webpack"},{"from":"WebPack","to":"webpack"},{"from":"BuildWebPack is OK","to":"BuildWebPack is OK"}],"tags":["noun"]},{"id":"01BQ92YXWR8T1QBP2JSKFH74RK","description":"","expected":"CoffeeScript","patterns":["CoffeScript"],"specs":[{"from":"CoffeScriptからES6","to":"CoffeeScriptからES6"}],"tags":["typo"]},{"id":"01BSADQQDT7DEBJT25555YCQ14","description":"Reference https://www.mysql.com/","expected":"MySQL","patterns":["/mysql/i"],"specs":[{"from":"mysql is a database.","to":"MySQL is a database."},{"from":"Oracle mysql","to":"Oracle MySQL"}],"tags":["noun"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YYBJP9EDR7KS993CMQAR","description":"","expected":"Virtual DOM","patterns":["/VirtualDOM/i"],"specs":[{"from":"VirtualDOM is virtual dom object","to":"Virtual DOM is virtual dom object"}],"tags":["noun","opinion","JavaScript"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01GN8FN82HBSNH9NJYT0F30Q8B","description":"","expected":"prototype pollution","patterns":["prototype pollusion"],"allows":[],"specs":[{"from":"prototype pollution","to":"prototype pollution"},{"from":"prototype pollusion","to":"prototype pollution"}],"tags":["noun"]},{"id":"01F0W5YS57VWFW3DAP3EC92D6Y","description":"https://github.com/sveltejs/kit","expected":"SvelteKit","patterns":["/SveleteKit/i"],"allows":[],"specs":[{"from":"SveleteKit","to":"SvelteKit"},{"from":"SvelteKit","to":"SvelteKit"}],"tags":["noun"]},{"id":"01BQ92YXWVMAMKQ3VK2DNYNCCP","description":"Customのtypoです","expected":"Custom","patterns":["/Custome\\b/i"],"specs":[{"from":"WebVRをCustome Elementのタグで書く","to":"WebVRをCustom Elementのタグで書く"}],"tags":["typo"]},{"id":"01BQ92YYSHHTGFD78FF7NB4RX9","description":"","expected":"HTML5 Rocks","patterns":["/HTML5Rocks/i","/HTML5Rock/i"],"specs":[{"from":"HTML5Rockに現在公開されている翻訳記事のまとめ","to":"HTML5 Rocksに現在公開されている翻訳記事のまとめ"}],"tags":["noun"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YXWVT5PGRGQCH1ZZ6S7V","description":"","expected":"operator","patterns":["oeprator"],"specs":[],"tags":["typo"]},{"id":"01BQ92YYBJBBNRPR2WYHMDYPPG","description":"Reference http://source-map.github.io/","expected":"Source Map","patterns":["/Source Map/i","/SourceMap/i"],"specs":[{"from":"多段SourceMapに対応","to":"多段Source Mapに対応"},{"from":"SourceMap","to":"Source Map"}],"tags":["noun","JavaScript"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YYBJENZB6M480KCJ0J99","description":"Reference http://jquery.com/","expected":"jQuery","patterns":["/jquery/i"],"specs":[{"from":"Jquery","to":"jQuery"},{"from":"jquery","to":"jQuery"},{"from":"jquery-ui","to":"jquery-ui"}],"tags":["noun","JavaScript"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YYBH4NNWB305Y1TD7B6N","description":"Reference https://www.w3.org/TR/webaudio/","expected":"Web Audio API","patterns":["/WebAudio API/i","/Web AudioAPI/i","/Web Audio API/i"],"specs":[{"from":"All scheduled times in the WebAudio API are relative to the value of currentTime.","to":"All scheduled times in the Web Audio API are relative to the value of currentTime."}],"tags":["noun","JavaScript"],"allows":["{{COMBINATION_WORD}}"]},{"id":"01BQ92YYBJDYYAR7DFH7M57G1P","description":"AngularJS(1.x)はJSを付けます","expected":"AngularJS","patterns":["/Angular\\.js/i","/angularjs/i"],"specs":[{"from":"Angular.js","to":"AngularJS"}],"tags":["JavaScript"]}]
\ No newline at end of file
diff --git a/.cache/kvs-node-localstorage/proofdict-lastUpdated b/.cache/kvs-node-localstorage/proofdict-lastUpdated
index da0fc5b..928f145 100644
--- a/.cache/kvs-node-localstorage/proofdict-lastUpdated
+++ b/.cache/kvs-node-localstorage/proofdict-lastUpdated
@@ -1 +1 @@
-1643713193328
\ No newline at end of file
+1688438957238
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 8922728..d78d905 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -3,5 +3,6 @@
   "editor.formatOnPaste": true,
   "editor.formatOnSave": true,
   "markdown.extension.toc.updateOnSave": false,
-  "ruby.rubocop.onSave": false
+  "ruby.rubocop.onSave": false,
+  "japanese-proofreading.textlint.全角文字と半角文字の間": false
 }
diff --git a/articles/9603a3990fcb18.md b/articles/9603a3990fcb18.md
index e3f7d01..17390b1 100644
--- a/articles/9603a3990fcb18.md
+++ b/articles/9603a3990fcb18.md
@@ -1,5 +1,5 @@
 ---
-title: "LangChainのSummarization ChainでWEBページを要約する時のプラクティス"
+title: "LangChainを使ってWEBサイトを要約する時のプラクティス"
 emoji: "🦜"
 type: "tech" # tech: 技術記事 / idea: アイデア
 topics: ["langchain", "openai", "typescript"]
@@ -15,7 +15,7 @@ LangChainを使ってWebページを要約しようと思ったら色々詰ま

 # 🚀 完成コード

-最初に完成コードから。LangChainを実行してWebページを要約するコード例です。
+最初に完成コードを。URLを引数で渡すとそのページの要約を出力してくれるスクリプトのコード例です。

 ```ts
 import { OpenAI } from "langchain/llms/openai";
@@ -48,12 +48,16 @@ const summarization = async (url: string) => {
   });
   const docs = await loader.loadAndSplit();

-  // 要約の実行
-  const model = new OpenAI({ openAIApiKey: process.env.OPEN_AI_API_KEY, temperature: 0, modelName: "gpt-3.5-turbo" });
+  // モデルの指定 OpenAI gpt-3.5-turbo
+  const model = new OpenAI({ openAIApiKey: process.env.OPENAI_API_KEY, temperature: 0, modelName: "gpt-3.5-turbo" });
+
+  // promptの作成
   const prompt = new PromptTemplate({
-    template: `以下の文章を要約してください。\n\n---\n"{text}"---\n\n要約:`,
+    template: `以下の文章を要約してください。\n\n---\n{text}\n---\n\n要約:`,
     inputVariables: ["text"],
   });
+
+  // 要約の実行
   const chain = loadSummarizationChain(model, {
     combineMapPrompt: prompt,
     combinePrompt: prompt,
@@ -84,26 +88,24 @@ https://note.com/ryo_kawamata/n/n4fc0fa900314
 消防士から未経験でエンジニアに転職し、1年間働いた後に退職した人物が、自身の経歴や転職の理由、エンジニアとしての経験について語っている。JavaやSpring Bootでの実務経験を通じて、良い設計や型の利点、テストの重要性などを学び、静的型付け言語が好きになった。退職理由は家庭の事情で、リモートワークができる環境を求めた。転職先はクラウド請求管理サービスの会社で、RailsとVue.jsを勉強中。今後は成長し、事業に大きな影響を与える成果を出したいと考えている。
 ```

-# 🛠️ ポイント解説
+コードはすべて以下のリポジトリで公開しています。

-## PuppeteerWebBaseLoaderで要約ページのテキスト情報のみを種痘
+https://github.com/kawamataryo/sandbox-summary-by-langhchain
+
+# 🛠️ ポイント
+
+## PuppeteerWebBaseLoaderで要約ページのテキスト情報のみを取得する
 SPAも考慮したWebページのテキスト情報を取得するために、PuppeteerWebBaseLoaderを使っています。ここでのポイントは、`evaluate`で取得したいテキスト情報のみを取得するようにすることです。

 PuppeteerWebBaseLoaderのデフォルトでは、ページ情報の取得に`document.body.innerHTML`を使っています。

 https://github.com/hwchase17/langchainjs/blob/8ddf206998f323ae2785371a6d0fdfabdf5a7ba2/langchain/src/document_loaders/web/puppeteer.ts#L61-L63

-`innerHTML`での取得だと、要約に不要なタグ情報や、インラインスタイル、スクリプト情報も含まれてしまいます。これがトークンの利用数をムダに増やしてしまう原因になります。
+`innerHTML`でのテキスト取得だと、要約に不要なHTMLのタグやCSS、scriptも含まれてしまいます。これがトークンの利用量をムダに増やしてしまう原因になります。

-`evaluate`をオプションで上書き、スクリプトやスタイル、タグ情報など不要な要素を削除した上で`innerText`で本文を取得することで、不要な情報によって要約結果が歪むことを防ぎ、APIのトークン利用数も節約できます。
+`evaluate`をオプションで上書き、必要な要素のみ`innerText`で本文を取得することで、不要な情報によって要約結果が歪むことを防ぎ、APIのトークン利用数も節約できます。

 ```ts
-//...
-  const loader = new PuppeteerWebBaseLoader(url, {
-    launchOptions: {
-      headless: "new",
-      args: ["--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"],
-    },
     async evaluate(page) {
       const result = await page.evaluate(() => {
         // 不要な要素を削除
@@ -119,54 +121,49 @@ https://github.com/hwchase17/langchainjs/blob/8ddf206998f323ae2785371a6d0fdfabdf
       })
       return result;
     },
-  });
-// ...
 ```

+:::message
+もし、要約を実行するWebページが限定されている場合は、それに合わせてevaluateの処理を変更することで、より正確な要約結果を得ることができます。
+:::
+
 ## LoadAndSplitでドキュメントを分割

 完成コードではPuppeteerWebBaseLoaderで取得したテキスト情報をOpenAIのAPIに渡す際に、`loadAndSplit`を実行しています。
-PuppeteerWebBaseLoaderのドキュメントには、`Load`の方で記載されているので間違える人がいるかもしれませんが(自分😇)、`loadAndSplit`でドキュメントを分割しなければ、Summarization Chain を利用する旨味がありません。

-loadでdocsを生成すると、ページ情報が以下のようにひとつのドキュメントとして扱われてしまいます。
+https://github.com/kawamataryo/sandbox-summary-by-langhchain/blob/main/index.ts#L29
+
+PuppeteerWebBaseLoaderのドキュメントには、`Load`の方で記載されているので間違えた人がいるかもしれません(私😇)。しかし`loadAndSplit`でドキュメントを分割しなければ、Summarization Chain を利用する旨味がありません。
+
+`load` でdocsを生成すると、ページ情報が以下のように単一のドキュメントとして扱われてしまいます。

 ```
 [
-  {
-    Document: {
-      "pageContent: '消防士からエンジニアへ、そして退職\n...."
-    },
-    metadata: { ... }
-  }
+  { Document: { "pageContent: '消防士からエンジニアへ、そして退職して\n...." }, metadata: { ... } }
 ]
 ```

-しかし、loadSplitでdocsを生成すると以下のように、よしなにドキュメントを分割してくれます。
+しかし、`loadSplit` でdocsを生成すると以下のように、ドキュメントを分割してくれます。

 ```
 [
-  {
-    Document: {
-      "pageContent: '消防士からエンジニアへ、そして退職\n...."
-    },
-    metadata: { ... }
-  },
-  {
-    Document: {
-      "pageContent: '前職では...
-    },
-    metadata: { ... }
-  }
-  {
-    Document: {
-      "pageContent: '筋肉.ktで筋肉を...
-    },
-    metadata: { ... }
-  },
+  { Document: { "pageContent: '消防士からエンジニアへ、そして退職\n...." }, metadata: { ... } }
+  { Document: { "pageContent: '前職では...'}, metadata: { ... } }
+  { Document: { "pageContent: '筋肉KTでLTして...'}, metadata: { ... } }
   ....
 ]
 ```

-とくにページの文章量が多い場合は、ドキュメントを分割しないと、最大トークン数を超過してしまい、Summarization Chainの実行時にエラーが発生します。
+Summarization Chainは、分割されたdocumentを要約するChainです。とくにページの文章量が多い場合は、ドキュメントを分割しないと、モデルの最大トークン数を超過してしまい実行時にエラーが発生します。

-```ts
+loadAndSplitの引数にsplitterを指定しない場合、documentの分割には[RecursiveCharacterTextSplitter](https://js.langchain.com/docs/modules/indexes/text_splitters/examples/recursive_character)が使用されます。他のsplitterも指定できますが、基本はRecursiveCharacterTextSplitterでよいと思います。
+
+https://js.langchain.com/docs/modules/indexes/text_splitters/
+
+## Promptの上書き
+
+Summarization ChainではデフォルトのPromptを上書きすることが可能です。
+
+## Mapで要約結果を整形
+
+https://docs.langchain.com/docs/components/chains/index_related_chains
diff --git a/articles/98b7cc1c67ad0c.md b/articles/98b7cc1c67ad0c.md
new file mode 100644
index 0000000..f7a336b
--- /dev/null
+++ b/articles/98b7cc1c67ad0c.md
@@ -0,0 +1,107 @@
+---
+title: "LangChain を使って Hacker News の日本語要約 Bluesky ボットを作ってみた"
+emoji: "🪿"
+type: "tech" # tech: 技術記事 / idea: アイデア
+topics: ["LangChain", "Bluesky", "TypeScript"]
+published: true
+---
+
+便利なので使ってみてくださいー!
+
+# 🤖 作ったもの
+
+[Hacker News](https://news.ycombinator.com/) のトップ記事の日本語要約を定期投稿する Bluesky のボットです。
+
+https://bsky.app/profile/hacker-news-jp.bsky.social
+
+![](/images/98b7cc1c67ad0c/2023-07-04-05-36-27.png)
+
+
+以下画像のように、Hacker News の記事のタイトルとリンクを投稿。そして、そのスレッドに記事内容の日本語要約も投稿してくれます。
+
+![](/images/98b7cc1c67ad0c/2023-07-04-05-38-37.:w
+png)
+
+コードはすべてこちらで公開しています。
+
+https://github.com/kawamataryo/bsky-hacker-news-jp
+
+# 💘 モチベーション
+
+Geek な人々が使っている Hacker News から海外の最新情報を得たい!でも英語が絶望的に苦手 & Hacker News のデザインが質素すぎて購読するのがしんどい。という理由から、怠惰に情報を得られる手段として作りました。
+
+今のところストレスなく情報を得られているので、作ってよかったなと思っています。
+
+:::message
+Hacker News に興味を持った理由は、Hacker News からいつも最新の技術記事を紹介してくれる同僚の存在が大きいです。その方の投稿は、LAPRAS Tech News Talk で見られるのでぜひこちらも。.!
+
+- 📺[Lapras Tech News Talk](https://www.youtube.com/playlist?list=PLKbaztxP2P4jpdF0P5YbJNJwFabB-pksK)
+:::
+
+# 🛠️ 技術的なポイント
+
+簡単に構成と、詰まったところを紹介します。
+
+## 構成図
+
+構成は以前記事にした[GitHub Trending Bot](https://zenn.dev/ryo_kawamata/articles/ad4b88908f610b)とほぼ同じです。
+
+Firebase Cloud Functions の定期実行をトリガーに、[Hacker NewsのAPI](https://github.com/HackerNews/API) から記事を取得、LangChain で日本語要約を作成し、Bluesky の API を使って投稿しています。重複投稿を防ぐために、投稿履歴は Firestore に保存しています。
+
+![](/images/98b7cc1c67ad0c/2023-07-04-08-18-21.png)
+
+## LangChainでの要約
+
+OpenAI の GPT-3.5 を LangChain の[summarization](https://js.langchain.com/docs/modules/chains/other_chains/summarization)経由で使い記事の要約文を生成しています。
+
+https://js.langchain.com/docs/modules/chains/other_chains/summarization
+
+[PuppeteerWebBaseLoader](https://js.langchain.com/docs/modules/indexes/document_loaders/examples/web_loaders/web_puppeteer)を使って、記事の内容から Document を作成、それを summarization に渡すけです。LangChain すごい。
+
+https://github.com/kawamataryo/bsky-hacker-news-jp/blob/main/functions/src/clients/openAIClient.ts#L16-L72
+
+Web ページのテキストを取得する PuppeteerWebBaseLoader の使い方で 2 つ詰まりどころがありました。
+
+1 つ目は取得テキストの指定です。
+当初、PuppeteerWebBaseLoader に何も option を与えず使っていたのですが、そうすると、うまく要約できないことが頻発しました。
+
+PuppeteerWebBaseLoader のデフォルトでは、ページ情報の取得に`document.body.innerHTML`を使われるため、要約に関係ないページの HTML タグ含めすべてを要約対象に入っていたことが原因でした。
+
+https://github.com/hwchase17/langchainjs/blob/8ddf206998f323ae2785371a6d0fdfabdf5a7ba2/langchain/src/document_loaders/web/puppeteer.ts#L61-L63
+
+これではトークンも無駄に消費してしまいお財布に痛いので、`document.body.innerText`を使うように evaluate オプションを指定しました。
+
+https://github.com/kawamataryo/bsky-hacker-news-jp/blob/main/functions/src/clients/openAIClient.ts#L46-L67
+
+2 つ目は`LoadAndSplit`メソッドの利用です。
+PuppeteerWebBaseLoader のドキュメントには、`Load`の方で記載されているので間違えた人がいるかもしれません(私😇)。しかし`loadAndSplit`でドキュメントを分割しなければ、Summarization Chain を利用する旨味がありません。
+
+Summarization Chain は、分割された document を再起的に要約する Chain です。とくにページの文章量が多い場合は、ドキュメントを分割しないと、モデルの最大トークン数を超過してしまい実行時エラーが発生します。トークン制限がきついモデルを使う場合は、必ず`LoadAndSplit`を使いましょう。
+
+https://github.com/kawamataryo/bsky-hacker-news-jp/blob/main/functions/src/clients/openAIClient.ts#L70
+
+## 記事要約のスレッド投稿
+
+LangChain で作成した要約文は、Bluesky の API を使って記事のスレッドに投稿しています。
+実装コードはこちらです。
+
+https://github.com/kawamataryo/bsky-hacker-news-jp/blob/main/functions/src/services/bskyService.ts#L173-L192
+
+Bluesky の投稿文字数の条件は 300 文字なので、要約文が 300 文字を超える場合は、要約文を分割して投稿しています。自然に読めるよう`。`や`、`の句点を起点に要約文を分割しています。
+
+![](/images/98b7cc1c67ad0c/2023-07-04-09-33-24.png)
+
+https://github.com/kawamataryo/bsky-hacker-news-jp/blob/main/functions/src/services/bskyService.ts#L133-L171
+
+分割のコードはほぼすべて ChatGPT に書いてもらいました。最高便利。
+
+# おわりに
+
+Twitter の API 制限等々の影響か、最近 Bluesky が盛り上がってきて楽しいです。Hacker News Bot も便利なのでぜひ使ってみてくださいー!
+
+
+https://bsky.app/profile/hacker-news-jp.bsky.social
+
+自分のアカウントはこちら。ボットやツールなど色々作ってます。
+
+https://bsky.app/profile/kawamataryo.bsky.social
diff --git a/articles/image.png b/articles/image.png
new file mode 100644
index 0000000..60d6fd6
Binary files /dev/null and b/articles/image.png differ
diff --git a/images/98b7cc1c67ad0c/2023-07-04-05-36-27.png b/images/98b7cc1c67ad0c/2023-07-04-05-36-27.png
new file mode 100644
index 0000000..8592b8c
Binary files /dev/null and b/images/98b7cc1c67ad0c/2023-07-04-05-36-27.png differ
diff --git a/images/98b7cc1c67ad0c/2023-07-04-05-38-09.png b/images/98b7cc1c67ad0c/2023-07-04-05-38-09.png
new file mode 100644
index 0000000..6d2e612
Binary files /dev/null and b/images/98b7cc1c67ad0c/2023-07-04-05-38-09.png differ
diff --git a/images/98b7cc1c67ad0c/2023-07-04-05-38-37.png b/images/98b7cc1c67ad0c/2023-07-04-05-38-37.png
new file mode 100644
index 0000000..8dd84ff
Binary files /dev/null and b/images/98b7cc1c67ad0c/2023-07-04-05-38-37.png differ
diff --git a/images/98b7cc1c67ad0c/2023-07-04-08-18-21.png b/images/98b7cc1c67ad0c/2023-07-04-08-18-21.png
new file mode 100644
index 0000000..26e12ed
Binary files /dev/null and b/images/98b7cc1c67ad0c/2023-07-04-08-18-21.png differ
diff --git a/images/98b7cc1c67ad0c/2023-07-04-09-33-24.png b/images/98b7cc1c67ad0c/2023-07-04-09-33-24.png
new file mode 100644
index 0000000..60d6fd6
Binary files /dev/null and b/images/98b7cc1c67ad0c/2023-07-04-09-33-24.png differ
  • Loading branch information
kawamataryo committed Jul 4, 2023
1 parent e75809c commit fbad9cf
Show file tree
Hide file tree
Showing 11 changed files with 154 additions and 49 deletions.
2 changes: 1 addition & 1 deletion .cache/kvs-node-localstorage/proofdict

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion .cache/kvs-node-localstorage/proofdict-lastUpdated
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1643713193328
1688438957238
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
"editor.formatOnPaste": true,
"editor.formatOnSave": true,
"markdown.extension.toc.updateOnSave": false,
"ruby.rubocop.onSave": false
"ruby.rubocop.onSave": false,
"japanese-proofreading.textlint.全角文字と半角文字の間": false
}
89 changes: 43 additions & 46 deletions articles/9603a3990fcb18.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: "LangChainのSummarization ChainでWEBページを要約する時のプラクティス"
title: "LangChainを使ってWEBサイトを要約する時のプラクティス"
emoji: "🦜"
type: "tech" # tech: 技術記事 / idea: アイデア
topics: ["langchain", "openai", "typescript"]
Expand All @@ -15,7 +15,7 @@ LangChainを使ってWebページを要約しようと思ったら色々詰ま

# 🚀 完成コード

最初に完成コードから。LangChainを実行してWebページを要約するコード例です
最初に完成コードを。URLを引数で渡すとそのページの要約を出力してくれるスクリプトのコード例です

```ts
import { OpenAI } from "langchain/llms/openai";
Expand Down Expand Up @@ -48,12 +48,16 @@ const summarization = async (url: string) => {
});
const docs = await loader.loadAndSplit();

// 要約の実行
const model = new OpenAI({ openAIApiKey: process.env.OPEN_AI_API_KEY, temperature: 0, modelName: "gpt-3.5-turbo" });
// モデルの指定 OpenAI gpt-3.5-turbo
const model = new OpenAI({ openAIApiKey: process.env.OPENAI_API_KEY, temperature: 0, modelName: "gpt-3.5-turbo" });

// promptの作成
const prompt = new PromptTemplate({
template: `以下の文章を要約してください。\n\n---\n"{text}"---\n\n要約:`,
template: `以下の文章を要約してください。\n\n---\n{text}\n---\n\n要約:`,
inputVariables: ["text"],
});

// 要約の実行
const chain = loadSummarizationChain(model, {
combineMapPrompt: prompt,
combinePrompt: prompt,
Expand Down Expand Up @@ -84,26 +88,24 @@ https://note.com/ryo_kawamata/n/n4fc0fa900314
消防士から未経験でエンジニアに転職し、1年間働いた後に退職した人物が、自身の経歴や転職の理由、エンジニアとしての経験について語っている。JavaやSpring Bootでの実務経験を通じて、良い設計や型の利点、テストの重要性などを学び、静的型付け言語が好きになった。退職理由は家庭の事情で、リモートワークができる環境を求めた。転職先はクラウド請求管理サービスの会社で、RailsとVue.jsを勉強中。今後は成長し、事業に大きな影響を与える成果を出したいと考えている。
```

# 🛠️ ポイント解説
コードはすべて以下のリポジトリで公開しています。

## PuppeteerWebBaseLoaderで要約ページのテキスト情報のみを種痘
https://github.com/kawamataryo/sandbox-summary-by-langhchain

# 🛠️ ポイント

## PuppeteerWebBaseLoaderで要約ページのテキスト情報のみを取得する
SPAも考慮したWebページのテキスト情報を取得するために、PuppeteerWebBaseLoaderを使っています。ここでのポイントは、`evaluate`で取得したいテキスト情報のみを取得するようにすることです。

PuppeteerWebBaseLoaderのデフォルトでは、ページ情報の取得に`document.body.innerHTML`を使っています。

https://github.com/hwchase17/langchainjs/blob/8ddf206998f323ae2785371a6d0fdfabdf5a7ba2/langchain/src/document_loaders/web/puppeteer.ts#L61-L63

`innerHTML`での取得だと、要約に不要なタグ情報や、インラインスタイル、スクリプト情報も含まれてしまいます。これがトークンの利用数をムダに増やしてしまう原因になります
`innerHTML`でのテキスト取得だと、要約に不要なHTMLのタグやCSS、scriptも含まれてしまいます。これがトークンの利用量をムダに増やしてしまう原因になります

`evaluate`をオプションで上書き、スクリプトやスタイル、タグ情報など不要な要素を削除した上で`innerText`で本文を取得することで、不要な情報によって要約結果が歪むことを防ぎ、APIのトークン利用数も節約できます。
`evaluate`をオプションで上書き、必要な要素のみ`innerText`で本文を取得することで、不要な情報によって要約結果が歪むことを防ぎ、APIのトークン利用数も節約できます。

```ts
//...
const loader = new PuppeteerWebBaseLoader(url, {
launchOptions: {
headless: "new",
args: ["--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"],
},
async evaluate(page) {
const result = await page.evaluate(() => {
// 不要な要素を削除
Expand All @@ -119,54 +121,49 @@ https://github.com/hwchase17/langchainjs/blob/8ddf206998f323ae2785371a6d0fdfabdf
})
return result;
},
});
// ...
```

:::message
もし、要約を実行するWebページが限定されている場合は、それに合わせてevaluateの処理を変更することで、より正確な要約結果を得ることができます。
:::

## LoadAndSplitでドキュメントを分割

完成コードではPuppeteerWebBaseLoaderで取得したテキスト情報をOpenAIのAPIに渡す際に、`loadAndSplit`を実行しています。
PuppeteerWebBaseLoaderのドキュメントには、`Load`の方で記載されているので間違える人がいるかもしれませんが(自分😇)、`loadAndSplit`でドキュメントを分割しなければ、Summarization Chain を利用する旨味がありません。

loadでdocsを生成すると、ページ情報が以下のようにひとつのドキュメントとして扱われてしまいます。
https://github.com/kawamataryo/sandbox-summary-by-langhchain/blob/main/index.ts#L29

PuppeteerWebBaseLoaderのドキュメントには、`Load`の方で記載されているので間違えた人がいるかもしれません(私😇)。しかし`loadAndSplit`でドキュメントを分割しなければ、Summarization Chain を利用する旨味がありません。

`load` でdocsを生成すると、ページ情報が以下のように単一のドキュメントとして扱われてしまいます。

```
[
{
Document: {
"pageContent: '消防士からエンジニアへ、そして退職\n...."
},
metadata: { ... }
}
{ Document: { "pageContent: '消防士からエンジニアへ、そして退職して\n...." }, metadata: { ... } }
]
```

しかし、loadSplitでdocsを生成すると以下のように、よしなにドキュメントを分割してくれます
しかし、`loadSplit` でdocsを生成すると以下のように、ドキュメントを分割してくれます

```
[
{
Document: {
"pageContent: '消防士からエンジニアへ、そして退職\n...."
},
metadata: { ... }
},
{
Document: {
"pageContent: '前職では...
},
metadata: { ... }
}
{
Document: {
"pageContent: '筋肉.ktで筋肉を...
},
metadata: { ... }
},
{ Document: { "pageContent: '消防士からエンジニアへ、そして退職\n...." }, metadata: { ... } }
{ Document: { "pageContent: '前職では...'}, metadata: { ... } }
{ Document: { "pageContent: '筋肉KTでLTして...'}, metadata: { ... } }
....
]
```

とくにページの文章量が多い場合は、ドキュメントを分割しないと、最大トークン数を超過してしまい、Summarization Chainの実行時にエラーが発生します
Summarization Chainは、分割されたdocumentを要約するChainです。とくにページの文章量が多い場合は、ドキュメントを分割しないと、モデルの最大トークン数を超過してしまい実行時にエラーが発生します

```ts
loadAndSplitの引数にsplitterを指定しない場合、documentの分割には[RecursiveCharacterTextSplitter](https://js.langchain.com/docs/modules/indexes/text_splitters/examples/recursive_character)が使用されます。他のsplitterも指定できますが、基本はRecursiveCharacterTextSplitterでよいと思います。

https://js.langchain.com/docs/modules/indexes/text_splitters/

## Promptの上書き

Summarization ChainではデフォルトのPromptを上書きすることが可能です。

## Mapで要約結果を整形

https://docs.langchain.com/docs/components/chains/index_related_chains
107 changes: 107 additions & 0 deletions articles/98b7cc1c67ad0c.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
---
title: "LangChain を使って Hacker News の日本語要約 Bluesky ボットを作ってみた"
emoji: "🪿"
type: "tech" # tech: 技術記事 / idea: アイデア
topics: ["LangChain", "Bluesky", "TypeScript"]
published: true
---

便利なので使ってみてくださいー!

# 🤖 作ったもの

[Hacker News](https://news.ycombinator.com/) のトップ記事の日本語要約を定期投稿する Bluesky のボットです。

https://bsky.app/profile/hacker-news-jp.bsky.social

![](/images/98b7cc1c67ad0c/2023-07-04-05-36-27.png)


以下画像のように、Hacker News の記事のタイトルとリンクを投稿。そして、そのスレッドに記事内容の日本語要約も投稿してくれます。

![](/images/98b7cc1c67ad0c/2023-07-04-05-38-37.:w
png)

コードはすべてこちらで公開しています。

https://github.com/kawamataryo/bsky-hacker-news-jp

# 💘 モチベーション

Geek な人々が使っている Hacker News から海外の最新情報を得たい!でも英語が絶望的に苦手 & Hacker News のデザインが質素すぎて購読するのがしんどい。という理由から、怠惰に情報を得られる手段として作りました。

今のところストレスなく情報を得られているので、作ってよかったなと思っています。

:::message
Hacker News に興味を持った理由は、Hacker News からいつも最新の技術記事を紹介してくれる同僚の存在が大きいです。その方の投稿は、LAPRAS Tech News Talk で見られるのでぜひこちらも。.!

- 📺[Lapras Tech News Talk](https://www.youtube.com/playlist?list=PLKbaztxP2P4jpdF0P5YbJNJwFabB-pksK)
:::

# 🛠️ 技術的なポイント

簡単に構成と、詰まったところを紹介します。

## 構成図

構成は以前記事にした[GitHub Trending Bot](https://zenn.dev/ryo_kawamata/articles/ad4b88908f610b)とほぼ同じです。

Firebase Cloud Functions の定期実行をトリガーに、[Hacker NewsのAPI](https://github.com/HackerNews/API) から記事を取得、LangChain で日本語要約を作成し、Bluesky の API を使って投稿しています。重複投稿を防ぐために、投稿履歴は Firestore に保存しています。

![](/images/98b7cc1c67ad0c/2023-07-04-08-18-21.png)

## LangChainでの要約

OpenAI の GPT-3.5 を LangChain の[summarization](https://js.langchain.com/docs/modules/chains/other_chains/summarization)経由で使い記事の要約文を生成しています。

https://js.langchain.com/docs/modules/chains/other_chains/summarization

[PuppeteerWebBaseLoader](https://js.langchain.com/docs/modules/indexes/document_loaders/examples/web_loaders/web_puppeteer)を使って、記事の内容から Document を作成、それを summarization に渡すけです。LangChain すごい。

https://github.com/kawamataryo/bsky-hacker-news-jp/blob/main/functions/src/clients/openAIClient.ts#L16-L72

Web ページのテキストを取得する PuppeteerWebBaseLoader の使い方で 2 つ詰まりどころがありました。

1 つ目は取得テキストの指定です。
当初、PuppeteerWebBaseLoader に何も option を与えず使っていたのですが、そうすると、うまく要約できないことが頻発しました。

PuppeteerWebBaseLoader のデフォルトでは、ページ情報の取得に`document.body.innerHTML`を使われるため、要約に関係ないページの HTML タグ含めすべてを要約対象に入っていたことが原因でした。

https://github.com/hwchase17/langchainjs/blob/8ddf206998f323ae2785371a6d0fdfabdf5a7ba2/langchain/src/document_loaders/web/puppeteer.ts#L61-L63

これではトークンも無駄に消費してしまいお財布に痛いので、`document.body.innerText`を使うように evaluate オプションを指定しました。

https://github.com/kawamataryo/bsky-hacker-news-jp/blob/main/functions/src/clients/openAIClient.ts#L46-L67

2 つ目は`LoadAndSplit`メソッドの利用です。
PuppeteerWebBaseLoader のドキュメントには、`Load`の方で記載されているので間違えた人がいるかもしれません(私😇)。しかし`loadAndSplit`でドキュメントを分割しなければ、Summarization Chain を利用する旨味がありません。

Summarization Chain は、分割された document を再起的に要約する Chain です。とくにページの文章量が多い場合は、ドキュメントを分割しないと、モデルの最大トークン数を超過してしまい実行時エラーが発生します。トークン制限がきついモデルを使う場合は、必ず`LoadAndSplit`を使いましょう。

https://github.com/kawamataryo/bsky-hacker-news-jp/blob/main/functions/src/clients/openAIClient.ts#L70

## 記事要約のスレッド投稿

LangChain で作成した要約文は、Bluesky の API を使って記事のスレッドに投稿しています。
実装コードはこちらです。

https://github.com/kawamataryo/bsky-hacker-news-jp/blob/main/functions/src/services/bskyService.ts#L173-L192

Bluesky の投稿文字数の条件は 300 文字なので、要約文が 300 文字を超える場合は、要約文を分割して投稿しています。自然に読めるよう````の句点を起点に要約文を分割しています。

![](/images/98b7cc1c67ad0c/2023-07-04-09-33-24.png)

https://github.com/kawamataryo/bsky-hacker-news-jp/blob/main/functions/src/services/bskyService.ts#L133-L171

分割のコードはほぼすべて ChatGPT に書いてもらいました。最高便利。

# おわりに

Twitter の API 制限等々の影響か、最近 Bluesky が盛り上がってきて楽しいです。Hacker News Bot も便利なのでぜひ使ってみてくださいー!


https://bsky.app/profile/hacker-news-jp.bsky.social

自分のアカウントはこちら。ボットやツールなど色々作ってます。

https://bsky.app/profile/kawamataryo.bsky.social
Binary file added articles/image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/98b7cc1c67ad0c/2023-07-04-05-36-27.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/98b7cc1c67ad0c/2023-07-04-05-38-09.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/98b7cc1c67ad0c/2023-07-04-05-38-37.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/98b7cc1c67ad0c/2023-07-04-08-18-21.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/98b7cc1c67ad0c/2023-07-04-09-33-24.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit fbad9cf

Please sign in to comment.