Simple Ways to Buy Telegram Accounts Telegram is a popular messaging platform used by individuals, businesses, creators, and online communities. Some people look for established Telegram accounts because they want an existing username, profile, or online presence. We’re Always Online — 24/7 Reply/Contact 📲▶ Telegram: @usanovapro 📞▶ WhatsApp: ▶+1(208) 402-6908 🌐Visit Now Link ; infousanovapro@gmail.com However, buying an account from another person can involve important security, privacy, ownership, and fraud risks. Before considering an account purchase, it is important to understand the safer options and what to watch for. Why Do People Want Existing Telegram Accounts? There are several reasons someone might be interested in an existing account. An account may already have a username that a buyer wants. A business or community manager may also believe that an established account will save time compared with starting from scratch. However, account age or an existing profile does not automatically make an account more trustworthy or valuable. The Simplest Option: Create Your Own Account For most users, creating a new Telegram account is the safest approach. When you create your own account, you control the registration information, security settings, profile, and account history from the beginning. You also avoid many problems associated with accounts previously controlled by unknown users. If You Are Considering an Existing Account If you have a legitimate reason to evaluate an existing account, research carefully before making any transaction. Verify Ownership Make sure the person offering the account has legitimate authority to transfer it. If ownership cannot be clearly established, consider that a major warning sign. Understand the Account's History Ask what you can legitimately learn about the account's previous use. An account with an unknown history may carry security or reputation risks. Review Telegram's Current Policies Telegram's rules and policies may change. Review the current requirements before considering an account transfer and ensure that your intended use complies with them. Be Careful With Sellers Avoid sellers who pressure you to pay immediately or refuse to provide basic information. Promises such as "guaranteed permanent access" or "never banned" should be treated skeptically. We’re Always Online — 24/7 Reply/Contact 📲▶ Telegram: @usanovapro 📞▶ WhatsApp: ▶+1(208) 402-6908 🌐Visit Now Link ; infousanovapro@gmail.com Avoid Suspicious Account Offers Some advertisements may claim to offer special or "verified" Telegram accounts. Be cautious if an offer includes claims such as: "100% unbannable" "Permanent verification" "Zero risk" "Impossible to recover" "Complete anonymity" These claims cannot reliably guarantee the long-term security of an account. Protect Your Telegram Credentials Never give your Telegram verification code or password to a seller or stranger. A verification code is sensitive authentication information. Sharing it can allow someone else to gain access to an account. Also avoid entering Telegram credentials into unfamiliar websites or links. Check for Account Security Issues An existing account may have active sessions on devices belonging to previous users. Security should therefore be considered carefully before relying on any account previously controlled by someone else. For accounts you legitimately control, regularly review active sessions, enable two-step verification, and remove devices you no longer recognize. Watch Out for Payment Scams Online transactions involving digital accounts can attract fraudulent sellers. Be cautious when someone: Demands immediate payment Refuses to explain ownership Offers an unusually cheap account Provides only screenshots as proof Disappears after receiving payment Requests sensitive login information Do not let urgency pressure you into making a decision. Why a New Account May Be Better Creating your own Telegram account offers several advantages. You have direct control over: Registration Security settings Two-step verification Profile information Active sessions Account history This makes it easier to establish clear ownership and maintain account security. Frequently Asked Questions Is it safe to buy a Telegram account? Buying an account from an unknown seller can involve significant risks, including fraud, ownership disputes, privacy problems, and unknown account history. Is an old account more valuable? Not necessarily. Age alone does not prove that an account is legitimate, secure, or useful. Should I share my Telegram login code with a seller? No. Keep all authentication codes private. Can I create my own Telegram account instead? Yes. For most legitimate purposes, creating your own account is the simplest and safest option. Conclusion There may be reasons to consider an existing Telegram account, but purchasing one can introduce risks that are easy to overlook. If you are evaluating an account, verify legitimate ownership, understand its history, review Telegram's current policies, and avoid suspicious sellers. Never share authentication codes or passwords. For most people, creating and securing their own Telegram account remains the most straightforward way to establish a reliable presence on the platform. We’re Always Online — 24/7 Reply/Contact 📲▶ Telegram: @usanovapro 📞▶ WhatsApp: ▶+1(208) 402-6908 🌐Visit Now Link ; infousanovapro@gmail.com
AI가 정말 빠르게 변화하고 있다고 느끼는 요즘입니다. 2달 전까지만 해도 최신 개념인 Loop Engineering에 대해서 블로그를 쓰려고 했는데 벌써 Loop Engineering을 넘어서 Graph Engineering이라는 이름이 나왔다고 하네요. 그래도 하던 것부터 마무리 하고 다음으로 넘어가겠습니다. Intro 말을 안해도 다들 AI가 정말 빠르게 발전하고 있다는 것을 느끼고 계실겁니다. 제가 AI를 처음 사용했을 때를 생각해 봅니다 24년 하반기까지만 해도 제가 느끼는 AI의 성능은 썩 좋지 않았습니다. md파일 먹이기 이런 개념도 없었을 뿐더러 뭐하나 물어보면 이상한 답변을 줄 때도 많았습니다. 저만 그런게 아니었나 봅니다. 한층 한층 문제들이 개선되더니 정말 기하급수적인 속도로 지금까지 와버렸습니다. 먼저 어떻게 여기까지 왔나 살펴보겠습니다. Prompt Engineering 프롬프트를 입력하여 AI에게 답변을 듣는 방식입니다. 아직까지 대부분의 사람들이 이 방식으로 AI를 사용하고 있을 것이라 생각합니다. 간단한 질문 (요리 레시피 알려줘, 사주 봐줘) 들은 프롬프트 입력만으로 충분히 해결됩니다. 더 나아가 "너는 10년차 컨설턴트야", "너는 운동 코치야" 라면서 AI에게 페르소나를 입혀주기도 합니다. 그래서 Persona Engineering 이라는 단어가 등장하기도 합니다. 하지만 규모가 큰 작업을 하고 있다면? 회사 업무나 프로젝트 등 장기간 업무를 진행해야 하는 상황입니다. AI가 이전 내용을 기억하지 못하는 상황이 발생합니다. 처음부터 다시 알려줘야 하는 상황이 반복됩니다. Context Engineering 프롬프트 입력 전에 AI가 알아야 할 사전 지식을 미리 각인시켜주고, 매 턴 모델이 뭘 볼지, 뭘 뺄지 정하는 방식입니다. AI에게 작업을 맡기기 전에 미리 필요한 지식을 입력해주고, 실질적으로 업무에 도움이 되는 지식을 AI가 선별해 유용한 답변을 받을 수 있습니다. 하지만 여기서 만족하지 않습니다. AI는 행동하지 않습니다. 결국 답변을 받은 사람이 행동으로 연결해야 한다는 귀찮음이 존재합니다. Harness Engineering AI에게 손발을 달아주고, 규제를 걸어줌으로써 안정적이고 정확하게 AI가 스스로 동작하는 방식입니다. MCP, Tool, SubAgent 등 기존에 등장한 도구들을 AI에게 쥐어줌으로써 사고만 하던 AI에서 행동까지 연결하는 AI로 만들어 줍니다. 하지만 아무런 규제 없이 AI에게 과한 권한을 부여해준다면, AI가 무슨 일을 벌일지 예측할 수 없습니다. 그래서 도구와 더불어, 컨텍스트 관리, 권한 규제 등을 같이 입력해 어느 정도 사람이 통제할 수 있는 AI로 만듭니다. 충분히 쓸만해 보입니다. 그렇지만 한번 행동을 하면 결과가 좋든 나쁘든 사람이 확인을 해야합니다. 결과를 확인하고, 수정하고, 다시 실행하고. 상당히 귀찮습니다. Loop Engineering 사람이 매번 띄우는 대신, 띄우고 검사하고 멈추는 일까지 시스템에 맡기는 방식입니다. 인간의 판단 기준을 검증기와 정지 규칙에 한 번 입력해 놓으면, 루프는 검증기 기준과 정지 규칙을 모두 통과할 때까지 반복하여 돕니다. 결국 루프의 판단은 모델의 성능이 아니라 인간이 박아둔 판단에 기반합니다. Loop의 구성요소 루프라고 하면 while 문 한 줄이 떠오르지만, 실제로 굴러가는 루프에는 조각이 몇 개 더 붙습니다. Addy Osmani는 이를 여섯 조각으로 분류했습니다. Automations - 시작 트리거 '언제 무슨 일을 해줘' 요청입니다. 사람이 "이것 좀 해줘"라고 직접 말하는 단계가 사라집니다. Claude Code 의 /loop(주기 실행) 와 /goal(조건 충족까지 실행) 이 여기에 해당합니다. Worktrees - 병렬 작업 공간 격리 여러 에이전트를 동시에 돌리면 같은 파일을 서로 덮어씁니다. git worktree 로 작업 공간을 분리해야 단일 스레드를 벗어날 수 있습니다. Skills - 프로젝트 지식 입력 매 사이클마다 "우리 프로젝트는 이렇게 돌아갑니다"를 다시 설명할 수는 없습니다. 처음으로 작업을 시작(콜드 스타트)한 모델은 모르는 부분을 추측합니다. 저희는 왜 모델이 그렇게 추측했는지 알 수가 없습니다. 이를 방지하기 위해 사전 지식을 파일로 박아둡니다. Connectors - 바깥 세상과의 연결 MCP로 이슈 트래커, DB, CI를 붙입니다. 여기서 "여기 수정안입니다"가 "PR 열었고 티켓 걸어뒀습니다"로 바뀝니다. Sub-agents - 작업 모델, 검사 모델 분리 작업을 수행하는 모델은 자신이 한 작업을 후하게 채점합니다. 그래서 작업 수행 에이전트와 작업 검사 에이전트를 나눕니다. 모델이나 추론 강도를 다르게 주기도 합니다. State / Memory - 기억 저장소 마크다운 파일이든 보드든, 밖에 적어둬야 다음 사이클이 이어집니다. "에이전트는 잊지만 레포는 잊지 않는다." Osmani의 표현입니다. 여섯 개를 다 갖춰도 빠진 게 하나 있습니다. 정지 규칙 입니다. 언제 멈출지를 정하지 않은 루프는 언제 어디서 끝날지 아무도 모릅니다. 밤새 돌려 수백 달러를 태웠다는 후기는 대부분 정지 규칙이 비어 있던 경우입니다. CodeReferee 사실 Loop Engineering을 알고 있기 전부터, 진행하고 있던 프로젝트가 있습니다. 깃허브 레포의 안정성을 검사하고, 개선사항을 알려주는 서비스입니다. Input을 깃허브 레포로 받으면 검증, 피드백, 수정을 거쳐 안정적인 코드로 재탄생시켜주는 서비스입니다. 알게 모르게 이미 Loop Engineering 개념을 적용하고 있었네요. 자세히 한번 살펴보겠습니다. CodeReferee 루프 Repository URL입력 → Preflight → Sandbox 실행 → Judge → Critic → Refiner → 리포트 먼저 Repository URL을 입력으로 받으면 실행 가능한 유효한 주소인지 검사하는 단계( Preflight )를 거칩니다. Preflight가 통과하면 샌드박스에서 Chaos Engineering 을 통해 장애를 주입하고 평가 결과를 도출합니다 결과를 보고 Judge가 Fail을 주면 Critic이 원인을 분석하고 Refiner가 수정안( diff )을 냅니다. 그 수정안을 샌드박스에 적용해 다시 돌립니다. 여기서 봐야할 점은 Loop 구조가 _run_refinement_rounds 함수 하나에 들어 있고, 70줄이 안 된다는 것입니다. 또한 이 70줄에서 대부분이 "반복"이 아니라 "중단"이라는 점입니다. 검증기 검증기는 Judge입니다. 제가 미리 적어둔 판정 기준표를 보고 Pass와 Fail만 냅니다. LLM이 혼자 판단하지 않게 하려는 목적입니다. 인간이 설정한 Chaos Engineering 통과 기준을 넘은 레포에만 '안정' 판정을 주고, 검사 보고서를 함께 냅니다. 정지 규칙 라운드 상한 3회 - 비용적 측면에서 Trade-off 고려 Refiner가 수정안을 못 내면 즉시 중단 - 고칠 수단이 없는데 반복해봐야 같은 결과입니다. 수정안이 1MB를 넘으면 중단 - 수정 범위가 그 정도면 판정 자체를 믿을 수 없습니다. 인프라 오류면 판정을 건너뛰고 중단 - Docker가 죽은 걸 사용자 코드 실패로 보고하면 안 됩니다. 아직 없는 두 조각 구성요소 여섯 개 중 둘이 제 루프에 없습니다. Automations 가 없습니다. 아직 사람이 띄웁니다. 검증과 중단은 시스템이 하는데, 레포 입력은 사용자가 진행합니다. 그래서 엄밀히 말하면 완전히 도는 루프가 아니라, 띄우면 끝까지 알아서 가는 루프입니다. Worktrees 도 없습니다. 이건 필요가 없어서 안 넣었습니다. 레포 하나를 받아 검증하는 구조라 에이전트 여러 개가 같은 파일을 다툴 일이 없습니다. 병렬은 레포 여러 개를 동시에 검증할 때 필요할 것이라 그때 붙일 계획입니다. 느낀점 구성요소를 다 채우는 게 목표가 아니라는 걸 여기서 배웠습니다. Worktrees는 안 넣은 게 맞고, Automations는 사용자가 레포 입력을 하지 않고 자동화할 방법이 있는지 고민 중에 있습니다. 정지 규칙이 본체였습니다. 제 루프의 정지 조건은 설계가 멋져서 넣은 게 아닙니다. 반복 상한이 없으면 같은 실패를 세 번 더 겪습니다. 수정안 크기 상한이 없으면 레포를 새로 쓴 걸 "수정"으로 받습니다. 인프라 오류와 코드 실패를 구분하지 않으면 Docker가 죽은 걸 사용자 잘못으로 보고합니다. 하나하나 당해보고 박아둔 규칙입니다. 루프는 제 판단의 증폭기입니다. 좋은 기준을 넣으면 좋은 판단이 빠른 속도로 반복되고, 나쁜 기준을 넣으면 자신만만한 오답이 빠른 속도로 반복됩니다. 객관적 숫자를 기반으로 기준을 세우려고 했습니다. 프롬프트를 더 잘 쓰는 일에서 멈추는 조건을 더 잘 쓰는 일로. 재미있는 쪽은 전자지만 결과를 정하는 건 후자였습니다.
HTTP란? HyperText Transfer Protocal 웹에서 브라우저와 서버가 통신하기 위한 포로토콜(규약) → 브라우저가 사이트에 접속할 때 서버에서 웹페이지를 구성하는 데이터 (HTML, CSS, JavaScript 등)을 주고받는 규칙 요청(Request)와 응답(Response)의 형태로 진행된다. HTTP의 특징 커넥션당 하나의 요청과 응답만 처리 즉, 여러개의 요청을 한번에 전송/응답할 수 없다. 기본 80번 포트 사용 무상태성 서버가 클라이언트의 상태를 보존하지 않기에 로그인과 같이 유저의 상태를 유지해야하는 서비스일 경우 브라우저 쿠키, 서버 세션, 토큰 등을 이용해 상태를 유지해야 한다. 비연결성 기본적으로 연결을 유지하지 않기에 요청을 보낼 때마다 연결했다 끊는 작업을 반복한다. 각각의 자원을 다운로드하기 위해 연결과 종료를 반복하는 비효율이 생긴다. 트래픽이 많지 않고 빠른 응답을 제공할 수 있는 경우 효율적이지만 트래픽이 많고 큰 규모의 운영을 할 때는 한계를 가진다. HTTP의 한계 데이터가 전혀 암호화되어 있지 않은 평문 통신이다. 중간에서 통신 내용을 엿보거나 가로채는 공격이 가능하다. 스니핑 네트워크상에서 다른 사용자들이 주고받는 패킷(데이터 조각)을 몰래 엿보거나 도청하는 해킹 기법 중간자 공격 두 통신 당사자 사이에 공격자가 몰래 끼어들어 중계하는 공격 HTTP 흐름 브라우저 주소창에 URL을 입력한다. DNS 조회 도메인 주소(예: www.naver.com)를 실제 서버의 IP 주소로 바꾼다. TCP 연결 3-way handshake로 브라우저와 서버가 연결을 맺는다. HTTPS라면 이후 TLS 핸드셰이크까지 진행한다. HTTP 요청 브라우저가 서버에 요청 메시지를 보낸다. 요청 메시지는 요청 라인(메서드, 경로, 버전), 헤더, 바디로 구성된다. HTTP 응답 서버가 요청을 처리하고 응답 메시지를 보낸다. 응답 메시지는 상태 라인(버전, 상태 코드), 헤더, 바디로 구성된다. 렌더링 브라우저가 받은 HTML, CSS, JS를 해석해 화면에 그린다. 이후 연결은 버전에 따라 유지되거나 종료된다. HTTP 요청 메서드 클라이언트가 서버에게 어떤 동작을 원하는지 알려주는 방법이다. 주요 메서드 GET 리소스를 조회한다. 데이터를 URL의 쿼리 스트링에 담아 보내기 때문에 주소창에 노출된다. POST 데이터를 서버에 보내 새로운 리소스를 생성한다. 데이터를 바디에 담아 보낸다. PUT 리소스 전체를 교체한다. 리소스가 없으면 새로 생성한다. PATCH 리소스의 일부만 수정한다. DELETE 리소스를 삭제한다. OPTIONS 서버가 지원하는 메서드를 확인한다. CORS의 사전 요청(Preflight)에 사용된다. 멱등성 같은 요청을 여러 번 보내도 결과가 같은 성질이다. GET, PUT, DELETE는 멱등하고 POST는 멱등하지 않다. 예) 게시글 작성 POST를 3번 보내면 게시글이 3개 생긴다. HTTP 응답 상태 코드 상태 코드는 클라이언트가 보낸 요청의 처리 상태를 응답에서 알려주는 기능으로서, 3자리 숫자로 만들어져 있으며, 100 ~ 500 번대 숫자로 이루어져 있다. 1xx (정보) 요청을 받았고 처리 중이라는 의미다. 실무에서 직접 볼 일은 거의 없다. 2xx (성공) 200 OK: 요청 성공 201 Created: 요청이 성공해 새로운 리소스가 생성됨 (주로 POST) 204 No Content: 요청은 성공했지만 돌려줄 데이터가 없음 (주로 DELETE) 3xx (리다이렉션) 301 Moved Permanently: 리소스가 영구적으로 다른 주소로 이동함 302 Found: 리소스가 일시적으로 다른 주소로 이동함 304 Not Modified: 리소스가 변경되지 않았으니 브라우저 캐시를 사용하라는 의미 4xx (클라이언트 오류) 400 Bad Request: 요청 형식이 잘못됨 401 Unauthorized: 인증이 필요함 (로그인 안 된 상태) 403 Forbidden: 인증은 됐지만 권한이 없음 404 Not Found: 요청한 리소스가 없음 5xx (서버 오류) 500 Internal Server Error: 서버 내부에서 오류가 발생함 502 Bad Gateway: 중간 서버(게이트웨이)가 잘못된 응답을 받음 503 Service Unavailable: 서버 과부하나 점검으로 일시적으로 요청을 처리할 수 없음 HTTP의 버전별 비교 HTTP/1.1 지속 연결 keepalive 라는 기능이 추가되어 연결이 이루어지고 난 뒤 각각의 자원들을 요청하고, 모든 자원에 대한 응답이 돌아온 후에 연결을 종료한다. HOL(Head-of-Line) Blocking 앞 요청의 응답이 늦으면 뒤 요청도 줄줄이 대기하는 문제 발생 HTTP/2.0 다중 요청/응답 가능 커넥션당 여러개의 요청과 응답을 주고받을 수 있다. HTTP/2.0은 여러 리소스의 동시 전송이 가능하므로 HTTP/1.1에 비해 페이지 로드 속도가 약 50% 정도 빠르다고 알려져 있다. HTTP/3.0 QUIC 프로토콜 기반: TCP 대신 UDP 위에서 동작 TCP 보낸 데이터를 확실히 받았는지 확인하면서 보내는 방식 데이터가 빠짐없이, 순서대로 도착 확인 절차가 많아서 느림 UDP 데이터를 받았는지 확인 없이 되는대로 응답을 보내는 방식 확인 절차가 없어서 매우 빠름 데이터가 빠지거나 순서가 뒤섞일 수 있음 QUIC은 UDP 위에서 재전송·순서 보장을 직접 해준다 TCP HOL Blocking 해결: 스트림이 독립적이라 한 스트림의 패킷이 유실돼도 다른 스트림은 영향을 받지 않음 HTTPS란? 보안이 강화된 HTTP 프로토콜로 HTTPS 의 S는 Secure를 의미한다. HTTPS의 특징 HTTP 통신 내용을 SSL 또는 그 후속 기술인 TLS 프로토콜을 이용해 암호화 한다. 기본 443번 포트 사용 아래 3가지 핵심 목표를 가진다 기밀성 데이터를 암호화해서 제 3자가 데이터를 훔쳐보더라도 내용을 해석할 수 없도록 한다. 무결성 전송 중에 데이터가 변조되지 않았음을 보장 누군가 내용을 바꾸면 받는 쪽에서 알아챌 수 있다. 인증 인증서를 통해 지금 접속한 서버가 위조되지 않은 진짜 서버임을 보장한다. SSL/TLS 핸드셰이크 브라우저와 서버가 본격적으로 암호화 통신을 시작하기 전에 서로를 확인하고 데이터 암호화 규칙을 정하는 과정 SSL/TLS 핸드셰이크 과정 Client Hello 클라이언트가 서버에 연결을 요청하며 아래 정보를 보낸다. 지원하는 TLS 버전 사용 가능한 암호화 방식 목록 클라이언트 랜덤 값 Server Hello 서버가 응답하며 아래 정보를 보낸다. 클라이언트가 보낸 목록 중 서버가 선택한 TLS 버전과 암호화 방식 서버 랜덤 값 Certificate 서버가 자신의 SSL 인증서를 보낸다. 인증서 안에는 서버의 공개키와 도메인 정보, CA의 전자서명이 들어 있다. 이후 Server Hello Done 메시지로 서버 쪽 전송이 끝났음을 알린다. 인증서 검증 클라이언트는 받은 인증서가 믿을 수 있는지 확인한다. 브라우저에 내장된 신뢰할 수 있는 CA가 서명했는지 유효 기간이 지나지 않았는지 인증서의 도메인이 접속한 주소와 일치하는지 검증에 실패하면 브라우저가 경고 화면을 띄운다. Client Key Exchange 클라이언트가 Pre-Master Secret이라는 랜덤 값을 새로 만든다. 이 값을 인증서에 들어 있던 서버의 공개키로 암호화해서 서버에 보낸다. 공개키로 암호화한 값은 서버의 개인키로만 풀 수 있기 때문에, 중간에서 가로채도 내용을 알 수 없다. 세션키 생성 서버는 자신의 개인키로 Pre-Master Secret을 복호화한다. 이제 클라이언트와 서버는 같은 재료 3개를 갖게 된다. Client Random + Server Random + Pre-Master Secret 양쪽이 이 재료로 각자 같은 계산을 해서 동일한 세션키(대칭키)를 만든다. 세션키 자체는 네트워크로 전송되지 않는다. Finished 양쪽이 "이제부터 세션키로 암호화한다"는 신호를 보낸다. 그동안 주고받은 핸드셰이크 내용을 세션키로 암호화한 Finished 메시지를 서로 보내 확인한다. 양쪽이 정상적으로 복호화되면 핸드셰이크가 완료된다. 암호화 통신 시작 이후 모든 HTTP 데이터는 세션키(대칭키)로 암호화해서 주고받는다. SSL 인증서의 중요성 인증서 기반 통신이 안전한 이유는 신뢰할 수 있는 제3자, 즉 인증 기관이 존재하기 때문 브라우저는 이 SSL 인증서를 확인할 때 코모도/고대디 같은 공신력 있는 인증 기관에서 발급한 것이 맞는지 브라우저에 내장된 인증 기관 목록과 비교해 철저히 검사한다. 만약 공격자가 만든 가짜 인증서라면 브라우저는 즉시 ‘이 사이트는 안전하지 않습니다’라는 경고창을 띄우고 접속을 차단한다. 검증 과정을 통과했다는 것은 현재 접속한 웹 사이트는 사칭된 웹 사이트가 아니라 진짜 해당 회사나 기관이 운영하는 진짜 서버라는 뜻이 된다. SSL 인증서는 암호화에 사용되는 공개키를 안전하게 전달하는 역할과 동시에, 접속하려는 서버의 신원을 확실하게 증명하는 역할을 한다. 이 2가지가 결합되어 HTTPS 통신은 높은 수준의 보안을 유지할 수 있다. 비대칭키와 대칭키를 섞어 쓰는 이유 비대칭키(공개키/개인키) 키를 안전하게 전달할 수 있지만 연산이 느림 대칭키(세션키) 연산이 빠르지만 같은 키를 양쪽이 안전하게 나눠 갖기 어려움 그래서 핸드셰이크에서만 비대칭키를 써서 대칭키 재료를 안전하게 전달하고, 실제 데이터는 빠른 대칭키로 암호화 Mixed Content HTTPS 페이지에서 HTTP 리소스를 불러오면 브라우저가 차단하거나 경고 HSTS 브라우저가 해당 도메인에 항상 HTTPS로만 접속하도록 강제하는 헤더
이게 뭐예요? Codex Gauge 는 맥 상단 메뉴바에서 Codex의 남은 사용량을 확인하는 앱 이에요. 남은 사용량을 고양이와 밥그릇, 게이지, 퍼센트로 보여줘요. 사용량이 넉넉하면 통통한 고양이 옆에 사료가 가득 있어요. 사용량이 줄어들면 고양이도 홀쭉해지고, 밥그릇도 비어가요. 밥이 없어질수록 표정도 짜증 나요. 제가 쓰려고 만들었는데 다른 분들도 쓸 수 있게 올려봤어요. 👉 GitHub 구경하기 👉 앱 다운로드 이렇게 보여요 메뉴바 구성은 이렇게 되어 있어요. [고양이와 밥그릇] [남은 사용량 게이지] [퍼센트] 게이지는 100%에서 시작해서 남은 사용량에 따라 줄어들어요. 고양이는 가만히 앉아서 작은 움직임만 보여줘요. 맥 전용이고 M1 이상만 되어요 macOS 13 이상 Apple Silicon, M1 이상 ChatGPT 계정으로 Codex 로그인 필요 Codex CLI 별도 설치 필요 윈도우는 없어서 못 만들었어요. CPU 폭주도 잡았어요 🐈 만들고 보니까 메뉴바 앱이 CPU 코어 하나를 계속 쓰고 있었어요. 고양이 밥보다 제 맥 배터리를 더 많이 먹고 있었어요. 메뉴바 갱신이 반복되는 문제를 수정하고, 이미지 처리와 캐시도 정리했어요. 항목 이전 개선 후 앱 CPU 사용률 약 99.8% 약 4.0% 메모리 부담 약 248MB 약 47MB 조회당 서버 실행 2회 1회 개발 맥에서 측정한 결과예요. 개선 후 10분간 CPU 평균과 마지막 메모리 부담(physical footprint)을 비교했어요. 환경에 따라 달라질 수 있어요. 화면이 꺼지거나 세션이 비활성화되면 애니메이션과 자동 조회도 쉬어요. 절전 모드와 동작 줄이기에서는 고양이도 쉬어요. 고양이 32프레임은 그대로예요. 테스트 61개도 통과했어요. 설치는 이렇게 하셔요 Releases 에서 앱 ZIP을 받아요. 압축을 풀고 Codex Gauge.app 을 응용 프로그램 폴더에 넣어요. Codex CLI가 없으면 터미널에서 아래 명령어를 한 번 실행해요. curl -fsSL https://chatgpt.com/codex/install.sh | sh Codex CLI 공식 설치 안내 앱을 더블 클릭하고, 로그인이 필요하면 브라우저에서 내 ChatGPT 계정으로 로그인해요. 위에 고양이 뜨면 된 거예요. 기존 버전을 쓰고 있다면 앱을 종료한 뒤 새 앱으로 교체해 주세요. 인증되지 않은 앱 이어요 아직 애플 개발자 서명과 공증을 받지 않은 앱이에요. 실행이 막히면 출처를 확인하고 시스템 설정 → 개인정보 보호 및 보안 → 그래도 열기 로 열어요. 마음껏 수정하세요 소스도 같이 올려뒀어요. 제가 쓰고 싶어서 만든 작은 앱이라 부족한 부분이 있을 수 있어요. 문제가 생기면 GitHub Issues 에 남겨주세요. 고양이가 마음에 들면 GitHub 스타도 하나 주셔요. ⭐
https://shroomsxpress.net/ Mushrooms Delivery Canada and magic mushrooms online in Canada, no prescription needed, delivery available worldwide. Magic mushrooms for sale near me in Canada, also called psychedelic mushrooms, cause hallucinogenic/psychedelic effects, which are believed to uplift your mood, and research has shown an increase in mental health conditions using psilocybin
코드 위치 HTML 태그의 이벤트 리스너 속성에 작성 내에 작성 자바스크립트 파일에 작성 URL부분에 작성 1. onclick="this.src="banana.png" 3. <script src="name.js">추가 작성</script> 4. <a href="javascript:자바스크립트 코드">링크</a> 자바스크립트로 HTML 콘텐츠 출력 document.write("<h3>Welcome!<h3>"); 자바스크립트 다이얼로그: 사용자 입력 및 메시지 출력 prompt(”메시지”,”디폴트 입력값”); - return var confirm(”메시지”) - return true|false alert(”메시지”); 전역변수와 지역변수 전역 변수: 함수 밖에서 선언되거나 함수내에서 var키워드 없이 선언 지역 변수: 함수 안에서 var키워드로 선언 this로 전역 변수 접근 → this.x(전역 변수) / x(지역 변수) [함수 내] 상수의 종류 : 8진수 : 0으로 시작 16진수 : 0x로 시작. 문자열 : “” | ‘’ 으로 묶음. NaN==수가 아님을 뜻함(ex.parseInt(”abc”)) <!DOCTYPE html> <html> <head> <title>함수 만들기</title> <script> function ggd(n){ var m=parseInt(n); if(isNaN5(m)||m<1||m>9){ alert("잘못입력하셨습니다"); return; } for(var i=1;i<=9;i++){ document.write(m+'x'+i+'='+m*i+'<br>'); } } </script> </head> <body> <h3>구구단 출력 함수 만들기</h3> <hr> <script> var n=prompt("구구단 몇 단을 원하세요",""); ggd(n); </script> </body> </html> == vs === 핵심 차이 연산자 이름 비교방식 == 동등 연산자 타입이 달라도 자동 형변환에서 값만 비교 === 일치 연산자 타입까지 같아야 true, 형변환 없음 예시로 확인 console.log(5=="5"); //true console.log(5==="5"); //false console.log(0==false); //true console.log(0===false); //false console.log(null == underfined); //true console.log(null === underfined); //false console.log("313" == 313); //true console.log("313" === 313); //false 자바스크립트 객체의 유형 코어 객체 Array Date String Math HTML DOM 객체 브라우저 관련 객체(BOM) Array var plots=[1,2,3,4,5]; 형식으로 만들거나 new Array()로도 만들 수 있음 Array에서는 현재 크기의 +1에 해당하는 공간에 변수를 대입하는 경우, 오류가 아닌 공간을 늘려 할당해줌. 여러 타입의 데이터가 섞여 저장 가능 주요 메서드 concat(arr) 현재 배열에 arr원소를 덧붙여 만듦 join([separator]) 모든 원소를 string화 (공백이면 ,로 구분 but, separator로 인자구분) reverse() 역순 slice(idxA[,dixB]) : A~B직전까지 부분집합 추출 sort() toString() ,로 구분 Math Math.abs() Math.sqrt(); Math.PI Math.random() 사용자 객체 만들기 <!DOCTYPE html> <html> <head> <title>new Object()로 사용자 객체 만들기</title> <script> function inquiry(){return this.balance;} function deposit(money){this.balance+=money;} function withdraw(money){this.balance-=money;return money;} var account=new Object(); account.owner="황기태"; account.code="111"; account.balance=35000; account.inquiry=inquiry; account.deposit=deposit; account.withdraw=withdraw; </script> </head> <body> <h3>new Object()로 사용자 객체 만들기</h3> <hr> <script> document.write("account : "); document.write(account.owner+","); document.write(account.code+","); document.write(account.balance+"<br>"); account.deposit(10000); document.write(account.inquiry()+"<br>"); account.withdraw(5000); document.write(account.inquiry()+"<br>"); </script> </body> </html> 리터럴 표기법으로 만들기 <!DOCTYPE html> <html> <head> <title>new Object()로 사용자 객체 만들기</title> <script> var account={ owner:"황기태", code:"111", balance:35000, inquiry:function(){return this.balance;}, deposit:function(money){this.balance+=money;}, withdraw:function(money){this.balance-=money;return money;} }; </script> </head> <body> <h3>new Object()로 사용자 객체 만들기</h3> <hr> <script> document.write("account : "); document.write(account.owner+","); document.write(account.code+","); document.write(account.balance+"<br>"); account.deposit(10000); document.write(account.inquiry()+"<br>"); account.withdraw(5000); document.write(account.inquiry()+"<br>"); </script> </body> </html>
Telegram Accounts: The Complete Easy Buying Guide Telegram is a popular messaging platform used for personal communication, online communities, customer support, and business networking. Because established accounts may already have usernames or activity, some people consider purchasing an existing Telegram account instead of creating one. We’re Always Online — 24/7 Reply/Contact 📲▶ Telegram: @usanovapro 📞▶ WhatsApp: ▶+1(208) 402-6908 🌐Visit Now Link ; infousanovapro@gmail.com However, buying an account from another person can involve security, privacy, ownership, fraud, and platform-policy concerns. Before considering an account purchase, it is important to understand the risks and available alternatives. This guide covers the essential information beginners should know. What Is a Telegram Account? A Telegram account is a user's identity on the platform. It can be used to send messages, join groups, follow channels, and communicate with other users. An existing account may contain: A username A profile picture and biography Contacts Group memberships Channel memberships An activity history These characteristics do not necessarily indicate that an account is trustworthy or secure. Why Do People Consider Buying Telegram Accounts? Some people are interested in existing accounts because they want an established online presence. For example, someone may want a particular username, while a business may want to begin using Telegram without starting with a completely new profile. However, an established account is not automatically more useful than a new one. Your intended purpose should determine whether you actually need an existing account. Risks of Buying an Existing Account Ownership and Recovery One of the biggest concerns is whether the seller actually has legitimate authority to transfer the account. If another person retains access to important authentication or recovery information, ownership can become disputed later. Unknown History An account may have been used for activities you know nothing about. Previous spam, unwanted communications, or other problematic activity could create difficulties for the new user. Fraudulent Sellers Online sellers may provide misleading information or fail to deliver what was promised. We’re Always Online — 24/7 Reply/Contact 📲▶ Telegram: @usanovapro 📞▶ WhatsApp: ▶+1(208) 402-6908 🌐Visit Now Link ; infousanovapro@gmail.com Be especially cautious when a seller pressures you to pay quickly or refuses to explain the account's history. Privacy Concerns Existing accounts can have connections to previous conversations, contacts, groups, and other users. This can create privacy concerns that should be considered before taking control of an account. Platform Policies Telegram's policies and requirements can change. An account transfer may also have implications depending on how the account was created or used. Always review the current rules before considering an account purchase or transfer. What to Consider Before Buying If you are evaluating an existing account for a legitimate purpose, take time to research it. Verify Ownership Make sure the seller has legitimate authority over the account. If they cannot clearly explain ownership, consider that a significant warning sign. Consider the Account's History Do not assume that an older account is automatically valuable. Consider whether its previous activity could create security, reputation, or platform-related problems. Look for Red Flags Be cautious of advertisements promising: Guaranteed permanent access "Unbannable" accounts Permanent verification Complete anonymity Zero risk These claims should not be treated as guarantees. Never Share Authentication Codes Your Telegram login or verification code is sensitive information. Never provide it to a seller, buyer, stranger, or someone claiming to offer technical support. Likewise, avoid entering Telegram credentials into unfamiliar websites or forms. Creating Your Own Account For many users, creating a new Telegram account is the simplest and safest alternative. With an account you create yourself, you control: Registration information Security settings Two-step verification Profile information Active sessions Account history You also avoid inheriting unknown activity from another person. We’re Always Online — 24/7 Reply/Contact 📲▶ Telegram: @usanovapro 📞▶ WhatsApp: ▶+1(208) 402-6908 🌐Visit Now Link ; infousanovapro@gmail.com How to Secure Your Telegram Account Whether you use a new account or legitimately manage an existing one, follow basic security practices. Enable Two-Step Verification Use a strong and unique password for additional protection. Monitor Active Sessions Regularly check which devices are signed into your account and remove unfamiliar sessions. Protect Your Personal Information Think carefully about what you publish publicly and what information you share with other users. Be Careful With Links and Files Avoid suspicious links, downloads, and login pages sent by unknown users. Keep Your Software Updated Use the official Telegram application and keep it updated to receive current security improvements and features. Common Questions Is buying a Telegram account safe? Buying an account from an unknown seller can involve significant risks, including fraud, ownership disputes, privacy concerns, and unknown account history. Is an old Telegram account better? Not necessarily. Account age alone does not establish legitimacy, security, or value. Do I need to buy an account to use Telegram? No. You can create your own account and manage it directly. Can a previous owner regain an account? If another person retains access to important authentication or recovery mechanisms, there can be a risk of future access disputes. Should I give a seller my Telegram code? No. Keep verification and authentication codes private. Final Thoughts Buying an existing Telegram account may seem like a convenient shortcut, but it can introduce risks that are easy to overlook. Ownership, account history, privacy, fraud, security, and platform policies should all be considered before proceeding. For most legitimate personal and business purposes, creating your own Telegram account provides clearer ownership and greater control. If you are considering an existing account, research carefully, avoid suspicious offers, protect your authentication information, and ensure that your intended use follows Telegram's current policies.
13주차 1. 배움 정리 Multi-Agent Orchestration 지난주에는 하나의 큰 작업을 여러 작업으로 나누고 각각을 적절한 Agent에게 맡기는 멀티에이전트 구조를 살펴봤다. 이번 주에는 여기에서 더 나아가 여러 Agent가 실제로 하나의 시스템처럼 동작하려면 어떻게 관리해야 하는지를 생각해봤다. 여러 Agent를 사용하는 것 자체가 Multi-Agent System 이라면, 이 Agent들이 어떤 순서로 작업하고 서로 어떤 정보를 주고받으며 결과를 어떻게 합칠지를 관리하는 것이 Multi-Agent Orchestration 에 가깝다. Goal ↓ Task Decomposition ↓ Agent 선택 ↓ Execution ↓ Communication ↓ Result Merge ↓ Evaluation 결국 핵심은 Agent의 숫자가 아니라 작업과 Agent 사이의 관계를 어떻게 정의하는가 인 것 같다. Supervisor, Router와 Handoff 여러 Agent 중 어떤 Agent가 작업해야 하는지를 정하는 방법도 여러 가지가 있다. Router 는 입력이나 현재 상태를 보고 적절한 Agent 또는 경로를 선택하는 역할로 볼 수 있다. → Research Agent Input → Router → Coding Agent → Review Agent Supervisor 는 조금 더 넓게 전체 목표와 진행 상태를 보고 하위 Agent들에게 작업을 배분하거나 다음 작업을 결정하는 역할을 할 수 있다. ┌→ Agent A Goal → Supervisor → Agent B └→ Agent C ↓ Result 반면 Handoff 는 한 Agent가 자신의 작업을 처리하다가 다른 Agent가 더 적합하다고 판단했을 때 작업의 제어권이나 필요한 Context를 넘기는 방식으로 볼 수 있다. Agent A ↓ 현재 작업 수행 ↓ Agent B가 더 적절함 ↓ Handoff ↓ Agent B 세 가지 모두 Agent를 연결하지만 역할이 조금 다르다. Router → 어디로 보낼 것인가 Supervisor → 전체 작업을 어떻게 조율할 것인가 Handoff → 현재 작업을 누구에게 넘길 것인가 이렇게 구분하니 멀티에이전트 오케스트레이션에서 "Agent를 연결한다"는 말 안에도 여러 종류의 관계가 있다는 것을 이해하기 쉬웠다. Workflow와 Task Graph 멀티에이전트의 실행 구조는 Graph 형태로 표현할 수 있다. Node → Agent / Tool / Task Edge → 작업 사이의 관계 예를 들어 순차 작업은 A → B → C 이고 독립적인 작업은 → B → A → → E → C → → D → 처럼 Fan-out / Fan-in 구조로 표현할 수 있다. 작업 간 선후 관계만 있고 순환이 없다면 DAG(Directed Acyclic Graph) 형태로 생각할 수도 있다. 이때 중요한 것은 Agent보다 먼저 Task Dependency 를 보는 것이다. Task A ↓ Task B는 A가 필요한가? ↓ YES → Dependency NO ↓ 병렬 실행 가능 여부 확인 작업의 Dependency가 강하면 순차 실행이 필요하고, 서로 독립적이면 병렬 처리가 가능하다. 그래서 멀티에이전트의 병렬성도 Agent를 많이 만든다고 생기는 것이 아니라 작업 자체에 존재하는 독립성을 발견했을 때 활용할 수 있는 것 이라고 이해하는 편이 맞는 것 같다. State와 Context 여러 Agent가 작업하려면 현재 작업이 어떤 상태인지 공유할 필요도 있다. Context 는 현재 Agent가 판단하는 데 필요한 정보에 가깝고, State 는 Workflow 전체에서 현재 어떤 일이 진행되고 있는지를 표현하는 정보에 더 가깝게 볼 수 있다. State 현재 Task 완료된 Task 각 Agent의 결과 오류 여부 다음 실행 대상 각 Agent에게 전체 State를 모두 전달할 필요는 없다. 예를 들어 Research Agent에게 Coding Agent 내부의 모든 실행 기록까지 전달하는 것은 불필요할 수 있다. Global State ↓ 현재 Agent에게 필요한 정보 선택 ↓ Local Context ↓ Agent 이전에 Context를 공부하면서 생각했던 것처럼 필요한 정보만 적절한 시점에 전달하는 것 이 여러 Agent가 존재할 때 더 중요해지는 것 같다. A2A와 MCP Agent를 연결하는 방법을 살펴보면서 A2A 와 MCP 도 다시 구분해서 생각할 수 있었다. 둘 다 시스템 사이를 연결하기 위한 방법이지만 연결하려는 대상이 다르다. MCP Agent ↓ Tool / Resource / External System A2A Agent ↓ Agent MCP는 Agent가 외부 Tool이나 데이터에 접근할 수 있는 인터페이스를 제공하는 데 가깝고, A2A는 서로 독립적으로 동작하는 Agent가 작업을 요청하거나 결과를 주고받기 위한 통신에 초점이 있다. 따라서 하나의 멀티에이전트 시스템에서도 둘을 같이 사용할 수 있다. Agent A │ │ A2A ↓ Agent B │ │ MCP ↓ Tool / DB / API 이렇게 보면 A2A와 MCP는 경쟁 관계라기보다 서로 다른 연결 계층을 해결하는 기술 로 보는 것이 자연스러운 것 같다. 여러 Agent를 연결할수록 Interface가 중요해진다 Agent A가 Agent B에게 결과를 넘긴다고 해서 문자열 하나만 전달하면 항상 제대로 협업할 수 있는 것은 아니다. { task, input, constraints, result, status, metadata } 처럼 서로 약속된 형태가 필요할 수 있다. 결국 Agent 사이에서도 기존 소프트웨어에서 사용하던 Interface 또는 Contract 개념이 중요해진다. Agent A ↓ Contract ↓ Agent B 내부 구현이 바뀌어도 Contract가 유지된다면 다른 구성요소에 미치는 영향을 줄일 수 있다. 이것은 Agent를 독립적인 모듈로 교체하기 위해서도 중요하다. Agent v1 ↓ 같은 Interface ↓ Agent v2로 교체 가능 그래서 모듈화에서 중요한 것은 단순히 파일이나 서비스를 분리하는 것이 아니라 분리된 요소 사이에 안정적인 경계를 만드는 것 이라고 생각한다. Trace와 Observability Agent가 하나일 때도 Trace가 필요하지만 여러 Agent가 존재하면 실행 경로가 훨씬 복잡해진다. → Agent A → Tool A User → Router → Agent B → Tool B → Agent C → Tool C ↓ Merge 최종 결과만 보면 어떤 Agent가 선택됐고 어디에서 문제가 생겼는지 알기 어렵다. 그래서 멀티에이전트에서는 Trace , Logs , Metrics 같은 정보를 통해 실행을 관찰할 필요가 있다. Trace → 어떤 실행 경로를 거쳤는가 Logs → 어떤 이벤트와 오류가 발생했는가 Metrics → 얼마나 오래 걸렸고 얼마나 많은 자원을 사용했는가 이를 합쳐서 보면 Observability는 단순히 시스템이 실행됐는지를 보는 것이 아니라 시스템 내부에서 어떤 일이 일어났는지를 외부에서 판단할 수 있게 만드는 것 에 가깝다. 멀티에이전트에서는 Agent마다 모델 호출과 Tool 사용이 추가될 수 있기 때문에 Latency나 Token Cost 같은 값도 관찰할 필요가 있다. Evaluation도 여러 단계가 필요하다 멀티에이전트의 결과가 좋다고 해서 반드시 좋은 시스템이라고 할 수는 없다. 예를 들어 System A Agent A → Agent B → Result 와 System B Agent A → Agent C → Agent D → Agent B → Result 가 같은 결과를 만들었다면 최종 결과만으로는 두 시스템의 차이를 확인하기 어렵다. 그래서 평가도 여러 수준으로 볼 필요가 있다. Result Quality → 최종 결과가 좋은가 Task Quality → 각 Agent가 맡은 작업을 잘했는가 Trajectory → 적절한 실행 경로를 사용했는가 Latency → 실행 시간이 적절한가 Cost → 필요 이상의 자원을 사용하지 않았는가 Reliability → 반복해도 안정적으로 동작하는가 이전에 생각했던 Prediction → Action → Observation → Delta 구조도 여기에서 사용할 수 있을 것 같다. Expected Result vs Actual Result Expected Process vs Actual Process 결과의 차이와 과정의 차이를 따로 관찰할 수 있다. 핵심 개념들을 하나의 구조로 연결해보기 이번 주의 개념들을 다시 연결해보면 다음과 같이 볼 수 있을 것 같다. [Goal] ↓ Task Decomposition ↓ Task Graph / | \ Dependency Parallel Sequence ↓ Multi-Agent Orchestration ↓ ┌─────────────┼─────────────┐ Router Supervisor Handoff └─────────────┼─────────────┘ ↓ Agent ↓ ┌─────────┴─────────┐ A2A MCP ↓ ↓ Agent Tool / Data └─────────┬─────────┘ ↓ State Update ↓ Trace / Metrics / Logs ↓ Evaluation ↓ Feedback 각 개념을 이렇게 놓으면 서로의 역할이 조금 더 명확해진다. Task Decomposition → 무엇으로 나눌 것인가 Task Graph → 나눈 작업은 어떤 관계인가 Orchestration → 전체 실행을 어떻게 조율할 것인가 Router / Supervisor / Handoff → 실행 주체를 어떻게 선택하고 전환할 것인가 State → 현재 시스템은 어떤 상태인가 Context → 현재 Agent에게 어떤 정보가 필요한가 A2A → Agent와 Agent를 어떻게 연결할 것인가 MCP → Agent와 Tool을 어떻게 연결할 것인가 Trace / Observability → 무엇이 일어났는지 어떻게 알 것인가 Evaluation → 잘했는지 어떻게 판단할 것인가 여기까지 정리하고 나면 그 위에서 조금 더 추상적인 구조도 보인다. Goal ↓ Decompose ↓ Coordinate ↓ Execute ↓ Observe ↓ Evaluate ↓ Update ↺ 지난주에는 하나의 작업을 적절하게 나누고 여러 실행 주체에게 위임하는 것 이 중요하다고 생각했다. 이번 주에는 거기에서 더 나아가 나눈 작업과 실행 주체 사이의 관계, 상태, 인터페이스, 관찰과 평가까지 있어야 하나의 시스템이 된다 는 생각을 하게 되었다. 즉 멀티에이전트의 핵심은 Agent가 많다는 것이 아니라 독립적인 여러 실행 주체를 하나의 목적 아래 안정적으로 조율할 수 있는 구조 에 있는 것 같다. 2. 문제 및 개선 문제 구성요소를 많이 추가하면 시스템이 좋아질 것이라는 생각을 하기 쉽다. Multi-Agent, A2A, MCP, Supervisor, Observability처럼 적용할 수 있는 기술과 개념이 많아지면서 모두 사용해보고 싶어진다. 하지만 각각을 추가할 때마다 상태 관리, 통신, 오류 처리와 평가해야 할 범위도 같이 증가한다. 오케스트레이션 구조를 먼저 만들고 실제 작업의 특성을 나중에 맞추기 쉽다. 병렬 처리나 Supervisor 구조가 좋아 보여도 실제 Task의 Dependency가 강하다면 그 구조를 사용할 이유가 적을 수 있다. Agent 사이에 전달할 Context의 범위를 정하기 어렵다. 정보가 부족하면 다음 Agent가 제대로 판단하지 못하지만 너무 많은 정보를 넘기면 Context가 커지고 불필요한 정보까지 영향을 줄 수 있다. 결과와 과정 중 어디까지 평가해야 하는지 기준이 아직 명확하지 않다. 모든 Trace를 상세하게 평가하면 비용이 커지고, 최종 결과만 보면 실행 과정의 문제를 놓칠 수 있다. 개선 기술을 추가하기 전에 해결하려는 문제를 먼저 적는다. Problem ↓ 필요한 역할 ↓ 가장 단순한 구조 ↓ 필요한 기술 A2A를 사용한다 보다 독립적인 Agent 사이에서 표준화된 통신이 필요하다 가 먼저 와야 한다. Agent보다 Task Graph를 먼저 만든다. Goal ↓ Tasks ↓ Dependency ↓ Workflow ↓ Agent Assignment 어떤 Agent를 쓸지보다 어떤 작업이 있고 서로 어떤 관계인지부터 정하는 것이 좋을 것 같다. Global State와 Local Context를 구분한다. 전체 시스템에서는 필요한 정보를 유지하되 각 Agent에게는 현재 작업에 필요한 정보만 전달하는 방향을 실험해보고 싶다. 평가도 목적에 따라 필요한 수준만 선택한다. 결과가 중요한 작업 → Result 중심 비용이 중요한 작업 → Cost / Latency 추가 안정성이 중요한 작업 → Trace / Reliability 추가 모든 평가를 항상 하는 것보다 작업의 목적에 따라 필요한 평가 항목을 선택하는 것이 좋을 것 같다. 3. 소감 이번 주에는 멀티에이전트를 여러 AI가 협력하는 기능으로 보는 것에서 조금 더 벗어나 실제 시스템의 관점에서 생각해볼 수 있었다. Router , Supervisor , Handoff , State , A2A , MCP , Trace , Evaluation 같은 용어를 하나씩 보면 서로 다른 기술처럼 보이지만 다시 연결해보면 결국 하나의 질문으로 모인다. 여러 개의 독립적인 실행 주체를 어떻게 하나의 목표를 향해 움직이게 할 것인가? Agent를 여러 개 만드는 것 자체는 생각보다 어려운 문제가 아닐 수도 있다. 오히려 어떤 작업을 나눌지, 어떤 Agent가 맡을지, 어떤 정보를 전달할지, 실패하면 어떻게 처리할지, 마지막에 무엇을 기준으로 잘했다고 판단할지가 더 어려운 문제인 것 같다. 이런 구조를 공부하면서 기존 소프트웨어 시스템과 비슷한 부분도 많이 보였다. 함수나 서비스 사이에는 Interface가 있고, 분산 시스템에는 통신과 상태 관리가 필요하며, 복잡한 시스템에서는 Observability가 필요하다. Agent가 새로운 실행 주체로 추가됐다고 해서 기존 소프트웨어의 원리가 사라지는 것이 아니라 오히려 기존에 사용하던 개념들을 Agent에 맞게 다시 적용하는 부분도 많은 것 같다. 지난주에는 모든 일을 하나의 주체가 잘하는 것보다 일을 적절하게 나누고 연결하는 것이 중요하다. 고 생각했다. 이번 주에는 여기에서 한 단계 더 나아가 잘 나누는 것만으로는 부족하고, 나뉜 것들이 어떤 규칙으로 상호작용하고 전체 상태를 어떻게 관찰할 것인지까지 정의해야 시스템이 된다. 는 생각을 하게 되었다. 그리고 그 구조를 만들 때도 처음부터 복잡한 멀티에이전트 시스템을 만드는 것보다 Single Agent ↓ Task 분리 ↓ 작은 Workflow ↓ 필요한 Agent 추가 ↓ 관찰 ↓ 실제로 필요한 관리 구조 추가 처럼 복잡도가 필요한 만큼만 확장하는 것이 더 좋은 방법일 것 같다. 결국 새로운 용어와 기술을 많이 아는 것도 중요하지만, 각 개념이 어떤 문제를 해결하고 전체 시스템의 어디에 위치하는지를 이해하는 것 이 더 중요하다고 생각한다.
뭐만들지... 이번 주는 최종 프로젝트 주제를 정하고 실제로 무엇을 만들지 구체화하는 데 시간을 많이 썼다. 지금까지 배운 내용을 생각하면 쓸 수 있는 기술은 꽤 많았다. LLM, Hugging Face 모델, RAG, LoRA, LangChain, LangGraph, MCP까지. 근데 막상 프로젝트를 시작하려고 하니 이걸 어디에 어떻게 써야 하는지부터 다시 생각하게 됐다. 수업에서 하나씩 배울 때와 하나의 서비스로 묶으려고 할 때는 또 느낌이 달랐다. 배운 건 많다. 그래서 이걸 어디에 쓰지...? 처음에는 개발팀 문서화 도우미, 소상공인 지원사업 신청 보조, 창작물 검수처럼 여러 주제를 비교했다. 비슷한 서비스가 있는지, 데이터를 구할 수 있는지, 반복되는 업무가 충분히 있는지도 같이 확인했다. 아이디어를 적을 때는 괜찮아 보였는데 실제 입력 데이터와 결과물을 생각하면 막히는 부분이 있었다. 계약서는 어디서 구할지, 검수 결과가 맞는지는 누가 판단할지, 사람이 하던 일 중 어디까지 자동화할 수 있을지 같은 것들이다. 멘토링을 거치면서 기획을 설명하는 순서도 조금 정리됐다. 어떤 배경에서 문제가 생겼고, 누가 불편을 겪고 있으며, 우리가 만든 서비스로 어떤 일이 달라지는지. 이 흐름부터 설명할 수 있어야 했다. 훈민정음을 예로 들었던 것도 결국 왜 필요한지와 사용했을 때의 변화를 먼저 생각해 보자는 이야기로 받아들였다. 효과를 설명할 때도 검수 시간이 줄어들 것 같다는 말에서 한 단계 더 들어가야 했다. 실제로 얼마나 걸렸는지, 어떤 오류를 놓쳤는지 비교할 방법이 필요했다. 아직 측정하지 않은 숫자를 적어두면 나중에 설명하기도 어려울 것 같다. 그렇게 정리한 주제가 광고 창작물 검수 업무를 돕는 서비스 다. 프로젝트 이름은 일단 동작그만 밑장빼기냐 . 숏츠, 인스타 릴스, 광고 이미지처럼 확인해야 할 콘텐츠는 많고 AI를 활용해서 만드는 경우도 있다. 사람이 직접 검수하더라도 상품명이나 할인율, 브랜드 표기처럼 놓칠 수 있는 부분이 생긴다. 여기에 계약서와 명세서까지 대조하려면 확인할 항목이 더 늘어난다. 그래서 문서에서 검수 기준을 읽고, 결과물이 그 기준에 맞는지 확인한 다음, 문제가 있는 위치와 근거를 보여주는 흐름을 생각했다. 사람이 최종적으로 판단할 때 참고할 수 있도록 만드는 방향이다. 처음부터 영상까지 모두 다루기는 범위가 커서 우선 광고 이미지부터 시작하기로 했다. 실제로 어떤 모습일지 보기 위해 딸기라떼 예시도 준비했다. 정상 이미지 한 장과 조건을 다르게 만든 오류 이미지 다섯 장으로 검사 흐름을 확인했다. 상품명, 할인율, 브랜드, 딸기 개수, 음료 외관처럼 비교할 항목이 있으니 화면과 기능을 이야기하기가 훨씬 쉬워졌다. Codex와 에이전트를 활용해서 기본 시제품도 만들어 실행해 봤다. 그런데 여기서 확인할 부분이 있었다. 화면에서는 검사가 되고 결과도 나오는데, 지금 이 결과를 실제로 누가 판단하고 있는가 하는 점이었다. 확인해 보니 초기 구현은 Mac 내장 OCR과 미리 정한 조건을 비교하는 방식이었다. MCP 도구 호출도 들어가 있었지만, 구상했던 LLM이나 RAG, 다중 에이전트 협업까지 연결된 상태는 아니었다. 화면은 돌아간다. 근데 내가 생각한 LLM은 아직 연결 전이었다. 그래도 이 과정을 통해 현재 구현과 앞으로 구현할 내용을 구분할 수 있었다. AI가 만들어 준 코드를 사용할 때도 어떤 모델이 호출되는지, 어디까지가 고정된 규칙인지 직접 확인해야겠다는 생각이 들었다. 화면에 결과가 나오는 것만 보고 넘어가면 내가 만든 프로젝트를 설명할 때부터 막힐 수 있다. 검사 기준을 입력하는 화면도 다시 봤다. 예제에서는 딸기라떼, 할인율, 브랜드 같은 값을 미리 넣어두었지만 실제 서비스에서는 문서를 읽어서 기준을 가져와야 한다. 사용자가 내용을 전부 다시 입력해야 한다면 우리가 줄이려던 업무가 그대로 남는다. 그래서 목표 흐름을 이렇게 잡았다. 문서 등록 → 기준 추출 → 사람 확인 → 이미지 검수 → 근거 검증 → 결과 보고 → 수정본 재검수 문서가 모호하거나 모델이 판단하기 어려운 경우에는 사람이 확인할 수 있어야 한다. 기준 자체를 잘못 읽으면 뒤에서 이미지를 열심히 검사해도 결과가 틀릴 수 있기 때문이다. 데이터를 준비하는 과정에서도 생각할 부분이 있었다. 검사용 입력 문서 안에 어떤 항목을 의도적으로 누락했는지 적혀 있으면, 모델이 결과물을 제대로 확인하지 않고도 정답을 알 수 있다. 예제를 설명하려고 넣은 문장이 평가에서는 정답을 알려주는 힌트가 될 수 있었다. 검사 문제와 답지를 같이 주고 있었던 셈이다... 앞으로는 모델에 제공할 자료와 평가용 정답을 분리해야 한다. 같은 원본에서 만든 오류 이미지 몇 장으로 동작을 확인하는 것과, 새로운 광고에서도 잘 검사하는지 평가하는 것도 나눠서 봐야겠다. 기술 구성도 역할별로 다시 정리했다. 문서에서 기준을 추출하고 설명을 작성하는 LLM은 Qwen3-8B 를 우선 후보로 잡았다. 요청 유형을 분류하는 모델은 KLUE RoBERTa 를 미세조정하는 방향으로 생각했다. 이미지 문자 추출은 PaddleOCR , 시각적인 요소를 확인하는 모델은 Qwen3-VL 을 검토했다. 아직 후보를 정리한 단계라 실제 데이터로 실행하고 비교하는 과정이 필요하다. 검색은 BGE-M3를 활용한 Dense 검색과 BM25를 결합하는 Hybrid RAG 로 설계했다. 표현이 달라도 관련된 내용을 찾는 경우와 상품명이나 정확한 문구를 찾는 경우를 함께 고려하기 위해서다. LoRA나 QLoRA는 기본 모델을 먼저 평가하고 필요한 경우 실험하기로 했다. Ragas는 검색과 답변의 품질을 확인하는 데 활용하고, 이미지 검수에서 오류를 놓치는 문제는 별도의 평가 기준을 마련해야 한다고 정리했다. 기술 이름을 나열할 때는 금방 끝났는데, 각각 어디에서 쓰고 무엇으로 평가할지 적으니 생각보다 오래 걸렸다. 실행 환경도 팀 기준으로 다시 생각했다. 처음에는 내 맥에서 실행하는 방법도 고려했지만 여러 팀원이 함께 개발해야 하니 Mac MLX는 공통 구성에서 제외했다. 로컬 CUDA 환경이나 RunPod의 GPU 서버를 활용하는 방향으로 잡았다. 내 컴퓨터에서 편한 것과 팀이 같이 사용하기 편한 것은 따로 확인해야 했다. 프로젝트 주제가 MCP 기반 다중 에이전트 업무자동화 인 만큼 에이전트 역할도 구체화했다. 업무 총괄, 문서·기준 분석, 근거 검색, 문구 검수, 시각 요소 검수, 근거·결과 검증, 보고서 작성, 수정·재검수 관리까지 여덟 역할로 나눴다. LangGraph로 작업 순서와 상태를 관리하고, 각 역할이 MCP를 통해 문서 조회나 검색, OCR, 결과 저장 같은 도구를 사용하는 구조다. 결과가 불확실하면 다시 확인하거나 사람에게 넘기는 흐름도 포함했다. 여덟 개라고 적고 끝낼 수는 없었다. 서로 어떤 결과를 전달하는지, 같은 일을 중복해서 하고 있지는 않은지까지 정해야 했다. 워크플로우로 그려보니 말로 설명할 때 빠뜨렸던 연결도 조금씩 보였다. 기획서와 기능정의서도 여러 번 수정했다. 처음에는 내용을 자세히 적는 데 집중했는데 발표용으로 보니 길었다. 그래서 상세 기능정의서와 발표용 기획서를 나누고, HTML에서 필요한 내용을 버튼으로 찾아볼 수 있게 정리했다. 배경부터 문제, 해결 방법, 기대효과로 이어지도록 문장도 다듬었다. 내가 내용을 알고 있다고 해서 읽는 사람도 같은 순서로 이해하는 건 아니었다. 기획도 생각보다 할 일이 많다... 이번 주에는 주제 선정과 요구사항 정리, 예제 데이터 준비, 기본 시제품 확인, 기술 구성과 에이전트 구조 설계까지 진행했다. LLM 연결, 모델 학습, Hybrid RAG, 여덟 에이전트의 실제 협업은 앞으로 구현하고 검증해야 한다. 중간 발표는 10월 16일 , 최종 발표는 11월 20일 이다. 다음 주에는 실제 문서를 넣었을 때 기준이 제대로 추출되는지부터 확인하려고 한다. 추출한 기준으로 이미지를 검사하고 근거가 포함된 결과까지 나오는 흐름을 먼저 연결해야겠다. 모델 후보도 직접 실행해 보면서 처리 시간과 오류를 기록할 필요가 있다. 중간 발표에서는 실제 데이터를 넣고 어떤 결과가 나오는지 보여줄 수 있어야 한다. 기획서는 꽤 채웠다. 이제 실제로 돌려볼 차례. 이번 주에는 무엇을 만들지 정리했다면, 다음 주에는 그 안에 적어둔 내용이 실제 데이터에서도 돌아가는지 확인해 봐야겠다.
문제 소개 프로그래머스 Lv.1 · 최대공약수와 최소공배수 자연수 n과 m이 주어진다. 두 수의 최대공약수와 최소공배수를 구해 [최대공약수, 최소공배수] 배열로 반환하면 된다. 두 수는 모두 1 이상 1,000,000 이하다. 예를 들어 3과 12면 [3, 12] , 2와 5면 [1, 10] 을 반환한다. 접근 방법 최대공약수: 두 수 중 작은 쪽까지 1부터 차례로 나눠 본다. 두 수를 모두 나누어떨어지게 하는 수가 나올 때마다 answer[0] 을 갱신한다. 반복이 끝나면 마지막으로 저장된 값이 가장 큰 공약수다. 공약수는 작은 수보다 클 수 없으므로 작은 수까지만 확인한다. 최소공배수: 두 수의 곱 = 최대공약수 × 최소공배수 라는 성질을 이용한다. 그래서 n * m / 최대공약수 로 바로 구한다. 개선할 점도 두 가지 있다. 오버플로: n과 m이 최대 1,000,000이라 n * m 은 최대 1012까지 커진다. 이 값은 int 범위(약 21억)를 넘는다. 예를 들어 n=1,000,000, m=999,999면 최소공배수가 틀리게 나온다. (long) n * m / gcd 처럼 long으로 계산하거나, n / gcd * m 처럼 먼저 나누는 편이 안전하다. 유클리드 호제법: gcd(a, b) = gcd(b, a % b) 를 쓰면 최대공약수를 O(log min(n, m))에 구할 수 있다. 풀이 코드 class Solution { public int[] solution(int n, int m) { int[] answer = new int[2]; if(n < m) { for (int i = 1; i <= n; i++) { if(n % i == 0 && m % i == 0) { answer[0] = i; } } } else { for (int j = 1; j <= m; j++) { if(m % j == 0 && n % j == 0) { answer[0] = j; } } } answer[1] = n * m / answer[0]; return answer; } } 시간 복잡도 O(min(n, m)). 최대공약수를 찾을 때 1부터 두 수 중 작은 값까지 한 번씩 확인하기 때문이다.
thymeleaf는 Spring Boot에서 Model에 담은 데이터를 html에 바인딩이 가능하다 바인딩을 위해서는 html 태그안에 thymeleaf 표준 표현식 구문을 입력하여 데이터 바인딩을 할 수 있다. 표현식 변수 표현식: ${...} 선택 변수 표현식: *{...} Message 표현: #{...} Link URL 표현: @{...} Fragment 표현식: ~{...} 문법 작성에 오타가 있는 경우 다음과 같이 브라우저에 오류가 출력된다. 항상 문법 오타에 주의 할 것. 문법 사용법 th:text html요소에 텍스트를 바인딩 할 때 사용된다. <p>안녕하세요</p> 예를 들어 다음과 같은 <p> 태그에 안녕하세요 부분을 Model에서 가져온 데이터로 출력한다고 가정하자. 먼저 템플릿에서 사용할 데이터를 컨트롤러에서 전달해야한다. @GetMapping("/test") public String test(Model model){ model.addAttribute("A", "123"); // Model에 변수 A에 "123"값을 추가 return "articles/test"; } 다음과 같이 컨트롤러에 메소드를 만들고 Model에 템플릿에 사용할 데이터를 넣어줘야 사용이 가능하다. <p th:text="${A}">안녕하세요</p> Model에 넣은 데이터를 사용하기 위해 다음과 같이 데이터 바인딩을 해주었다. 이같은 경우는 A 변수에 담긴 값이 <p> 태그의 텍스트인 안녕하세요 를 대체하게 되어 123 값이 출력된다. Model에 등록한 데이터는 변수이기 때문에 변수 표현식인 ${...} 를 사용했다. <p>123</p> 브라우저에서는 출력결과로 123이 렌더링되어 출력된다. th:href <a> 태그에 링크를 바인딩 할 때 사용된다. <a th:href="@{/articles}">링크</a> 링크를 바인딩하기위해 Link URL 표현식 @{...} 를 사용했다. 변수를 바인딩 하는게 아니기때문에 Model에서 데이터를 가져올 필요가 없다. 이같은 경우 <a> 태그를 클릭하게 되면 /articles 경로로 이동되게 된다. <a href="/articles">링크</a> 브라우저에 렌더링된 결과. 만약 링크에 변수를 바인딩하고 싶을 수도 있다. <a th:href="@{/articles/{id}(id=${A})}">링크</a> 링크의 변수가 필요한부분을 임의의 이름을 지어 변수이름을 만들고 {} 로 감싼후 (id=${A}) 를 뒤에 추가하여 임의의 변수 id 에 값을 넣어줘야한다. <a href="/articles/123">링크</a> 브라우저에 렌더링된 결과. A 의 값이 123 이기에 렌더링후 id 가 대체되었다. 만약 URL에 여러개의 데이터를 바인딩하고 싶을 수 있다. <a th:href="@{/articles/{id1}/{id2}(id1=${A}, id2=${B})}">링크</a> 위와 같이 , 으로 구분해서 데이터를 넣어주면 된다. th:fregment 웹페이지를 만들다보면 웹페이지마다 공통된 부분이 존재 할 수 있다. 공통된 부분을 재사용하기 위해 사용 할 수 있다. <div th:fragment="A"> abc </div> 위와 같이 사용하면 A라는 이름으로 <div>abc<div> 요소가 fregment화된다. 생성된 fregment는 다른 곳에서 재사용 할 수 있다. <div th:replace="~{layouts/test :: A}"></div> 생성된 fregment는 th:replace 문법을 통해 요소를 대체 할 수 있으며 재사용이 필요한곳에 사용해주면 된다. Fragment 표현식 ~{...} 를 사용해야하며 표현식안에 첫번째 부분인 layouts/test 부분은 fregment를 등록한 html의 디렉토리 위치를 기입해줘야하며 :: 으로 분리후 뒤에는 사용할 fregment의 이름을 적어줘야한다. <div>abc</div> 브라우저의 렌더링 결과로 <div> 태그 자체를 대체하여 fregmnet로 대체되었다. fregment를 사용하는 방법으로 한가지 더있는데 th:insert 이다 <div th:insert="~{articles/test :: A}"></div> th:replace 와 사용방법은 동일하지만 <div> 태그가 대체되는것이 아닌 <div> 태그의 하위에 fregment가 삽입된다. <div> <div>abc</div> </div> 브라우저에 렌더링 결과로 th:insert 를 적용한 태그는 대체되지않고 그대로 있으며 자식으로 fregment가 생성된 것을 알 수 있다. th:each 여러개의 데이터를 출력하고 싶을때 주로 사용된다. @GetMapping("/test") public String test(Model model){ List<Article> articleList = new ArrayList<>( Arrays.asList( new Article(1L, "title1", "content1"), new Article(2L, "title2", "content2"), new Article(3L, "title3", "content3") ) ); model.addAttribute("articleList", articleList); return "articles/test"; } 먼저 컨트롤러에서 템플릿에 사용할 여러개의 데이터를 준비하여 보내준다. 예제에서는 ArrayList인 articleList 에 3개의 데이터를 담아 Model에 등록시켜두었다. public class Article { private Long id; private String title; private String content; public Article(Long id, String title, String content) { this.id = id; this.title = title; this.content = content; } } 예제에서 사용된 Article 클래스는 위와 같은 모습이다. <div th:each="article : ${articleList}"> <p>id</p> <p>제목</p> <p>내용</p> </div> th:each로 여러개의 데이터를 가진 List를 출력 할 수 있다. articleList 에 담긴 데이터 개수만큼 순회하면서 순서대로 임의의 변수 article 에 데이터가 하나씩 들어가게된다. 임의의변수는 이름을 마음대로 해도된다. <div> <p>id</p> <p>제목</p> <p>내용</p> </div> <div> <p>id</p> <p>제목</p> <p>내용</p> </div> <div> <p>id</p> <p>제목</p> <p>내용</p> </div> 브라우저에 렌더링 결과로 articleList 에 담긴 3개의 데이터만큼 html 요소가 3번 반복되었다. 하지만 데이터 갯수만큼 요소만 반복되었지 데이터가 바인딩되어있지 않다. <div th:each="article : ${articleList}"> <p th:text="${article.id}">id</p> <p th:text="${article.title}">제목</p> <p th:text="${article.content}">내용</p> </div> 다음과 같이 데이터 바인딩을 해주게 되면 articleList 에서 순서대로 하나씩 article 에 데이터가 들어오기때문에 여러개의 데이터를 출력 할 수 있게된다. <div> <p>1</p> <p>title1</p> <p>content1</p> </div> <div> <p>2</p> <p>title2</p> <p>content2</p> </div> <div> <p>3</p> <p>title3</p> <p>content3</p> </div> 브라우저에 렌더링 결과로 articleList 의 데이터를 잘 출력한것을 볼 수있다. th:classappend html 태그에 클래스를 추가 할 수 있다. <p th:classappend="${A}">테스트</p> 예제는 Model에 123 값을 가진 A 변수가 등록되어있다고 가정한다. <p class="123">테스트</p> 브라우저에 렌더링 결과로 123 이름의 클래스가 등록된 것을 볼 수 있다. 만약 임의의 문자열과 변수를 합칠려면 어떻게 해야할까? <p th:classappend="|article-${A}|">테스트</p> 예제와 같이 리터럴 대체문법인 | 버티컬바로 감싸준 후 작성하면된다. 💡 `"article- + ${article.id}"` 다음과 같이 + 를 사용해도 가능하다. <p class="article-123">테스트</p> 브라우저에 렌더링 결과로 다음과 같이 문자열과 변수가 합쳐진모습을 볼 수 있다. 여러개의 클래스를 추가하고 싶다면 <p th:classappend="|article-${A} AAA|">테스트</p> 띄어쓰기로 구분해서 작성하면된다. <p class="article-123 AAA">테스트</p> 브라우저 렌더링 결과로 article-123 클래스와 AAA 클래스가 추가되었다. th:if th:unless thymeleaf에서 사용 할 수 있는 조건문이다. 조건문이 true 인 경우 html요소를 렌더링하고 false 이면 렌더링하지 않는다. @GetMapping("/test") public String test(Model model){ int a = 1; model.addAttribute("a", a); return "articles/test"; } 예제 테스트를 위해 컨트롤러에 a 라는 이름의 속성을 추가해두었다. <div th:if="${a}">a가 존재합니다.</div> 위의 코드는 th:if 문법으로 Model에 a 라는 속성(Attribute)가 있는지 확인하고 있는 코드이다. 만약 a 라는 속성이 Model에 등록되어있다면 <div>a가 존재합니다.</div> 가 렌더링되어 브라우저에 나타날 것이고 만약 등록되지 않았다면 브라우저에는 아무것도 렌더링되지 않는다. <div>a가 존재합니다.</div> 브라우저 렌더링 결과로 Model에 속성 a 가 존재하기 때문에 조건문이 true 가 되어 렌더링이 성공했다. <div th:unless="${a}">a가 존재하지 않습니다.</div> 다음은 th:unless 문으로 th:if 의 부정 이다. th:if 경우 a 속성이 존재하면 true 였지만 th:unless 의 경우는 a 속성이 존재하지않아야 true 인 것이다. <!-- 아무것도 렌더링되지않음 --> 브라우저 렌더링 결과로 Model에 속성 a 가 존재하기때문에 false 가 되어 아무것도 렌더링되지 않았다. @GetMapping("/test") public String test(Model model){ int a = 1; return "articles/test"; } th:unless 를 더 테스트하기 위해 컨트롤러를 수정하여 Model에 속성 a 를 등록하지 않게 수정한다. <div>a가 존재하지 않습니다.</div> 브라우저 렌더링 결과로 속성 a 가 존재하지 않기때문에 조건문이 true 가 되어 렌더링에 성공했다. <div th:if="!${a}">a가 존재하지 않습니다.</div> th:if 에 not연산자인 ! 를 붙히면 th:unless 와 동일한 효과를 낸다. 차이는 없으니 취향껏 방법을 선택하면된다. <div th:if="${a == 1}">a가 1과 같다</div> <div th:if="${a > 1}">a가 1보다 크다</div> <div th:if="${a < 1}">a가 1보다 작다</div> <div th:unless="${a == 1}">a가 1과 다르다</div> <div th:if="!${a == 1}">a가 1과 다르다</div> 변수표현식 ${...} 안에는 비교연산자 사용도 가능하다.