From 5c06181e6418c7630876873aa20e9bf58504c053 Mon Sep 17 00:00:00 2001 From: Justin Yoo Date: Sun, 4 Oct 2026 10:22:12 +0900 Subject: [PATCH 01/22] docs: add Korean (ko-kr) localizations for workshop content Localizes all workshop lessons, READMEs, and LIVE_DEMO guides into Korean under localizations/ko-kr/, mirroring the source layout. Adds .localization-state.json to track per-file source and output SHA-256 baselines for incremental updates. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- localizations/ko-kr/.localization-state.json | 240 +++ localizations/ko-kr/README.md | 200 ++ .../dotnet/museum-exhibit-studio/README.md | 76 + .../go/museum-exhibit-studio/README.md | 64 + .../java/museum-exhibit-studio/README.md | 72 + .../nodejs/museum-exhibit-studio/README.md | 48 + .../python/museum-exhibit-studio/README.md | 79 + .../rust/museum-exhibit-studio/README.md | 50 + .../ko-kr/start-accessibility/README.md | 31 + localizations/ko-kr/start-intro/README.md | 55 + .../ko-kr/start-intro/dotnet/LIVE_DEMO.md | 176 ++ .../ko-kr/start-intro/dotnet/README.md | 36 + .../ko-kr/start-intro/go/LIVE_DEMO.md | 185 ++ localizations/ko-kr/start-intro/go/README.md | 25 + .../ko-kr/start-intro/java/LIVE_DEMO.md | 196 ++ .../ko-kr/start-intro/java/README.md | 29 + .../ko-kr/start-intro/nodejs/LIVE_DEMO.md | 185 ++ .../ko-kr/start-intro/nodejs/README.md | 28 + .../ko-kr/start-intro/python/LIVE_DEMO.md | 201 ++ .../ko-kr/start-intro/python/README.md | 33 + .../ko-kr/start-intro/rust/LIVE_DEMO.md | 217 +++ .../ko-kr/start-intro/rust/README.md | 28 + localizations/ko-kr/start-museum/README.md | 42 + localizations/ko-kr/workshop/00-preflight.md | 632 ++++++ .../ko-kr/workshop/01-first-session.md | 750 +++++++ localizations/ko-kr/workshop/02-streaming.md | 968 +++++++++ localizations/ko-kr/workshop/03-local-tool.md | 981 ++++++++++ localizations/ko-kr/workshop/04-mcp-safety.md | 1723 +++++++++++++++++ .../ko-kr/workshop/05-combine-tools.md | 1574 +++++++++++++++ .../ko-kr/workshop/06-structured-report.md | 1172 +++++++++++ .../ko-kr/workshop/07-run-explain.md | 428 ++++ .../ko-kr/workshop/08-model-selection.md | 681 +++++++ .../workshop/09-interactive-html-report.md | 585 ++++++ localizations/ko-kr/workshop/10-complete.md | 64 + .../ko-kr/workshop/intro-00-preflight.md | 211 ++ .../ko-kr/workshop/intro-01-sdk-basics.md | 104 + .../ko-kr/workshop/intro-02-hello-world.md | 74 + .../ko-kr/workshop/intro-03-podcast-agent.md | 82 + .../ko-kr/workshop/intro-04-wrap-up.md | 86 + .../ko-kr/workshop/museum-00-preflight.md | 258 +++ .../museum-01-first-curator-session.md | 377 ++++ .../workshop/museum-02-stream-the-curator.md | 374 ++++ .../ko-kr/workshop/museum-03-curator-voice.md | 447 +++++ .../workshop/museum-04-approved-facts.md | 1108 +++++++++++ .../workshop/museum-06-prove-the-structure.md | 291 +++ .../workshop/museum-07-wikipedia-research.md | 1234 ++++++++++++ .../museum-08-interactive-exhibit-page.md | 491 +++++ .../ko-kr/workshop/museum-09-complete.md | 63 + 48 files changed, 17054 insertions(+) create mode 100644 localizations/ko-kr/.localization-state.json create mode 100644 localizations/ko-kr/README.md create mode 100644 localizations/ko-kr/finished/dotnet/museum-exhibit-studio/README.md create mode 100644 localizations/ko-kr/finished/go/museum-exhibit-studio/README.md create mode 100644 localizations/ko-kr/finished/java/museum-exhibit-studio/README.md create mode 100644 localizations/ko-kr/finished/nodejs/museum-exhibit-studio/README.md create mode 100644 localizations/ko-kr/finished/python/museum-exhibit-studio/README.md create mode 100644 localizations/ko-kr/finished/rust/museum-exhibit-studio/README.md create mode 100644 localizations/ko-kr/start-accessibility/README.md create mode 100644 localizations/ko-kr/start-intro/README.md create mode 100644 localizations/ko-kr/start-intro/dotnet/LIVE_DEMO.md create mode 100644 localizations/ko-kr/start-intro/dotnet/README.md create mode 100644 localizations/ko-kr/start-intro/go/LIVE_DEMO.md create mode 100644 localizations/ko-kr/start-intro/go/README.md create mode 100644 localizations/ko-kr/start-intro/java/LIVE_DEMO.md create mode 100644 localizations/ko-kr/start-intro/java/README.md create mode 100644 localizations/ko-kr/start-intro/nodejs/LIVE_DEMO.md create mode 100644 localizations/ko-kr/start-intro/nodejs/README.md create mode 100644 localizations/ko-kr/start-intro/python/LIVE_DEMO.md create mode 100644 localizations/ko-kr/start-intro/python/README.md create mode 100644 localizations/ko-kr/start-intro/rust/LIVE_DEMO.md create mode 100644 localizations/ko-kr/start-intro/rust/README.md create mode 100644 localizations/ko-kr/start-museum/README.md create mode 100644 localizations/ko-kr/workshop/00-preflight.md create mode 100644 localizations/ko-kr/workshop/01-first-session.md create mode 100644 localizations/ko-kr/workshop/02-streaming.md create mode 100644 localizations/ko-kr/workshop/03-local-tool.md create mode 100644 localizations/ko-kr/workshop/04-mcp-safety.md create mode 100644 localizations/ko-kr/workshop/05-combine-tools.md create mode 100644 localizations/ko-kr/workshop/06-structured-report.md create mode 100644 localizations/ko-kr/workshop/07-run-explain.md create mode 100644 localizations/ko-kr/workshop/08-model-selection.md create mode 100644 localizations/ko-kr/workshop/09-interactive-html-report.md create mode 100644 localizations/ko-kr/workshop/10-complete.md create mode 100644 localizations/ko-kr/workshop/intro-00-preflight.md create mode 100644 localizations/ko-kr/workshop/intro-01-sdk-basics.md create mode 100644 localizations/ko-kr/workshop/intro-02-hello-world.md create mode 100644 localizations/ko-kr/workshop/intro-03-podcast-agent.md create mode 100644 localizations/ko-kr/workshop/intro-04-wrap-up.md create mode 100644 localizations/ko-kr/workshop/museum-00-preflight.md create mode 100644 localizations/ko-kr/workshop/museum-01-first-curator-session.md create mode 100644 localizations/ko-kr/workshop/museum-02-stream-the-curator.md create mode 100644 localizations/ko-kr/workshop/museum-03-curator-voice.md create mode 100644 localizations/ko-kr/workshop/museum-04-approved-facts.md create mode 100644 localizations/ko-kr/workshop/museum-06-prove-the-structure.md create mode 100644 localizations/ko-kr/workshop/museum-07-wikipedia-research.md create mode 100644 localizations/ko-kr/workshop/museum-08-interactive-exhibit-page.md create mode 100644 localizations/ko-kr/workshop/museum-09-complete.md diff --git a/localizations/ko-kr/.localization-state.json b/localizations/ko-kr/.localization-state.json new file mode 100644 index 0000000..fad066f --- /dev/null +++ b/localizations/ko-kr/.localization-state.json @@ -0,0 +1,240 @@ +{ + "version": 1, + "files": { + "finished/dotnet/museum-exhibit-studio/README.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "92a3a865a7d716a258a67ac86f2ce591b45b39056ee5715ffb220ccd7b563dec", + "localized_sha256": "41b9d524a3bd8ad88b39c6b22c5196b549987c7e606e1563d1d58dace99a14c1" + }, + "finished/go/museum-exhibit-studio/README.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "3f752313417f588c0098522d0c24cca03c4e62346998a208495c0491b2b5f3d0", + "localized_sha256": "31bffe4a9066792a088b9b485f7c87699ca0ca460e1c8884948c1b734e30870d" + }, + "finished/java/museum-exhibit-studio/README.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "7a7ad8adda838e5ad4d669ec85c7e9b9fac028640c2dffbfcaaac9da7df33cdc", + "localized_sha256": "b8665811f2d9f2491f05f17e4f0a490622cf3593d32d3a36223f9731a19db3b7" + }, + "finished/nodejs/museum-exhibit-studio/README.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "c30d5a7c133ac08a59b45d6379ca218bf32d499bb6ec0fb11b126e76da377cac", + "localized_sha256": "d1072dc6079ee210d2a826de8c3ff5802ec7063a34dafc21aed953860758b47f" + }, + "finished/python/museum-exhibit-studio/README.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "f61f169c36cccbc080e2ae09679f81c7d1193049c6b9790935eb77bd352c441b", + "localized_sha256": "59d267117fa300ba9fb075838f589a9c03fae7c5d45c5d8d5d13aafb834756ab" + }, + "finished/rust/museum-exhibit-studio/README.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "aece4fd6ecafed833feed3abdad151621b7b2725f74b021ef3d8edb31781573f", + "localized_sha256": "06bea0d4e85e0780cc8172359532bcc487d36d735f5692c9b7e54bf39b2e5eeb" + }, + "README.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "3ee97349b427602cea6e1730a18abeefda903f101813dd8bee956c00d1ea1c32", + "localized_sha256": "db9aac3f4329014ac778c012c3ff9d320489342faff803b4ad3d439c6d4406a8" + }, + "start-accessibility/README.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "8f1826ac9b470328c4b29106e7aa353df1ce165fff1943ac29206123b2d7e9cc", + "localized_sha256": "3475101347dcac86b107df48243c20d92728015a85b2346c82c28ac28797ff43" + }, + "start-intro/dotnet/LIVE_DEMO.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "8a064be9bdf22dd594fb8033ed2b4670a1ffc2167cb292bf38741e8080ee3740", + "localized_sha256": "e724d011b44dab53c947a2fed837f701de658d243d77ee5ce49bb9622201ce47" + }, + "start-intro/dotnet/README.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "768979d6d8d706ffcf32f90bbce8a538f594d0a8c14ca8630c40af1e58586990", + "localized_sha256": "2f958fb85da47dd4f801ee83ec2d7fd11d33d62a1239647ebb6653d9bdc8fd3e" + }, + "start-intro/go/LIVE_DEMO.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "53251d6e74279c31f172d4d955e7c0347cbed96864090250d9f2e168a7cc047d", + "localized_sha256": "4cabb495d1c1687722f0c4c99dc45af85e39303657af6b46c19d76a7739ea05e" + }, + "start-intro/go/README.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "e6677894a9d9bbdd727b4db70826b0d59b10312cb6b0d27234de67d870a6ed9d", + "localized_sha256": "b205e011365a654cab583b859b3d688128de8401c232e517a80686dce98cc4dd" + }, + "start-intro/java/LIVE_DEMO.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "fefaa841cfa2d7beb56c74d455f5aaa2d0a839300a1a3a92f96b22a7ce21e3d8", + "localized_sha256": "1dc4c952df5375af3f262736337c8e8410b67cf631786301120998ddca5ed660" + }, + "start-intro/java/README.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "c1cd500c9ed179d035d5455fa4947f41b274ff285e08fbd79afceb048ea12292", + "localized_sha256": "a15fb604b98df42ed184b8b6ea6ef4908b3e40d15194917547e868ee264442dc" + }, + "start-intro/nodejs/LIVE_DEMO.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "42d649ada4fb8055e3424186b4f02febbada53d23a093e0e6eba573aed06a91e", + "localized_sha256": "90e5bdaa182f52bb0a702dca76a34cd500227b76346a79f1958236787b484be2" + }, + "start-intro/nodejs/README.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "0dcccffced18b4c28c37906888e57d4f58736b030ddea9a2cb9de6e20ca3a87c", + "localized_sha256": "8c16096c3f025e47e0dce63dfb24d4ec2ede1b17d47b07bb61682d354fdc5407" + }, + "start-intro/python/LIVE_DEMO.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "aabc990950d6ec5a0e62415e9bf6d70d8606115b132528e9b08fe8bd89199a9b", + "localized_sha256": "474679d0ee56836940289814504ea109861f11700b9c1cad36d883e5a7318ca2" + }, + "start-intro/python/README.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "e9651232435f946aa00e348de7b81c92e1d2903ce627f50edb5d8c9b3120fe9f", + "localized_sha256": "bdc010e21ea7b3d929a60c5fd395d210421edba4eb619fd11849998f22566848" + }, + "start-intro/README.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "a16e948ccf67ac41694adb1839755a3ebf54471319e063d59b8ab4e2bae14830", + "localized_sha256": "2934981bc0b5b2eee117b4580a34ee0339c4175ab441eb3c2aee392ae09dc115" + }, + "start-intro/rust/LIVE_DEMO.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "e18d6bf592d05364a30d1c6c14f1fc61ae3e75a146d1c7071864faf16329b74b", + "localized_sha256": "c2154a0cad2cf7173755e0e3ee4862033bdca6d820d06a1e69638773d4cdf448" + }, + "start-intro/rust/README.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "39d4b8c87ff2f1dbfd21b81f69284d84733cfc1675fba4361f7b2ec8626d827f", + "localized_sha256": "9d07cc6244e6f1dd79932e69fb99fdd0d31ea93d8ea4e0c4f6834e274bbeeef9" + }, + "start-museum/README.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "32ac1ea3ecdf75961736c8932630244883bfbfb81f16926c7bc1bdab12b3a7d6", + "localized_sha256": "5d222190bf7b4838004a17d0e9c45c21dc4647b27b2e2aa637185239417b726b" + }, + "workshop/00-preflight.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "60579b55489803004dea298904588630873e371c6df09bcc1eae4371a7837da2", + "localized_sha256": "20f5f3bf772e47b7fdeafb2c947255ff4347a74a84dc7825f0d2af60120e2140" + }, + "workshop/01-first-session.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "5f025391fd92d9ec6ed779285007ce279b918265fcca265bc83f8b8db450d4f4", + "localized_sha256": "f92f7e731538d0c8ba04550a70d2009ec2574b166e66fef80bfd8bc8f1d0a1da" + }, + "workshop/02-streaming.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "3661a58657f49aef0edf94c59403cbcc4946818433faecb125e2b119ec1a0b4c", + "localized_sha256": "fac9f5e7aca9c35e0e01ec7a91a396970bb8bdf148b25859cb235a2e0f0a4c55" + }, + "workshop/03-local-tool.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "10fe6a2c80075e298b1fd92be61bcefbb874884f5d86954f75e5a6c9f6c9ae17", + "localized_sha256": "995141b4e53a8188d38780cebc629420b5e18b16d8021d859d0d77b50be23de4" + }, + "workshop/04-mcp-safety.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "ca45f2b9b3a5b88911a9f6bd0560b2ec960670ee30eae0528edae107dc4aa596", + "localized_sha256": "bfba8aeda9508871ca651c3cceb09467f5332fd4d0ce8bb719734b34c4786627" + }, + "workshop/05-combine-tools.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "4fb915016a414f7b7a7cf06d5a89c61504efe18e9a3a26a84c0cab85f07b85cb", + "localized_sha256": "c6be8186ade7d3cc5e12d4e299831c1abad7fbb760973ecf66c4d13124110511" + }, + "workshop/06-structured-report.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "bc96063f7a2f1e6a72a61b2a99b3e86abf5ed505826f84007f55692f3c0eafb4", + "localized_sha256": "e42d9d002556ccfe5b9548c057a21fc01ff51547f331dcf97eec474a200ebf58" + }, + "workshop/07-run-explain.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "bf4cb1d6026cd3a085a89a9c8f854cf5568e92884e1f705faf68f909f0cf0435", + "localized_sha256": "292353a8e3e422ed69f981b0ca846c1811cb1a6e624e7243f592f6a8b72ee73c" + }, + "workshop/08-model-selection.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "d285069e0f7f3829c2e6f78102dff6402026fbcc72a846c3e5fbb4f5fd96d979", + "localized_sha256": "f564d94aa7ff2da05a3fc97e89cdd3073cd7cf6dd41d426f5114be3db9ae364c" + }, + "workshop/09-interactive-html-report.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "eba9ce17fb51f714a431bfd411d5e1e05b6ec9f1275272ef5ded9c35288d2a03", + "localized_sha256": "0f48a6bfa20df1177e06348d280a1df5128fbee2b7eb6c85e91867220c656cdd" + }, + "workshop/10-complete.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "561eb69fe554a37ff0cfe8283d0b3c9966e546d8fc7f844058b37614d0be47b5", + "localized_sha256": "3d7c13b3cf996dead6cbea7eae9c9d233762c9a26115d2d0f904546d9c6dd1ed" + }, + "workshop/intro-00-preflight.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "a80d4bf2167083672daff75dd160212801d2b36868408f7ca630759abbfbe150", + "localized_sha256": "3e64856aafc2f2e12af38f5ed31ce307c2e127a5d1edc687b4fd07da27eddc85" + }, + "workshop/intro-01-sdk-basics.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "e08b14fc073e65cd061d57808e8ce6b6eb37c2dd810bbb761d43468325ba23a3", + "localized_sha256": "788e1da3983e945d104940b14ea2616e29ce27bd1400bbccafb9afa8c0203017" + }, + "workshop/intro-02-hello-world.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "f3d854bbb49486e0608915e3503fb75ac4b8574f7b067debd870202976d85d61", + "localized_sha256": "5426c736bbf71eedb8ab40f59b91056749404c8c0bdb56c80fe0d9adc12b5d5e" + }, + "workshop/intro-03-podcast-agent.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "8fe755c5d4a50851cf1d647057c785c3b4208a9cac7fc6d24742ac1e1673f10f", + "localized_sha256": "e8667d397c35d8ffd04e12c4456efc486db952342b24b54a79e135d5dc6300ed" + }, + "workshop/intro-04-wrap-up.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "61c72ec6152b918c4d459193230537bc69706ab2228c8659109a45c59a289a4a", + "localized_sha256": "ef91b8635b53d07f712f13d10433e87cc0ba62a3c5a47dd5c56d980be53e3ffa" + }, + "workshop/museum-00-preflight.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "ed800141333156739757e46c9251974ea06ac1bc1a8143a7d896b519bbc346b3", + "localized_sha256": "7f0d149380670f22b189a1c61d83acf6e976b15e9f9ae257e69064c57eac1559" + }, + "workshop/museum-01-first-curator-session.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "afe8570dbe5a09e1ca837deac3b9ea12ab133555f7472c5a18fda5bfe66da113", + "localized_sha256": "cc0d94ddb7fac54b2d163ae46bc10e6fabb475f63b239ace47d9cf1afad883c6" + }, + "workshop/museum-02-stream-the-curator.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "51323c50aa7d907a2e14ee00c8032785c441262b142e4b6e9bdd560c6007c21a", + "localized_sha256": "834d403d94b75d1936190f8725deb46b06b8d68c65ecdb7f669d2ee893516c0f" + }, + "workshop/museum-03-curator-voice.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "4953b2c169bbdd7c82857306a29d9bb0b1c45d34a2fe91a35331588fdea282b9", + "localized_sha256": "320ba34aacdb05d332ca0219de7ecee8709cb58aa7bf4f03af40bb081135fbaa" + }, + "workshop/museum-04-approved-facts.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "28a9b1895f2eaef30bc0d03f2b6b2e52b8ea942ad87c6fbed80add4ab9023072", + "localized_sha256": "a568f8cda89c3332ecfd6fc244305c966596de8f79ac85a2fccb27857a676661" + }, + "workshop/museum-06-prove-the-structure.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "2d70c2588b5c6963a4fd4ccfc8789a93de6ea94b96b229a6d852433515023418", + "localized_sha256": "8d147f74dfc6172088f3dfcab90a128effc4e1fc7d12b8dd533dffb8e82efd0f" + }, + "workshop/museum-07-wikipedia-research.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "9152f8de12778bf75ec423f5c532fa5e3798d7844c8d6a908c4c1bf9a1d395f8", + "localized_sha256": "497f817183872d09a7592df2b369a9e6db60dfd47f273816a251cf715f510531" + }, + "workshop/museum-08-interactive-exhibit-page.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "67af68a9c40d19348c41b5b15d243a63bdde31faed0776a8e59630d02eb75b1e", + "localized_sha256": "f3f2a28d4cccb4f3418e55186e70907b7b6c5e2140a25cfacf47e38d9ec6f812" + }, + "workshop/museum-09-complete.md": { + "source_commit": "19effbb2f458e5c040c7676b897e957e261ce058", + "source_sha256": "0fc2662a019ee15b01b25b54845a1b0daa5a67c9751a6e4c57e6beb14cc63947", + "localized_sha256": "aad869fba9d0195f8e7b90ba5f8ed590be38271cd9657974fd49c36ec5906640" + } + } +} diff --git a/localizations/ko-kr/README.md b/localizations/ko-kr/README.md new file mode 100644 index 0000000..50ff77c --- /dev/null +++ b/localizations/ko-kr/README.md @@ -0,0 +1,200 @@ +# GitHub Copilot SDK 워크숍 + +지금 시작합니다: http://github.github.com/copilot-sdk-workshop/ + +.NET, Node.js/TypeScript, Python, Go, Rust 또는 Java로 진행하는 세 가지 실습형 GitHub Copilot SDK 워크숍 중 하나를 선택합니다. + +- **SDK 101(30분):** 스트리밍 hello world로 시작한 다음, + [포함된 입문용 스타터](../../start-intro/README.md)의 미리 빌드된 RSS 도구를 사용하여 작은 팟캐스트 + 에이전트를 만듭니다. +- **접근성 검토 도구:** 웹 페이지를 검사하고, 애플리케이션에서 관리하는 WCAG 지침을 참조하여 + 근거 기반 보고서를 생성하는 SDLC 개발자 도구를 만듭니다. +- **박물관 전시 스튜디오:** 교육 담당자가 승인한 사실을 관람객용 전시 문구로 변환하는 비 SDLC + 큐레이터를 만듭니다. 결정론적 기능 경계 안에서 로컬 조회를 통해 출처가 표시된 Wikipedia + 조사 내용을 선택적으로 보강할 수 있습니다. + +SDK를 처음 사용한다면 SDK 101부터 시작합니다. 입문 및 심화 워크숍 전반에서 다음 내용을 학습합니다. + +1. Copilot 클라이언트와 대화 세션을 만듭니다. +2. 지속적으로 적용할 에이전트 정책과 작업별 데이터를 분리합니다. +3. 도구 허용 목록의 범위를 엄격하게 제한하여 로컬 도구와 MCP 도구 중에서 선택합니다. +4. 애플리케이션 코드에서 기능, 입력, 제한 시간, 유효성 검사 및 수명 주기 경계를 적용합니다. +5. 모델이 추론할 수 있는 내용과 애플리케이션이 입증해야 하는 내용을 설명합니다. + +SDK 101의 안내식 수업 시간은 정확히 30분입니다. +접근성 검토 도구에는 약 115분, 박물관 전시 스튜디오에는 약 90분이 필요합니다. +컴퓨터 설정, 인증 및 종속성 다운로드는 각 워크숍의 시간 제한이 없는 사전 준비 단계에서 +별도로 진행합니다. +두 심화 워크숍에는 대화형 HTML 수업이 포함되며, 축하와 리소스 안내로 마무리합니다. + +## 워크숍 시작 + +리포지토리의 **Deploy to GitHub Pages** 워크플로에서 생성된 GitHub Pages URL을 엽니다. +워크숍 결과물과 언어를 선택한 다음 선택한 워크숍을 시작합니다. 사이트는 런타임에 Pages +기본 URL을 확인하므로 조직 또는 사용자 Pages 호스트 이름을 하드 코딩하지 않습니다. + +복제한 리포지토리에서 사이트를 미리 보려면 다음 명령을 실행합니다. + +```bash +git clone https://github.com/github/copilot-sdk-workshop.git +cd copilot-sdk-workshop +python3 -m http.server 8000 +``` + +를 엽니다. 브라우저가 수업 뷰어에서 사용하는 Markdown 요청을 +차단하므로 `step.html`을 `file://` URL로 열지 않습니다. + +## 필수 조건 + +6개 언어의 런타임을 모두 설치하지 말고 선택한 언어의 런타임만 설치합니다. 각 트랙의 사전 준비에서 +해당 요구 사항을 안내합니다. Node.js SDK 101에는 22.12 이상이 필요하며, Java +스타터에는 Maven 3.9 이상도 필요합니다. + +- [.NET 10 SDK](https://learn.microsoft.com/dotnet/core/install/) +- [Node.js 22 이상](https://nodejs.org/) +- [Python 3.11 이상](https://www.python.org/downloads/) +- [Go 1.24 이상](https://go.dev/dl/) +- [Rust 1.94 이상](https://rustup.rs/) +- [Java 17 이상](https://adoptium.net/) +- [GitHub Copilot CLI](https://docs.github.com/en/copilot/how-tos/set-up/install-copilot-cli) +- GitHub Copilot 구독 또는 평가판 +- 브라우저 기반 실습용 Microsoft Edge(워크숍 기본값) 또는 Google Chrome + +사전 준비에서는 설치 확인, 인증, 운영 체제별 명령, 예상 출력 및 문제 해결 방법을 안내합니다. + +## 리포지토리 구조 + +```text +copilot-sdk-workshop/ +|-- docs/ GitHub Pages site and controlled target page +|-- workshop/ SDK 101, two deeper tracks, and completion resources +|-- start-intro/ SDK 101 starters and podcast helpers in all six languages +|-- start-accessibility/ Accessibility Reviewer starters in all six languages +|-- start-museum/ Museum Exhibit Studio starters in all six languages +|-- finished/dotnet/ +| |-- hello-copilot-sdk/ Completed local-tool example in every language +| |-- accessibility-report/ Completed .NET local + MCP reporter +| `-- museum-exhibit-studio/ Museum curator with application-owned fact and research lookups +|-- finished/nodejs/ Completed TypeScript projects +|-- finished/python/ Completed Python projects +|-- finished/go/ Completed Go projects +|-- finished/rust/ Completed Rust projects +|-- finished/java/ Completed Maven Java projects +|-- src/BlazorApp/ Source counterpart of the deployed target +|-- scripts/ Deterministic content and build validation +`-- .github/workflows/ Validation and Pages deployment +``` + +## 변경 내용 검증 + +```bash +bash scripts/validate-workshop.sh +``` + +이 명령은 수업 구조, 내부 링크, 사이트 동작 훅, 프로젝트 지원 범위 및 입문 트랙의 정확한 +30분 수업 시간 배정을 확인합니다. +그런 다음 브라우저와 독립적인 언어 선택, 사이트 흐름 및 완료 테스트를 실행하고, Copilot 인증, +브라우저 실행 또는 프롬프트 전송 없이 모든 입문용, 접근성 및 박물관 스타터와 모든 완성 프로젝트, +Blazor 대상을 복원, 빌드 또는 구문 검사합니다. 박물관 프로젝트에는 테스트, 모의 객체 또는 +픽스처가 포함되지 않으므로 해당 대상은 복원과 빌드만 수행합니다. + +언어 ID를 전달하면 하나의 스모크 빌드 대상을 실행할 수 있습니다. + +```bash +bash scripts/validate-workshop.sh nodejs +``` + +풀 리퀘스트는 콘텐츠 검증과 6개 언어의 스모크 빌드를 별도의 GitHub Actions 작업으로 +실행하므로, 실패 시 영향을 받은 SDK 트랙을 확인할 수 있습니다. + +## SDK 101 워크숍 + +[`workshop/intro-00-preflight.md`](workshop/intro-00-preflight.md)에서 시작합니다. 시간 제한이 +있는 세션을 시작하기 **전에** 선택한 런타임을 설치하고 Copilot을 인증한 후 종속성을 다운로드합니다. +안내식 수업 네 개는 SDK 기본 사항(5분), 스트리밍 hello world(10분), +팟캐스트 에이전트(12분), 요약(3분)으로 구성됩니다. + +학습자는 이 리포지토리를 한 번 복제하고 +[`start-intro/`](../../start-intro/README.md)의 진입점을 편집합니다. 스타터에는 +모든 소스 파일, 종속성 매니페스트, 잠금 파일뿐 아니라 RSS 조회, 모델 및 에피소드 선택, +대화형 도구 승인을 위한 미리 빌드된 도우미가 포함되어 있습니다. 두 번째 리포지토리를 복제하거나 +더 긴 워크숍을 진행할 필요가 없습니다. + +스타터의 진입점 옆에서 `LIVE_DEMO.md`를 엽니다. 실습형 워크숍은 소스 데모의 +**hello world 편집 네 단계**, 즉 클라이언트 시작, 인증 확인, 세션 생성 및 메시지 전송을 +따릅니다. 같은 애플리케이션의 **Act Two**에서 계속하여 모델과 에피소드를 선택하고, +기능을 허용한 후 프롬프트를 바꿉니다. 웹사이트는 이러한 로컬 가이드 섹션을 직접 렌더링하므로 +편집기 가이드와 온라인 워크숍에서 동일한 코드를 설명합니다. + +이 트랙에서는 클라이언트/세션 수명 주기, 스트리밍, 로컬 도구 등록, 명확한 목적의 +시스템 메시지 및 권한을 다룹니다. MCP, 자동화된 출력 유효성 검사 및 HTML 캡스톤은 +심화 워크숍에서 다룹니다. 생성된 팟캐스트 문구를 게시하기 전에 원본과 대조하여 검토합니다. + +## 박물관 전시 스튜디오 워크숍 + +박물관 전시 스튜디오 스타터는 `start-museum/`에 있고 완성된 참조 구현은 +`finished//museum-exhibit-studio`에 있습니다. 각 스타터에는 학습자가 편집하지 +않는 미리 빌드된 큐레이터 도우미 모듈 하나가 포함되어 있습니다. 이 모듈에는 승인된 사실 집합과 +그 범위, 스트리밍 출력기, 결정론적 전시 유효성 검사, 기본적으로 거부하는 권한 처리기를 갖춘 +범위 제한 Wikipedia MCP 서버, 단일 파일 `exhibit.html` 쓰기 권한 및 간단한 터미널 +프롬프트가 들어 있습니다. + +학습자는 `start-museum/`에서 직접 작업하며 수업을 진행하는 동안 하나의 프로젝트를 +확장하고 각 단계에서 실행합니다. 세션 설정, 큐레이터 및 조사 시스템 메시지, 프롬프트 빌더, +수명 주기와 제한 시간을 관리하는 세션 실행기 하나 및 `main`만 작성합니다. 완성된 샘플은 별도의 +참조 아키텍처가 아니라 학습자가 최종적으로 완성하는 결과물입니다. + +학습자용 트랙은 +[`workshop/museum-00-preflight.md`](workshop/museum-00-preflight.md)에서 시작하여 첫 세션, +스트리밍, 큐레이터 문체, 승인된 사실, 구조 검사 및 Wikipedia MCP 조사라는 7개 단계를 거친 후 +대화형 `exhibit.html` 캡스톤을 진행하고 [축하와 리소스](workshop/museum-09-complete.md)로 +마무리합니다. + +사용할 수 있고 출처가 표시된 조사 내용이 있으면 큐레이터는 설명문과 관람객 질문을 작성하기 전에 +`approved_fact_lookup`과 읽기 전용 `approved_wikipedia_fact_lookup`을 호출합니다. 두 번째 +도구는 실시간 Wikipedia 접근 또는 사람이 검증한 사실이 아니라 캡처한 조사 내용을 반환합니다. +승인된 사실을 우선하며, 조사를 거절했거나 조사에 실패했거나 출처가 없으면 단일 도구 생성 경로를 +유지합니다. 구조적 유효성 검사만으로 사실에 근거했음을 입증할 수는 없으므로, 게시 전에 조사한 +주장을 검토합니다. + +Rust 검사는 모든 워크숍 프로젝트에서 하나의 Cargo 대상 디렉터리를 공유하여 SDK 종속성을 +반복해서 컴파일하지 않습니다. + +## 배포 + +검증을 통과하면 `main`에 푸시합니다. +[Pages 워크플로](../../.github/workflows/deploy.yml)는 `docs/`와 `workshop/`의 Markdown +수업을 게시합니다. 빌드 및 콘텐츠 검증은 검증 워크플로에서 별도로 실행합니다. + +리포지토리 설정에서 GitHub Pages를 사용하도록 설정하고 소스로 **GitHub Actions**를 선택합니다. +배포 작업은 환경에 정식 워크숍 URL을 보고합니다. + +배포 워크플로는 게시된 모든 HTML 페이지, 사이트 자산 및 Markdown 수업을 확인합니다. +기본적으로 GitHub Pages가 반환한 URL을 검사합니다. 향후 공개 도메인 또는 사용자 지정 +도메인을 대신 검증하려면 리포지토리 Actions 변수 `WORKSHOP_SITE_URL`을 해당 사이트의 기본 +URL로 설정합니다. 같은 검사를 다음과 같이 수동으로 실행할 수 있습니다. + +```bash +WORKSHOP_SITE_URL=https://workshop.example.com/ python3 scripts/validate_deployment.py +``` + +## 참고 자료 + +- [.NET용 GitHub Copilot SDK](https://github.com/github/copilot-sdk/tree/main/dotnet) +- [Node.js/TypeScript용 GitHub Copilot SDK](https://github.com/github/copilot-sdk/tree/main/nodejs) +- [Python용 GitHub Copilot SDK](https://github.com/github/copilot-sdk/tree/main/python) +- [Go용 GitHub Copilot SDK](https://github.com/github/copilot-sdk/tree/main/go) +- [Rust용 GitHub Copilot SDK](https://github.com/github/copilot-sdk/tree/main/rust) +- [Java용 GitHub Copilot SDK](https://github.com/github/copilot-sdk/tree/main/java) +- [Copilot SDK 쿡북](https://github.com/github/copilot-sdk/tree/main/cookbook) +- [Copilot SDK API 및 소스](https://github.com/github/copilot-sdk) +- [GitHub Copilot CLI 설치](https://docs.github.com/en/copilot/how-tos/set-up/install-copilot-cli) +- [Playwright MCP](https://github.com/microsoft/playwright-mcp) +- [Model Context Protocol](https://modelcontextprotocol.io/) + +## 라이선스 + +이 프로젝트에는 [MIT License](../../LICENSE)가 적용됩니다. + +이 워크숍은 교육 목적으로 현 상태 그대로 제공됩니다. 완전한 프로덕션 서비스를 제공하기보다 +개념과 패턴을 설명하는 것을 목적으로 합니다. diff --git a/localizations/ko-kr/finished/dotnet/museum-exhibit-studio/README.md b/localizations/ko-kr/finished/dotnet/museum-exhibit-studio/README.md new file mode 100644 index 0000000..4c46161 --- /dev/null +++ b/localizations/ko-kr/finished/dotnet/museum-exhibit-studio/README.md @@ -0,0 +1,76 @@ +# 박물관 전시 스튜디오 + +이 완성된 .NET 샘플은 GitHub Copilot SDK를 사용해 승인된 사실을 바탕으로 작은 박물관 +전시를 생성합니다. 미리 빌드된 `Helpers/Curator*.cs` 파일은 사실 집합, 경계 검사, +스트리밍, 결정론적 유효성 검사, 범위가 제한된 Wikipedia 권한, 범위가 제한된 +`exhibit.html` 쓰기 권한, 터미널 입력 도우미를 제공합니다. `Program.cs`는 학습자가 +작성하는 부분으로 유지되며, 큐레이터 및 조사 시스템 메시지를 정의하고, 프롬프트를 +구성하고, 세 가지 세션 구성을 인라인으로 만들고, 콘솔 흐름을 조정합니다. + +## 샘플 실행 + +리포지토리 루트에서 다음 명령을 실행합니다. + +```bash +dotnet run --project finished/dotnet/museum-exhibit-studio +``` + +실행 전에 `COPILOT_MODEL`을 설정하면 모델을 선택할 수 있습니다. 그렇지 않으면 Copilot +런타임이 기본 모델을 선택합니다. 이 샘플은 인증된 GitHub Copilot CLI가 필요합니다. + +모델에 연결하지 않고 빌드하려면 다음 명령을 실행합니다. + +```bash +dotnet build finished/dotnet/museum-exhibit-studio +``` + +## 이 샘플에서 배울 내용 + +생성 세션은 항상 애플리케이션이 소유한 도구 `approved_fact_lookup`를 등록하고 허용 목록에 +추가합니다. 사용할 수 있는 인용된 조사 결과가 있으면 읽기 전용 로컬 도구 +`approved_wikipedia_fact_lookup`도 등록하고 허용 목록에 추가하며, 프롬프트에서 +내러티브와 방문자 질문을 작성하기 전에 두 도구를 모두 호출하도록 요청합니다. +replace-mode 시스템 메시지는 승인된 사실에 우선권을 주고 도구 결과를 지침이 아니라 +데이터로 취급합니다. `CuratorFacts.CreateApprovedFactLookup`는 모델이 사실을 보기 전에 +해당 사실을 제한하고, `CuratorFacts.BoundFacts`는 생성 또는 조사 전송 전에 매번 사실을 +잘라내고 검증하며, `CuratorStreamer.StreamExhibitAsync`는 명시적 시간 제한과 함께 +모델 출력을 스트리밍합니다. + +선택적 Wikipedia 조사는 의도적으로 가볍게 구성되어 있습니다. 별도의 세션은 +`CuratorSafety.WikipediaPermissionHandler`를 통해 범위가 제한된 `search` 및 +`readArticle` MCP 도구만 노출합니다. 모델은 산문 형식의 메모와 마지막의 +`## Sources` 목록을 작성합니다. 애플리케이션은 인용된 출처 제목과 URL을 추출하고 +요약 본문은 유지합니다. `CuratorFacts.CreateApprovedWikipediaFactLookup`는 네트워크 +접근 없이 해당 본문과 인용의 캡처된 스냅샷을 반환합니다. 조사는 보충 자료일 뿐이며 +교육자가 승인한 사실과 결합되지 않습니다. 여기서 "approved"는 사람이 검증했다는 뜻이 +아니라 애플리케이션이 수용했다는 뜻입니다. 조사를 거부하면 단일 도구 경로가 유지됩니다. +조사가 실패하거나 인용이 없는 요약이 반환되면 경고를 출력하고 동일한 대체 경로를 +따릅니다. 출처는 전시 뒤에도 계속 출력됩니다. + +생성 후에는 결정론적 유효성 검사가 제목, `## Narrative`, 100~140단어 내러티브 길이, +`## Visitor questions`, 정확히 세 개의 번호 매긴 질문, 물음표, 금지된 어휘를 +검사합니다. 이러한 구조 검사는 사실적 근거를 증명하지 않으므로 사람의 검토는 여전히 +필요합니다. + +선택적 캡스톤은 `builtin:apply_patch` 또는 `builtin:create`로 `exhibit.html`을 +생성합니다. `CuratorSafety.ExhibitWritePermission`은 애플리케이션 작업 디렉터리의 +해당 단일 파일만 허용하고 다른 모든 쓰기, 셸 또는 MCP 요청은 거부합니다. + +## 수동 확인 + +1. 샘플을 실행하고 기본 제공 사실 집합 중 하나를 수락합니다. +2. 조사에 참여하고 전시가 생성되기 전에 두 로컬 조회 이벤트가 모두 표시되는지, + 전시 뒤에 출처가 출력되는지 확인합니다. 조사를 거부하고 `approved_fact_lookup`만 + 호출되는지도 확인합니다. +3. 전시에 제목 하나, 100~140단어 내러티브 하나, 질문 세 개가 포함되는지 확인합니다. +4. 유효성 검사 요약과 사람 검토 경고가 표시되는지 확인합니다. +5. 선택 사항으로 `exhibit.html`을 생성하고 브라우저에서 독립 실행형 대화형 페이지를 + 검토합니다. + +이 애플리케이션은 학습자가 박물관 단원을 마친 뒤 최종적으로 갖게 되는 결과물이며, 별도의 +참조 아키텍처가 아닙니다. 엔트리포인트는 클라이언트를 시작하고, 세션을 만들고, 시간 +제한을 적용하고, 빈 출력을 거부하고, 모든 경로에서 정리하는 작은 세션 실행기 하나를 +유지합니다. 조사, 생성, 선택적 HTML 단계는 서로 다른 세션 구성을 사용하면서 이 실행기를 +재사용합니다. 다음 트랙을 따라가십시오. +[`workshop/museum-00-preflight.md`](https://github.com/github/copilot-sdk-workshop/blob/main/workshop/museum-00-preflight.md). + diff --git a/localizations/ko-kr/finished/go/museum-exhibit-studio/README.md b/localizations/ko-kr/finished/go/museum-exhibit-studio/README.md new file mode 100644 index 0000000..4e69911 --- /dev/null +++ b/localizations/ko-kr/finished/go/museum-exhibit-studio/README.md @@ -0,0 +1,64 @@ +# 박물관 전시 스튜디오 + +이 Go 샘플은 GitHub Copilot SDK를 집중된 박물관 큐레이션 하니스로 사용합니다. 이제 +애플리케이션은 두 파일 구조로 구성됩니다. + +- `curator.go`에는 승인된 사실 집합, 사실 경계, 응답 스트리밍, 구조 유효성 검사, + Wikipedia 권한, 출처 추출, 선택적 `exhibit.html` 쓰기 권한, 터미널 도우미를 제공하는 + 미리 빌드된 헬퍼 API가 들어 있습니다. +- `main.go`에는 프롬프트, 세션 구성, 콘솔 흐름, 정리를 담당하는 학습자 작성 + 오케스트레이션이 들어 있습니다. + +## 샘플 실행 + +이 디렉터리에서 다음 명령을 실행합니다. + +```bash +go run . +``` + +`COPILOT_MODEL`을 설정하면 생성 모델을 선택할 수 있습니다. 그렇지 않으면 런타임이 기본 +모델을 선택합니다. 인증된 GitHub Copilot CLI가 필요합니다. + +모델이나 Wikipedia에 연결하지 않고 빌드하려면 다음 명령을 실행합니다. + +```bash +go build -mod=readonly ./... +``` + +## 이 샘플에서 배울 내용 + +생성은 replace 방식 시스템 메시지를 사용하며, 경계가 설정된 승인된 사실을 반환하는 +`approved_fact_lookup`를 항상 등록하고 허용 목록에 추가합니다. 사용할 수 있는 인용된 +조사 결과가 있으면 읽기 전용 로컬 `approved_wikipedia_fact_lookup`도 등록하고 허용 +목록에 추가하며, 내러티브와 방문자 질문을 작성하기 전에 두 호출을 모두 수행하도록 +요청합니다. 이 조회는 실시간 Wikipedia 접근이 아니라 요약 본문과 인용의 스냅샷을 +반환합니다. 승인된 사실이 우선합니다. 생성은 이벤트 스트리밍과 120초 시간 제한도 +사용합니다. 선택적 Wikipedia 조사는 90초짜리 별도 세션에서 실행되며, 범위가 제한된 검색 +도구와 기사 읽기 도구만 노출하고 기본 거부 방식의 권한 처리기를 사용합니다. 조사는 검색, +읽기, 참조한 문서를 마지막의 `## Sources` 섹션에 인용합니다. 애플리케이션은 해당 본문과 +출처를 로컬 조회용으로 유지하지만 교육자가 승인한 사실과 병합하지는 않습니다. 여기서 +"approved"는 사람이 검증했다는 뜻이 아니라 애플리케이션이 보조 용도로 수용했다는 +뜻이므로, 결과를 지침이 아니라 데이터로 취급해야 합니다. 조사를 거부하면 단일 도구 경로가 +유지됩니다. 조사가 실패하거나 인용이 있는 요약을 사용할 수 없으면 경고를 출력하고 동일한 +대체 경로를 따릅니다. 엄격한 조사 JSON 계약이나 승인 루프는 없습니다. + +생성 후에는 결정론적 유효성 검사가 H1 하나, 필수 섹션, 100~140단어 내러티브, `?`로 +끝나는 정확히 세 개의 번호 매긴 방문자 질문, 금지된 소프트웨어 용어를 검사합니다. +참조한 Wikipedia 출처는 생성된 본문 밖에서 전시 뒤에 출력됩니다. 조사가 성공적으로 +실행되면 생성 전에 두 로컬 조회 이벤트가 모두 표시되는지 확인합니다. 구조 검사는 사실적 +근거를 증명하지 않으므로, 게시 전에 조사된 주장을 검토해야 합니다. + +선택 사항으로 애플리케이션은 `builtin:apply_patch` 또는 `builtin:create`를 사용해 +`exhibit.html` 생성을 Copilot에 요청할 수 있습니다. 해당 세션은 애플리케이션 작업 +디렉터리에서 정규화된 `exhibit.html` 하나에 대한 쓰기만 허용하고, 다른 모든 파일, 셸, +MCP 권한 요청은 거부합니다. HTML 프롬프트는 포함된 CSS와 JavaScript가 있는 독립 실행형 +시맨틱 문서, 사람 검토 경고, 접근 가능한 질문 필터를 요구합니다. + +이 애플리케이션은 학습자가 박물관 단원을 마친 뒤 최종적으로 갖게 되는 결과물이며, 별도의 +참조 아키텍처가 아닙니다. 엔트리포인트는 클라이언트를 시작하고, 세션을 만들고, 시간 +제한을 적용하고, 빈 출력을 거부하고, 모든 경로에서 정리하는 작은 세션 실행기 하나를 +유지합니다. 조사, 생성, 선택적 HTML 단계는 서로 다른 세션 구성을 사용하면서 이 실행기를 +재사용합니다. 다음 트랙을 따라가십시오. +[`workshop/museum-00-preflight.md`](https://github.com/github/copilot-sdk-workshop/blob/main/workshop/museum-00-preflight.md). + diff --git a/localizations/ko-kr/finished/java/museum-exhibit-studio/README.md b/localizations/ko-kr/finished/java/museum-exhibit-studio/README.md new file mode 100644 index 0000000..74db9be --- /dev/null +++ b/localizations/ko-kr/finished/java/museum-exhibit-studio/README.md @@ -0,0 +1,72 @@ +# 박물관 전시 스튜디오 + +이 Maven CLI 샘플은 GitHub Copilot SDK를 집중된 박물관 큐레이션 에이전트로 사용합니다. +박물관 교육자는 승인된 사실 집합 세 개 중 하나를 선택하거나 직접 경계가 설정된 사실을 +입력하고, 선택적으로 범위가 제한된 Wikipedia 배경 조사를 스트리밍하고, 방문자 대상 +전시 문구를 생성하고, 그 구조를 검증하고, 원하면 `exhibit.html` 캡스톤을 선택할 수 +있습니다. + +## 실행 + +이 디렉터리에서 다음 명령을 실행합니다. + +```bash +./mvnw compile exec:java +``` + +`COPILOT_MODEL`을 설정하면 모델을 선택할 수 있습니다. 그렇지 않으면 Copilot 런타임이 +기본 모델을 선택합니다. 이 샘플은 인증된 GitHub Copilot CLI가 필요합니다. + +모델에 연결하지 않고 컴파일하려면 다음 명령을 실행합니다. + +```bash +./mvnw compile +``` + +## 이 샘플에서 보여 주는 내용 + +학습자가 작성하는 `MuseumExhibitStudio` 엔트리포인트는 `new CopilotClient()`로 세션을 +직접 구성합니다. 미리 빌드된 `Curator*` 헬퍼는 승인된 사실, 스트리밍, 유효성 검사, +범위가 제한된 권한, 출처 추출, 터미널 프롬프트를 제공합니다. + +프롬프트 지침은 권한 경계가 아니므로, 애플리케이션은 다음도 함께 수행합니다. + +- 항상 `approved_fact_lookup`를 등록하고 허용 목록에 추가하며, 사용할 수 있는 인용된 + 조사 결과가 있을 때만 읽기 전용 로컬 `approved_wikipedia_fact_lookup`를 + 추가합니다. +- 기본 거부 방식의 권한 처리기를 통해 조사를 구성된 Wikipedia MCP 서버와 + `wikipedia-search` / `wikipedia-readArticle`로 제한합니다. +- 조사 본문과 마지막의 `## Sources` 인용을 두 번째 로컬 조회용으로 캡처하고, + 이를 교육자가 승인한 사실과 절대 병합하지 않으며 생성 단계에 실시간 Wikipedia 접근을 + 제공하지 않습니다. +- 모델에 전송하기 전에 매번 입력을 각 500자 이하인 사실 최대 20개로 제한합니다. +- 명시적 시간 제한을 사용하고, 빈 전시 출력을 거부하고, 성공과 실패 모두에서 세션 연결을 + 해제하고 클라이언트를 중지합니다. +- H1 하나, 필수 섹션, 100~140단어 내러티브, `?`로 끝나는 정확히 세 개의 번호 매긴 + 질문, 금지된 소프트웨어 어휘를 검사합니다. +- 선택 사항으로 `builtin:apply_patch`와 `builtin:create`가 애플리케이션 작업 + 디렉터리의 `exhibit.html` 하나만 쓸 수 있도록 허용합니다. + +조사가 존재할 때 큐레이터는 내러티브와 방문자 질문을 작성하기 전에 두 로컬 조회를 모두 +호출하도록 지시받습니다. 승인된 사실이 보충 조사보다 우선하며, 조사는 지침이 아니라 +데이터입니다. "Approved" 조사란 사람이 검증했다는 뜻이 아니라 애플리케이션이 +수용했다는 뜻입니다. 조사를 거부하면 단일 도구 경로가 유지됩니다. 조사가 실패하거나 +사용할 수 있는 인용 요약을 얻지 못하면 경고를 출력하고 동일한 대체 경로를 따릅니다. +성공적으로 조사한 경우 두 조회 이벤트가 모두 표시되는지 확인하십시오. 출처는 전시 뒤에도 +계속 출력됩니다. 검증기는 의미론적 사실 근거를 증명할 수 없습니다. 생성된 주장은 여전히 +사람의 검토 또는 별도의 평가기가 필요합니다. + +## 선택적 HTML 캡스톤 + +프롬프트가 나타나면 `exhibit.html`을 생성하려면 yes라고 답합니다. 고정된 Java SDK +1.0.11은 `fileName`과 같은 권한 필드를 보존하므로, 엄격한 권한 처리기는 정규화된 +경로가 이 디렉터리의 정확히 `exhibit.html`일 때만 쓰기를 승인합니다. 경로 데이터가 +없거나, 다른 파일 경로이거나, 쓰기 요청이 아닌 경우는 계속 거부되며, 광범위한 쓰기 +대체 경로는 없습니다. + +이 애플리케이션은 학습자가 박물관 단원을 마친 뒤 최종적으로 갖게 되는 결과물이며, 별도의 +참조 아키텍처가 아닙니다. 엔트리포인트는 클라이언트를 시작하고, 세션을 만들고, 시간 +제한을 적용하고, 빈 출력을 거부하고, 모든 경로에서 정리하는 작은 세션 실행기 하나를 +유지합니다. 조사, 생성, 선택적 HTML 단계는 서로 다른 세션 구성을 사용하면서 이 실행기를 +재사용합니다. 다음 트랙을 따라가십시오. +[`workshop/museum-00-preflight.md`](https://github.com/github/copilot-sdk-workshop/blob/main/workshop/museum-00-preflight.md). diff --git a/localizations/ko-kr/finished/nodejs/museum-exhibit-studio/README.md b/localizations/ko-kr/finished/nodejs/museum-exhibit-studio/README.md new file mode 100644 index 0000000..2c8312e --- /dev/null +++ b/localizations/ko-kr/finished/nodejs/museum-exhibit-studio/README.md @@ -0,0 +1,48 @@ +# 박물관 전시 스튜디오 + +이 완성된 Node.js/TypeScript 샘플은 이제 두 개의 소스 파일로 구성됩니다. + +- `src/curator.ts`에는 미리 빌드된 헬퍼 모듈이 들어 있습니다. 여기에는 승인된 사실, + 경계가 설정된 스트리밍, 결정론적 유효성 검사, 범위가 제한된 Wikipedia 권한, 선택적 + `exhibit.html` 쓰기 권한, 터미널 도우미가 포함됩니다. +- `src/index.ts`에는 학습자가 작성하는 오케스트레이션이 들어 있습니다. 여기에는 프롬프트, + 세션 구성, 사실 집합 선택, 선택적 조사, 생성, 유효성 검사, 선택적 HTML 캡스톤이 + 포함됩니다. + +## 샘플 실행 + +```bash +cd finished/nodejs/museum-exhibit-studio +npm ci +npm start +``` + +모델에 연결하지 않고 타입 검사를 하려면 `npm run build`를 사용합니다. + +## 안전성 구조 + +생성은 항상 경계가 설정된 승인된 사실을 반환하는 `approved_fact_lookup`를 등록하고 허용 +목록에 추가합니다. 사용할 수 있는 인용된 조사 결과가 있으면 읽기 전용 로컬 도구 +`approved_wikipedia_fact_lookup`도 등록하고 허용 목록에 추가하며, 큐레이터에게 +내러티브와 방문자 질문을 작성하기 전에 두 도구를 모두 호출하라고 요청합니다. 두 번째 +조회는 실시간 Wikipedia 접근이 아니라 조사 본문과 인용의 스냅샷을 반환합니다. 승인된 +사실이 우선하며, 조사는 지침이나 사람이 검증한 사실이 아니라 보충 데이터입니다. 선택적 +Wikipedia 조사는 별도 세션에서 실행되며, 범위가 제한된 `search` 및 `readArticle` +도구, 기본 거부 방식 권한 처리기, 인용된 `## Sources`, JSON 계약이나 추가 제안 승인 +루프가 없는 구성을 사용합니다. 조사는 교육자가 승인한 사실과 절대 병합되지 않습니다. +조사를 거부하면 원래의 단일 도구 경로가 유지됩니다. 조사가 실패하거나 인용이 있는 요약을 +사용할 수 없으면 경고를 출력하고 동일한 대체 경로를 따릅니다. 출처는 전시 뒤에도 계속 +출력됩니다. 조사 실행이 성공하면 생성 전에 두 로컬 조회 이벤트가 모두 나타나는지 +확인합니다. + +생성 후에는 결정론적 검사가 구조, 내러티브 길이, 방문자 질문, 금지된 어휘를 보고합니다. +선택한 경우 HTML 단계는 `builtin:apply_patch`와 `builtin:create`만 노출합니다. 이 +단계의 권한 처리기는 앱 디렉터리에서 정확히 `exhibit.html`에 쓰는 경우만 승인합니다. +구조 검사는 사실적 근거를 증명하지 않으므로, 게시 전에 조사된 주장을 검토해야 합니다. + +이 애플리케이션은 학습자가 박물관 단원을 마친 뒤 최종적으로 갖게 되는 결과물이며, 별도의 +참조 아키텍처가 아닙니다. 엔트리포인트는 클라이언트를 시작하고, 세션을 만들고, 시간 +제한을 적용하고, 빈 출력을 거부하고, 모든 경로에서 정리하는 작은 세션 실행기 하나를 +유지합니다. 조사, 생성, 선택적 HTML 단계는 서로 다른 세션 구성을 사용하면서 이 실행기를 +재사용합니다. 다음 트랙을 따라가십시오. +[`workshop/museum-00-preflight.md`](https://github.com/github/copilot-sdk-workshop/blob/main/workshop/museum-00-preflight.md). diff --git a/localizations/ko-kr/finished/python/museum-exhibit-studio/README.md b/localizations/ko-kr/finished/python/museum-exhibit-studio/README.md new file mode 100644 index 0000000..dd27ef9 --- /dev/null +++ b/localizations/ko-kr/finished/python/museum-exhibit-studio/README.md @@ -0,0 +1,79 @@ +# 박물관 전시 스튜디오 + +이 Python 샘플은 GitHub Copilot SDK를 집중된 박물관 전시 스튜디오로 사용합니다. +완성된 앱은 이제 두 개의 모듈로 구성됩니다. + +- `curator.py`에는 미리 빌드된 워크숍 헬퍼가 들어 있습니다. 여기에는 승인된 사실 집합, + 경계가 설정된 사실 유효성 검사, 스트리밍, 결정론적 구조 검사, 범위가 제한된 + Wikipedia 권한, 범위가 제한된 `exhibit.html` 쓰기 권한, 터미널 도우미가 포함됩니다. +- `main.py`에는 학습자가 작성하는 오케스트레이션이 들어 있습니다. 여기에는 프롬프트, + 세션 구성, 콘솔 흐름, 유효성 검사, 선택적 HTML 생성이 포함됩니다. + +## 샘플 실행 + +이 디렉터리에서 환경을 만들고 고정된 종속성을 설치합니다. + +```powershell +python -m venv .venv +.\.venv\Scripts\Activate.ps1 +python -m pip install -r requirements.txt +python main.py +``` + +`COPILOT_MODEL`을 설정하면 모델을 선택할 수 있습니다. 그렇지 않으면 런타임이 기본 +모델을 선택합니다. 인증된 GitHub Copilot CLI가 필요합니다. + +Wikipedia 조사는 조사 세션이 `npx`를 통해 고정된 `wikipedia-mcp@1.0.3` 패키지를 +실행하므로 Node.js가 필요합니다. 조사를 거부하면 MCP 서버는 시작되지 않습니다. + +모델에 연결하지 않고 소스를 검사하려면 다음 명령을 실행합니다. + +```powershell +python -m py_compile *.py +``` + +## 이 샘플에서 배울 내용 + +생성은 항상 경계가 설정된 승인된 사실을 반환하는 `approved_fact_lookup`를 등록하고 허용 +목록에 추가합니다. 사용할 수 있는 인용된 조사 결과가 있으면 읽기 전용 로컬 도구 +`approved_wikipedia_fact_lookup`도 등록하고 허용 목록에 추가하며, 내러티브와 방문자 +질문을 작성하기 전에 두 도구를 모두 호출하도록 요청합니다. 두 번째 조회는 실시간 +Wikipedia 접근이 아니라 요약 본문과 인용의 스냅샷을 반환합니다. 승인된 사실이 보충 +조사보다 우선합니다. 또한 replace-mode 큐레이터 시스템 메시지, 스트리밍, 120초 시간 +제한, 결정론적 구조 유효성 검사를 사용합니다. 가져온 모듈에는 부작용이 없으며, +`main.py`는 `if __name__ == "__main__"` 가드 뒤에서만 실행됩니다. + +선택적 Wikipedia 조사는 의도적으로 생성과 분리되어 있습니다. 조사 세션은 범위가 제한된 +Wikipedia 검색 및 기사 읽기 도구만 노출하고, 기본 거부 방식 권한 처리기를 사용하며, +산문 요약을 요청하고, 마지막의 `## Sources` 목록을 파싱합니다. 요약과 인용은 모두 +로컬 조회용으로 유지되지만 교육자가 승인한 사실과 병합되지는 않습니다. 여기서 +"approved"는 사람이 검증한 사실이 아니라 애플리케이션이 수용한 조사라는 뜻이므로, +이를 지침이 아니라 데이터로 취급해야 합니다. 조사를 거부하면 단일 도구 경로가 +유지됩니다. 조사가 실패하거나 인용이 있는 요약을 사용할 수 없으면 경고를 출력하고 +동일한 대체 경로를 따릅니다. 엄격한 조사 JSON 계약이나 추가 제안 승인 루프는 없습니다. + +유효성 검사 후 선택적 HTML 캡스톤은 `builtin:apply_patch`와 `builtin:create`만 +노출하며, 애플리케이션 작업 디렉터리에서 정확히 `exhibit.html`에 쓰는 경우만 승인합니다. +프롬프트는 포함된 CSS와 JavaScript가 들어 있는 독립 실행형 시맨틱 HTML 파일 하나, +사람 검토 경고, 접근 가능한 질문 필터를 요청합니다. + +프롬프트 지침과 구조 유효성 검사는 권한 경계나 사실 근거 경계가 아닙니다. 생성된 주장은 +여전히 사람의 검토 또는 별도의 평가기가 필요합니다. + +## 수동 확인 + +1. 기본 제공 사실 집합 각각으로 실행하고 생성 전에 선택된 사실이 출력되는지 확인합니다. +2. 전시에 제목 하나, 100~140단어 내러티브 하나, 방문자 질문 세 개가 포함되는지 + 확인합니다. +3. 유효성 검사 요약과 사실 근거 경고를 확인합니다. +4. 조사를 거부하고 도구 이벤트가 `approved_fact_lookup` 하나뿐인지 확인합니다. +5. 조사에 참여하고 생성 전에 두 로컬 조회 이벤트가 모두 나타나며, 출처가 전시 안이 + 아니라 뒤에 출력되는지 확인합니다. +6. `exhibit.html` 생성을 선택하고 해당 파일만 기록되는지 확인합니다. + +이 애플리케이션은 학습자가 박물관 단원을 마친 뒤 최종적으로 갖게 되는 결과물이며, 별도의 +참조 아키텍처가 아닙니다. 엔트리포인트는 클라이언트를 시작하고, 세션을 만들고, 시간 +제한을 적용하고, 빈 출력을 거부하고, 모든 경로에서 정리하는 작은 세션 실행기 하나를 +유지합니다. 조사, 생성, 선택적 HTML 단계는 서로 다른 세션 구성을 사용하면서 이 실행기를 +재사용합니다. 다음 트랙을 따라가십시오. +[`workshop/museum-00-preflight.md`](https://github.com/github/copilot-sdk-workshop/blob/main/workshop/museum-00-preflight.md). diff --git a/localizations/ko-kr/finished/rust/museum-exhibit-studio/README.md b/localizations/ko-kr/finished/rust/museum-exhibit-studio/README.md new file mode 100644 index 0000000..acde942 --- /dev/null +++ b/localizations/ko-kr/finished/rust/museum-exhibit-studio/README.md @@ -0,0 +1,50 @@ +# 박물관 전시 스튜디오 + +이 Rust 샘플은 GitHub Copilot SDK를 소프트웨어 엔지니어링이 아닌 작업에 집중된 에이전트 +하니스로 사용합니다. 미리 빌드된 헬퍼는 `src/lib.rs`에 있고, 학습자가 작성하는 +오케스트레이션은 `src/main.rs`에 있습니다. + +## 샘플 실행 + +```bash +cargo run --manifest-path finished/rust/museum-exhibit-studio/Cargo.toml --locked +``` + +생성 모델을 선택하려면 `COPILOT_MODEL`을 설정합니다. 이 샘플은 인증된 GitHub Copilot +CLI가 필요합니다. + +모델에 연결하지 않고 검사하려면 다음 명령을 실행합니다. + +```bash +cargo check --locked --manifest-path finished/rust/museum-exhibit-studio/Cargo.toml +``` + +## 이 샘플에서 배울 내용 + +생성 세션은 replace 방식 큐레이터 시스템 메시지를 사용하고, 승인된 사실을 검증하고, +120초 시간 제한으로 스트리밍하고, `approved_fact_lookup`를 항상 등록하고 허용 목록에 +추가하며, 빈 출력을 거부하고, 결정론적 구조 유효성 검사 결과를 출력합니다. 사용할 수 +있는 인용된 조사 결과가 있으면 읽기 전용 로컬 `approved_wikipedia_fact_lookup`도 +등록하고 허용 목록에 추가하며, 내러티브와 방문자 질문을 작성하기 전에 두 도구를 모두 +호출하도록 요청합니다. + +선택적 Wikipedia 조사는 별도로 수행됩니다. 범위가 제한된 `search` 및 `readArticle` +MCP 도구만 노출하고, 기본 거부 방식 권한 처리기를 사용하며, 요약과 인용된 출처를 +생성합니다. 새 로컬 조회는 실시간 Wikipedia 접근 없이 해당 본문과 인용의 스냅샷을 +반환하며, 조사 내용을 교육자가 승인한 사실과 병합하지 않습니다. 승인된 사실이 +우선합니다. "Approved" 조사란 사람이 검증한 사실이나 지침이 아니라 애플리케이션이 +수용한 보충 데이터라는 뜻입니다. 조사를 거부하면 단일 도구 경로가 유지됩니다. 조사가 +실패하거나 인용이 있는 요약을 사용할 수 없으면 경고를 출력하고 동일한 대체 경로를 +따릅니다. 출처는 전시 뒤에 출력되며, 조사가 성공한 경우 생성 전에 두 로컬 조회 이벤트가 +모두 표시되어야 합니다. 구조 검사는 사실적 근거를 증명하지 않으므로, 게시 전에 조사된 +주장을 검토해야 합니다. + +선택적 HTML 생성은 `builtin:apply_patch` 또는 `builtin:create`와 함께 단일 파일 권한 +처리기를 사용하며, 애플리케이션 작업 디렉터리의 `exhibit.html` 하나에만 쓸 수 있습니다. + +이 애플리케이션은 학습자가 박물관 단원을 마친 뒤 최종적으로 갖게 되는 결과물이며, 별도의 +참조 아키텍처가 아닙니다. 엔트리포인트는 클라이언트를 시작하고, 세션을 만들고, 시간 +제한을 적용하고, 빈 출력을 거부하고, 모든 경로에서 정리하는 작은 세션 실행기 하나를 +유지합니다. 조사, 생성, 선택적 HTML 단계는 서로 다른 세션 구성을 사용하면서 이 실행기를 +재사용합니다. 다음 트랙을 따라가십시오. +[`workshop/museum-00-preflight.md`](https://github.com/github/copilot-sdk-workshop/blob/main/workshop/museum-00-preflight.md). diff --git a/localizations/ko-kr/start-accessibility/README.md b/localizations/ko-kr/start-accessibility/README.md new file mode 100644 index 0000000..dfe44c7 --- /dev/null +++ b/localizations/ko-kr/start-accessibility/README.md @@ -0,0 +1,31 @@ +# 워크숍 시작 프로젝트 + +워크숍 홈페이지에서 선택한 언어의 디렉터리로 이동한 후 해당 디렉터리 안에서 직접 +작업합니다. 별도로 복사할 필요가 없습니다. 해당 디렉터리로 이동하고 같은 폴더를 편집기에서 +연 다음(폴더 안에서 `code .`을 실행하거나 다른 편집기의 폴더 열기 명령 사용), 모든 명령을 +해당 위치에서 실행합니다. 시작 프로젝트는 의도적으로 최소한의 스캐폴드(Scaffold)로 구성되어 있습니다. +애플리케이션 소유의 WCAG(Web Content Accessibility Guidelines) 카탈로그와 범위가 지정된 +권한/스냅샷 리더 헬퍼는 이후 단계를 위해 포함되어 있을 수 있지만, 해당 실행 엔트리 포인트는 +각 단계에 도달하기 전까지 Copilot 클라이언트, 세션, 스트리밍 흐름, 로컬 도구, MCP 서버 또는 +보고서를 연결하지 않습니다. + +| 언어 | 필수 조건 | 디렉터리 이동 및 확인 | +|---|---|---| +| .NET | [.NET 10 SDK](https://learn.microsoft.com/dotnet/core/install/) | `cd start-accessibility/dotnet && dotnet build` | +| Node.js | [Node.js 22+](https://nodejs.org/) | `cd start-accessibility/nodejs && npm install && npm run build` | +| Python | [Python 3.11+](https://www.python.org/downloads/) | `cd start-accessibility/python && python -m pip install -r requirements.txt && python -m py_compile *.py` | +| Go | [Go 1.24+](https://go.dev/dl/) | `cd start-accessibility/go && go build -mod=readonly ./...` | +| Rust | [Rust 1.94+](https://rustup.rs/) | `cd start-accessibility/rust && cargo check --locked` | +| Java | [Java 17+](https://adoptium.net/) (Maven Wrapper 포함) | `cd start-accessibility/java && ./mvnw compile` | + +이 파일을 제자리에서 편집하므로 변경 내용이 `git status`에 표시됩니다. 이는 정상입니다. +워크숍 리포지토리 루트에서 `git checkout -- .`을 실행하면 깨끗한 시작 프로젝트 상태로 복원됩니다. +Go, Rust, Java 트랙에서 나중에 애플리케이션을 실행하려면 +`PATH`에 [GitHub Copilot CLI](https://docs.github.com/en/copilot/how-tos/set-up/install-copilot-cli)가 +등록되어 있어야 합니다. SDK 설정 및 API 참고 자료는 +[공식 Copilot SDK 리포지토리](https://github.com/github/copilot-sdk)와 +[쿡북](https://github.com/github/copilot-sdk/tree/main/cookbook)에서 확인할 수 있습니다. + +워크숍 전체 과정에서 시작 프로젝트 디렉터리를 유지합니다. +[워크숍 홈페이지](../../../README.md#start-the-workshop)에서 대화형 뷰어로 돌아가고, +학습용 Markdown 파일을 직접 열지 않습니다. diff --git a/localizations/ko-kr/start-intro/README.md b/localizations/ko-kr/start-intro/README.md new file mode 100644 index 0000000..fc32422 --- /dev/null +++ b/localizations/ko-kr/start-intro/README.md @@ -0,0 +1,55 @@ +# SDK 101 시작 프로젝트 + +30분 분량의 SDK 101 워크숍을 위한 전체 시작 프로젝트가 이 디렉터리에 있습니다. +**이 워크숍 리포지토리를 한 번만 복제**하고, 언어 하나를 선택한 다음 해당 엔트리 포인트를 +제자리에서 편집합니다. 별도로 복제할 리포지토리나 복사할 프로젝트는 없습니다. + +| 언어 | 필수 조건 | 시작 프로젝트 참고 사항 | 데모 가이드 | 엔트리 포인트 | +| --- | --- | --- | --- | --- | +| .NET | .NET 10 SDK | [설정](dotnet/README.md) | [LIVE_DEMO](../../../start-intro/dotnet/LIVE_DEMO.md) | `dotnet/Program.cs` | +| Node.js | Node.js 22.12+ | [설정](nodejs/README.md) | [LIVE_DEMO](../../../start-intro/nodejs/LIVE_DEMO.md) | `nodejs/src/index.ts` | +| Python | Python 3.11+ | [설정](python/README.md) | [LIVE_DEMO](../../../start-intro/python/LIVE_DEMO.md) | `python/main.py` | +| Go | Go 1.24+ | [설정](go/README.md) | [LIVE_DEMO](../../../start-intro/go/LIVE_DEMO.md) | `go/main.go` | +| Java | Java 17+, Maven 3.9+ | [설정](java/README.md) | [LIVE_DEMO](../../../start-intro/java/LIVE_DEMO.md) | `java/src/main/java/demo/CopilotSdkLiveDemo.java` | +| Rust | Rust 1.94+ | [설정](rust/README.md) | [LIVE_DEMO](../../../start-intro/rust/LIVE_DEMO.md) | `rust/src/main.rs` | + +시간이 정해진 세션을 시작하기 전에 [사전 점검](../../../workshop/intro-00-preflight.md)을 완료합니다. +`copilot auth login`으로 인증하고 `start-intro/`로 이동한 다음, +해당 폴더를 편집기에서 엽니다(VS Code에서는 `code .` 실행). 종속성 관련 명령과 실행 명령을 +모두 해당 폴더에서 실행합니다. + +엔트리 포인트에는 의도적으로 자리 표시자가 포함되어 있습니다. 선택한 엔트리 포인트 옆의 +**`LIVE_DEMO.md`**를 엽니다. **1막의 네 가지 수정 단계**에 따라 클라이언트를 시작하고, +인증을 확인하고, 세션을 만들고, hello world를 전송합니다. 그런 다음 **2막**에 따라 +모델과 에피소드를 선택하고, 세션에 필요한 기능을 허용하고, 프롬프트를 교체합니다. + +워크숍 웹 사이트는 [hello-world 학습 과정](../../../workshop/intro-02-hello-world.md)과 +[팟캐스트 학습 과정](../../../workshop/intro-03-podcast-agent.md)에서 이와 동일한 가이드 섹션을 +표시하며, 다른 구현을 설명하지 않습니다. +이러한 헬퍼에는 모델 및 에피소드 선택, 형식이 지정된 RSS 조회 도구, 대화형 도구 승인이 포함되어 +있습니다. 워크숍 중에는 헬퍼를 변경하지 않습니다. + +종속성과 사용 가능한 잠금 파일이 포함되어 있습니다. 스모크 빌드(Smoke build)에는 Copilot 인증이나 +라이브 프롬프트가 필요하지 않습니다. 완성된 애플리케이션을 실행하려면 Copilot 액세스가 필요하며, +팟캐스트 워크플로(Workflow)에는 공식 RSS 피드에 대한 액세스도 필요합니다. +제자리에서 수행한 편집 내용이 `git status`에 표시되는 것은 정상입니다. + +## 출처 + +이 시작 프로젝트 소스와 `LIVE_DEMO.md` 가이드는 +[jamesmontemagno/copilot-sdk-intro](https://github.com/jamesmontemagno/copilot-sdk-intro)의 +리비전(Revision) +[`f744ec6`](https://github.com/jamesmontemagno/copilot-sdk-intro/tree/f744ec66b6429df876c18f443bd6b1c3144d4e97)에서 +가져왔습니다. +가이드는 원본의 2막 진행 방식과 번호가 매겨진 네 가지 hello-world 수정 단계를 유지합니다. +로컬 조정 사항은 이 리포지토리의 경로를 사용하고, 완료 대기 시간을 제한하고, SDK 리소스를 닫고, +hello world를 빈 도구 허용 목록으로 제한하며, 누락된 Java/Rust 스트리밍 구독을 제공합니다. +Go는 각 텍스트 조각을 두 번 출력하는 대신 구독 하나를 유지합니다. Node.js에서는 제한된 전송이 +콜백에서 예외를 발생시키는 대신 세션 오류를 전파합니다. +Node.js의 RSS 헬퍼는 파서 기반 XML 디코딩과 HTML-일반 텍스트 추출을 사용하며, +CDATA, 엔터티 디코딩, script/style 제외에 대한 회귀 테스트를 포함합니다. +6개 RSS 헬퍼는 모두 10초의 유한한 네트워크 타임아웃을 사용합니다. Java는 네임스페이스가 지정된 +재생 시간 메타데이터를 읽을 때 XML DOCTYPE 선언과 외부 리소스를 거부합니다. +Python과 Rust는 SDK의 사용자 지정 도구 권한 페이로드(Payload)를 인식합니다. +Rust는 분리된 입력 스레드에서 승인을 읽으므로 턴 타임아웃이 발생해도 런타임을 종료할 수 있습니다. +업스트림 리포지토리는 출처 표기를 위한 것이며 설정 요구 사항이 아닙니다. diff --git a/localizations/ko-kr/start-intro/dotnet/LIVE_DEMO.md b/localizations/ko-kr/start-intro/dotnet/LIVE_DEMO.md new file mode 100644 index 0000000..b2b1a00 --- /dev/null +++ b/localizations/ko-kr/start-intro/dotnet/LIVE_DEMO.md @@ -0,0 +1,176 @@ +# GitHub Podcast 라이브 데모: .NET + +## 세션 전 준비 + +1. 이 컴퓨터가 아직 인증되지 않았다면 `copilot auth login`을 실행합니다. +2. 포함된 스타터에서 작업합니다 and restore dependencies before the timed session: + +```powershell +cd start-intro/dotnet +dotnet restore +``` + +## 데모 소개 + +다음과 같이 말합니다: "GitHub Podcast용 팟캐스트 에이전트를 만듭니다. 실제 에피소드를 선택하고 공식 RSS 피드에서 확인된 메타데이터를 가져와 해당 사실을 후원사에 안전한 소셜 문구로 바꿀 수 있습니다." + +다음과 같이 말합니다: "가장 작은 Copilot SDK 대화에서 시작한 다음 목적, 정체성 및 애플리케이션 소유 도구를 부여합니다." + +## 1막: Hello World + +`Program.cs`에서 시작합니다. 여기에는 의도적으로 이름이 지정된 자리 표시자가 있습니다: `client`, `isAuthenticated`, and `session`. 스트리밍 이벤트 처리기와 완료 대기는 그대로 둡니다. + +### 1. 클라이언트 시작 + +다음으로 교체합니다 `CopilotClient client;` with: + +```csharp +await using var client = new CopilotClient(); +await client.StartAsync(); +``` + +다음과 같이 말합니다: "클라이언트는 Copilot 런타임에 연결하는 수단입니다. 명시적으로 시작하므로 애플리케이션이 수명 주기를 관리합니다." + +### 2. 인증 확인 + +다음으로 교체합니다 `var isAuthenticated = false;` with: + +```csharp +var isAuthenticated = (await client.GetAuthStatusAsync()).IsAuthenticated; +``` + +다음과 같이 말합니다: "세션을 만들기 전에 이 컴퓨터가 로그인되어 있는지 런타임에 확인할 수 있습니다." + +### 3. 세션 만들기 + +Add `using GitHub.Copilot.Rpc;` 파일 상단에 추가합니다. 다음으로 교체합니다 +`CopilotSession session = null!;` with: + +```csharp +await using var session = await client.CreateSessionAsync(new SessionConfig +{ + Model = Model, + Streaming = true, + AvailableTools = [], + OnPermissionRequest = PermissionHandler.ApproveAll +}); +``` + +다음과 같이 말합니다: "세션은 대화입니다. 모델을 선택하고 스트리밍을 활성화했습니다, and the event handler below already prints each text fragment as it arrives." + +다음과 같이 말합니다: "이 처리기는 권한 요청에 응답합니다. 빈 도구 허용 목록은 이 실습에서 도구 기능을 제거합니다. approve-all만으로는 안전 경계가 되지 않습니다." + +### 4. Hello World 전송 + +Under `// Step 5: Send the first message.`, type: + +```csharp +await session.SendAsync(new MessageOptions +{ + Prompt = "Hello world! In one sentence, say what the Copilot SDK helps a .NET app do." +}); +``` + +다음으로 교체합니다 the final `await complete.Task;` with: + +```csharp +await complete.Task.WaitAsync(TimeSpan.FromSeconds(60)); +Console.WriteLine(); +``` + +기존 event handler 이미 제공됩니다; it is not a fifth editing +exercise. It prints deltas, surfaces session errors, and finishes on idle. +`await using` closes the client and session after the run. + +다음과 같이 말합니다: "기본 형태는 다음과 같습니다: start a client, create a session, listen for events, and send a message. 이 흐름이 작동하면 팟캐스트 에이전트로 발전시킬 수 있습니다." + +예상 출력: a streamed one-sentence answer, followed by the existing `SessionIdleEvent` completing the program. + +다음 Hello World 확인 단계를 실행합니다 from `start-intro/dotnet`: + +```powershell +dotnet run +``` + +## 2막: 팟캐스트 에이전트로 전환 + +After Hello World, add the prewritten helpers in `Helpers` and `Tools` 같은 세션을 근거 기반 팟캐스트 흐름으로 전환합니다. + +다음과 같이 말합니다: "대화가 작동합니다. 이제 이를 팟캐스트 에이전트로 전환합니다: 선택한 GitHub Podcast 에피소드를 조사하고 사실을 만들지 않고 출시 문구를 준비하는 전문 도우미입니다." + +### 1. 발표자가 선택하도록 설정 + +Add usings for the prewritten helpers: + +```csharp +using CopilotSdkLiveDemo.Helpers; +using CopilotSdkLiveDemo.Tools; +``` + +인증 후 완료 신호와 세션을 만들기 전에 선택기를 추가합니다 +and 최신 에피소드 10개 중 하나를 선택합니다: + +```csharp +var model = await ModelSelector.PickAsync(client, Model); +var latestEpisodes = await GitHubPodcastEpisodeTool.GetLatestAsync(); +var selectedEpisode = EpisodeSelector.Pick(latestEpisodes); +var selectedEpisodeTitle = selectedEpisode.Title; +``` + +다음과 같이 말합니다: "이렇게 하면 데모를 실시간으로 진행할 수 있습니다. 현장에서 모델을 선택한 다음 실제 최신 GitHub Podcast 에피소드 10개 중 하나를 선택할 수 있습니다. 선택한 항목이 팟캐스트 에이전트의 작업이 됩니다." + +### 2. 세션에 기능 부여 + +애플리케이션 소유 도구를 만듭니다: + +```csharp +var episodeTool = GitHubPodcastEpisodeTool.CreateEpisodeTool(); +var latestEpisodesTool = GitHubPodcastEpisodeTool.CreateLatestEpisodesTool(); +``` + +다음으로 교체합니다 the hello-world `SessionConfig` fields with these, keeping +`await using var session = await client.CreateSessionAsync(...)`: + +```csharp +Model = model, +Streaming = true, +Tools = [episodeTool, latestEpisodesTool], +AvailableTools = ["get_github_podcast_episode", "get_latest_github_podcast_episodes"], +OnPermissionRequest = PermissionPrompt.RequestAsync, +SystemMessage = new SystemMessageConfig +{ + Mode = SystemMessageMode.Replace, + Content = "You are the launch assistant for The GitHub Podcast. Use supplied episode facts only." +} +``` + +다음과 같이 말합니다: "모델에 애플리케이션에 대한 임의의 접근 권한을 주지 않습니다. 범위가 좁고 형식이 지정된 기능 두 개를 부여하고 이름으로 허용 목록에 추가합니다, and swap Hello World's approve-all handler for one that prompts me, so I remain the approval point before a tool executes." + +다음과 같이 말합니다: "이 도구가 있어 일반 챗봇이 아니라 에이전트가 됩니다: 애플리케이션이 제어하는 신뢰할 수 있는 데이터 원본에 대해 작업을 수행할 수 있습니다." + +다음과 같이 말합니다: "The system message uses 다음으로 교체합니다, not Append. 기본 프롬프트를 상속하지 않고 애플리케이션이 이 세션의 에이전트 정체성과 근거 규칙을 완전히 제공합니다." + +### 3. 프롬프트 교체 + +다음으로 교체합니다 Hello World 선택한 근거 기반 에피소드 요청으로 교체합니다: + +```csharp +await session.SendAsync(new MessageOptions +{ + Prompt = $"Use get_github_podcast_episode for the episode titled \"{selectedEpisodeTitle}\". Return exactly a social headline and a sponsor-safe post under 280 characters. Use only facts returned by the tool; do not invent guests, sponsors, topics, or links." +}); +``` + +다음과 같이 말합니다: "에이전트가 에피소드 도구를 호출하면 읽기 전용 조회를 승인합니다. 응답은 만든 세부 정보가 아니라 공식 피드에 근거합니다." + +예상 단계: model selection, ten-episode selection, `[Tool call started]`, approval prompt, `[Tool call complete]`, then streamed launch copy. + +모델 상태 줄을 업데이트하여 to use the selected `model`, not `Model`. +이벤트 처리기를 유지합니다, bounded completion wait, and `await using` declarations. + +완성된 팟캐스트 에이전트를 실행합니다 같은 폴더에서: + +```powershell +dotnet run +``` + diff --git a/localizations/ko-kr/start-intro/dotnet/README.md b/localizations/ko-kr/start-intro/dotnet/README.md new file mode 100644 index 0000000..100807a --- /dev/null +++ b/localizations/ko-kr/start-intro/dotnet/README.md @@ -0,0 +1,36 @@ +# SDK 101 시작 프로젝트: .NET + +[.NET 10 SDK](https://learn.microsoft.com/dotnet/core/install/)와 +인증된 Copilot 액세스가 필요합니다. + +워크숍 리포지토리 루트에서 다음을 실행합니다. + +```shell +cd start-intro/dotnet +dotnet restore +``` + +이 폴더를 편집기에서 열고(`code .`) [LIVE_DEMO.md](../../../../start-intro/dotnet/LIVE_DEMO.md)의 1막에 나오는 +네 가지 번호가 매겨진 수정 단계를 따라 `Program.cs`를 편집합니다. 그런 다음 다음을 실행합니다. + +```shell +dotnet run +``` + +수정하지 않은 엔트리 포인트(Entrypoint)는 의도적으로 완성되지 않은 상태이며, 작동하는 hello world가 아닙니다. +팟캐스트 에이전트를 만들려면 같은 가이드의 2막을 계속 진행합니다. 미리 빌드된 +`Helpers/` 및 `Tools/` 코드는 편집하지 않고 재사용합니다. + +SDK에는 호환되는 런타임(Runtime)이 포함되어 있습니다. 런타임 다운로드가 차단되거나 CLI를 +찾을 수 없으면 먼저 네이티브 Copilot CLI를 설치하고 인증합니다. 리포지토리의 +`Directory.Build.props`는 일반적인 CLI 위치를 인식합니다. 다른 위치에 설치한 경우 +`COPILOT_CLI_BINARY_PATH`를 해당 네이티브 실행 파일로 설정합니다. +예를 들어 Windows PowerShell에서는 다음과 같이 실행합니다. + +```powershell +$env:COPILOT_CLI_BINARY_PATH = (Get-Command copilot.exe).Source +dotnet run +``` + +액세스 확인 및 문제 해결 방법은 [사전 점검](../../../../workshop/intro-00-preflight.md)을 참조하고, +API는 [공식 .NET SDK API](https://github.com/github/copilot-sdk/tree/main/dotnet)를 참조합니다. diff --git a/localizations/ko-kr/start-intro/go/LIVE_DEMO.md b/localizations/ko-kr/start-intro/go/LIVE_DEMO.md new file mode 100644 index 0000000..1c1f02a --- /dev/null +++ b/localizations/ko-kr/start-intro/go/LIVE_DEMO.md @@ -0,0 +1,185 @@ +# GitHub Podcast 라이브 데모: Go: Go + +## 세션 전 준비 + +1. 이 컴퓨터가 아직 인증되지 않았다면 `copilot auth login`을 실행합니다. +2. 종속성을 다운로드합니다 in the included starter before the timed session: + +```powershell +cd start-intro/go +go mod download +go mod verify +``` + +## 데모 소개 + +다음과 같이 말합니다: "GitHub Podcast용 팟캐스트 에이전트를 만듭니다. 실제 에피소드를 선택하고 공식 RSS 피드에서 확인된 메타데이터를 가져와 해당 사실을 후원사에 안전한 소셜 문구로 바꿀 수 있습니다." + +다음과 같이 말합니다: "가장 작은 Copilot SDK 대화에서 시작한 다음 목적, 정체성 및 애플리케이션 소유 도구를 부여합니다." + +## 1막: Hello World + +`main.go`에서 시작합니다. 여기에는 의도적으로 이름이 지정된 자리 표시자가 있습니다: `client`, `isAuthenticated`, and `session`. 이벤트 처리기는 그대로 둡니다. + +### 1. 클라이언트 시작 + +다음으로 교체합니다 `var client *copilot.Client` and the following `_ = client` with: + +```go +client := copilot.NewClient(&copilot.ClientOptions{LogLevel: "error"}) +if err := client.Start(context.Background()); err != nil { + panic(err) +} +defer client.Stop() +``` + +다음과 같이 말합니다: "클라이언트는 Copilot 런타임에 연결하는 수단입니다. 명시적으로 시작하므로 애플리케이션이 수명 주기를 관리합니다." + +### 2. 인증 확인 + +다음으로 교체합니다 `isAuthenticated := false` with: + +```go +authStatus, err := client.GetAuthStatus(context.Background()) +if err != nil { + panic(err) +} +isAuthenticated := authStatus.IsAuthenticated +``` + +다음과 같이 말합니다: "세션을 만들기 전에 이 컴퓨터가 로그인되어 있는지 런타임에 확인할 수 있습니다." + +### 3. 세션 만들기 + +다음으로 교체합니다 the session placeholder with: + +```go +session, err := client.CreateSession(context.Background(), &copilot.SessionConfig{ + Model: preferredModel, + Streaming: copilot.Bool(true), + AvailableTools: []string{}, + OnPermissionRequest: copilot.PermissionHandler.ApproveAll, +}) +if err != nil { + panic(err) +} +defer session.Disconnect() +``` + +다음과 같이 말합니다: "세션은 대화입니다. 모델을 선택하고 스트리밍을 활성화했습니다, and the event handler below prints each text fragment as it arrives." + +다음과 같이 말합니다: "이 처리기는 권한 요청에 응답합니다. 빈 도구 허용 목록은 이 실습에서 도구 기능을 제거합니다. approve-all만으로는 안전 경계가 되지 않습니다." + +### 4. Hello World 전송 + +Add `"time"` to the imports. Under `// Step 5: Send the first message.`, +replace `_ = context.Background()` with: + +```go +ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) +defer cancel() +if _, err := session.SendAndWait(ctx, copilot.MessageOptions{ + Prompt: "Hello world! In one sentence, say what the Copilot SDK helps a Go app do.", +}); err != nil { + panic(err) +} +fmt.Println() +``` + +기존 항목을 유지합니다 event subscription. `streamResponse`도 호출하지 않습니다. +해당 도우미는 두 번째 구독을 추가하여 각 텍스트 조각을 두 번 출력합니다. + +다음과 같이 말합니다: "기본 형태는 다음과 같습니다: start a client, create a session, listen for events, and send a message. 이 흐름이 작동하면 팟캐스트 에이전트로 발전시킬 수 있습니다." + +다음 Hello World 확인 단계를 실행합니다 now `go` 폴더에서: + +```powershell +go run . +``` + +예상 출력: a streamed one-sentence answer, followed by `SendAndWait` completing the turn. + +## 2막: 팟캐스트 에이전트로 전환 + +Hello World 이후 미리 작성된 도우미를 사용하여 in `helpers.go` and `permission_prompt.go` 같은 세션을 근거 기반 팟캐스트 흐름으로 전환합니다. + +다음과 같이 말합니다: "대화가 작동합니다. 이제 이를 팟캐스트 에이전트로 전환합니다: 선택한 GitHub Podcast 에피소드를 조사하고 사실을 만들지 않고 출시 문구를 준비하는 전문 도우미입니다." + +### 1. 발표자가 선택하도록 설정 + +인증 후 세션을 만들기 전에 다음을 추가합니다: + +```go +selectedModel, err := selectModel(context.Background(), client, preferredModel) +if err != nil { + panic(err) +} +latestEpisodes, err := getLatestEpisodes() +if err != nil { + panic(err) +} +selectedEpisode, err := pickEpisode(latestEpisodes) +if err != nil { + panic(err) +} +``` + +다음과 같이 말합니다: "이렇게 하면 데모를 실시간으로 진행할 수 있습니다. 현장에서 모델을 선택한 다음 실제 최신 GitHub Podcast 에피소드 10개 중 하나를 선택할 수 있습니다. 선택한 항목이 팟캐스트 에이전트의 작업이 됩니다." + +### 2. 세션에 기능 부여 + +도구를 만듭니다: + +```go +episodeTool := createEpisodeTool() +latestEpisodesTool := createLatestEpisodesTool() +``` + +세션을 만듭니다: + +```go +session, err := client.CreateSession(context.Background(), &copilot.SessionConfig{ + Model: selectedModel, + Streaming: copilot.Bool(true), + Tools: []copilot.Tool{episodeTool, latestEpisodesTool}, + AvailableTools: []string{"get_github_podcast_episode", "get_latest_github_podcast_episodes"}, + OnPermissionRequest: permissionPrompt, + SystemMessage: &copilot.SystemMessageConfig{ + Mode: "replace", + Content: "You are the launch assistant for The GitHub Podcast. Use supplied episode facts only.", + }, +}) +if err != nil { + panic(err) +} +defer session.Disconnect() +``` + +다음과 같이 말합니다: "모델에 애플리케이션에 대한 임의의 접근 권한을 주지 않습니다. 범위가 좁고 형식이 지정된 기능 두 개를 부여하고 이름으로 허용 목록에 추가합니다, and swap Hello World's approve-all handler for `permissionPrompt` in `permission_prompt.go`, which denies anything that is not one of these tools and asks me on stdin before one runs." + +다음과 같이 말합니다: "이 도구가 있어 일반 챗봇이 아니라 에이전트가 됩니다: 애플리케이션이 제어하는 신뢰할 수 있는 데이터 원본에 대해 작업을 수행할 수 있습니다." + +다음과 같이 말합니다: "시스템 메시지는 append가 아니라 replace를 사용합니다. 기본 프롬프트를 상속하지 않고 애플리케이션이 이 세션의 에이전트 정체성과 근거 규칙을 완전히 제공합니다." + +### 3. 프롬프트 교체 + +Build the prompt immediately before the existing bounded send: + +```go +prompt := fmt.Sprintf("Use get_github_podcast_episode for the episode titled %q. Return exactly a social headline and a sponsor-safe post under 280 characters. Use only facts returned by the tool; do not invent guests, sponsors, topics, or links.", selectedEpisode.Title) +``` + +`MessageOptions` 필드를 다음으로 변경합니다 `Prompt: prompt`. Update the model-status +line to print `selectedModel`. 기존 항목을 유지합니다 subscription, bounded context, +error check, and deferred cleanup; do not add a second subscription. + +다음과 같이 말합니다: "에이전트가 에피소드 도구를 호출하면 읽기 전용 조회를 승인합니다. 응답은 만든 세부 정보가 아니라 공식 피드에 근거합니다." + +완성된 팟캐스트 에이전트를 실행합니다 now `go` 폴더에서: + +```powershell +go run . +``` + +예상 단계: model selection, ten-episode selection, `[Tool call started]`, approval prompt, `[Tool call complete]`, then streamed launch copy. + diff --git a/localizations/ko-kr/start-intro/go/README.md b/localizations/ko-kr/start-intro/go/README.md new file mode 100644 index 0000000..101ecc7 --- /dev/null +++ b/localizations/ko-kr/start-intro/go/README.md @@ -0,0 +1,25 @@ +# SDK 101 시작 프로젝트: Go + +[Go 1.24 이상](https://go.dev/dl/)과 인증된 Copilot 액세스가 필요합니다. + +워크숍 리포지토리 루트에서 다음을 실행합니다. + +```shell +cd start-intro/go +go mod download +``` + +이 폴더를 편집기에서 열고(`code .`) [LIVE_DEMO.md](../../../../start-intro/go/LIVE_DEMO.md)의 1막에 나오는 +네 가지 번호가 매겨진 수정 단계를 따라 `main.go`를 편집합니다. 그런 다음 다음을 실행합니다. + +```shell +go run . +``` + +수정하지 않은 엔트리 포인트(Entrypoint)는 의도적으로 완성되지 않은 상태이며, 작동하는 hello world가 아닙니다. +팟캐스트 에이전트를 만들려면 같은 가이드의 2막을 계속 진행합니다. +`helpers.go`와 `permission_prompt.go`는 편집하지 않고 재사용합니다. + +Copilot 프롬프트를 보내지 않고 컴파일하려면 `go build -mod=readonly ./...`을 실행합니다. +액세스 확인 및 문제 해결 방법은 [사전 점검](../../../../workshop/intro-00-preflight.md)을 참조하고, +API는 [공식 Go SDK API](https://github.com/github/copilot-sdk/tree/main/go)를 참조합니다. diff --git a/localizations/ko-kr/start-intro/java/LIVE_DEMO.md b/localizations/ko-kr/start-intro/java/LIVE_DEMO.md new file mode 100644 index 0000000..3a9f7f5 --- /dev/null +++ b/localizations/ko-kr/start-intro/java/LIVE_DEMO.md @@ -0,0 +1,196 @@ +# GitHub Podcast 라이브 데모: Java: Java + +## 세션 전 준비 + +1. 이 컴퓨터가 아직 인증되지 않았다면 `copilot auth login`을 실행합니다. +2. 종속성을 컴파일합니다 in the included starter before the timed session: + +```powershell +cd start-intro/java +./mvnw compile +``` + +## 데모 소개 + +다음과 같이 말합니다: "GitHub Podcast용 팟캐스트 에이전트를 만듭니다. 실제 에피소드를 선택하고 공식 RSS 피드에서 확인된 메타데이터를 가져와 해당 사실을 후원사에 안전한 소셜 문구로 바꿀 수 있습니다." + +다음과 같이 말합니다: "가장 작은 Copilot SDK 대화에서 시작한 다음 목적, 정체성 및 애플리케이션 소유 도구를 부여합니다." + +## 1막: Hello World + +Start with `src\main\java\demo\CopilotSdkLiveDemo.java`. 여기에는 의도적으로 이름이 지정된 자리 표시자가 있습니다: `client`, `isAuthenticated`, and `session`. + +### 1. 클라이언트 시작 + +다음으로 교체합니다 `CopilotClient client;` with: + +```java +try (var client = new CopilotClient()) { +client.start().get(); +``` + +This resource block stays open until step 4 adds its closing brace. +실행하기 전에 네 가지 편집을 모두 완료합니다. + +다음과 같이 말합니다: "클라이언트는 Copilot 런타임에 연결하는 수단입니다. 명시적으로 시작하므로 애플리케이션이 수명 주기를 관리합니다." + +### 2. 인증 확인 + +다음으로 교체합니다 `boolean isAuthenticated = false;` with: + +```java +boolean isAuthenticated = client.getAuthStatus().get().isAuthenticated(); +``` + +다음과 같이 말합니다: "세션을 만들기 전에 이 컴퓨터가 로그인되어 있는지 런타임에 확인할 수 있습니다." + +### 3. 세션 만들기 + +다음으로 교체합니다 the session placeholder with: + +```java +var config = new SessionConfig() + .setModel(MODEL) + .setStreaming(true) + .setAvailableTools(List.of()) + .setOnPermissionRequest(PermissionHandler.APPROVE_ALL); +try (var session = client.createSession(config).get()) { +``` + +다음 가져오기를 추가합니다: + +```java +import com.github.copilot.generated.AssistantMessageDeltaEvent; +import com.github.copilot.generated.ToolExecutionStartEvent; +import com.github.copilot.generated.ToolExecutionCompleteEvent; +import com.github.copilot.rpc.MessageOptions; +import com.github.copilot.rpc.PermissionHandler; +import java.util.List; +import java.util.concurrent.TimeUnit; +``` + +다음으로 교체합니다 the comments under `// Step 4: Stream events from the assistant.` with: + +```java +session.on(AssistantMessageDeltaEvent.class, event -> { + String delta = event.getData().deltaContent(); + if (delta != null) { + System.out.print(delta); + } +}); +session.on(ToolExecutionStartEvent.class, event -> + System.out.println("\n[Tool call started] " + event.getData().toolName())); +session.on(ToolExecutionCompleteEvent.class, event -> + System.out.println("\n[Tool call complete]")); +``` + +스타터에서 표시한 위치에 스트리밍 처리기를 추가합니다. The session +resource block also stays open until step 4. + +다음과 같이 말합니다: "세션은 대화입니다. I chose the model and enabled streaming." + +다음과 같이 말합니다: "이 처리기는 권한 요청에 응답합니다. 빈 도구 허용 목록은 이 실습에서 도구 기능을 제거합니다. approve-all만으로는 안전 경계가 되지 않습니다." + +### 4. Hello World 전송 + +Under `// Step 5: Send the first message.`, remove the placeholder `var config` +and its `if (session != null)` guard. 다음으로 교체합니다 them with: + +```java +session.sendAndWait(new MessageOptions() + .setPrompt("Hello world! In one sentence, say what the Copilot SDK helps a Java app do.")) + .get(60, TimeUnit.SECONDS); +System.out.println(); +} +} +``` + +닫는 중괄호 두 개로 세션과 클라이언트 리소스 블록을 +3단계와 1단계에서 마칩니다. 기존 항목을 유지합니다 method and class closing braces. + +다음과 같이 말합니다: "기본 형태는 다음과 같습니다: start a client, create a session, listen for events, and send a message. 이 흐름이 작동하면 팟캐스트 에이전트로 발전시킬 수 있습니다." + +다음 Hello World 확인 단계를 실행합니다 now `java` 폴더에서: + +```powershell +./mvnw compile exec:java +``` + +예상 출력: a streamed one-sentence answer. `sendAndWait` waits for +completion and propagates errors; the resource blocks close both SDK resources. + +## 2막: 팟캐스트 에이전트로 전환 + +After Hello World, use the prewritten helper classes 같은 세션을 근거 기반 팟캐스트 흐름으로 전환합니다. + +다음과 같이 말합니다: "대화가 작동합니다. 이제 이를 팟캐스트 에이전트로 전환합니다: 선택한 GitHub Podcast 에피소드를 조사하고 사실을 만들지 않고 출시 문구를 준비하는 전문 도우미입니다." + +### 1. 발표자가 선택하도록 설정 + +가져오기를 추가합니다 for `com.github.copilot.rpc.SystemMessageConfig` and +`com.github.copilot.SystemMessageMode`. Keep `java.util.List` from Act One. +The `PermissionHandler` import is no longer used once `PermissionPrompt` replaces it below. + +Inside the client resource block, after authentication and before `var config`: + +```java +String selectedModel = ModelSelector.select(client, MODEL); +var latestEpisodes = GitHubPodcastEpisodeTool.latest(); +var selectedEpisode = GitHubPodcastEpisodeTool.pick(latestEpisodes); +``` + +다음과 같이 말합니다: "이렇게 하면 데모를 실시간으로 진행할 수 있습니다. 현장에서 모델을 선택한 다음 실제 최신 GitHub Podcast 에피소드 10개 중 하나를 선택할 수 있습니다. 선택한 항목이 팟캐스트 에이전트의 작업이 됩니다." + +### 2. 세션에 기능 부여 + +도구를 만듭니다: + +```java +var episodeTool = GitHubPodcastEpisodeTool.episodeTool(); +var latestEpisodesTool = GitHubPodcastEpisodeTool.latestEpisodesTool(); +``` + +다음으로 교체합니다 `var config` and its builder chain, keeping the existing +`try (var session = client.createSession(config).get())` block: + +```java +var config = new SessionConfig() + .setModel(selectedModel) + .setStreaming(true) + .setTools(List.of(episodeTool, latestEpisodesTool)) + .setAvailableTools(List.of("get_github_podcast_episode", "get_latest_github_podcast_episodes")) + .setOnPermissionRequest(PermissionPrompt.HANDLER) + .setSystemMessage(new SystemMessageConfig() + .setMode(SystemMessageMode.REPLACE) + .setContent("You are the launch assistant for The GitHub Podcast. Use supplied episode facts only.")); +``` + +다음과 같이 말합니다: "모델에 애플리케이션에 대한 임의의 접근 권한을 주지 않습니다. 범위가 좁고 형식이 지정된 기능 두 개를 부여하고 이름으로 허용 목록에 추가합니다, and swap Hello World's approve-all handler for `PermissionPrompt`, which denies anything that is not one of these tools and asks me on stdin before one runs." + +다음과 같이 말합니다: "이 도구가 있어 일반 챗봇이 아니라 에이전트가 됩니다: 애플리케이션이 제어하는 신뢰할 수 있는 데이터 원본에 대해 작업을 수행할 수 있습니다." + +다음과 같이 말합니다: "The system message uses REPLACE, not APPEND. 기본 프롬프트를 상속하지 않고 애플리케이션이 이 세션의 에이전트 정체성과 근거 규칙을 완전히 제공합니다." + +### 3. 프롬프트 교체 + +다음으로 교체합니다 only the argument of `.setPrompt(...)` in the existing bounded send: + +```java +"Use get_github_podcast_episode for the episode titled \"" + selectedEpisode.title() + "\"" + + ". Return exactly a social headline and a sponsor-safe post under 280 characters. " + + "Use only facts returned by the tool; do not invent guests, sponsors, topics, or links." +``` + +모델 상태 줄을 업데이트하여 to print `selectedModel`. Keep the subscriptions, +60-second wait, and both resource blocks unchanged. + +다음과 같이 말합니다: "에이전트가 에피소드 도구를 호출하면 읽기 전용 조회를 승인합니다. 응답은 만든 세부 정보가 아니라 공식 피드에 근거합니다." + +완성된 팟캐스트 에이전트를 실행합니다 now `java` 폴더에서: + +```powershell +./mvnw compile exec:java +``` + +예상 단계: model selection, ten-episode selection, tool execution, approval prompt, then grounded launch copy. + diff --git a/localizations/ko-kr/start-intro/java/README.md b/localizations/ko-kr/start-intro/java/README.md new file mode 100644 index 0000000..f22da36 --- /dev/null +++ b/localizations/ko-kr/start-intro/java/README.md @@ -0,0 +1,29 @@ +# SDK 101 시작 프로젝트: Java + +[Java 17 이상](https://adoptium.net/)과 인증된 Copilot 액세스가 +필요합니다. Maven Wrapper(`./mvnw`)가 포함되어 있으므로 Maven을 별도로 설치할 +필요가 없습니다. Windows에서는 `./mvnw` 대신 `mvnw.cmd`를 실행합니다. + +워크숍 리포지토리 루트에서 다음을 실행합니다. + +```shell +cd start-intro/java +./mvnw dependency:go-offline +``` + +이 폴더를 편집기에서 열고(`code .`) +[LIVE_DEMO.md](../../../../start-intro/java/LIVE_DEMO.md)의 1막에 나오는 +네 가지 번호가 매겨진 수정 단계를 따라 `src/main/java/demo/CopilotSdkLiveDemo.java`를 +편집합니다. 그런 다음 다음을 실행합니다. + +```shell +./mvnw compile exec:java +``` + +수정하지 않은 엔트리 포인트(Entrypoint)는 의도적으로 완성되지 않은 상태이며, 작동하는 hello world가 아닙니다. +팟캐스트 에이전트를 만들려면 같은 가이드의 2막을 계속 진행합니다. `demo` 패키지에 있는 +도구, 모델 선택 및 권한 헬퍼(Helper) 클래스를 재사용합니다. + +Copilot 프롬프트를 보내지 않고 컴파일하려면 `./mvnw compile`을 실행합니다. +액세스 확인 및 문제 해결 방법은 [사전 점검](../../../../workshop/intro-00-preflight.md)을 참조하고, +API는 [공식 Java SDK API](https://github.com/github/copilot-sdk/tree/main/java)를 참조합니다. diff --git a/localizations/ko-kr/start-intro/nodejs/LIVE_DEMO.md b/localizations/ko-kr/start-intro/nodejs/LIVE_DEMO.md new file mode 100644 index 0000000..bcd39cd --- /dev/null +++ b/localizations/ko-kr/start-intro/nodejs/LIVE_DEMO.md @@ -0,0 +1,185 @@ +# GitHub Podcast 라이브 데모: Node.js: Node.js + +## 세션 전 준비 + +1. 이 컴퓨터가 아직 인증되지 않았다면 `copilot auth login`을 실행합니다. +2. 종속성을 설치합니다 in the included starter before the timed session: + +```powershell +cd start-intro/nodejs +npm ci +``` + +## 데모 소개 + +다음과 같이 말합니다: "GitHub Podcast용 팟캐스트 에이전트를 만듭니다. 실제 에피소드를 선택하고 공식 RSS 피드에서 확인된 메타데이터를 가져와 해당 사실을 후원사에 안전한 소셜 문구로 바꿀 수 있습니다." + +다음과 같이 말합니다: "가장 작은 Copilot SDK 대화에서 시작한 다음 목적, 정체성 및 애플리케이션 소유 도구를 부여합니다." + +## 1막: Hello World + +`src\index.ts`를 엽니다. It has placeholders for `client`, authentication, `session`, and the first prompt. + +### 1. Start the client + +다음으로 교체합니다 `let client: CopilotClient;` with: + +```typescript +const client = new CopilotClient(); +try { + await client.start(); +``` + +`try` 블록은 4단계에서 `finally` 블록을 추가할 때까지 열어 둡니다. +실행하기 전에 네 가지 편집을 모두 완료합니다. + +다음과 같이 말합니다: "클라이언트는 Copilot 런타임에 연결하는 수단입니다. 명시적으로 시작하므로 애플리케이션이 수명 주기를 관리합니다." + +### 2. 인증 확인 + +다음으로 교체합니다 `const isAuthenticated = false;` and the following `if` block with: + +```typescript +const isAuthenticated = (await client.getAuthStatus()).isAuthenticated; +if (!isAuthenticated) { + throw new Error("Run 'copilot auth login' before continuing."); +} +``` + +다음과 같이 말합니다: "세션을 만들기 전에 이 컴퓨터가 로그인되어 있는지 런타임에 확인할 수 있습니다." + +### 3. 스트리밍 세션 만들기 + +다음으로 교체합니다 `let session: CopilotSession;` with: + +```typescript +const session = await client.createSession({ + model, + streaming: true, + availableTools: [], + onPermissionRequest: approveAll, +}); +try { +``` + +파일 상단의 SDK 가져오기에 `approveAll`을 추가합니다: + +```typescript +import { CopilotClient, approveAll, type CopilotSession } from "@github/copilot-sdk"; +``` + +다음과 같이 말합니다: "세션은 대화입니다. 모델을 선택하고 스트리밍을 활성화했습니다, and the event handler below already prints each text fragment as it arrives." + +The session `try` block also stays open until step 4. Leave the existing +streaming handler in place. + +다음과 같이 말합니다: "이 처리기는 권한 요청에 응답합니다. 빈 도구 허용 목록은 이 실습에서 도구 기능을 제거합니다. approve-all만으로는 안전 경계가 되지 않습니다." + +### 4. Hello World 전송 + +Under `// Step 5: Send the first message.`, type: + +```typescript +await session.sendAndWait({ + prompt: "Hello world! In one sentence, say what the Copilot SDK helps a Node.js app do.", +}, 60_000); +console.log(); +} finally { + await session.disconnect(); +} +} finally { + await client.stop(); +} +``` + +다음과 같이 말합니다: "기본 형태는 다음과 같습니다: start a client, create a session, listen for events, and send a message. 이 흐름이 작동하면 팟캐스트 에이전트로 발전시킬 수 있습니다." + +다음 Hello World 확인 단계를 실행합니다 now `nodejs` 폴더에서: + +```powershell +npm start +``` + +예상 출력: a streamed one-sentence answer, followed by `sendAndWait` +차례를 완료합니다. It propagates session errors and limits the wait to +60 seconds. 두 `finally` 블록이 세션과 클라이언트를 닫습니다. + +## 2막: 팟캐스트 에이전트로 전환 + +Hello World 이후 미리 작성된 도우미를 사용하여 in `src` 같은 세션을 근거 기반 팟캐스트 흐름으로 전환합니다. + +다음과 같이 말합니다: "대화가 작동합니다. 이제 이를 팟캐스트 에이전트로 전환합니다: 선택한 GitHub Podcast 에피소드를 조사하고 사실을 만들지 않고 출시 문구를 준비하는 전문 도우미입니다." + +### 1. 발표자가 선택하도록 설정 + +가져오기를 추가합니다: + +```typescript +import { selectModel } from "./model-selector.js"; +import { episodeTool, latestEpisodesTool, getLatestEpisodes, pickEpisode } from "./github-podcast-tools.js"; +import { permissionPrompt } from "./permission-prompt.js"; +``` + +Drop `approveAll` from the SDK import, since `permissionPrompt` replaces it below: + +```typescript +import { CopilotClient, type CopilotSession } from "@github/copilot-sdk"; +``` + +클라이언트 `try` 블록에서 인증 후 세션을 만들기 전에 다음을 추가합니다: + +```typescript +const selectedModel = await selectModel(client, model); +const latestEpisodes = await getLatestEpisodes(); +const selectedEpisode = await pickEpisode(latestEpisodes); +``` + +다음과 같이 말합니다: "이렇게 하면 데모를 실시간으로 진행할 수 있습니다. 현장에서 모델을 선택한 다음 실제 최신 GitHub Podcast 에피소드 10개 중 하나를 선택할 수 있습니다. 선택한 항목이 팟캐스트 에이전트의 작업이 됩니다." + +### 2. 세션에 기능 부여 + +다음으로 교체합니다 the session config with: + +```typescript +const session = await client.createSession({ + model: selectedModel, + streaming: true, + tools: [episodeTool, latestEpisodesTool], + availableTools: ["get_github_podcast_episode", "get_latest_github_podcast_episodes"], + onPermissionRequest: permissionPrompt, + systemMessage: { + mode: "replace", + content: "You are the launch assistant for The GitHub Podcast. Use supplied episode facts only.", + }, +}); +``` + +Keep the session `try` block after this configuration. + +다음과 같이 말합니다: "모델에 애플리케이션에 대한 임의의 접근 권한을 주지 않습니다. 범위가 좁고 형식이 지정된 기능 두 개를 부여하고 이름으로 허용 목록에 추가합니다, and swap Hello World's approve-all handler for one that prompts me, so I remain the approval point before a tool executes." + +다음과 같이 말합니다: "이 도구가 있어 일반 챗봇이 아니라 에이전트가 됩니다: 애플리케이션이 제어하는 신뢰할 수 있는 데이터 원본에 대해 작업을 수행할 수 있습니다." + +다음과 같이 말합니다: "시스템 메시지는 append가 아니라 replace를 사용합니다. 기본 프롬프트를 상속하지 않고 애플리케이션이 이 세션의 에이전트 정체성과 근거 규칙을 완전히 제공합니다." + +### 3. 프롬프트 교체 + +다음으로 교체합니다 only the prompt in the existing `sendAndWait` call with: + +```typescript +prompt: `Use get_github_podcast_episode for the episode titled "${selectedEpisode.title}". Return exactly a social headline and a sponsor-safe post under 280 characters. Use only facts returned by the tool; do not invent guests, sponsors, topics, or links.`, +``` + +다음과 같이 말합니다: "에이전트가 에피소드 도구를 호출하면 읽기 전용 조회를 승인합니다. 응답은 만든 세부 정보가 아니라 공식 피드에 근거합니다." + +완성된 팟캐스트 에이전트를 실행합니다 now `nodejs` 폴더에서: + +```powershell +npm start +``` + +예상 단계: model selection, ten-episode selection, `[Tool call started]`, approval prompt, `[Tool call complete]`, then streamed launch copy. + +모델 상태 줄을 업데이트하여 to use `selectedModel`. 이벤트 처리기를 유지합니다, +60-second timeout, and both `finally` blocks. + diff --git a/localizations/ko-kr/start-intro/nodejs/README.md b/localizations/ko-kr/start-intro/nodejs/README.md new file mode 100644 index 0000000..e80308d --- /dev/null +++ b/localizations/ko-kr/start-intro/nodejs/README.md @@ -0,0 +1,28 @@ +# SDK 101 시작 프로젝트: Node.js + +[Node.js 22.12 이상](https://nodejs.org/)과 인증된 Copilot 액세스가 필요합니다. + +워크숍 리포지토리 루트에서 다음을 실행합니다. + +```shell +cd start-intro/nodejs +npm ci +``` + +이 폴더를 편집기에서 열고(`code .`) [LIVE_DEMO.md](../../../../start-intro/nodejs/LIVE_DEMO.md)의 1막에 나오는 +네 가지 번호가 매겨진 수정 단계를 따라 `src/index.ts`를 편집합니다. 그런 다음 다음을 실행합니다. + +```shell +npm start +``` + +수정하지 않은 엔트리 포인트(Entrypoint)는 의도적으로 완성되지 않은 상태이며, 작동하는 hello world가 아닙니다. +팟캐스트 에이전트를 만들려면 같은 가이드의 2막을 계속 진행합니다. +`src/github-podcast-tools.ts`, `src/model-selector.ts`, +`src/permission-prompt.ts`는 편집하지 않고 재사용합니다. + +Copilot 프롬프트를 보내지 않고 타입 검사를 수행하려면 `npm run build`를 실행합니다. +Copilot 인증이나 네트워크 요청 없이 모의 피드 응답을 사용해 RSS 구문 분석의 회귀를 +검사하려면 `npm test`를 실행합니다. +액세스 확인 및 문제 해결 방법은 [사전 점검](../../../../workshop/intro-00-preflight.md)을 참조하고, +API는 [공식 Node.js SDK API](https://github.com/github/copilot-sdk/tree/main/nodejs)를 참조합니다. diff --git a/localizations/ko-kr/start-intro/python/LIVE_DEMO.md b/localizations/ko-kr/start-intro/python/LIVE_DEMO.md new file mode 100644 index 0000000..2d7f660 --- /dev/null +++ b/localizations/ko-kr/start-intro/python/LIVE_DEMO.md @@ -0,0 +1,201 @@ +# GitHub Podcast 라이브 데모: Python: Python + +## 세션 전 준비 + +1. 이 컴퓨터가 아직 인증되지 않았다면 `copilot auth login`을 실행합니다. +2. 포함된 스타터에서 환경을 만듭니다 before the timed session: + +```powershell +cd start-intro/python +python -m venv .venv +.\.venv\Scripts\python.exe -m pip install -r requirements.txt +``` + +macOS/Linux에서는 다음을 사용합니다: `.venv/bin/python` instead of `.\.venv\Scripts\python.exe`. + +## 데모 소개 + +다음과 같이 말합니다: "GitHub Podcast용 팟캐스트 에이전트를 만듭니다. 실제 에피소드를 선택하고 공식 RSS 피드에서 확인된 메타데이터를 가져와 해당 사실을 후원사에 안전한 소셜 문구로 바꿀 수 있습니다." + +다음과 같이 말합니다: "가장 작은 Copilot SDK 대화에서 시작한 다음 목적, 정체성 및 애플리케이션 소유 도구를 부여합니다." + +## 1막: Hello World + +`main.py`에서 시작합니다. 여기에는 의도적으로 이름이 지정된 자리 표시자가 있습니다: `client`, `is_authenticated`, and `session`. 스트리밍 이벤트 처리기와 완료 대기는 그대로 둡니다. + +### 1. 클라이언트 시작 + +다음으로 교체합니다 `client: CopilotClient` with: + +```python +client = CopilotClient() +await client.start() +``` + +다음과 같이 말합니다: "클라이언트는 Copilot 런타임에 연결하는 수단입니다. 명시적으로 시작하므로 애플리케이션이 수명 주기를 관리합니다." + +### 2. 인증 확인 + +다음으로 교체합니다 `is_authenticated = False` and its `if` block with: + +```python +is_authenticated = (await client.get_auth_status()).isAuthenticated +if not is_authenticated: + await client.stop() + raise RuntimeError("Run 'copilot auth login' before continuing.") +``` + +다음과 같이 말합니다: "세션을 만들기 전에 이 컴퓨터가 로그인되어 있는지 런타임에 확인할 수 있습니다." + +### 3. 세션 만들기 + +다음으로 교체합니다 `session = None` with: + +```python +session = await client.create_session( + model=MODEL, + streaming=True, + available_tools=[], + on_permission_request=PermissionHandler.approve_all, +) +``` + +Widen the SDK import at the top of the file: + +```python +from copilot import CopilotClient, PermissionHandler +``` + +다음과 같이 말합니다: "세션은 대화입니다. 모델을 선택하고 스트리밍을 활성화했습니다, and the event handler below already prints each text fragment as it arrives." + +다음과 같이 말합니다: "이 처리기는 권한 요청에 응답합니다. 빈 도구 허용 목록은 이 실습에서 도구 기능을 제거합니다. approve-all만으로는 안전 경계가 되지 않습니다." + +### 4. Hello World 전송 + +다음으로 교체합니다 everything from `# Step 5: Send the first message.` to the end of +`main()` with this indented block. Keep the module's +`if __name__ == "__main__":` block: + +```python + try: + await session.send( + "Hello world! In one sentence, say what the Copilot SDK helps a Python app do." + ) + await asyncio.wait_for(done.wait(), timeout=60) + if error is not None: + raise error + print() + finally: + await session.disconnect() + await client.stop() +``` + +다음과 같이 말합니다: "기본 형태는 다음과 같습니다: start a client, create a session, listen for events, and send a message. 이 흐름이 작동하면 팟캐스트 에이전트로 발전시킬 수 있습니다." + +다음 Hello World 확인 단계를 실행합니다 `start-intro/python`에서: + +```powershell +.\.venv\Scripts\python.exe main.py +``` + +On macOS/Linux: + +```bash +.venv/bin/python main.py +``` + +예상 출력: a streamed one-sentence answer, followed by `SessionIdleData` completing the program. + +## 2막: 팟캐스트 에이전트로 전환 + +Hello World 이후 미리 작성된 도우미를 사용하여 같은 세션을 근거 기반 팟캐스트 흐름으로 전환합니다. + +다음과 같이 말합니다: "대화가 작동합니다. 이제 이를 팟캐스트 에이전트로 전환합니다: 선택한 GitHub Podcast 에피소드를 조사하고 사실을 만들지 않고 출시 문구를 준비하는 전문 도우미입니다." + +### 1. 발표자가 선택하도록 설정 + +가져오기를 추가합니다: + +```python +from github_podcast_tools import ( + get_latest, + get_latest_github_podcast_episodes, + get_github_podcast_episode, + pick_episode, +) +from model_selector import select_model +from permission_prompt import permission_prompt +``` + +Narrow the SDK import back, since `permission_prompt` replaces `PermissionHandler` below: + +```python +from copilot import CopilotClient +``` + +인증 확인 후 다음을 추가합니다: + +```python +model = await select_model(client, MODEL) +latest_episodes = get_latest() +selected_episode = pick_episode(latest_episodes) +``` + +다음과 같이 말합니다: "이렇게 하면 데모를 실시간으로 진행할 수 있습니다. 현장에서 모델을 선택한 다음 실제 최신 GitHub Podcast 에피소드 10개 중 하나를 선택할 수 있습니다. 선택한 항목이 팟캐스트 에이전트의 작업이 됩니다." + +### 2. 세션에 기능 부여 + +Create the session with tools: + +```python +session = await client.create_session( + model=model, + streaming=True, + tools=[get_github_podcast_episode, get_latest_github_podcast_episodes], + available_tools=["get_github_podcast_episode", "get_latest_github_podcast_episodes"], + on_permission_request=permission_prompt, + system_message={ + "mode": "replace", + "content": "You are the launch assistant for The GitHub Podcast. Use supplied episode facts only.", + }, +) +``` + +다음과 같이 말합니다: "모델에 애플리케이션에 대한 임의의 접근 권한을 주지 않습니다. 범위가 좁고 형식이 지정된 기능 두 개를 부여하고 이름으로 허용 목록에 추가합니다, and swap Hello World's approve-all handler for one that prompts me, so I remain the approval point before a tool executes." + +다음과 같이 말합니다: "이 도구가 있어 일반 챗봇이 아니라 에이전트가 됩니다: 애플리케이션이 제어하는 신뢰할 수 있는 데이터 원본에 대해 작업을 수행할 수 있습니다." + +다음과 같이 말합니다: "시스템 메시지는 append가 아니라 replace를 사용합니다. 기본 프롬프트를 상속하지 않고 애플리케이션이 이 세션의 에이전트 정체성과 근거 규칙을 완전히 제공합니다." + +### 3. 프롬프트 교체 + +다음으로 교체합니다 the prompt with: + +```python +await session.send( + f"Use get_github_podcast_episode for the episode titled \"{selected_episode.title}\". " + "Return exactly a social headline and a sponsor-safe post under 280 characters. " + "Use only facts returned by the tool; do not invent guests, sponsors, topics, or links." +) +``` + +다음과 같이 말합니다: "에이전트가 에피소드 도구를 호출하면 읽기 전용 조회를 승인합니다. 응답은 만든 세부 정보가 아니라 공식 피드에 근거합니다." + +이벤트 처리기를 유지합니다, completion wait, and cleanup unchanged. 다음으로 교체합니다 only +the `session.send(...)` call inside the existing `try` block. Update the +model-status line to print the selected `model`. + +완성된 팟캐스트 에이전트를 실행합니다 같은 폴더에서: + +```powershell +.\.venv\Scripts\python.exe main.py +``` + +On macOS/Linux: + +```bash +.venv/bin/python main.py +``` + +예상 단계: model selection, ten-episode selection, `[Tool call started]`, approval prompt, `[Tool call complete]`, then streamed launch copy. + diff --git a/localizations/ko-kr/start-intro/python/README.md b/localizations/ko-kr/start-intro/python/README.md new file mode 100644 index 0000000..9a54202 --- /dev/null +++ b/localizations/ko-kr/start-intro/python/README.md @@ -0,0 +1,33 @@ +# SDK 101 시작 프로젝트: Python + +[Python 3.11 이상](https://www.python.org/downloads/)과 인증된 Copilot 액세스가 필요합니다. + +Windows PowerShell에서 워크숍 리포지토리 루트로 이동한 후 다음을 실행합니다. + +```powershell +cd start-intro/python +python -m venv .venv +.\.venv\Scripts\python.exe -m pip install -r requirements.txt +``` + +macOS/Linux에서는 다음을 실행합니다. + +```bash +cd start-intro/python +python3 -m venv .venv +.venv/bin/python -m pip install -r requirements.txt +``` + +이 폴더를 편집기에서 열고(`code .`) [LIVE_DEMO.md](../../../../start-intro/python/LIVE_DEMO.md)의 1막에 나오는 +네 가지 번호가 매겨진 수정 단계를 따라 `main.py`를 편집합니다. +Windows에서는 `.\.venv\Scripts\python.exe main.py`를 실행하고, +macOS/Linux에서는 `.venv/bin/python main.py`를 실행합니다. 인터프리터(Interpreter)를 직접 사용하면 +활성화 및 실행 정책을 변경하지 않아도 됩니다. + +수정하지 않은 엔트리 포인트(Entrypoint)는 의도적으로 완성되지 않은 상태이며, 작동하는 hello world가 아닙니다. +팟캐스트 에이전트를 만들려면 같은 가이드의 2막을 계속 진행합니다. +`github_podcast_tools.py`, `model_selector.py`, `permission_prompt.py`는 +편집하지 않고 재사용합니다. + +액세스 확인 및 문제 해결 방법은 [사전 점검](../../../../workshop/intro-00-preflight.md)을 참조하고, +API는 [공식 Python SDK API](https://github.com/github/copilot-sdk/tree/main/python)를 참조합니다. diff --git a/localizations/ko-kr/start-intro/rust/LIVE_DEMO.md b/localizations/ko-kr/start-intro/rust/LIVE_DEMO.md new file mode 100644 index 0000000..338f4ee --- /dev/null +++ b/localizations/ko-kr/start-intro/rust/LIVE_DEMO.md @@ -0,0 +1,217 @@ +# GitHub Podcast 라이브 데모: Rust: Rust + +## 세션 전 준비 + +1. 이 컴퓨터가 아직 인증되지 않았다면 `copilot auth login`을 실행합니다. +2. 포함된 스타터에서 종속성을 다운로드하고 확인합니다 before the timed session: + +```powershell +cd start-intro/rust +cargo check --locked +``` + +## 데모 소개 + +다음과 같이 말합니다: "GitHub Podcast용 팟캐스트 에이전트를 만듭니다. 실제 에피소드를 선택하고 공식 RSS 피드에서 확인된 메타데이터를 가져와 해당 사실을 후원사에 안전한 소셜 문구로 바꿀 수 있습니다." + +다음과 같이 말합니다: "가장 작은 Copilot SDK 대화에서 시작한 다음 목적, 정체성 및 애플리케이션 소유 도구를 부여합니다." + +## 1막: Hello World + +`src\main.rs`에서 시작합니다. 여기에는 의도적으로 이름이 지정된 자리 표시자가 있습니다: `client`, `is_authenticated`, and the session. + +Add this import: + +```rust +use std::time::Duration; +use github_copilot_sdk::types::{MessageOptions, SessionConfig}; +``` + +다음으로 교체합니다 the existing `SessionConfig` import; keep `std::io::{self, Write}`. + +### 1. 클라이언트 시작 + +다음으로 교체합니다 the client placeholder with: + +```rust +let client = Client::start(ClientOptions::default()).await?; +``` + +다음과 같이 말합니다: "클라이언트는 Copilot 런타임에 연결하는 수단입니다. 명시적으로 시작하므로 애플리케이션이 수명 주기를 관리합니다." + +### 2. 인증 확인 + +다음으로 교체합니다 `let is_authenticated = false;` and its `if` block with: + +```rust +let is_authenticated = client.get_auth_status().await?.is_authenticated; +if !is_authenticated { + client.stop().await?; + return Err("Run 'copilot auth login' before continuing.".into()); +} +``` + +다음과 같이 말합니다: "세션을 만들기 전에 이 컴퓨터가 로그인되어 있는지 런타임에 확인할 수 있습니다." + +### 3. 세션 만들기 + +다음으로 교체합니다 `let session_is_created = false;` and the empty +`if session_is_created { ... }` block with: + +```rust +let mut config = SessionConfig::default(); +config.model = Some(MODEL.to_owned()); +config.streaming = Some(true); +config.available_tools = Some(vec![]); +config.permission_handler = Some(github_copilot_sdk::permission::approve_all()); +let session = client.create_session(config).await?; +``` + +다음과 같이 말합니다: "세션은 대화입니다. I chose the model and enabled streaming." + +다음과 같이 말합니다: "이 처리기는 권한 요청에 응답합니다. 빈 도구 허용 목록은 이 실습에서 도구 기능을 제거합니다. approve-all만으로는 안전 경계가 되지 않습니다." + +### 4. Hello World 전송 + +Under `// Step 5: Send the first message.`, replace the three `let _ = ...` +placeholder lines with: + +```rust +let prompt = "Hello world! In one sentence, say what the Copilot SDK helps a Rust app do.".to_owned(); +let turn = async { + let mut events = session.subscribe(); + let send = session.send(MessageOptions::new(prompt)); + tokio::pin!(send); + let mut sent = false; + let mut idle = false; + while !sent || !idle { + tokio::select! { + result = &mut send, if !sent => { + result?; + sent = true; + } + event = events.recv() => { + let event = event?; + match event.event_type.as_str() { + "assistant.message_delta" => { + if let Some(delta) = event.data.get("deltaContent").and_then(|v| v.as_str()) { + print!("{delta}"); + io::stdout().flush()?; + } + } + "tool.execution_start" => println!("\n[Tool call started] {}", event.data), + "tool.execution_complete" => println!("\n[Tool call complete]"), + "session.error" => { + return Err(format!("Copilot session failed: {}", event.data).into()); + } + "session.idle" => idle = true, + _ => {} + } + } + } + } + Ok::<(), Box>(()) +}; +let result = tokio::time::timeout(Duration::from_secs(60), turn).await; +session.disconnect().await?; +client.stop().await?; +result??; +println!(); +``` + +마지막 `Ok(())`를 유지합니다. 이 루프는 누락된 스트리밍 구독을 추가하고, +전송 전에 수신을 시작하며 전송 완료와 idle을 모두 기다립니다. It surfaces +errors 또는 60초 제한 시간 세션과 클라이언트를 닫은 후 + +다음과 같이 말합니다: "기본 형태는 다음과 같습니다: start a client, create a session, subscribe to events, and send a message. 이 흐름이 작동하면 팟캐스트 에이전트로 발전시킬 수 있습니다." + +다음 Hello World 확인 단계를 실행합니다 now `rust` 폴더에서: + +```powershell +cargo run --locked +``` + +예상 출력: a one-sentence answer streamed to the terminal, then program exit. + +## 2막: 팟캐스트 에이전트로 전환 + +After Hello World, use the prewritten code in `src\workshop.rs` 같은 세션을 근거 기반 팟캐스트 흐름으로 전환합니다. + +다음과 같이 말합니다: "대화가 작동합니다. 이제 이를 팟캐스트 에이전트로 전환합니다: 선택한 GitHub Podcast 에피소드를 조사하고 사실을 만들지 않고 출시 문구를 준비하는 전문 도우미입니다." + +### 1. 발표자가 선택하도록 설정 + +Add: + +```rust +mod workshop; +``` + +Add `SystemMessageConfig` to the existing types import: + +```rust +use github_copilot_sdk::types::{MessageOptions, SessionConfig, SystemMessageConfig}; +``` + +After authentication, before constructing `config`: + +```rust +let selected_model = workshop::select_model(&client, MODEL).await?; +let latest_episodes = workshop::get_latest_episodes().await?; +let selected_episode = workshop::pick_episode(&latest_episodes)?; +``` + +다음과 같이 말합니다: "이렇게 하면 데모를 실시간으로 진행할 수 있습니다. 현장에서 모델을 선택한 다음 실제 최신 GitHub Podcast 에피소드 10개 중 하나를 선택할 수 있습니다. 선택한 항목이 팟캐스트 에이전트의 작업이 됩니다." + +### 2. 세션에 기능 부여 + +세션을 만듭니다: + +```rust +let mut config = SessionConfig::default(); +config.model = selected_model; +config.streaming = Some(true); +config.tools = Some(vec![workshop::episode_tool(), workshop::latest_episodes_tool()]); +config.available_tools = Some(vec![ + "get_github_podcast_episode".to_owned(), + "get_latest_github_podcast_episodes".to_owned(), +]); +config.permission_handler = Some(workshop::permission_prompt()); +config = config.with_system_message( + SystemMessageConfig::new() + .with_mode("replace") + .with_content( + "You are the launch assistant for The GitHub Podcast. Use supplied episode facts only.", + ), +); +let session = client.create_session(config).await?; +``` + +다음과 같이 말합니다: "모델에 애플리케이션에 대한 임의의 접근 권한을 주지 않습니다. 범위가 좁고 형식이 지정된 기능 두 개를 부여하고 이름으로 허용 목록에 추가합니다, and swap Hello World's approve-all handler for `workshop::permission_prompt`, which denies anything that is not one of these tools and asks me on stdin before one runs." + +다음과 같이 말합니다: "이 도구가 있어 일반 챗봇이 아니라 에이전트가 됩니다: 애플리케이션이 제어하는 신뢰할 수 있는 데이터 원본에 대해 작업을 수행할 수 있습니다." + +다음과 같이 말합니다: "시스템 메시지는 append가 아니라 replace를 사용합니다. 기본 프롬프트를 상속하지 않고 애플리케이션이 이 세션의 에이전트 정체성과 근거 규칙을 완전히 제공합니다." + +### 3. 프롬프트 교체 + +다음으로 교체합니다 only the `let prompt = ...` line. Keep the streaming loop, +timeout, and cleanup: + +```rust +let prompt = format!( + "Use get_github_podcast_episode for the episode titled \"{}\". Return exactly a social headline and a sponsor-safe post under 280 characters. Use only facts returned by the tool; do not invent guests, sponsors, topics, or links.", + selected_episode.title, +); +``` + +다음과 같이 말합니다: "에이전트가 에피소드 도구를 호출하면 읽기 전용 조회를 승인합니다. 응답은 만든 세부 정보가 아니라 공식 피드에 근거합니다." + +완성된 팟캐스트 에이전트를 실행합니다 now `rust` 폴더에서: + +```powershell +cargo run --locked +``` + +예상 단계: model selection, ten-episode selection, tool execution, approval prompt, then grounded launch copy. + diff --git a/localizations/ko-kr/start-intro/rust/README.md b/localizations/ko-kr/start-intro/rust/README.md new file mode 100644 index 0000000..ccbb86c --- /dev/null +++ b/localizations/ko-kr/start-intro/rust/README.md @@ -0,0 +1,28 @@ +# SDK 101 시작 프로젝트: Rust + +[Rust 1.94 이상](https://rustup.rs/)과 인증된 Copilot 액세스가 필요합니다. +Windows에서는 기본 MSVC 툴체인(Toolchain)에 C++ 빌드 도구와 Windows +SDK도 필요합니다. 세션을 시작하기 전에 [Rust 설치 단계](https://doc.rust-lang.org/book/ch01-01-installation.html)를 +완료합니다. + +워크숍 리포지토리 루트에서 다음을 실행합니다. + +```shell +cd start-intro/rust +cargo fetch --locked +cargo check --locked +``` + +이 폴더를 편집기에서 열고(`code .`) [LIVE_DEMO.md](../../../../start-intro/rust/LIVE_DEMO.md)의 1막에 나오는 +네 가지 번호가 매겨진 수정 단계를 따라 `src/main.rs`를 편집합니다. 그런 다음 다음을 실행합니다. + +```shell +cargo run --locked +``` + +수정하지 않은 엔트리 포인트(Entrypoint)는 의도적으로 완성되지 않은 상태이며, 작동하는 hello world가 아닙니다. +팟캐스트 에이전트를 만들려면 같은 가이드의 2막을 계속 진행합니다. +`src/workshop.rs`는 편집하지 않고 재사용합니다. + +액세스 확인 및 문제 해결 방법은 [사전 점검](../../../../workshop/intro-00-preflight.md)을 참조하고, +API는 [공식 Rust SDK API](https://github.com/github/copilot-sdk/tree/main/rust)를 참조합니다. diff --git a/localizations/ko-kr/start-museum/README.md b/localizations/ko-kr/start-museum/README.md new file mode 100644 index 0000000..5f23e13 --- /dev/null +++ b/localizations/ko-kr/start-museum/README.md @@ -0,0 +1,42 @@ +# 박물관 전시 스튜디오 시작 프로젝트 + +워크숍에서 사용할 언어의 디렉터리를 선택하고 해당 디렉터리 안에서 직접 작업합니다. 디렉터리로 +이동한 후 같은 폴더를 편집기에서 열고(폴더 안에서 `code .`을 실행하거나 다른 편집기의 폴더 열기 +명령 사용) 터미널도 해당 위치에 유지합니다. 이 시작 프로젝트에는 고정된 종속성, 최소 실행 파일, +미리 빌드된 큐레이터 헬퍼 모듈 하나가 포함되어 있습니다. 헬퍼에는 직접 작성할 필요가 없는 기반 +기능이 들어 있습니다. 여기에는 승인된 사실 집합과 해당 범위, 큐레이터에게 이러한 사실을 전달하는 +미리 빌드된 `approved_fact_lookup` 로컬 도구, 사용할 수 있는 조사 결과가 있을 때 캡처된 조사 내용과 +인용을 반환하는 미리 빌드된 `approved_wikipedia_fact_lookup` 로컬 도구, 스트리밍 출력기, +결정론적 전시 검증, 범위가 지정된 Wikipedia MCP 서버와 기본 거부 방식의 권한 처리기, +단일 파일 `exhibit.html` 쓰기 권한, 간단한 터미널 프롬프트가 포함됩니다. +헬퍼는 편집하지 않습니다. + +시작 프로젝트에는 큐레이터 시스템 메시지, 전시 프롬프트, 세션 구성, 도구 등록 또는 오케스트레이션 +(Orchestration)이 포함되어 있지 **않습니다**. 학습 과정에서 이를 직접 작성합니다. 먼저 세션을 만들고, +이어서 스트리밍, 큐레이터 음성, 사실 도구 등록과 제한된 세션 실행기를 사용하는 해당 프롬프트, +검증 보고서, 범위가 지정된 Wikipedia 조사, 대화형 `exhibit.html` 페이지를 구현합니다. +각 시작 프로젝트의 엔트리 포인트에는 각 단계의 코드를 정확히 어디에 작성해야 하는지 나타내는 +주석이 있습니다. [`workshop/museum-00-preflight.md`](../../../workshop/museum-00-preflight.md)에서 시작합니다. + +6단계에서는 승인된 사실 조회와 함께 Wikipedia 조회를 조건부로 등록하고, 내러티브와 방문자 질문을 +작성하기 전에 두 도구를 모두 호출하도록 큐레이터에게 요청합니다. 조사 내용은 보충 자료이며 교육자가 +검증한 자료가 아닙니다. 승인된 사실이 우선합니다. 조사를 거부하거나 사용할 수 있는 인용 요약을 +받지 못하면 `approved_fact_lookup`만 사용해 생성을 진행합니다. + +| 언어 | 헬퍼 모듈 | 디렉터리 이동, 빌드 및 실행 | +|---|---|---| +| .NET | `Helpers/Curator*.cs` | `cd start-museum/dotnet && dotnet build && dotnet run` | +| Node.js | `src/curator.ts` | `cd start-museum/nodejs && npm ci && npm run build && npm start` | +| Python | `curator.py` | `cd start-museum/python && python -m venv .venv && .venv/bin/python -m pip install -r requirements.txt && .venv/bin/python main.py` | +| Go | `curator.go` | `cd start-museum/go && go build -mod=readonly ./... && go run .` | +| Rust | `src/lib.rs` | `cd start-museum/rust && cargo check --locked && cargo run --locked` | +| Java | `src/main/java/workshop/Curator*.java` | `cd start-museum/java && ./mvnw compile && ./mvnw exec:java` | + +시작 프로젝트를 실행하면 프로젝트 식별 정보가 출력되며 Copilot을 시작하거나 인증을 요구하지 않습니다. +이 파일을 제자리에서 편집하므로 변경 내용이 `git status`에 표시됩니다. 이는 정상입니다. 리포지토리 +루트에서 `git checkout -- .`을 실행하면 깨끗한 시작 프로젝트 상태로 복원됩니다. + +각 시작 프로젝트에는 완성된 애플리케이션에 필요한 종속성이 이미 고정되어 있으므로 워크숍 중에 +프로젝트 매니페스트(Manifest)를 편집할 필요가 없습니다. Rust 시작 프로젝트는 `src/lib.rs`에서 +`museum_exhibit_studio` 라이브러리 크레이트(Crate)를 빌드합니다. `src/main.rs`에서 해당 헬퍼를 +가져옵니다. diff --git a/localizations/ko-kr/workshop/00-preflight.md b/localizations/ko-kr/workshop/00-preflight.md new file mode 100644 index 0000000..07a101d --- /dev/null +++ b/localizations/ko-kr/workshop/00-preflight.md @@ -0,0 +1,632 @@ +# 사전 점검: 컴퓨터 준비 + +> **시간 제한 없음** +> 115분 워크숍을 시작하기 전에 이 페이지를 완료합니다. + +## 준비할 항목 + +사전 점검이 끝나면 리포지토리를 복제하고 Copilot CLI를 인증하며 +스타터 프로젝트를 빌드하고 Playwright MCP를 다운로드하여 사용할 수 있습니다. + +모델 선택과 대화형 HTML 보고서를 포함한 아홉 가지 실습 단계를 모두 진행한 후 +축하와 추가 리소스로 마무리합니다. + +:::language dotnet +## 필요한 항목 + +| 요구 사항 | 워크숍에서 필요한 이유 | 확인 | +|---|---|---| +| [.NET 10 SDK](https://learn.microsoft.com/dotnet/core/install/) | 빌드 및 실행 the C# console application | `dotnet --version` | +| [Node.js 22 이상](https://nodejs.org/) | Playwright MCP 서버를 실행합니다 | `node --version` | +| [GitHub Copilot CLI](https://docs.github.com/en/copilot/how-tos/set-up/install-copilot-cli) | SDK에서 사용하는 Copilot 런타임을 제공합니다 | `copilot --version` | +| [GitHub Copilot access](https://github.com/features/copilot) | Copilot 요청을 인증합니다 | `copilot login` | +| Microsoft Edge (default) or Google Chrome | Playwright가 대상 페이지를 검사할 수 있게 합니다 | 워크숍 전에 브라우저를 한 번 엽니다 | + +명령은 다음과 같은 형식의 출력을 반환해야 합니다. + +```text +$ dotnet --version +10.0.x +$ node --version +v22.x.x +$ copilot --version +GitHub Copilot CLI ... +``` +::: + +:::language nodejs +## 필요한 항목 + +| 요구 사항 | 워크숍에서 필요한 이유 | 확인 | +|---|---|---| +| [Node.js 22.12 이상](https://nodejs.org/) | TypeScript 워크숍 앱과 Playwright MCP를 실행합니다 | `node --version` | +| [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) | `@github/copilot-sdk`와 빌드 도구를 설치합니다 | `npm --version` | +| [GitHub Copilot CLI](https://docs.github.com/en/copilot/how-tos/set-up/install-copilot-cli) | SDK에서 사용하는 Copilot 런타임을 제공합니다 | `copilot --version` | +| [GitHub Copilot access](https://github.com/features/copilot) | Copilot 요청을 인증합니다 | `copilot login` | +| Microsoft Edge (default) or Google Chrome | Playwright가 대상 페이지를 검사할 수 있게 합니다 | 워크숍 전에 브라우저를 한 번 엽니다 | + +명령은 다음과 같은 형식의 출력을 반환해야 합니다. + +```text +$ node --version +v22.12.x +$ npm --version +10.x.x +$ copilot --version +GitHub Copilot CLI ... +``` + +공식 문서는 다음을 참조합니다 +[Node.js SDK 설치 가이드](https://github.com/github/copilot-sdk/tree/main/nodejs). +::: + +:::language python +## 필요한 항목 + +| 요구 사항 | 워크숍에서 필요한 이유 | 확인 | +|---|---|---| +| [Python 3.11 이상](https://www.python.org/downloads/) | 비동기 워크숍 애플리케이션을 실행합니다 | `python --version` | +| [pip](https://pip.pypa.io/en/stable/installation/) | 고정된 `github-copilot-sdk` 휠을 설치합니다 | `python -m pip --version` | +| [Node.js 22 이상](https://nodejs.org/) | Playwright MCP 서버를 실행합니다 | `node --version` | +| [GitHub Copilot CLI](https://docs.github.com/en/copilot/how-tos/set-up/install-copilot-cli) | `COPILOT_CLI_PATH`를 통한 선택적 로컬 런타임 재정의 | `copilot --version` | +| [GitHub Copilot access](https://github.com/features/copilot) | Copilot 요청을 인증합니다 | `copilot login` | +| Microsoft Edge (default) or Google Chrome | Playwright가 대상 페이지를 검사할 수 있게 합니다 | 워크숍 전에 브라우저를 한 번 엽니다 | + +명령은 다음과 같은 형식의 출력을 반환해야 합니다. + +```text +$ python --version +Python 3.11.x +$ node --version +v22.x.x +$ copilot --version +GitHub Copilot CLI ... +``` + +Python SDK는 처음 사용할 때 고정된 런타임을 다운로드할 수 있습니다. 공식 문서는 다음을 참조합니다 +[Python SDK 설치 가이드](https://github.com/github/copilot-sdk/tree/main/python). +::: + +:::language go +## 필요한 항목 + +| 요구 사항 | 워크숍에서 필요한 이유 | 확인 | +|---|---|---| +| [Go 1.24 이상](https://go.dev/dl/) | 빌드 및 실행 the Go workshop module | `go version` | +| [Node.js 22 이상](https://nodejs.org/) | Playwright MCP 서버를 실행합니다 | `node --version` | +| [GitHub Copilot CLI](https://docs.github.com/en/copilot/how-tos/set-up/install-copilot-cli) | SDK를 위해 `PATH`(또는 `COPILOT_CLI_PATH`)에 필요합니다 | `copilot --version` | +| [GitHub Copilot access](https://github.com/features/copilot) | Copilot 요청을 인증합니다 | `copilot login` | +| Microsoft Edge (default) or Google Chrome | Playwright가 대상 페이지를 검사할 수 있게 합니다 | 워크숍 전에 브라우저를 한 번 엽니다 | + +명령은 다음과 같은 형식의 출력을 반환해야 합니다. + +```text +$ go version +go version go1.24.x ... +$ node --version +v22.x.x +$ copilot --version +GitHub Copilot CLI ... +``` + +공식 문서는 다음을 참조합니다 +[Go SDK 설치 가이드](https://github.com/github/copilot-sdk/tree/main/go). +::: + +:::language rust +## 필요한 항목 + +| 요구 사항 | 워크숍에서 필요한 이유 | 확인 | +|---|---|---| +| [Rust 1.94 이상](https://rustup.rs/) | 비동기 Rust 워크숍 크레이트를 빌드합니다 | `rustc --version` | +| [Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html) | 잠긴 종속성을 확인하고 앱을 실행합니다 | `cargo --version` | +| [Node.js 22 이상](https://nodejs.org/) | Playwright MCP 서버를 실행합니다 | `node --version` | +| [GitHub Copilot CLI](https://docs.github.com/en/copilot/how-tos/set-up/install-copilot-cli) | 번들 바이너리만 사용하지 않을 때 사용하는 런타임입니다 | `copilot --version` | +| [GitHub Copilot access](https://github.com/features/copilot) | Copilot 요청을 인증합니다 | `copilot login` | +| Microsoft Edge (default) or Google Chrome | Playwright가 대상 페이지를 검사할 수 있게 합니다 | 워크숍 전에 브라우저를 한 번 엽니다 | + +명령은 다음과 같은 형식의 출력을 반환해야 합니다. + +```text +$ rustc --version +rustc 1.94.x +$ cargo --version +cargo 1.94.x +$ node --version +v22.x.x +$ copilot --version +GitHub Copilot CLI ... +``` + +공식 문서는 다음을 참조합니다 +[Rust SDK 설치 가이드](https://github.com/github/copilot-sdk/tree/main/rust). +::: + +:::language java +## 필요한 항목 + +| 요구 사항 | 워크숍에서 필요한 이유 | 확인 | +|---|---|---| +| [Java 17 이상](https://adoptium.net/) (JDK) | Compiles and runs the Maven workshop app | `java -version` | +| [Node.js 22 이상](https://nodejs.org/) | Playwright MCP 서버를 실행합니다 | `node --version` | +| [GitHub Copilot CLI](https://docs.github.com/en/copilot/how-tos/set-up/install-copilot-cli) | Required on `PATH` for the Java SDK runtime | `copilot --version` | +| [GitHub Copilot access](https://github.com/features/copilot) | Copilot 요청을 인증합니다 | `copilot login` | +| Microsoft Edge (default) or Google Chrome | Playwright가 대상 페이지를 검사할 수 있게 합니다 | 워크숍 전에 브라우저를 한 번 엽니다 | + +명령은 다음과 같은 형식의 출력을 반환해야 합니다. + +```text +$ java -version +openjdk version "17.x.x" ... +$ node --version +v22.x.x +$ copilot --version +GitHub Copilot CLI ... +``` + +No separate Maven install is needed: each Java project includes the Maven Wrapper (`./mvnw`), +which downloads the right Maven version on first use. On Windows, run `mvnw.cmd` instead of +`./mvnw`. Use Maven for this track. Do not substitute JBang or Gradle. 공식 문서는 다음을 참조합니다 +[Java SDK 설치 가이드](https://github.com/github/copilot-sdk/tree/main/java). +::: + +## 1. 리포지토리 복제 및 스타터 선택 + +```bash +git clone https://github.com/github/copilot-sdk-workshop.git +cd copilot-sdk-workshop +``` + +리포지토리 **안에서 직접 작업합니다**. 복사 단계는 없습니다. 스타터 +directory for your language and stay there for the whole workshop. That means you are editing +추적되는 리포지토리 파일을 수정하므로 변경 사항이 `git status`에 표시됩니다. 이는 예상된 동작입니다. 깨끗한 +스타터로 되돌리려면 리포지토리 루트에서 `git checkout -- .`을 실행하여 편집 내용을 삭제합니다. + +## 2. Copilot 인증 + +공식 방법으로 CLI를 설치한 후 +[official setup guide](https://docs.github.com/en/copilot/how-tos/set-up/install-copilot-cli), then run: + +```bash +copilot login +``` + +브라우저 인증 흐름을 완료합니다 so later SDK calls can reach GitHub Copilot. + +## 3. Playwright MCP 준비 + +다음 명령을 한 번 실행하여 다운로드합니다 the pinned package and print its options 서버를 시작하지 않고: + +```bash +npx -y @playwright/mcp@0.0.78 --help +``` + +패키지 버전은 고정되어 있으므로 모두가 동일한 도구 이름과 동작을 확인합니다. 코드는 +Microsoft Edge with `--browser=msedge`. Google Chrome을 준비한 경우, use +`--browser=chrome` when the argument appears in Step 4. + +:::language dotnet +## 4. 스타터로 이동하여 빌드 + +나중에 `dotnet build`가 Copilot CLI를 찾지 못하면 현재 터미널에 경로를 설정합니다: + +
+
+ + +
+
+
$env:COPILOT_CLI_BINARY_PATH = (Get-Command copilot).Source
+
+ +
+ +다음 디렉터리로 이동하여 .NET starter 빌드합니다. 이 디렉터리에서 계속 작업합니다 이후 모든 단계에서: + +```bash +cd start-accessibility/dotnet +dotnet build +``` + +빌드에 성공하면 ends with: + +```text +Build succeeded. + 0 Warning(s) + 0 Error(s) +``` + +다음 디렉터리에서 작업하므로 `start-accessibility/dotnet` for the rest of the workshop, so keep this terminal here. From +this folder, enter `code .` VS Code에서 열려면, 또는 선호하는 편집기에서 폴더를 엽니다. + +접속할 수 있는지 확인하려면 제어 대상 페이지를 한 번 엽니다. + +```text +{{TARGET_APP_URL}} +``` + +
+사전 점검 문제 해결 + +| 증상 | 해결 방법 | +|---|---| +| `copilot` is not recognized | 터미널을 다시 시작합니다 after installation, or set `COPILOT_CLI_BINARY_PATH` with the command above. | +| Copilot에서 인증을 요청합니다 | `copilot login`을 실행하고 브라우저 인증을 완료한 후 다시 시도합니다. | +| NuGet restore cannot reach the package source | Check proxy or package-source settings, then run `dotnet restore`. | +| `npx` is not recognized | Node.js를 설치합니다 22 이상 and restart the terminal. | +| 나중에 브라우저가 시작되지 않습니다 | Edge 또는 Chrome을 설치합니다, or follow the [Playwright MCP browser configuration](https://github.com/microsoft/playwright-mcp#configuration). | + +
+ +> **다음 조건을 충족하면 1단계를 시작합니다:** `dotnet build` 성공하고, `copilot login` 완료되고, and the target page +> opens. +::: + +:::language nodejs +## 4. 스타터로 이동하여 빌드 + +나중에 SDK가 Copilot CLI를 찾지 못하면 현재 터미널에서 설치 경로를 지정합니다: + +
+
+ + +
+
+
$env:COPILOT_CLI_PATH = (Get-Command copilot).Source
+
+ +
+ +다음 디렉터리로 이동하여 Node.js starter, install dependencies, and type-check. 이 디렉터리에서 계속 작업합니다 for +every later step: + +```bash +cd start-accessibility/nodejs +npm install +npm run build +``` + +형식 검사가 성공하면 TypeScript 오류 없이 끝납니다(`tsc --noEmit`의 출력이 비어 있음). +`package.json` start script is `tsx src/index.ts`. + +다음 디렉터리에서 작업하므로 `start-accessibility/nodejs` for the rest of the workshop, so keep this terminal here. From +this folder, enter `code .` VS Code에서 열려면, 또는 선호하는 편집기에서 폴더를 엽니다. + +접속할 수 있는지 확인하려면 제어 대상 페이지를 한 번 엽니다. + +```text +{{TARGET_APP_URL}} +``` + +
+사전 점검 문제 해결 + +| 증상 | 해결 방법 | +|---|---| +| `node` or `npm` is not recognized | Node.js를 설치합니다 22.12 이상 and restart the terminal. | +| Engine warning about Node version | Upgrade to Node.js 22.12+; the starter declares `"node": ">=22.12.0"`. | +| `npm install` fails on the lockfile | Stay in `start-accessibility/nodejs` and keep `package-lock.json`; do not delete it. | +| `copilot` is not recognized | 터미널을 다시 시작합니다 after installation, or set `COPILOT_CLI_PATH` with the command above. | +| Copilot에서 인증을 요청합니다 | `copilot login`을 실행하고 브라우저 인증을 완료한 후 다시 시도합니다. | +| `npx` cannot download Playwright MCP | Check network access, then rerun the warm-up command from section 3. | +| 나중에 브라우저가 시작되지 않습니다 | Edge 또는 Chrome을 설치합니다, or follow the [Playwright MCP browser configuration](https://github.com/microsoft/playwright-mcp#configuration). | + +
+ +> **다음 조건을 충족하면 1단계를 시작합니다:** `npm run build` 성공하고, `copilot login` 완료되고, and the target page +> opens. +::: + +:::language python +## 4. 스타터로 이동하여 빌드 + +선택 사항: force the SDK to use your installed CLI instead of downloading a runtime: + +
+
+ + +
+
+
$env:COPILOT_CLI_PATH = (Get-Command copilot).Source
+
+ +
+ +다음 디렉터리로 이동하여 Python starter, create a virtual environment, install pinned requirements, and +compile-check. 이 디렉터리에서 계속 작업합니다 이후 모든 단계에서: + +
+
+ + +
+
+
cd start-accessibility/python
+python -m venv .venv
+.venv\Scripts\Activate.ps1
+python -m pip install -r requirements.txt
+python -m py_compile main.py workshop.py report.py accessibility_rule_catalog.py
+
+ +
+ +설치에 성공하면 `github-copilot-sdk==...`를 포함한 확인된 패키지가 출력됩니다. 성공적인 +compile check prints no output. 가상 환경을 활성화한 상태로 유지합니다 for later steps. + +다음 디렉터리에서 작업하므로 `start-accessibility/python` for the rest of the workshop, so keep this terminal here. From +this folder, enter `code .` VS Code에서 열려면, 또는 선호하는 편집기에서 폴더를 엽니다. + +Optionally pre-download the runtime now so the first Step 1 run is faster: + +```bash +python -m copilot download-runtime +``` + +접속할 수 있는지 확인하려면 제어 대상 페이지를 한 번 엽니다. + +```text +{{TARGET_APP_URL}} +``` + +
+사전 점검 문제 해결 + +| 증상 | 해결 방법 | +|---|---| +| `python` points at Python 2 또는 없습니다 | Use Python 3.11+ (`python3` on macOS/Linux) and recreate the venv. | +| `pip install` cannot reach PyPI | Check proxy settings, then rerun `python -m pip install -r requirements.txt`. | +| 패키지 버전이 잘못됨 | 고정된 `requirements.txt`에서만 설치하고 `==` 고정을 완화하지 않습니다. | +| 나중에 런타임 다운로드 실패 | `python -m copilot download-runtime`을 실행하거나 작동하는 CLI로 `COPILOT_CLI_PATH`를 설정합니다. | +| Copilot에서 인증을 요청합니다 | `copilot login`을 실행하고 브라우저 인증을 완료한 후 다시 시도합니다. | +| `npx` is not recognized | Node.js를 설치합니다 22 이상 and restart the terminal. | +| 나중에 브라우저가 시작되지 않습니다 | Edge 또는 Chrome을 설치합니다, or follow the [Playwright MCP browser configuration](https://github.com/microsoft/playwright-mcp#configuration). | + +
+ +> **다음 조건을 충족하면 1단계를 시작합니다:** the pinned requirements install, `py_compile` 성공하고, `copilot login` is +> complete, 대상 페이지가 열립니다. +::: + +:::language go +## 4. 스타터로 이동하여 빌드 + +Go SDK는 `PATH` 또는 `COPILOT_CLI_PATH`를 통해 Copilot CLI를 찾습니다: + +
+
+ + +
+
+
$env:COPILOT_CLI_PATH = (Get-Command copilot).Source
+
+ +
+ +다음 디렉터리로 이동하여 Go starter and build with the lock enforced. 이 디렉터리에서 계속 작업합니다 for every later +step: + +```bash +cd start-accessibility/go +go build -mod=readonly ./... +``` + +빌드에 성공하면 prints no errors and produces a binary in the starter directory. Keep `go.sum` +intact so module resolution stays deterministic. + +다음 디렉터리에서 작업하므로 `start-accessibility/go` for the rest of the workshop, so keep this terminal here. From +this folder, enter `code .` VS Code에서 열려면, 또는 선호하는 편집기에서 폴더를 엽니다. + +접속할 수 있는지 확인하려면 제어 대상 페이지를 한 번 엽니다. + +```text +{{TARGET_APP_URL}} +``` + +
+사전 점검 문제 해결 + +| 증상 | 해결 방법 | +|---|---| +| `go: go.mod requires go >= 1.24` | Go 1.24 이상을 설치하고 터미널을 다시 엽니다. | +| `missing go.sum entry` | Restore the committed `go.sum`; build with `-mod=readonly` instead of rewriting the lock. | +| Module download blocked | Configure `GOPROXY`/proxy access, then retry the build from the starter directory. | +| `copilot`을 인식하지 못함 | CLI를 설치하고 터미널을 다시 시작하거나 `COPILOT_CLI_PATH`를 설정합니다. | +| Copilot에서 인증을 요청합니다 | `copilot login`을 실행하고 브라우저 인증을 완료한 후 다시 시도합니다. | +| `npx` is not recognized | Node.js를 설치합니다 22 이상 and restart the terminal. | +| 나중에 브라우저가 시작되지 않습니다 | Edge 또는 Chrome을 설치합니다, or follow the [Playwright MCP browser configuration](https://github.com/microsoft/playwright-mcp#configuration). | + +
+ +> **다음 조건을 충족하면 1단계를 시작합니다:** `go build -mod=readonly ./...` 성공하고, `copilot login` 완료되고, and +> the target page opens. + +Compare with +[`finished/go/hello-copilot-sdk`](https://github.com/github/copilot-sdk-workshop/tree/main/finished/go/hello-copilot-sdk) +if you want a later 참조 point after Step 1. +::: + +:::language rust +## 4. 스타터로 이동하여 빌드 + +나중에 런타임이 CLI를 찾지 못하면 `COPILOT_CLI_PATH`를 설정합니다: + +
+
+ + +
+
+
$env:COPILOT_CLI_PATH = (Get-Command copilot).Source
+
+ +
+ +다음 디렉터리로 이동하여 Rust starter and check it against the lockfile. 이 디렉터리에서 계속 작업합니다 for every +later step: + +```bash +cd start-accessibility/rust +cargo check --locked +``` + +검사에 성공하면 ends with a `Finished` line and no errors. Keep `Cargo.lock` committed so the +crate graph stays pinned. + +다음 디렉터리에서 작업하므로 `start-accessibility/rust` for the rest of the workshop, so keep this terminal here. From +this folder, enter `code .` VS Code에서 열려면, 또는 선호하는 편집기에서 폴더를 엽니다. + +접속할 수 있는지 확인하려면 제어 대상 페이지를 한 번 엽니다. + +```text +{{TARGET_APP_URL}} +``` + +
+사전 점검 문제 해결 + +| 증상 | 해결 방법 | +|---|---| +| `rustc 1.xx is too old` | `rustup update`로 Rust 1.94 이상을 설치하고 터미널을 다시 엽니다. | +| Lockfile mismatch with `--locked` | Keep the starter `Cargo.lock`; do not run unconstrained `cargo update`. | +| Crate download blocked | Check network/proxy access to crates.io, then retry `cargo check`. | +| 나중에 런타임을 시작할 수 없음 | `copilot`을 설치하고 인증하거나 `COPILOT_CLI_PATH`를 설정합니다. | +| Copilot에서 인증을 요청합니다 | `copilot login`을 실행하고 브라우저 인증을 완료한 후 다시 시도합니다. | +| `npx` is not recognized | Node.js를 설치합니다 22 이상 and restart the terminal. | +| 나중에 브라우저가 시작되지 않습니다 | Edge 또는 Chrome을 설치합니다, or follow the [Playwright MCP browser configuration](https://github.com/microsoft/playwright-mcp#configuration). | + +
+ +> **다음 조건을 충족하면 1단계를 시작합니다:** `cargo check --locked` 성공하고, `copilot login` 완료되고, and the +> target page opens. + +Compare with +[`finished/rust/hello-copilot-sdk`](https://github.com/github/copilot-sdk-workshop/tree/main/finished/rust/hello-copilot-sdk) +if you want a later 참조 point after Step 1. +::: + +:::language java +## 4. 스타터로 이동하여 빌드 + +Java SDK는 애플리케이션 시작 시 `PATH`에서 Copilot CLI를 찾습니다. 빌드 전에 +다음으로 확인합니다: + +```bash +copilot --version +``` + +다음 디렉터리로 이동하여 Java starter and compile with Maven. 이 디렉터리에서 계속 작업합니다 이후 모든 단계에서: + +```bash +cd start-accessibility/java +./mvnw compile +``` + +컴파일에 성공하면 다음으로 끝납니다: + +```text +[INFO] BUILD SUCCESS +``` + +`pom.xml`은 이미 `exec-maven-plugin`을 다음과 같이 구성합니다. +`mainClass` `workshop.AccessibilityReport`. Stay on Maven for this track. + +다음 디렉터리에서 작업하므로 `start-accessibility/java` for the rest of the workshop, so keep this terminal here. From +this folder, enter `code .` VS Code에서 열려면, 또는 선호하는 편집기에서 폴더를 엽니다. + +접속할 수 있는지 확인하려면 제어 대상 페이지를 한 번 엽니다. + +```text +{{TARGET_APP_URL}} +``` + +
+사전 점검 문제 해결 + +| 증상 | 해결 방법 | +|---|---| +| `java`를 인식하지 못함 | JDK 17 이상을 설치한 후 터미널을 다시 시작합니다. | +| `./mvnw: Permission denied` | `chmod +x mvnw`를 실행하거나 `sh mvnw`를 사용합니다. Windows에서는 `mvnw.cmd`를 사용합니다. | +| Compiler release errors | Confirm `java -version` reports 17 이상; the POM sets `maven.compiler.release` to 17. | +| Dependency download fails | Check Maven Central / proxy settings, then rerun `./mvnw compile`. | +| Tempted to switch tools | Do not replace Maven with JBang or Gradle for this workshop. | +| `copilot`을 인식하지 못함 | CLI를 설치하고 터미널을 다시 시작한 후 `copilot --version`을 확인합니다. | +| Copilot에서 인증을 요청합니다 | `copilot login`을 실행하고 브라우저 인증을 완료한 후 다시 시도합니다. | +| `npx` is not recognized | Node.js를 설치합니다 22 이상 and restart the terminal. | +| 나중에 브라우저가 시작되지 않습니다 | Edge 또는 Chrome을 설치합니다, or follow the [Playwright MCP browser configuration](https://github.com/microsoft/playwright-mcp#configuration). | + +
+ +> **다음 조건을 충족하면 1단계를 시작합니다:** `./mvnw compile` prints `BUILD SUCCESS`, `copilot login` 완료되고, and the +> target page opens. + +Compare with +[`finished/java/hello-copilot-sdk`](https://github.com/github/copilot-sdk-workshop/tree/main/finished/java/hello-copilot-sdk) +if you want a later 참조 point after Step 1. +::: + +## 자세히 알아보기 + +설치할 SDK의 설명서는 이 워크숍 외부에 있습니다. 다음 페이지를 +1단계 전에 북마크해 두면 좋습니다. + +- [GitHub Copilot SDK how-tos](https://docs.github.com/en/copilot/how-tos/copilot-sdk): GitHub's + own SDK 설명서, including the prerequisites this preflight mirrors. +- [Copilot SDK 설명서 map](https://github.com/github/copilot-sdk/blob/main/docs/README.md): + the index for setup, authentication, features, and troubleshooting. +- [Default setup: the bundled CLI](https://github.com/github/copilot-sdk/blob/main/docs/setup/bundled-cli.md): + how the SDK locates and starts the Copilot CLI, and how to point it at a different binary. +- [Debugging guide](https://github.com/github/copilot-sdk/blob/main/docs/troubleshooting/debugging.md): + 가장 먼저 확인할 자료 실행이 출력 생성 전에 실패한 경우. + +:::language dotnet +- [.NET SDK 참조](https://github.com/github/copilot-sdk/blob/main/dotnet/README.md): + 패키지 설치 방법과 최소 예제 for the .NET SDK. +::: + +:::language nodejs +- [Node.js SDK 참조](https://github.com/github/copilot-sdk/blob/main/nodejs/README.md): + 패키지 설치 방법과 최소 예제 for the Node.js SDK. +::: + +:::language python +- [Python SDK 참조](https://github.com/github/copilot-sdk/blob/main/python/README.md): + 패키지 설치 방법과 최소 예제 for the Python SDK. +::: + +:::language go +- [Go SDK 참조](https://github.com/github/copilot-sdk/blob/main/go/README.md): + 모듈 설치 방법과 최소 예제 for the Go SDK. +::: + +:::language rust +- [Rust SDK 참조](https://github.com/github/copilot-sdk/blob/main/rust/README.md): + 크레이트 설치 방법과 최소 예제 for the Rust SDK. +::: + +:::language java +- [Java SDK 참조](https://github.com/github/copilot-sdk/blob/main/java/README.md): + 종속성 좌표와 최소 예제 for the Java SDK. +::: + +[1단계: 첫 Copilot 세션 만들기]로 계속 진행합니다.(01-first-session.md). + + diff --git a/localizations/ko-kr/workshop/01-first-session.md b/localizations/ko-kr/workshop/01-first-session.md new file mode 100644 index 0000000..6219494 --- /dev/null +++ b/localizations/ko-kr/workshop/01-first-session.md @@ -0,0 +1,750 @@ +# 1단계: 첫 번째 Copilot 세션 만들기 + +> **소요 시간:** 10분 + +## 만들 내용 + +콘솔 애플리케이션을 Copilot 런타임에 연결하고, 대화를 만든 다음, 프롬프트를 전송하고 +응답을 출력합니다. + +:::language dotnet +## GitHub Copilot SDK와 런타임 알아보기 + +**GitHub Copilot SDK**는 애플리케이션에서 Copilot을 에이전트로 실행하는 데 사용하는 .NET API입니다. +[**Copilot 런타임**](https://github.com/github/copilot-sdk/blob/main/docs/features/agent-loop.md)은 +프롬프트를 받고 모델을 호출하며 도구를 관리합니다. `CopilotClient`는 C# 코드를 이 런타임에 +연결합니다. + +`CopilotSession`은 하나의 연속된 대화를 나타냅니다. 대화의 컨텍스트를 구성하는 메시지와 +도구 결과를 보관합니다. 애플리케이션에서는 클라이언트 하나를 계속 유지하고, 독립적인 대화마다 +세션을 만듭니다. + +## 클라이언트와 세션을 분리하는 이유 + +이러한 책임을 분리하면 개별 대화가 끝난 뒤에도 런타임 연결을 유지할 수 있습니다. 또한 스트리밍과 +도구를 다루기 전에 작동하는 간단한 예제를 확인할 수 있습니다. + +이 시점의 콘솔 앱은 단순히 `CopilotClient -> CopilotSession -> model response` 구조입니다. +::: + +:::language nodejs +## GitHub Copilot SDK와 런타임 알아보기 + +**GitHub Copilot SDK**는 애플리케이션에서 Copilot을 에이전트로 실행하는 데 사용하는 Node.js API입니다. +[**Copilot 런타임**](https://github.com/github/copilot-sdk/blob/main/docs/features/agent-loop.md)은 +프롬프트를 받고 모델을 호출하며 도구를 관리합니다. `CopilotClient`는 TypeScript 코드를 이 +런타임에 연결합니다. + +`createSession`에서 만든 세션은 하나의 연속된 대화를 나타냅니다. 대화의 컨텍스트를 구성하는 +메시지와 도구 결과를 보관합니다. 애플리케이션에서는 클라이언트 하나를 계속 유지하고, 독립적인 +대화마다 세션을 만듭니다. + +## 클라이언트와 세션을 분리하는 이유 + +이러한 책임을 분리하면 개별 대화가 끝난 뒤에도 런타임 연결을 유지할 수 있습니다. 또한 스트리밍과 +도구를 다루기 전에 작동하는 간단한 예제를 확인할 수 있습니다. + +이 시점의 콘솔 앱은 단순히 `CopilotClient -> session -> model response` 구조입니다. +::: + +:::language python +## GitHub Copilot SDK와 런타임 알아보기 + +**GitHub Copilot SDK**는 애플리케이션에서 Copilot을 에이전트로 실행하는 데 사용하는 Python API입니다. +[**Copilot 런타임**](https://github.com/github/copilot-sdk/blob/main/docs/features/agent-loop.md)은 +프롬프트를 받고 모델을 호출하며 도구를 관리합니다. `CopilotClient`는 Python 코드를 이 +런타임에 연결합니다. + +`create_session`에서 만든 세션은 하나의 연속된 대화를 나타냅니다. 대화의 컨텍스트를 구성하는 +메시지와 도구 결과를 보관합니다. 애플리케이션에서는 클라이언트 하나를 계속 유지하고, 독립적인 +대화마다 세션을 만듭니다. + +## 클라이언트와 세션을 분리하는 이유 + +이러한 책임을 분리하면 개별 대화가 끝난 뒤에도 런타임 연결을 유지할 수 있습니다. 또한 스트리밍과 +도구를 다루기 전에 작동하는 간단한 예제를 확인할 수 있습니다. + +이 시점의 콘솔 앱은 단순히 `CopilotClient -> session -> model response` 구조입니다. +::: + +:::language go +## GitHub Copilot SDK와 런타임 알아보기 + +**GitHub Copilot SDK**는 애플리케이션에서 Copilot을 에이전트로 실행하는 데 사용하는 Go API입니다. +[**Copilot 런타임**](https://github.com/github/copilot-sdk/blob/main/docs/features/agent-loop.md)은 +프롬프트를 받고 모델을 호출하며 도구를 관리합니다. `copilot.NewClient`는 Go 코드를 이 +런타임에 연결합니다. + +`CreateSession`에서 만든 세션은 하나의 연속된 대화를 나타냅니다. 대화의 컨텍스트를 구성하는 +메시지와 도구 결과를 보관합니다. 애플리케이션에서는 클라이언트 하나를 계속 유지하고, 독립적인 +대화마다 세션을 만듭니다. + +## 클라이언트와 세션을 분리하는 이유 + +이러한 책임을 분리하면 개별 대화가 끝난 뒤에도 런타임 연결을 유지할 수 있습니다. 또한 스트리밍과 +도구를 다루기 전에 작동하는 간단한 예제를 확인할 수 있습니다. + +이 시점의 콘솔 앱은 단순히 `Client -> Session -> model response` 구조입니다. +::: + +:::language rust +## GitHub Copilot SDK와 런타임 알아보기 + +**GitHub Copilot SDK**는 애플리케이션에서 Copilot을 에이전트로 실행하는 데 사용하는 Rust API입니다. +[**Copilot 런타임**](https://github.com/github/copilot-sdk/blob/main/docs/features/agent-loop.md)은 +프롬프트를 받고 모델을 호출하며 도구를 관리합니다. `Client`는 Rust 코드를 이 런타임에 +연결합니다. + +`create_session`에서 만든 세션은 하나의 연속된 대화를 나타냅니다. 대화의 컨텍스트를 구성하는 +메시지와 도구 결과를 보관합니다. 애플리케이션에서는 클라이언트 하나를 계속 유지하고, 독립적인 +대화마다 세션을 만듭니다. + +## 클라이언트와 세션을 분리하는 이유 + +이러한 책임을 분리하면 개별 대화가 끝난 뒤에도 런타임 연결을 유지할 수 있습니다. 또한 스트리밍과 +도구를 다루기 전에 작동하는 간단한 예제를 확인할 수 있습니다. + +이 시점의 콘솔 앱은 단순히 `Client -> session -> model response` 구조입니다. +::: + +:::language java +## GitHub Copilot SDK와 런타임 알아보기 + +**GitHub Copilot SDK**는 애플리케이션에서 Copilot을 에이전트로 실행하는 데 사용하는 Java API입니다. +[**Copilot 런타임**](https://github.com/github/copilot-sdk/blob/main/docs/features/agent-loop.md)은 +프롬프트를 받고 모델을 호출하며 도구를 관리합니다. `CopilotClient`는 Java 코드를 이 +런타임에 연결합니다. + +`createSession`에서 만든 세션은 하나의 연속된 대화를 나타냅니다. 대화의 컨텍스트를 구성하는 +메시지와 도구 결과를 보관합니다. 애플리케이션에서는 클라이언트 하나를 계속 유지하고, 독립적인 +대화마다 세션을 만듭니다. + +## 클라이언트와 세션을 분리하는 이유 + +이러한 책임을 분리하면 개별 대화가 끝난 뒤에도 런타임 연결을 유지할 수 있습니다. 또한 스트리밍과 +도구를 다루기 전에 작동하는 간단한 예제를 확인할 수 있습니다. + +이 시점의 콘솔 앱은 단순히 `CopilotClient -> session -> model response` 구조입니다. +::: + +## 첫 번째 Copilot 세션 시작하기 + +:::language dotnet +`Program.cs`를 열고 **파일 전체를 바꿉니다**: + +```csharp +using GitHub.Copilot; +using GitHub.Copilot.Rpc; + +Console.WriteLine("=== First Copilot session ===\n"); + +await using var client = new CopilotClient(); +await client.StartAsync(); + +var ping = await client.PingAsync("workshop"); +Console.WriteLine($"Connected to the Copilot runtime: {ping.Message}"); + +await using var session = await client.CreateSessionAsync(new SessionConfig +{ + OnPermissionRequest = PermissionHandler.ApproveAll, +}); +var response = await session.SendAndWaitAsync( + "In one sentence, explain why an accessible name matters for a form input."); + +if (response is null) +{ + throw new InvalidOperationException("Copilot completed without an assistant message."); +} + +Console.WriteLine($"\nCopilot: {response.Data.Content}"); +``` + +ping 호출은 런타임 연결을 확인합니다. 완료 응답 전송은 세션이 유휴 상태가 될 때까지 기다리므로, +최종 답변만 필요할 때 적합합니다. +::: + +:::language nodejs +`src/index.ts`를 열고 **파일 전체를 바꿉니다**: + +```typescript +import { approveAll, CopilotClient } from "@github/copilot-sdk"; + +const client = new CopilotClient(); +await client.start(); +try { + const session = await client.createSession({ onPermissionRequest: approveAll }); + try { + const response = await session.sendAndWait({ prompt: "Reply with one sentence confirming this Copilot session is ready." }); + console.log(response?.data && "content" in response.data ? response.data.content : response); + } finally { + await session.disconnect(); + } +} finally { + await client.stop(); +} +``` + +`sendAndWait`는 세션이 유휴 상태가 될 때까지 기다리므로, 최종 답변만 필요할 때 적합합니다. +런타임이 정상적으로 종료되도록 `finally` 블록에서 항상 세션과 클라이언트를 중지합니다. +::: + +:::language python +`main.py`를 열고 **파일 전체를 바꿉니다**: + +```python +import asyncio + +from copilot import CopilotClient, PermissionHandler +from copilot.session_events import AssistantMessageData, SessionErrorData, SessionIdleData + + +async def main() -> None: + async with CopilotClient() as client: + async with await client.create_session( + on_permission_request=PermissionHandler.approve_all + ) as session: + done = asyncio.Event() + error: RuntimeError | None = None + + def on_event(event) -> None: + nonlocal error + match event.data: + case AssistantMessageData(content=content): + print(content) + case SessionErrorData(message=message): + error = RuntimeError(message) + done.set() + case SessionIdleData(): + done.set() + + session.on(on_event) + await session.send("In one sentence, explain why an accessible name matters for a form input.") + await done.wait() + if error is not None: + raise error + + +if __name__ == "__main__": + asyncio.run(main()) +``` + +Python은 완료된 응답을 한 번에 반환하는 도우미를 호출하는 대신 세션 이벤트를 수신합니다. +어시스턴트 메시지를 출력하고, 세션 오류를 실패로 처리하며, 종료하기 전에 유휴 이벤트를 기다립니다. +::: + +:::language go +`main.go`를 열고 **파일 전체를 바꿉니다**: + +```go +package main + +import ( + "context" + "fmt" + + copilot "github.com/github/copilot-sdk/go" +) + +func main() { + client := copilot.NewClient(&copilot.ClientOptions{LogLevel: "error"}) + if err := client.Start(context.Background()); err != nil { + panic(err) + } + defer client.Stop() + + session, err := client.CreateSession(context.Background(), &copilot.SessionConfig{ + OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + }) + if err != nil { + panic(err) + } + defer session.Disconnect() + + response, err := session.SendAndWait(context.Background(), copilot.MessageOptions{ + Prompt: "In one sentence, explain why an accessible name matters for a form input.", + }) + if err != nil { + panic(err) + } + if response != nil { + if message, ok := response.Data.(*copilot.AssistantMessageData); ok { + fmt.Println(message.Content) + } + } +} +``` + +`SendAndWait`는 세션이 유휴 상태가 될 때까지 기다리므로, 최종 답변만 필요할 때 적합합니다. +`defer`는 종료 과정에서 세션 연결을 끊고 클라이언트를 중지합니다. +::: + +:::language rust +`src/main.rs`를 열고 **파일 전체를 바꿉니다**: + +```rust +use github_copilot_sdk::permission; +use github_copilot_sdk::types::{MessageOptions, SessionConfig}; +use github_copilot_sdk::{Client, ClientOptions}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::start(ClientOptions::default()).await?; + let session = client + .create_session(SessionConfig::default().with_permission_handler(permission::approve_all())) + .await?; + let response = session + .send_and_wait(MessageOptions::new( + "In one sentence, explain why an accessible name matters for a form input.", + )) + .await?; + + if let Some(message) = response { + if let Some(content) = message.data.get("content").and_then(|value| value.as_str()) { + println!("{content}"); + } + } + + session.disconnect().await?; + client.stop().await?; + Ok(()) +} +``` + +`send_and_wait`는 세션이 유휴 상태가 될 때까지 기다리므로, 최종 답변만 필요할 때 적합합니다. +반환하기 전에 세션 연결을 끊고 클라이언트를 중지합니다. +::: + +:::language java +`src/main/java/workshop/AccessibilityReport.java`를 열고 **파일 전체를 바꿉니다**: + +```java +package workshop; + +import com.github.copilot.CopilotClient; +import com.github.copilot.rpc.PermissionHandler; +import com.github.copilot.rpc.MessageOptions; +import com.github.copilot.rpc.SessionConfig; + +public final class AccessibilityReport { + private AccessibilityReport() { + } + + public static void main(String[] args) throws Exception { + try (var client = new CopilotClient()) { + client.start().get(); + var session = client + .createSession(new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get(); + var response = session.sendAndWait(new MessageOptions() + .setPrompt("In one sentence, explain why an accessible name matters for a form input.")) + .get(); + if (response == null) { + throw new IllegalStateException("Copilot completed without an assistant message."); + } + System.out.println(response.getData().content()); + } + } +} +``` + +`sendAndWait`는 세션이 유휴 상태가 될 때까지 기다리므로, 최종 답변만 필요할 때 적합합니다. +try-with-resources 블록은 `main`이 종료될 때 클라이언트를 닫습니다. +::: + +이 세션은 권한 처리기만 설정하므로 SDK의 기본 페르소나(Persona)로 실행됩니다. 여기서 변경하지 +않은 설정은 세 가지 모드를 제공하는 +[시스템 메시지](https://github.com/github/copilot-sdk/blob/main/docs/getting-started.md#customize-the-system-message)입니다. +기본값은 `append`입니다. 작성한 콘텐츠가 SDK에서 관리하는 프롬프트 뒤에 추가되며, SDK가 주입하는 +환경 컨텍스트, 도구 지침, 보안 가드레일과 함께 기본 CLI 페르소나가 유지됩니다. `replace`는 전체 +프롬프트를 작성한 콘텐츠로 바꿉니다. `customize`는 나머지 부분을 유지하면서 어조, 지침, 코드 변경 +규칙 등의 개별 섹션을 재정의합니다. 이 워크숍에서는 기본값을 사용하므로 표시되는 모든 답변은 표준 +페르소나에서 생성됩니다. 애플리케이션에 고유한 말투나 범위가 필요할 때 나머지 두 모드를 사용합니다. + +## 실행하기 + +:::language dotnet +```bash +dotnet run +``` +::: +:::language nodejs +```bash +npm start +``` +::: +:::language python +```bash +python main.py +``` +::: +:::language go +```bash +go run . +``` +::: +:::language rust +```bash +cargo run +``` +::: +:::language java +```bash +./mvnw compile exec:java +``` +::: + +:::language dotnet +정확한 응답은 달라질 수 있지만 출력은 다음과 같은 형태여야 합니다: + +```text +=== First Copilot session === + +Connected to the Copilot runtime: ... + +Copilot: An accessible name lets assistive technology identify the input's purpose. +``` +::: + +:::language nodejs +정확한 응답은 달라질 수 있지만 출력은 다음과 같은 형태여야 합니다: + +```text +This Copilot session is ready and waiting for your next prompt. +``` +::: + +:::language python +정확한 응답은 달라질 수 있지만 출력은 다음과 같은 형태여야 합니다: + +```text +An accessible name lets assistive technology identify the input's purpose. +``` +::: + +:::language go +정확한 응답은 달라질 수 있지만 출력은 다음과 같은 형태여야 합니다: + +```text +An accessible name lets assistive technology identify the input's purpose. +``` +::: + +:::language rust +정확한 응답은 달라질 수 있지만 출력은 다음과 같은 형태여야 합니다: + +```text +An accessible name lets assistive technology identify the input's purpose. +``` +::: + +:::language java +정확한 응답은 달라질 수 있지만 출력은 다음과 같은 형태여야 합니다: + +```text +An accessible name lets assistive technology identify the input's purpose. +``` +::: + +
+실행 문제 해결 + +| 증상 | 해결 방법 | +|---|---| +| 인증 또는 권한 부여 오류 | `copilot login`을 다시 실행한 다음 프로젝트를 다시 실행합니다. | +| 런타임 실행 파일을 찾을 수 없음 | 사전 점검 지침에 따라 `COPILOT_CLI_BINARY_PATH`를 설정합니다. | +| 요청 시간 초과 | GitHub Copilot에 대한 네트워크 액세스를 확인하고 다시 시도합니다. 이 예제는 실패를 숨기지 않습니다. | + +
+ +> **스트리밍을 시작할 준비가 되는 시점:** 터미널에 완전한 Copilot 응답 하나가 출력됩니다. + +## 이해도 확인 + +일반적으로 애플리케이션 수명 동안 유지해야 하는 개체는 무엇이며, 한 대화의 컨텍스트를 소유하는 +개체는 무엇입니까? + +:::language dotnet +
+답 확인 + +런타임 연결 수명 동안 `CopilotClient`를 유지합니다. `CopilotSession`은 한 대화의 메시지와 +도구 컨텍스트를 소유합니다. + +
+::: + +:::language nodejs +
+답 확인 + +런타임 연결 수명 동안 `CopilotClient`를 유지합니다. `createSession`에서 만든 세션은 한 대화의 +메시지와 도구 컨텍스트를 소유합니다. + +
+::: + +:::language python +
+답 확인 + +런타임 연결 수명 동안 `CopilotClient`를 유지합니다. `create_session`에서 만든 세션은 한 대화의 +메시지와 도구 컨텍스트를 소유합니다. + +
+::: + +:::language go +
+답 확인 + +런타임 연결 수명 동안 `copilot.NewClient`에서 만든 클라이언트를 유지합니다. `CreateSession`에서 +만든 세션은 한 대화의 메시지와 도구 컨텍스트를 소유합니다. + +
+::: + +:::language rust +
+답 확인 + +런타임 연결 수명 동안 `Client`를 유지합니다. `create_session`에서 만든 세션은 한 대화의 메시지와 +도구 컨텍스트를 소유합니다. + +
+::: + +:::language java +
+답 확인 + +런타임 연결 수명 동안 `CopilotClient`를 유지합니다. `createSession`에서 만든 세션은 한 대화의 +메시지와 도구 컨텍스트를 소유합니다. + +
+::: + +:::language dotnet +
+1단계 전체 구현 + +작성한 결과를 다음의 1단계 전체 구현과 비교합니다. + +```csharp +using GitHub.Copilot; + +Console.WriteLine("=== First Copilot session ===\n"); + +await using var client = new CopilotClient(); +await client.StartAsync(); + +var ping = await client.PingAsync("workshop"); +Console.WriteLine($"Connected to the Copilot runtime: {ping.Message}"); + +await using var session = await client.CreateSessionAsync(new SessionConfig()); +var response = await session.SendAndWaitAsync( + "In one sentence, explain why an accessible name matters for a form input."); + +if (response is null) +{ + throw new InvalidOperationException("Copilot completed without an assistant message."); +} + +Console.WriteLine($"\nCopilot: {response.Data.Content}"); +``` +
+::: + +:::language nodejs +
+1단계 전체 구현 + +작성한 결과를 다음의 1단계 전체 구현과 비교합니다. + +```typescript +import { CopilotClient } from "@github/copilot-sdk"; + +const client = new CopilotClient(); +await client.start(); +try { + const session = await client.createSession({}); + try { + const response = await session.sendAndWait({ prompt: "Reply with one sentence confirming this Copilot session is ready." }); + console.log(response?.data && "content" in response.data ? response.data.content : response); + } finally { + await session.disconnect(); + } +} finally { + await client.stop(); +} +``` +
+::: + +:::language python +
+1단계 전체 구현 + +작성한 결과를 다음의 1단계 전체 구현과 비교합니다. + +```python +import asyncio + +from copilot import CopilotClient +from copilot.session_events import AssistantMessageData, SessionErrorData, SessionIdleData + + +async def main() -> None: + async with CopilotClient() as client: + async with await client.create_session() as session: + done = asyncio.Event() + error: RuntimeError | None = None + + def on_event(event) -> None: + nonlocal error + match event.data: + case AssistantMessageData(content=content): + print(content) + case SessionErrorData(message=message): + error = RuntimeError(message) + done.set() + case SessionIdleData(): + done.set() + + session.on(on_event) + await session.send("In one sentence, explain why an accessible name matters for a form input.") + await done.wait() + if error is not None: + raise error + + +if __name__ == "__main__": + asyncio.run(main()) +``` +
+::: + +:::language go +
+1단계 전체 구현 + +작성한 결과를 다음의 1단계 전체 구현과 비교합니다. + +```go +package main + +import ( + "context" + "fmt" + + copilot "github.com/github/copilot-sdk/go" +) + +func main() { + client := copilot.NewClient(&copilot.ClientOptions{LogLevel: "error"}) + if err := client.Start(context.Background()); err != nil { + panic(err) + } + defer client.Stop() + + session, err := client.CreateSession(context.Background(), &copilot.SessionConfig{}) + if err != nil { + panic(err) + } + defer session.Disconnect() + + response, err := session.SendAndWait(context.Background(), copilot.MessageOptions{ + Prompt: "In one sentence, explain why an accessible name matters for a form input.", + }) + if err != nil { + panic(err) + } + if response != nil { + if message, ok := response.Data.(*copilot.AssistantMessageData); ok { + fmt.Println(message.Content) + } + } +} +``` +
+::: + +:::language rust +
+1단계 전체 구현 + +작성한 결과를 다음의 1단계 전체 구현과 비교합니다. + +```rust +use github_copilot_sdk::types::{MessageOptions, SessionConfig}; +use github_copilot_sdk::{Client, ClientOptions}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::start(ClientOptions::default()).await?; + let session = client.create_session(SessionConfig::default()).await?; + let response = session + .send_and_wait(MessageOptions::new( + "In one sentence, explain why an accessible name matters for a form input.", + )) + .await?; + + if let Some(message) = response { + if let Some(content) = message.data.get("content").and_then(|value| value.as_str()) { + println!("{content}"); + } + } + + session.disconnect().await?; + client.stop().await?; + Ok(()) +} +``` +
+::: + +:::language java +
+1단계 전체 구현 + +작성한 결과를 다음의 1단계 전체 구현과 비교합니다. + +```java +package workshop; + +import com.github.copilot.CopilotClient; +import com.github.copilot.rpc.MessageOptions; +import com.github.copilot.rpc.SessionConfig; + +public final class AccessibilityReport { + private AccessibilityReport() { + } + + public static void main(String[] args) throws Exception { + try (var client = new CopilotClient()) { + client.start().get(); + var session = client.createSession(new SessionConfig()).get(); + var response = session.sendAndWait(new MessageOptions() + .setPrompt("In one sentence, explain why an accessible name matters for a form input.")) + .get(); + if (response == null) { + throw new IllegalStateException("Copilot completed without an assistant message."); + } + System.out.println(response.getData().content()); + } + } +} +``` +
+::: + +## 자세히 알아보기 + +- [첫 번째 Copilot 기반 앱 빌드](https://docs.github.com/en/copilot/how-tos/copilot-sdk/getting-started): + 동일한 첫 번째 클라이언트, 세션, 프롬프트를 다루는 GitHub 튜토리얼입니다. +- [세션 재개 및 지속성](https://github.com/github/copilot-sdk/blob/main/docs/features/session-persistence.md): + 세션의 대화 상태를 유지하고 다시 시작한 후 재개하는 방법을 설명합니다. +- [컨텍스트 지우기](https://github.com/github/copilot-sdk/blob/main/docs/features/context-management.md): + 새 세션을 만들지 않고 세션 내부의 대화를 바꾸는 방법을 설명합니다. +- [인증](https://github.com/github/copilot-sdk/blob/main/docs/auth/README.md): + `copilot login` 이후 클라이언트에서 사용할 수 있는 자격 증명을 설명합니다. + +[2단계: 응답 스트리밍](02-streaming.md)으로 계속 진행합니다. diff --git a/localizations/ko-kr/workshop/02-streaming.md b/localizations/ko-kr/workshop/02-streaming.md new file mode 100644 index 0000000..dd9c268 --- /dev/null +++ b/localizations/ko-kr/workshop/02-streaming.md @@ -0,0 +1,968 @@ +# 2단계: 응답 스트리밍 + +> **소요 시간:** 10분 + +## 확인할 내용 + +스트리밍이 활성화된 세션을 구성하고 완성되는 과정을 표시합니다. 대부분의 언어 과정에서는 +세션이 아직 작업 중일 때 응답 텍스트를 출력합니다. Java 과정에서도 동일한 스트리밍 세션 +구성을 활성화하지만, `sendAndWait`가 반환한 완성된 어시스턴트 메시지를 출력합니다. + +## 스트리밍으로 달라지는 사용자 경험 + +[**스트리밍(Streaming)**](https://github.com/github/copilot-sdk/blob/main/docs/features/streaming-events.md)은 +답변 자체를 바꾸지 않습니다. 이벤트 스트림을 구독하는 애플리케이션이 답변을 받는 시점을 +바꿉니다. 하나의 완성된 메시지를 기다리는 대신 세션은 턴이 진행되는 동안 계속 이벤트를 +내보냅니다. + +- 어시스턴트 메시지 델타 이벤트에는 새로 생성된 응답 텍스트 조각이 각각 포함됩니다. +- 완성된 어시스턴트 메시지 이벤트에는 전체 메시지가 포함됩니다. +- 세션 유휴 이벤트는 턴과 모든 도구 작업이 완료되었음을 의미합니다. +- 세션 오류 이벤트는 실패한 턴을 보고합니다. + +## 점진적 출력이 더 나은 경험을 제공하는 이유 + +텍스트가 도착하는 즉시 표시하면 애플리케이션의 응답성이 더 뛰어난 것처럼 느껴집니다. +이후에는 동일한 이벤트 스트림에서 로컬 도구와 MCP 도구의 활동도 표시합니다. + +이제 세션 흐름은 `response deltas -> final message -> idle`입니다. + +:::language dotnet +## C#에서 응답 스트리밍 + +### 1. 스트리밍 도우미 추가 + +`Helpers/ResponseStreamer.cs`를 만듭니다. + +```csharp +using GitHub.Copilot; + +namespace HelloCopilotSDK.Helpers; + +public static class ResponseStreamer +{ + public static async Task SendAndPrintAsync(CopilotSession session, string prompt) + { + var completed = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var receivedDelta = false; + + using var subscription = session.On(sessionEvent => + { + switch (sessionEvent) + { + case AssistantMessageDeltaEvent delta when !string.IsNullOrEmpty(delta.Data.DeltaContent): + receivedDelta = true; + Console.Write(delta.Data.DeltaContent); + break; + case AssistantMessageEvent message when !receivedDelta: + Console.Write(message.Data.Content); + break; + case SessionIdleEvent: + Console.WriteLine(); + completed.TrySetResult(); + break; + case SessionErrorEvent error: + completed.TrySetException(new InvalidOperationException(error.Data.Message)); + break; + } + }); + + await session.SendAsync(new MessageOptions { Prompt = prompt }); + await completed.Task; + } +} +``` + +최종 메시지를 처리하는 분기는 런타임이 델타를 보내지 않고 완료되는 경우에 대응합니다. +오류가 발생하면 성공한 턴처럼 보이지 않도록 예외와 함께 작업을 완료합니다. + +### 2. 도우미 사용 + +`Program.cs`에 `using HelloCopilotSDK.Helpers;`를 추가한 다음, 세션 및 응답 코드를 +다음과 같이 바꿉니다. + +```csharp +await using var session = await client.CreateSessionAsync(new SessionConfig +{ + Streaming = true +}); + +Console.WriteLine("\nCopilot:"); +await ResponseStreamer.SendAndPrintAsync( + session, + "Explain accessible names in three short bullet points."); +``` + +## 실행 + +```bash +dotnet run +``` + +프로세스가 종료되기 전에 글머리 기호 항목이 점진적으로 표시되기 시작해야 합니다. + +```text +Connected to the Copilot runtime: ... + +Copilot: +- Gives a control a programmatic identity. +- Helps screen-reader users understand its purpose. +- Connects visible labels to form controls. +``` + +
+이번 실행 문제 해결 + +| 증상 | 해결 방법 | +|---|---| +| 텍스트가 마지막에만 표시됨 | 이 세션의 `SessionConfig`에 `Streaming = true`가 있는지 확인합니다. | +| 텍스트가 표시되기 전에 애플리케이션이 종료됨 | 도우미가 `SendAsync` 이후에 `completed.Task`를 기다리는지 확인합니다. | +| 텍스트가 두 번 출력됨 | `AssistantMessageEvent`에 `when !receivedDelta` 가드를 유지합니다. | + +
+ +> **다음 조건을 충족하면 도구를 추가할 준비가 된 것입니다.** 구성된 응답 경로가 답변을 출력하고, +> 세션 오류를 숨기지 않은 채 턴을 완료합니다. + +
+2단계 전체 구현 + +작성한 내용을 다음의 전체 2단계 구현과 비교합니다. + +`Helpers/ResponseStreamer.cs`: + +```csharp +using GitHub.Copilot; + +namespace HelloCopilotSDK.Helpers; + +public static class ResponseStreamer +{ + public static async Task SendAndPrintAsync(CopilotSession session, string prompt) + { + var completed = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var receivedDelta = false; + + using var subscription = session.On(sessionEvent => + { + switch (sessionEvent) + { + case AssistantMessageDeltaEvent delta when !string.IsNullOrEmpty(delta.Data.DeltaContent): + receivedDelta = true; + Console.Write(delta.Data.DeltaContent); + break; + case AssistantMessageEvent message when !receivedDelta: + Console.Write(message.Data.Content); + break; + case SessionIdleEvent: + Console.WriteLine(); + completed.TrySetResult(); + break; + case SessionErrorEvent error: + completed.TrySetException(new InvalidOperationException(error.Data.Message)); + break; + } + }); + + await session.SendAsync(new MessageOptions { Prompt = prompt }); + await completed.Task; + } +} +``` + +`Program.cs`: + +```csharp +using GitHub.Copilot; +using HelloCopilotSDK.Helpers; + +Console.WriteLine("=== Streaming from Copilot ===\n"); + +await using var client = new CopilotClient(); +await client.StartAsync(); + +var ping = await client.PingAsync("workshop"); +Console.WriteLine($"Connected to the Copilot runtime: {ping.Message}\n"); + +await using var session = await client.CreateSessionAsync(new SessionConfig +{ + Streaming = true +}); + +Console.WriteLine("Copilot:"); +await ResponseStreamer.SendAndPrintAsync( + session, + "Explain accessible names in three short bullet points."); +``` + +
+::: + +:::language nodejs +## TypeScript에서 응답 스트리밍 + +### 1. 스트리밍 도우미 살펴보기 + +`src/workshop.ts`를 엽니다. 스타터 프로젝트는 이미 `streamResponse`를 내보냅니다. +이 함수는 `session.on`으로 구독하고, 어시스턴트 델타를 출력하며, 최종 메시지 폴백을 유지하고, +세션 오류가 발생하면 Promise를 거부하며, 유휴 상태가 되면 완료됩니다. + +```typescript +export async function streamResponse(session: CopilotSession, prompt: string): Promise { + await new Promise((resolve, reject) => { + let receivedDelta = false; + const unsubscribe = session.on((event) => { + if (event.type === "assistant.message_delta" && event.data.deltaContent) { + receivedDelta = true; + process.stdout.write(event.data.deltaContent); + } else if (event.type === "assistant.message" && !receivedDelta) { + process.stdout.write(event.data.content); + } else if (event.type === "tool.execution_start") { + console.log(`\n[tool:start] ${event.data.toolName}`); + } else if (event.type === "tool.execution_complete") { + console.log(`[tool:done] success=${event.data.success}`); + } else if (event.type === "session.error") { + reject(new Error(event.data.message)); + } else if (event.type === "session.idle") { + console.log(); + unsubscribe(); + resolve(); + } + }); + void session.send({ prompt }).catch(reject); + }); +} +``` + +도구 시작 및 완료 분기는 이 단계에서는 호출되지 않지만, 이후에 도구를 등록하면 +유용하게 사용됩니다. + +### 2. 도우미를 진입점에 연결 + +`src/index.ts`를 다음 내용으로 바꿉니다. + +```typescript +import { CopilotClient } from "@github/copilot-sdk"; +import { streamResponse } from "./workshop.js"; + +const client = new CopilotClient(); +await client.start(); +try { + const session = await client.createSession({ streaming: true }); + try { + await streamResponse( + session, + "Describe why streaming improves an interactive assistant in one sentence.", + ); + } finally { + await session.disconnect(); + } +} finally { + await client.stop(); +} +``` + +## 실행 + +```bash +npm start +``` + +한 문장으로 된 응답이 이벤트 콜백을 통해 점진적으로 표시되기 시작해야 합니다. + +```text +Streaming shows partial answers as soon as tokens arrive, so the assistant feels responsive while it works. +``` + +
+이번 실행 문제 해결 + +| 증상 | 해결 방법 | +|---|---| +| 텍스트가 마지막에만 표시됨 | `createSession`에 `streaming: true`가 전달되는지 확인합니다. | +| 텍스트가 표시되기 전에 프로세스가 종료됨 | `streamResponse`가 완료되기 전에 `session.idle`을 기다리는지 확인합니다. | +| 텍스트가 두 번 출력됨 | `assistant.message` 분기에 `!receivedDelta` 가드를 유지합니다. | +| `./workshop.js` 모듈을 찾을 수 없음 | 소스 파일이 `workshop.ts`이더라도 도우미를 `./workshop.js`로 가져옵니다. | + +
+ +> **다음 조건을 충족하면 도구를 추가할 준비가 된 것입니다.** 구성된 응답 경로가 답변을 출력하고, +> 세션 오류를 숨기지 않은 채 턴을 완료합니다. + +
+2단계 전체 구현 + +작성한 내용을 다음의 전체 2단계 구현과 비교합니다. + +`src/workshop.ts` (`streamResponse`): + +```typescript +export async function streamResponse(session: CopilotSession, prompt: string): Promise { + await new Promise((resolve, reject) => { + let receivedDelta = false; + const unsubscribe = session.on((event) => { + if (event.type === "assistant.message_delta" && event.data.deltaContent) { + receivedDelta = true; + process.stdout.write(event.data.deltaContent); + } else if (event.type === "assistant.message" && !receivedDelta) { + process.stdout.write(event.data.content); + } else if (event.type === "tool.execution_start") { + console.log(`\n[tool:start] ${event.data.toolName}`); + } else if (event.type === "tool.execution_complete") { + console.log(`[tool:done] success=${event.data.success}`); + } else if (event.type === "session.error") { + reject(new Error(event.data.message)); + } else if (event.type === "session.idle") { + console.log(); + unsubscribe(); + resolve(); + } + }); + void session.send({ prompt }).catch(reject); + }); +} +``` + +`src/index.ts`: + +```typescript +import { CopilotClient } from "@github/copilot-sdk"; +import { streamResponse } from "./workshop.js"; + +const client = new CopilotClient(); +await client.start(); +try { + const session = await client.createSession({ streaming: true }); + try { + await streamResponse( + session, + "Describe why streaming improves an interactive assistant in one sentence.", + ); + } finally { + await session.disconnect(); + } +} finally { + await client.stop(); +} +``` + +
+::: + +:::language python +## Python에서 응답 스트리밍 + +### 1. 세션 이벤트 구독 + +`main.py`를 스트리밍을 활성화하고, `AssistantMessageDeltaData`를 처리하며, +`AssistantMessageData` 폴백을 유지하고, `SessionErrorData`를 노출하며, +`SessionIdleData`를 기다리는 비동기 진입점으로 바꿉니다. + +```python +import asyncio + +from copilot import CopilotClient +from copilot.session_events import ( + AssistantMessageData, + AssistantMessageDeltaData, + SessionErrorData, + SessionIdleData, +) + + +async def main() -> None: + async with CopilotClient() as client: + async with await client.create_session(streaming=True) as session: + done = asyncio.Event() + error: RuntimeError | None = None + received_delta = False + + def on_event(event) -> None: + nonlocal error, received_delta + match event.data: + case AssistantMessageDeltaData(delta_content=delta) if delta: + received_delta = True + print(delta, end="", flush=True) + case AssistantMessageData(content=content) if content and not received_delta: + print(content) + case SessionErrorData(message=message): + error = RuntimeError(message) + done.set() + case SessionIdleData(): + done.set() + + session.on(on_event) + await session.send( + "Explain accessible names in three short bullet points." + ) + await done.wait() + if error is not None: + raise error + + +if __name__ == "__main__": + asyncio.run(main()) +``` + +최종 메시지를 처리하는 분기는 런타임이 델타 없이 완료되는 경우에 대응합니다. +세션 오류가 발생하면 `error`를 설정하고 대기를 완료하여 턴이 성공한 것처럼 보이지 않게 합니다. + +## 실행 + +```bash +python main.py +``` + +글머리 기호 항목이 이벤트 콜백을 통해 점진적으로 표시되기 시작해야 합니다. + +```text +- Gives a control a programmatic identity. +- Helps screen-reader users understand its purpose. +- Connects visible labels to form controls. +``` + +
+이번 실행 문제 해결 + +| 증상 | 해결 방법 | +|---|---| +| 텍스트가 마지막에만 표시됨 | `create_session`에 `streaming=True`가 전달되는지 확인합니다. | +| 텍스트가 표시되기 전에 프로세스가 종료됨 | `session.send` 이후에 `await done.wait()`를 호출하는지 확인합니다. | +| 텍스트가 두 번 출력됨 | `AssistantMessageData`에 `not received_delta` 가드를 유지합니다. | +| 세션 이벤트 import 오류 | `copilot.session_events`에서 이벤트 형식을 import합니다. | + +
+ +> **다음 조건을 충족하면 도구를 추가할 준비가 된 것입니다.** 구성된 응답 경로가 답변을 출력하고, +> 세션 오류를 숨기지 않은 채 턴을 완료합니다. + +
+2단계 전체 구현 + +작성한 내용을 다음의 전체 2단계 구현과 비교합니다. + +`main.py`: + +```python +import asyncio + +from copilot import CopilotClient +from copilot.session_events import AssistantMessageData, AssistantMessageDeltaData, SessionErrorData, SessionIdleData + + +async def main() -> None: + async with CopilotClient() as client: + async with await client.create_session(streaming=True) as session: + done = asyncio.Event() + error: RuntimeError | None = None + received_delta = False + + def on_event(event) -> None: + nonlocal error, received_delta + match event.data: + case AssistantMessageDeltaData(delta_content=delta) if delta: + received_delta = True + print(delta, end="", flush=True) + case AssistantMessageData(content=content) if content and not received_delta: + print(content) + case SessionErrorData(message=message): + error = RuntimeError(message) + done.set() + case SessionIdleData(): + done.set() + + session.on(on_event) + await session.send("Explain accessible names in three short bullet points.") + await done.wait() + if error is not None: + raise error + + +if __name__ == "__main__": + asyncio.run(main()) +``` + +
+::: + +:::language go +## Go에서 응답 스트리밍 + +### 1. 스트리밍 도우미 추가 + +`main.go`의 패키지 내용을 `session.On`으로 구독하고, +`AssistantMessageDeltaData`를 출력하며, `SendAndWait` 이후에 +`AssistantMessageData` 폴백을 유지하고, 전송 오류를 반환하는 `streamResponse` +도우미로 바꿉니다. + +```go +package main + +import ( + "context" + "fmt" + + copilot "github.com/github/copilot-sdk/go" +) + +func streamResponse(session *copilot.Session, prompt string) error { + receivedDelta := false + unsubscribe := session.On(func(event copilot.SessionEvent) { + if delta, ok := event.Data.(*copilot.AssistantMessageDeltaData); ok { + receivedDelta = true + fmt.Print(delta.DeltaContent) + } + }) + defer unsubscribe() + + response, err := session.SendAndWait(context.Background(), copilot.MessageOptions{Prompt: prompt}) + if err == nil && !receivedDelta && response != nil { + if message, ok := response.Data.(*copilot.AssistantMessageData); ok { + fmt.Print(message.Content) + } + } + fmt.Println() + return err +} +``` + +### 2. 스트리밍 세션 생성 및 도우미 호출 + +도우미 아래에 `main`을 추가합니다. + +```go +func main() { + client := copilot.NewClient(&copilot.ClientOptions{LogLevel: "error"}) + if err := client.Start(context.Background()); err != nil { + panic(err) + } + defer client.Stop() + + session, err := client.CreateSession(context.Background(), &copilot.SessionConfig{ + Streaming: copilot.Bool(true), + }) + if err != nil { + panic(err) + } + defer session.Disconnect() + + if err := streamResponse(session, "Explain accessible names in three short bullet points."); err != nil { + panic(err) + } +} +``` + +## 실행 + +```bash +go run . +``` + +글머리 기호 항목이 이벤트 콜백을 통해 점진적으로 표시되기 시작해야 합니다. + +```text +- Gives a control a programmatic identity. +- Helps screen-reader users understand its purpose. +- Connects visible labels to form controls. +``` + +
+이번 실행 문제 해결 + +| 증상 | 해결 방법 | +|---|---| +| 텍스트가 마지막에만 표시됨 | `SessionConfig`에 `Streaming: copilot.Bool(true)`가 설정되어 있는지 확인합니다. | +| 프로세스가 출력 없이 종료됨 | `streamResponse`가 `SendAndWait`를 사용하고 오류를 반환하는지 확인합니다. | +| 텍스트가 두 번 출력됨 | `AssistantMessageData`를 출력하기 전에 `!receivedDelta` 가드를 유지합니다. | +| import 경로 오류 | `copilot "github.com/github/copilot-sdk/go"`를 사용합니다. | + +
+ +> **다음 조건을 충족하면 도구를 추가할 준비가 된 것입니다.** 구성된 응답 경로가 답변을 출력하고, +> 세션 오류를 숨기지 않은 채 턴을 완료합니다. + +
+2단계 전체 구현 + +작성한 내용을 다음의 전체 2단계 구현과 비교합니다. + +`main.go`: + +```go +package main + +import ( + "context" + "fmt" + + copilot "github.com/github/copilot-sdk/go" +) + +func streamResponse(session *copilot.Session, prompt string) error { + receivedDelta := false + unsubscribe := session.On(func(event copilot.SessionEvent) { + if delta, ok := event.Data.(*copilot.AssistantMessageDeltaData); ok { + receivedDelta = true + fmt.Print(delta.DeltaContent) + } + }) + defer unsubscribe() + + response, err := session.SendAndWait(context.Background(), copilot.MessageOptions{Prompt: prompt}) + if err == nil && !receivedDelta && response != nil { + if message, ok := response.Data.(*copilot.AssistantMessageData); ok { + fmt.Print(message.Content) + } + } + fmt.Println() + return err +} + +func main() { + client := copilot.NewClient(&copilot.ClientOptions{LogLevel: "error"}) + if err := client.Start(context.Background()); err != nil { + panic(err) + } + defer client.Stop() + + session, err := client.CreateSession(context.Background(), &copilot.SessionConfig{ + Streaming: copilot.Bool(true), + }) + if err != nil { + panic(err) + } + defer session.Disconnect() + + if err := streamResponse(session, "Explain accessible names in three short bullet points."); err != nil { + panic(err) + } +} +``` + +
+::: + +:::language rust +## Rust에서 응답 스트리밍 + +### 1. 스트리밍 도우미 매크로 추가 + +`src/main.rs`를 `session.subscribe()`를 호출하고, `tokio::select!`로 어시스턴트 +델타를 출력하며, 최종 메시지 폴백을 유지하고, 전송 완료와 `session.idle`이 모두 +발생할 때까지 기다리는 `stream_response!` 매크로로 바꿉니다. + +```rust +use std::io::{self, Write}; + +use github_copilot_sdk::types::SessionConfig; +use github_copilot_sdk::{Client, ClientOptions}; + +macro_rules! stream_response { + ($session:expr, $prompt:expr) => {{ + let mut events = $session.subscribe(); + let send = $session.send($prompt); + tokio::pin!(send); + let mut sent = false; + let mut idle = false; + let mut received_delta = false; + + while !sent || !idle { + tokio::select! { + result = &mut send, if !sent => { + result?; + sent = true; + } + event = events.recv() => { + let event = event?; + match event.event_type.as_str() { + "assistant.message_delta" => { + if let Some(delta) = event.data.get("deltaContent").and_then(|value| value.as_str()) { + received_delta = true; + print!("{delta}"); + io::stdout().flush()?; + } + } + "assistant.message" if !received_delta => { + if let Some(content) = event.data.get("content").and_then(|value| value.as_str()) { + print!("{content}"); + io::stdout().flush()?; + } + } + "session.error" => { + let message = event.data.get("message").and_then(|value| value.as_str()) + .unwrap_or("Copilot session failed"); + return Err(std::io::Error::new(std::io::ErrorKind::Other, message.to_owned()).into()); + } + "session.idle" => idle = true, + _ => {} + } + } + } + } + println!(); + }}; +} +``` + +### 2. 스트리밍 세션 생성 및 매크로 호출 + +매크로 아래에 비동기 진입점을 추가합니다. + +```rust +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::start(ClientOptions::default()).await?; + let mut config = SessionConfig::default(); + config.streaming = Some(true); + let session = client.create_session(config).await?; + + stream_response!( + session, + "Explain accessible names in three short bullet points.".to_owned() + ); + session.disconnect().await?; + client.stop().await?; + Ok(()) +} +``` + +## 실행 + +```bash +cargo run +``` + +글머리 기호 항목이 이벤트 구독을 통해 점진적으로 표시되기 시작해야 합니다. + +```text +- Gives a control a programmatic identity. +- Helps screen-reader users understand its purpose. +- Connects visible labels to form controls. +``` + +
+이번 실행 문제 해결 + +| 증상 | 해결 방법 | +|---|---| +| 텍스트가 마지막에만 표시됨 | `create_session`을 호출하기 전에 `config.streaming = Some(true)`인지 확인합니다. | +| 텍스트가 표시되기 전에 프로세스가 종료됨 | `while !sent \|\| !idle` 루프를 유지하고 `session.idle`을 기다립니다. | +| 텍스트가 두 번 출력됨 | `"assistant.message"`에 `if !received_delta` 가드를 유지합니다. | +| 출력이 버퍼링된 것처럼 보임 | 델타 콘텐츠를 `print!`로 출력할 때마다 표준 출력을 플러시합니다. | + +
+ +> **다음 조건을 충족하면 도구를 추가할 준비가 된 것입니다.** 구성된 응답 경로가 답변을 출력하고, +> 세션 오류를 숨기지 않은 채 턴을 완료합니다. + +
+2단계 전체 구현 + +작성한 내용을 다음의 전체 2단계 구현과 비교합니다. + +`src/main.rs`: + +```rust +use std::io::{self, Write}; + +use github_copilot_sdk::types::SessionConfig; +use github_copilot_sdk::{Client, ClientOptions}; + +macro_rules! stream_response { + ($session:expr, $prompt:expr) => {{ + let mut events = $session.subscribe(); + let send = $session.send($prompt); + tokio::pin!(send); + let mut sent = false; + let mut idle = false; + let mut received_delta = false; + + while !sent || !idle { + tokio::select! { + result = &mut send, if !sent => { + result?; + sent = true; + } + event = events.recv() => { + let event = event?; + match event.event_type.as_str() { + "assistant.message_delta" => { + if let Some(delta) = event.data.get("deltaContent").and_then(|value| value.as_str()) { + received_delta = true; + print!("{delta}"); + io::stdout().flush()?; + } + } + "assistant.message" if !received_delta => { + if let Some(content) = event.data.get("content").and_then(|value| value.as_str()) { + print!("{content}"); + io::stdout().flush()?; + } + } + "session.error" => { + let message = event.data.get("message").and_then(|value| value.as_str()) + .unwrap_or("Copilot session failed"); + return Err(std::io::Error::new(std::io::ErrorKind::Other, message.to_owned()).into()); + } + "session.idle" => idle = true, + _ => {} + } + } + } + } + println!(); + }}; +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::start(ClientOptions::default()).await?; + let mut config = SessionConfig::default(); + config.streaming = Some(true); + let session = client.create_session(config).await?; + + stream_response!( + session, + "Explain accessible names in three short bullet points.".to_owned() + ); + session.disconnect().await?; + client.stop().await?; + Ok(()) +} +``` + +
+::: + +:::language java +## Java에서 응답 스트리밍 + +### 1. 세션에서 스트리밍 활성화 + +Java SDK 구현에서는 스트리밍이 활성화된 `SessionConfig`와 `sendAndWait`를 사용한 다음, +완성된 어시스턴트 메시지를 출력합니다. `src/main/java/workshop/AccessibilityReport.java`를 +다음 내용으로 바꿉니다. + +```java +package workshop; + +import com.github.copilot.CopilotClient; +import com.github.copilot.rpc.MessageOptions; +import com.github.copilot.rpc.PermissionHandler; +import com.github.copilot.rpc.SessionConfig; + +public final class AccessibilityReport { + private AccessibilityReport() { + } + + public static void main(String[] args) throws Exception { + try (var client = new CopilotClient()) { + client.start().get(); + var session = client.createSession(new SessionConfig() + .setStreaming(true) + .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get(); + var response = session.sendAndWait(new MessageOptions() + .setPrompt("Explain accessible names in three short bullet points.")) + .get(); + if (response == null) { + throw new IllegalStateException("Copilot completed without an assistant message."); + } + System.out.println(response.getData().content()); + } + } +} +``` + +`setStreaming(true)`를 사용하면 이 단계가 다른 언어 과정과 동일하게 구성됩니다. +Java 구현은 `sendAndWait`에서 완성된 응답을 기다렸다가 턴이 끝나면 전체 메시지를 출력합니다. + +## 실행 + +```bash +./mvnw compile exec:java +``` + +프로세스가 종료되기 전에 완성된 응답이 출력되어야 합니다. + +```text +- Gives a control a programmatic identity. +- Helps screen-reader users understand its purpose. +- Connects visible labels to form controls. +``` + +
+이번 실행 문제 해결 + +| 증상 | 해결 방법 | +|---|---| +| 응답이 출력되지 않음 | `SessionConfig`에 `setStreaming(true)`가 있고 `sendAndWait`를 호출하는지 확인합니다. | +| null 응답으로 프로세스가 실패함 | `response == null` 가드를 유지하고 메시지 없이 턴이 완료되면 예외를 발생시킵니다. | +| Maven이 메인 클래스를 찾지 못함 | 스타터 디렉터리에서 `./mvnw compile exec:java`를 실행합니다. | + +
+ +> **다음 조건을 충족하면 도구를 추가할 준비가 된 것입니다.** 구성된 응답 경로가 답변을 출력하고, +> 세션 오류를 숨기지 않은 채 턴을 완료합니다. + +
+2단계 전체 구현 + +작성한 내용을 다음의 전체 2단계 구현과 비교합니다. + +`src/main/java/workshop/AccessibilityReport.java`: + +```java +package workshop; + +import com.github.copilot.CopilotClient; +import com.github.copilot.rpc.MessageOptions; +import com.github.copilot.rpc.PermissionHandler; +import com.github.copilot.rpc.SessionConfig; + +public final class AccessibilityReport { + private AccessibilityReport() { + } + + public static void main(String[] args) throws Exception { + try (var client = new CopilotClient()) { + client.start().get(); + var session = client.createSession(new SessionConfig() + .setStreaming(true) + .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get(); + var response = session.sendAndWait(new MessageOptions() + .setPrompt("Explain accessible names in three short bullet points.")) + .get(); + if (response == null) { + throw new IllegalStateException("Copilot completed without an assistant message."); + } + System.out.println(response.getData().content()); + } + } +} +``` + +
+::: + +## 이해도 확인 + +이벤트 스트리밍보다 완료 응답 전송 방식이 더 적합한 경우는 언제입니까? + +
+정답 확인 + +점진적 출력이나 중간 이벤트가 필요하지 않은 백그라운드 작업 또는 단순한 요청/응답 코드에는 +완료 응답 전송 방식을 사용합니다. + +
+ +## 자세히 알아보기 + +- [방향 전환 및 큐잉](https://github.com/github/copilot-sdk/blob/main/docs/features/steering-and-queueing.md): + 턴이 아직 실행 중일 때 방향을 바꾸거나 작업을 큐에 추가하기 위해 다른 메시지를 보냅니다. +- [세션 제한](https://github.com/github/copilot-sdk/blob/main/docs/features/session-limits.md): + 세션에서 토큰 생성을 시작하기 전에 AI Credits 예산을 설정합니다. +- [사용량 및 청구 메트릭](https://github.com/github/copilot-sdk/blob/main/docs/features/usage-and-billing.md): + 동일한 이벤트 스트림에서 토큰 수, 컨텍스트 창 사용량 및 비용을 읽습니다. + +[3단계: 애플리케이션 소유 지식 추가](03-local-tool.md)로 계속 진행합니다. diff --git a/localizations/ko-kr/workshop/03-local-tool.md b/localizations/ko-kr/workshop/03-local-tool.md new file mode 100644 index 0000000..5f4a37d --- /dev/null +++ b/localizations/ko-kr/workshop/03-local-tool.md @@ -0,0 +1,981 @@ +# 3단계: 애플리케이션 소유 지식 추가 + +> **소요 시간:** 15분 + +## 추가할 내용 + +애플리케이션이 소유한 웹 콘텐츠 접근성 지침(WCAG) 카탈로그에서 정확한 성공 기준과 해결 방법을 검색하는 타입이 지정된 로컬 도구를 Copilot에 제공합니다. + +## 애플리케이션이 소유한 도구를 Copilot에 제공 + +**도구 호출(Tool calling)**을 사용하면 모델이 답변을 작성하는 동안 기능을 요청할 수 있습니다. [**로컬 도구(Local tool)**](https://github.com/github/copilot-sdk/blob/main/docs/getting-started.md#how-tools-work)는 애플리케이션 프로세스 내에서 실행됩니다. 모델이 도구를 요청할 시점을 결정하지만 데이터, 유효성 검사, 실행, 결과는 계속 코드에서 관리합니다. + +이 단계에서는 애플리케이션이 소유한 WCAG 지침을 `accessibility_rule_lookup`으로 공개하고, 해당 도구를 세션에 등록한 후 모델에서 사용할 수 있도록 명시적으로 설정합니다. + +## 신뢰할 수 있는 자체 데이터 원본 사용 + +모델의 일반 지식은 애플리케이션이 소유한 데이터를 대체할 수 없습니다. 이 로컬 도구는 모든 프롬프트에 전체 카탈로그를 넣는 대신 테스트할 수 있는 결정론적 코드에서 작고 정확한 결과를 반환합니다. + +여기서 `skip permission`을 사용하는 것은 도구가 애플리케이션 소유 데이터만 읽기 때문입니다. 다음 단계의 외부 MCP 프로세스에는 별도의 권한 경계를 적용합니다. + +:::language dotnet +## C# 조회 연결 + +### 1. 카탈로그 조회 도구 추가 + +`Helpers/AccessibilityRuleCatalog.cs`의 맨 위에 다음 코드를 삽입합니다. + +```csharp +using System.ComponentModel; +using GitHub.Copilot; +using Microsoft.Extensions.AI; +``` + +`AccessibilityRuleCatalog` 내부에서 기존 `Rules` 배열 뒤에 다음 코드를 삽입합니다. + +```csharp +public static AIFunction CreateLookupTool() => CopilotTool.DefineTool( + ([Description("The accessibility issue or WCAG criterion to look up.")] string query) => + Task.FromResult(Lookup(query)), + toolOptions: new CopilotToolOptions { SkipPermission = true }, + factoryOptions: new AIFunctionFactoryOptions + { + Name = "accessibility_rule_lookup", + Description = "Looks up read-only WCAG guidance maintained by this application." + }); + +public static AccessibilityRule Lookup(string query) +{ + var normalizedQuery = query.Trim(); + return Rules.FirstOrDefault(rule => + normalizedQuery.Contains(rule.Criterion, StringComparison.OrdinalIgnoreCase) || + normalizedQuery.Contains(rule.Title, StringComparison.OrdinalIgnoreCase) || + rule.Keywords.Any(keyword => + normalizedQuery.Contains(keyword, StringComparison.OrdinalIgnoreCase))) + ?? new AccessibilityRule( + "No exact match", + "Criterion not found", + "The issue is not represented in the workshop catalog.", + "Verify the evidence and consult the complete WCAG reference.", + []); +} +``` + +### 2. 도구 활동 표시 + +`Helpers/ResponseStreamer.cs`에서 `SessionIdleEvent` 앞에 다음 case를 삽입합니다. + +```csharp +case ToolExecutionStartEvent tool: + Console.WriteLine($"\n[tool:start] {tool.Data.ToolName}"); + break; +case ToolExecutionCompleteEvent tool: + Console.WriteLine($"[tool:done] success={tool.Data.Success}"); + break; +``` + +### 3. 도구 등록 및 요청 + +`Program.cs`의 세션 구성과 전송 호출을 다음 코드로 바꿉니다. + +```csharp +await using var session = await client.CreateSessionAsync(new SessionConfig +{ + Streaming = true, + Tools = [AccessibilityRuleCatalog.CreateLookupTool()], + AvailableTools = ["accessibility_rule_lookup"] +}); + +Console.WriteLine("\nCopilot:"); +await ResponseStreamer.SendAndPrintAsync( + session, + "Use accessibility_rule_lookup to explain how to fix an input with no accessible name."); +``` + +## 실행 + +```bash +dotnet run +``` + +도구 이름과 4.1.2에 매핑된 결과가 표시되는지 확인합니다. + +```text +[tool:start] accessibility_rule_lookup +[tool:done] success=True + +WCAG 4.1.2 Name, Role, Value ... +``` + +
+실행 문제 해결 + +| 증상 | 해결 방법 | +|---|---| +| 도구 이벤트가 표시되지 않음 | 이 학습 단계에서는 명시적인 `Use accessibility_rule_lookup` 지침을 유지합니다. | +| 컴파일러에서 `AIFunction`을 찾을 수 없음 | 카탈로그 파일에 `using Microsoft.Extensions.AI;`를 추가합니다. | +| 정확히 일치하는 항목이 없다는 결과가 표시됨 | 프롬프트에 시작 데이터의 키워드인 `accessible name`이 포함되어 있는지 확인합니다. | + +
+ +
+3단계 전체 구현 + +작성한 버전을 다음의 완전한 3단계 구현과 비교합니다. + +`Program.cs`: + +```csharp +using GitHub.Copilot; +using HelloCopilotSDK.Helpers; + +Console.WriteLine("=== Application-owned WCAG guidance ===\n"); + +await using var client = new CopilotClient(); +await client.StartAsync(); + +var ping = await client.PingAsync("workshop"); +Console.WriteLine($"Connected to the Copilot runtime: {ping.Message}\n"); + +await using var session = await client.CreateSessionAsync(new SessionConfig +{ + Streaming = true, + Tools = [AccessibilityRuleCatalog.CreateLookupTool()], + AvailableTools = ["accessibility_rule_lookup"] +}); + +Console.WriteLine("Copilot:"); +await ResponseStreamer.SendAndPrintAsync( + session, + "Use accessibility_rule_lookup to explain how to fix an input with no accessible name."); +``` + +카탈로그 도구와 조회 기능은 `Helpers/AccessibilityRuleCatalog.cs`에 있습니다. 도구 시작 및 완료 출력 기능은 `Helpers/ResponseStreamer.cs`에 있습니다. + +
+::: + +:::language nodejs +## TypeScript 조회 연결 + +### 1. 미리 빌드된 타입 지정 도구 확인 + +`src/workshop.ts`를 엽니다. 스타터 프로젝트에는 이미 카탈로그를 가져오고 다음 로컬 도구를 정의하는 코드가 있습니다. + +```typescript +export const accessibilityRuleLookup = defineTool("accessibility_rule_lookup", { + description: "Looks up read-only WCAG guidance maintained by this application.", + parameters: z.object({ query: z.string().describe("The accessibility issue or WCAG criterion to look up.") }), + skipPermission: true, + handler: async ({ query }) => { + const normalized = query.trim().toLowerCase(); + return accessibilityRules.find((rule) => normalized.includes(rule.criterion.toLowerCase()) || normalized.includes(rule.title.toLowerCase()) || rule.keywords.some((keyword) => normalized.includes(keyword))) ?? noMatch; + }, +}); +``` + +Zod 스키마는 모델에 타입이 지정된 `query` 인수를 제공합니다. 핸들러는 애플리케이션이 계속 소유하는 `accessibilityRules`를 검색합니다. 이 도구는 애플리케이션 소유의 읽기 전용 데이터만 반환하므로 `skipPermission: true`를 의도적으로 사용합니다. + +### 2. 도구 활동 출력 확인 + +같은 파일에서 `streamResponse`는 이미 도구 수명 주기 이벤트를 출력합니다. + +```typescript +else if (event.type === "tool.execution_start") console.log(`\n[tool:start] ${event.data.toolName}`); +else if (event.type === "tool.execution_complete") console.log(`[tool:done] success=${event.data.success}`); +``` + +모델이 로컬 도구를 호출하는 시점을 확인할 수 있도록 이 분기를 유지합니다. + +### 3. 도구 등록 및 요청 + +`src/index.ts`에서 스트리밍 도우미와 함께 도구를 가져옵니다. + +```typescript +import { accessibilityRuleLookup, streamResponse } from "./workshop.js"; +``` + +세션 생성과 전송 호출을 다음 코드로 바꿉니다. + +```typescript +const session = await client.createSession({ + streaming: true, + tools: [accessibilityRuleLookup], + availableTools: ["accessibility_rule_lookup"], +}); +try { + await streamResponse( + session, + "Use accessibility_rule_lookup to explain WCAG 4.1.2.", + ); +} finally { + await session.disconnect(); +} +``` + +`tools`는 구현을 등록합니다. `availableTools`는 모델이 호출할 수 있는 도구의 허용 목록입니다. + +## 실행 + +```bash +npm start +``` + +도구 이름과 WCAG 4.1.2 지침이 표시되는지 확인합니다. + +```text +[tool:start] accessibility_rule_lookup +[tool:done] success=true + +WCAG 4.1.2 Name, Role, Value ... +``` + +
+실행 문제 해결 + +| 증상 | 해결 방법 | +|---|---| +| TypeScript에서 `zod`를 확인할 수 없음 | 스타터 프로젝트 디렉터리에서 `npm install`을 실행합니다. | +| 도구 이벤트가 표시되지 않음 | `tools`와 `availableTools` 모두에 도구 이름을 유지하고, 프롬프트에 명시적인 지침을 유지합니다. | +| 조회 결과와 일치하는 항목이 없음 | 카탈로그에 모두 포함된 `4.1.2` 또는 `accessible name`에 관해 질문합니다. | +| 도구 이벤트가 출력되지 않음 | `streamResponse`가 계속 `tool.execution_start`와 `tool.execution_complete`를 처리하는지 확인합니다. | + +
+ +
+3단계 전체 구현 + +작성한 버전을 다음의 완전한 3단계 구현과 비교합니다. + +`src/index.ts`: + +```typescript +import { CopilotClient } from "@github/copilot-sdk"; +import { accessibilityRuleLookup, streamResponse } from "./workshop.js"; + +const client = new CopilotClient(); +await client.start(); +try { + const session = await client.createSession({ + streaming: true, + tools: [accessibilityRuleLookup], + availableTools: ["accessibility_rule_lookup"], + }); + try { + await streamResponse(session, "Use accessibility_rule_lookup to explain WCAG 4.1.2."); + } finally { + await session.disconnect(); + } +} finally { + await client.stop(); +} +``` + +타입이 지정된 도구 정의와 도구 활동 출력 기능은 `src/workshop.ts`에 있습니다. + +
+::: + +:::language python +## Python 조회 연결 + +### 1. 미리 빌드된 타입 지정 도구 확인 + +`workshop.py`를 엽니다. 스타터 프로젝트에는 이미 매개 변수 모델과 로컬 도구가 정의되어 있습니다. + +```python +class LookupParams(BaseModel): + query: str = Field(description="The accessibility issue or WCAG criterion to look up.") + + +@define_tool(name="accessibility_rule_lookup", description="Looks up read-only WCAG guidance maintained by this application.", skip_permission=True) +def accessibility_rule_lookup(params: LookupParams) -> dict[str, object]: + query = params.query.strip().lower() + rule = next((item for item in ACCESSIBILITY_RULES if item.criterion.lower() in query or item.title.lower() in query or any(keyword in query for keyword in item.keywords)), None) + if rule is None: + return {"criterion": "No exact match", "title": "Criterion not found", "when_it_applies": "The issue is not represented in the workshop catalog.", "recommendation": "Verify the evidence and consult the complete WCAG reference."} + return rule.__dict__ +``` + +Pydantic은 모델에 표시되는 인수를 설명하고, 핸들러는 애플리케이션이 계속 소유하는 `ACCESSIBILITY_RULES`를 검색합니다. 이 도구는 애플리케이션 소유의 읽기 전용 데이터만 반환하므로 `skip_permission=True`를 의도적으로 사용합니다. + +### 2. 도구 등록 및 요청 + +`main.py`에서 도구를 가져옵니다. + +```python +from workshop import accessibility_rule_lookup +``` + +세션 생성과 전송 호출을 다음 코드로 바꿉니다. 세션 블록 안에 2단계의 이벤트 핸들러를 유지합니다. + +```python +async with await client.create_session( + streaming=True, + tools=[accessibility_rule_lookup], + available_tools=["accessibility_rule_lookup"], +) as session: + done = asyncio.Event() + error: RuntimeError | None = None + received_delta = False + + def on_event(event) -> None: + nonlocal error, received_delta + match event.data: + case AssistantMessageDeltaData(delta_content=delta) if delta: + received_delta = True + print(delta, end="", flush=True) + case AssistantMessageData(content=content) if content and not received_delta: + print(content) + case SessionErrorData(message=message): + error = RuntimeError(message) + done.set() + case SessionIdleData(): + done.set() + + session.on(on_event) + await session.send( + "Use accessibility_rule_lookup to explain WCAG 4.1.2." + ) + await done.wait() + if error is not None: + raise error +``` + +`tools`는 구현을 등록합니다. `available_tools`는 모델이 호출할 수 있는 도구의 허용 목록입니다. + +## 실행 + +```bash +python main.py +``` + +응답에는 카탈로그의 WCAG 4.1.2 제목과 권장 사항이 사용되어야 합니다. + +```text +WCAG 4.1.2 Name, Role, Value ... +Associate a visible