[{"data":1,"prerenderedAt":1512},["ShallowReactive",2],{"navigation_docs_ko":3,"-ko-note-spring-spring-ai":267,"-ko-note-spring-spring-ai-surround":1509},[4,130,253],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":33},"Note (개발 지식)","emojione-v1:note-pad","\u002Fko\u002Fnote","ko\u002F1.note",[10,34,48,74,84,94,110,120],{"title":11,"icon":12,"path":13,"stem":14,"children":15,"page":33},"Spring","logos:spring-icon","\u002Fko\u002Fnote\u002Fspring","ko\u002F1.note\u002F0.spring",[16,21,25,29],{"title":17,"path":18,"stem":19,"icon":20},"Spring AI 핵심 개념과 LLM 애플리케이션 구조 이해","\u002Fko\u002Fnote\u002Fspring\u002Fspring-ai","ko\u002F1.note\u002F0.spring\u002F1.spring-ai","lucide:file-text",{"title":22,"path":23,"stem":24,"icon":20},"[Java] Record와 @Version으로 이해하는 불변 데이터와 동시성 제어","\u002Fko\u002Fnote\u002Fspring\u002Frecord-and-version-concurrency","ko\u002F1.note\u002F0.spring\u002F2.record-and-version-concurrency",{"title":26,"path":27,"stem":28,"icon":20},"[JPA] @PersistenceContext와 EntityManager로 이해하는 영속성 컨텍스트","\u002Fko\u002Fnote\u002Fspring\u002Fspring-persistence-context","ko\u002F1.note\u002F0.spring\u002F3.spring-persistence-context",{"title":30,"path":31,"stem":32,"icon":20},"[Java] YearMonth로 월 단위 날짜를 다루는 방법","\u002Fko\u002Fnote\u002Fspring\u002Fjava-year-month","ko\u002F1.note\u002F0.spring\u002F4.java-year-month",false,{"title":35,"icon":36,"path":37,"stem":38,"children":39,"page":33},"Javascript","skill-icons:javascript","\u002Fko\u002Fnote\u002Fjavascript","ko\u002F1.note\u002F1.javascript",[40,44],{"title":41,"path":42,"stem":43,"icon":20},"Promise.all과 구조 분해 할당을 활용한 데이터 처리 패턴","\u002Fko\u002Fnote\u002Fjavascript\u002Fdestructuring","ko\u002F1.note\u002F1.javascript\u002F1.destructuring",{"title":45,"path":46,"stem":47,"icon":20},"JavaScript 배열 고차 함수와 데이터 처리 패턴 이해","\u002Fko\u002Fnote\u002Fjavascript\u002Ffilter","ko\u002F1.note\u002F1.javascript\u002F2.filter",{"title":49,"icon":50,"path":51,"stem":52,"children":53,"page":33},"Nuxt4","logos:nuxt-icon","\u002Fko\u002Fnote\u002Fnuxt4","ko\u002F1.note\u002F2.nuxt4",[54,58,62,66,70],{"title":55,"path":56,"stem":57,"icon":20},"Nuxt 4를 선택한 이유와 아키텍처 및 실무 가이드","\u002Fko\u002Fnote\u002Fnuxt4\u002Fnuxt-info","ko\u002F1.note\u002F2.nuxt4\u002F1.nuxt-info",{"title":59,"path":60,"stem":61,"icon":20},"Nuxt useAsyncData 동작 원리와 SSR 데이터 페칭 최적화","\u002Fko\u002Fnote\u002Fnuxt4\u002Fnuxt-use_async_data","ko\u002F1.note\u002F2.nuxt4\u002F2.nuxt-use_async_data",{"title":63,"path":64,"stem":65,"icon":20},"Nuxt Content v3 데이터 모델과 Query 시스템 이해","\u002Fko\u002Fnote\u002Fnuxt4\u002Fnuxt-content-v3","ko\u002F1.note\u002F2.nuxt4\u002F3.nuxt-content-v3",{"title":67,"path":68,"stem":69,"icon":20},"Nuxt Auto-imports 내부 동작과 컴파일 과정 분석","\u002Fko\u002Fnote\u002Fnuxt4\u002Fnuxt-auto-import","ko\u002F1.note\u002F2.nuxt4\u002F4.nuxt-auto-import",{"title":71,"path":72,"stem":73,"icon":20},"Nuxt 렌더링 방식 이해: SSR, SSG, ISR, CSR 구현 방법","\u002Fko\u002Fnote\u002Fnuxt4\u002Fnuxt-rendering-strategy","ko\u002F1.note\u002F2.nuxt4\u002F5.nuxt-rendering-strategy",{"title":75,"icon":76,"path":77,"stem":78,"children":79,"page":33},"Nitro","unjs:nitro","\u002Fko\u002Fnote\u002Fnitro","ko\u002F1.note\u002F3.nitro",[80],{"title":81,"path":82,"stem":83,"icon":20},"Nitro 아키텍처와 서버 엔진 구조","\u002Fko\u002Fnote\u002Fnitro\u002Fnitro","ko\u002F1.note\u002F3.nitro\u002Fnitro",{"title":85,"icon":86,"path":87,"stem":88,"children":89,"page":33},"Devops","vscode-icons:file-type-pipeline","\u002Fko\u002Fnote\u002Fdevops","ko\u002F1.note\u002F4.devops",[90],{"title":91,"path":92,"stem":93,"icon":20},"서버사이드 WebAssembly(Wasm)와 차세대 실행 환경","\u002Fko\u002Fnote\u002Fdevops\u002Fwebassembly","ko\u002F1.note\u002F4.devops\u002F1.webAssembly",{"title":95,"icon":96,"path":97,"stem":98,"children":99,"page":33},"Tooling","lucide:wrench","\u002Fko\u002Fnote\u002Ftooling","ko\u002F1.note\u002F4.tooling",[100],{"title":101,"icon":102,"path":103,"stem":104,"children":105,"page":33},"Package Manager","devicon:npm","\u002Fko\u002Fnote\u002Ftooling\u002Fpackage-manager","ko\u002F1.note\u002F4.tooling\u002F1.package-manager",[106],{"title":107,"path":108,"stem":109,"icon":20},"Package Lock 파일의 역할과 의존성 관리 원리","\u002Fko\u002Fnote\u002Ftooling\u002Fpackage-manager\u002Fpackage-lock","ko\u002F1.note\u002F4.tooling\u002F1.package-manager\u002F1.package-lock",{"title":111,"icon":112,"path":113,"stem":114,"children":115,"page":33},"Web","streamline-plump:web","\u002Fko\u002Fnote\u002Fweb","ko\u002F1.note\u002F4.web",[116],{"title":117,"path":118,"stem":119,"icon":20},"웹 애플리케이션 렌더링 아키텍처 이해","\u002Fko\u002Fnote\u002Fweb\u002Frendering-modes","ko\u002F1.note\u002F4.web\u002F2.rendering-modes",{"title":121,"icon":122,"path":123,"stem":124,"children":125,"page":33},"SEO","icon-park-solid:seo","\u002Fko\u002Fnote\u002Fseo","ko\u002F1.note\u002F6.seo",[126],{"title":127,"path":128,"stem":129,"icon":20},"[SEO] 개발 블로그 검색 노출을 위한 robots.txt와 SEO 설정 가이드","\u002Fko\u002Fnote\u002Fseo\u002Fseo","ko\u002F1.note\u002F6.seo\u002F1.seo",{"title":131,"icon":132,"path":133,"stem":134,"children":135,"page":33},"Project (개발일지)","icon-park-solid:branch-two","\u002Fko\u002Fproject","ko\u002F2.project",[136,187],{"title":137,"icon":138,"path":139,"stem":140,"children":141,"page":33},"Brolog","custom:symbol","\u002Fko\u002Fproject\u002Fbrolog","ko\u002F2.project\u002F1.brolog",[142,146,150,154,158,162,166,170,174,179,183],{"title":143,"path":144,"stem":145,"icon":20},"[Brolog] 프로젝트 개요","\u002Fko\u002Fproject\u002Fbrolog\u002Foutline","ko\u002F2.project\u002F1.brolog\u002F1.outline",{"title":147,"path":148,"stem":149,"icon":20},"[Brolog] Nuxt Multi Layer 프로젝트 Vercel 배포 (3) - Runtime 기능 문제 분석과 AI Assistant 전환","\u002Fko\u002Fproject\u002Fbrolog\u002Fvercel-deploy3-ai-integration","ko\u002F2.project\u002F1.brolog\u002F10.vercel-deploy3-ai-integration",{"title":151,"path":152,"stem":153,"icon":20},"[Brolog] AI Assistant 적용 과정 - Agent 구조와 API 사용량 관리 시 고려사항","\u002Fko\u002Fproject\u002Fbrolog\u002Fai-assistant-application-process","ko\u002F2.project\u002F1.brolog\u002F11.ai-assistant-application-process",{"title":155,"path":156,"stem":157,"icon":20},"[Brolog] 검색 엔진 노출을 위한 SEO 적용 과정","\u002Fko\u002Fproject\u002Fbrolog\u002Fbrolog-seo","ko\u002F2.project\u002F1.brolog\u002F12.brolog-seo",{"title":159,"path":160,"stem":161,"icon":20},"[Brolog] 프로젝트 구조","\u002Fko\u002Fproject\u002Fbrolog\u002Fstructure","ko\u002F2.project\u002F1.brolog\u002F2.structure",{"title":163,"path":164,"stem":165,"icon":20},"[Brolog] Nuxt Content 네비게이션 아이콘 커스텀 빌드","\u002Fko\u002Fproject\u002Fbrolog\u002Fcustom-icon","ko\u002F2.project\u002F1.brolog\u002F3.custom-icon",{"title":167,"path":168,"stem":169,"icon":20},"[Brolog] Nuxt4 Auto-imports 동작 원리와 자동 import 시스템 이해","\u002Fko\u002Fproject\u002Fbrolog\u002Fnuxt-auto-import","ko\u002F2.project\u002F1.brolog\u002F4.nuxt-auto-import",{"title":171,"path":172,"stem":173,"icon":20},"[Brolog] 개발 블로그 메인 페이지 구현","\u002Fko\u002Fproject\u002Fbrolog\u002Fdevelop-main","ko\u002F2.project\u002F1.brolog\u002F5.develop-main",{"title":175,"path":176,"stem":177,"icon":178},"[Brolog] 브랜드 아이덴티티 적용 - Custom 404 페이지와 Default No Image 구현","\u002Fko\u002Fproject\u002Fbrolog\u002Fcustom-error-and-default-thumbnail","ko\u002F2.project\u002F1.brolog\u002F6.custom-error-and-default-thumbnail","lucide:image",{"title":180,"path":181,"stem":182,"icon":20},"[Brolog] Nuxt Multi Layer 프로젝트 Vercel 배포 (1)","\u002Fko\u002Fproject\u002Fbrolog\u002Fvercel-deploy1","ko\u002F2.project\u002F1.brolog\u002F8.vercel-deploy1",{"title":184,"path":185,"stem":186,"icon":20},"[Brolog] Nuxt Multi Layer 프로젝트 Vercel 배포 (2) - Build Pipeline 오류 분석과 Nuxt 실행 구조 이해","\u002Fko\u002Fproject\u002Fbrolog\u002Fvercel-deploy2-error-analyze","ko\u002F2.project\u002F1.brolog\u002F9.vercel-deploy2-error-analyze",{"title":188,"icon":189,"path":190,"stem":191,"children":192,"page":33},"Brobean","custom:brobean","\u002Fko\u002Fproject\u002Fbrobean","ko\u002F2.project\u002F2.brobean",[193,197,201,205,209,213,217,221,225,229,233,237,241,245,249],{"title":194,"path":195,"stem":196,"icon":20},"[BroBean] 연인과 함께 사용하는 가계부: BroBean - 프로젝트 기획과 이름의 탄생","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-01","ko\u002F2.project\u002F2.brobean\u002F1.brobean-dev-log-01",{"title":198,"path":199,"stem":200,"icon":20},"[BroBean] 연인과 함께 사용하는 가계부: BroBean 프로젝트 시작 - Nuxt 4 기반 프론트 개발 환경 구축","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-02","ko\u002F2.project\u002F2.brobean\u002F2.brobean-dev-log-02",{"title":202,"path":203,"stem":204,"icon":20},"[BroBean] BroBean 프로젝트 #3 - 모바일 앱 전환을 고려한 공통 레이아웃 설계","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-03","ko\u002F2.project\u002F2.brobean\u002F3.brobean-dev-log-03",{"title":206,"path":207,"stem":208,"icon":20},"[BroBean] BroBean 프로젝트 #4 - 홈 화면(Dashboard) 설계와 Mock 데이터 구성","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-04","ko\u002F2.project\u002F2.brobean\u002F4.brobean-dev-log-04",{"title":210,"path":211,"stem":212,"icon":20},"[BroBean] BroBean 프로젝트 #5 돈을 모으면 캐릭터도 성장한다 - 브로빈 성장 시스템 설계","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-05","ko\u002F2.project\u002F2.brobean\u002F5.brobean-dev-log-05",{"title":214,"path":215,"stem":216,"icon":20},"[BroBean] BroBean 프로젝트 #6 씨앗 심기와 밭 가꾸기 기능 설계","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-06","ko\u002F2.project\u002F2.brobean\u002F6.brobean-dev-log-06",{"title":218,"path":219,"stem":220,"icon":20},"[BroBean] BroBean 프로젝트 #7 성장 일지와 나무 키우기 화면 구현","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-07","ko\u002F2.project\u002F2.brobean\u002F7.brobean-dev-log-07",{"title":222,"path":223,"stem":224,"icon":20},"[BroBean] BroBean 프로젝트 #8 수확 정산으로 한눈에 보는 소비 흐름 - 소비 리포트 & 정산 화면 구현","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-08","ko\u002F2.project\u002F2.brobean\u002F8.brobean-dev-log-08",{"title":226,"path":227,"stem":228,"icon":20},"[BroBean] BroBean 프로젝트 #9 알림부터 마이페이지까지 - 사용자 편의 기능과 개인 농장 설정 화면 구현","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-09","ko\u002F2.project\u002F2.brobean\u002F9.brobean-dev-log-09",{"title":230,"path":231,"stem":232,"icon":20},"[BroBean] BroBean 프로젝트 #10 로그인부터 파트너 초대까지 - 계정 인증과 커플 연결 화면 구현","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-10","ko\u002F2.project\u002F2.brobean\u002F910.brobean-dev-log-10",{"title":234,"path":235,"stem":236,"icon":20},"[BroBean] BroBean 프로젝트 #11 개인과 커플을 모두 위한 모드 분리 - Pinia 기반 개인\u002F커플 모드 전환 구현","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-11","ko\u002F2.project\u002F2.brobean\u002F911.brobean-dev-log-11",{"title":238,"path":239,"stem":240,"icon":20},"[BroBean] BroBean 프로젝트 #12 인증 상태 복구와 Nuxt Plugin 실행 순서 제어","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-12","ko\u002F2.project\u002F2.brobean\u002F912.brobean-dev-log-12",{"title":242,"path":243,"stem":244,"icon":20},"[BroBean] BroBean 프로젝트 #13 개인·커플 모드와 가계부 API 구현 - 쿼리 성능을 다시 돌아보다","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-13","ko\u002F2.project\u002F2.brobean\u002F913.brobean-dev-log-13",{"title":246,"path":247,"stem":248,"icon":20},"[BroBean] BroBean 프로젝트 #14 순환 참조 오류를 ObjectProvider로 해결하기","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-14","ko\u002F2.project\u002F2.brobean\u002F914.brobean-dev-log-14",{"title":250,"path":251,"stem":252,"icon":20},"[BroBean] BroBean 프로젝트 #15 JPA 페이지네이션 적용, Page와 Slice의 차이","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-15","ko\u002F2.project\u002F2.brobean\u002F915.brobean-dev-log-15",{"title":254,"icon":255,"path":256,"stem":257,"children":258,"page":33},"Error (트러블슈팅)","emojione:stop-sign","\u002Fko\u002Ftrouble-shooting","ko\u002F3.trouble-shooting",[259],{"title":137,"icon":138,"path":260,"stem":261,"children":262,"page":33},"\u002Fko\u002Ftrouble-shooting\u002Fbrolog","ko\u002F3.trouble-shooting\u002F1.brolog",[263],{"title":264,"path":265,"stem":266,"icon":20},"[Nuxt Content] Nuxt Icon 오류 해결과 Custom Collection 기반 오프라인 아이콘 구성","\u002Fko\u002Ftrouble-shooting\u002Fbrolog\u002Fnuxt-icon-restricted-network-troubleshooting","ko\u002F3.trouble-shooting\u002F1.brolog\u002F1.nuxt-icon-restricted-network-troubleshooting",{"id":268,"title":17,"body":269,"date":1494,"description":1495,"extension":1496,"links":1497,"meta":1498,"navigation":1504,"path":18,"seo":1505,"stem":19,"__hash__":1508},"docs_ko\u002Fko\u002F1.note\u002F0.spring\u002F1.spring-ai.md",{"type":270,"value":271,"toc":1480},"minimark",[272,1476],[273,274,276,281,291,295,298,300,305,313,321,323,327,330,382,384,388,397,400,501,508,514,863,871,878,1288,1290,1294,1301,1308,1437,1439,1443,1446],"u-page-card",{":spotlight":275},"true",[277,278,280],"h1",{"id":279},"왜-spring-ai인가","왜 Spring AI인가?",[282,283],"img",{"alt":284,"className":285,"src":290},"Spring AI Official Logo",[286,287,288,289],"mx-auto","object-fill","object-contain","animate-pulse","images\u002Fspring-ai-logo.svg",[292,293,294],"p",{},"그동안 LLM 연동 및 RAG 시스템 구축 등 AI 애플리케이션 개발 생태계는 Python 중심의 프레임워크(LangChain, LlamaIndex)가 주도해 왔다. 이로 인해 대규모 인프라를 Java 기반으로 운영하던 엔터프라이즈 환경에서는 기술 파편화라는 한계에 부딪혔다. Spring AI는 \"AI 모델을 하나의 추상화된 서비스(POJO)로 다루자\"는 철학 아래, 기존 Spring의 IoC\u002FDI 컨셉을 그대로 계승하여 강력한 자바 AI 파이프라인을 구축할 수 있게 돕는 공식 프로젝트다.",[296,297],"hr",{},[296,299],{},[301,302,304],"h2",{"id":303},"_1-spring-ai의-본질과-psa-철학","1. Spring AI의 본질과 PSA 철학",[292,306,307,308,312],{},"Spring AI의 가장 핵심적인 강점은 **Portable Service Abstraction(PSA)**에 있다. 과거 Spring이 데이터베이스(JDBC\u002FJPA)나 메시지 큐(AMQP)를 추상화하여 구현체에 종속되지 않는 코드를 작성하게 해주었듯, Spring AI 역시 OpenAI, Anthropic Claude, Google Gemini, 혹은 로컬의 Ollama까지 ",[309,310,311],"strong",{},"동일한 인터페이스 레이어","로 제어할 수 있게 추상화한다.",[292,314,315,316,320],{},"이를 통해 개발자는 비즈니스 로직의 변경 없이 ",[317,318,319],"code",{},"application.yml"," 환경 설정만 바꾸는 것으로 AI 모델 공급업체를 언제든지 스위칭할 수 있는 유연성을 확보하게 된다.",[296,322],{},[301,324,326],{"id":325},"_2-핵심-아키텍처-구성-요소","2. 핵심 아키텍처 구성 요소",[292,328,329],{},"Spring AI는 복잡한 AI 워크플로우와 메모리 메커니즘을 추상화하기 위해 다음과 같은 핵심 인터페이스 컴포넌트를 제공한다.",[331,332,333,351,374],"ul",{},[334,335,336,346,347,350],"li",{},[309,337,338,341,342,345],{},[317,339,340],{},"ChatModel"," & ",[317,343,344],{},"StreamingChatModel",":"," LLM 인프라와 통신하기 위한 최상위 인터페이스다. 동기식 단순 응답뿐만 아니라 실시간 글자 스트리밍(",[317,348,349],{},"Flux"," 반환) 구조를 완벽하게 지원한다.",[334,352,353,361,362,365,366,369,370,373],{},[309,354,355,341,358,345],{},[317,356,357],{},"Prompt",[317,359,360],{},"Template"," 대화의 컨텍스트를 규정한다. 구조화된 ",[317,363,364],{},"SystemMessage",", 사용자의 ",[317,367,368],{},"UserMessage",", 모델의 이전 답변인 ",[317,371,372],{},"AssistantMessage","를 빌더 패턴으로 조합하여 프롬프트 엔지니어링을 관리한다.",[334,375,376,381],{},[309,377,378,345],{},[317,379,380],{},"VectorStore"," RAG(검색 증강 생성) 아키텍처의 핵심 축이다. 텍스트를 벡터화(Embedding)하여 저장하고 유사도 검색(Similarity Search)을 수행하는 Pgvector, Pinecone, Redis, Milvus 등의 외부 벡터 DB를 동일한 코드로 핸들링한다.",[296,383],{},[301,385,387],{"id":386},"_3-실무-필수-개발-패턴-및-구현-코드","3. 실무 필수 개발 패턴 및 구현 코드",[389,390,392,393,396],"h3",{"id":391},"의존성-관리-및-bom-설정-buildgradle","① 의존성 관리 및 BOM 설정 (",[317,394,395],{},"build.gradle",")",[292,398,399],{},"Spring AI는 릴리즈 주기가 활발하므로, 의존성 결합을 방지하기 위해 BOM(Bill of Materials)을 사용해 버전을 통합 관리하는 것이 좋다.",[401,402,407],"pre",{"className":403,"code":404,"filename":395,"language":405,"meta":406,"style":406},"language-groovy shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","dependencies {\n    implementation 'org.springframework.ai:spring-ai-openai-spring-boot-starter'\n    implementation 'org.springframework.boot:spring-boot-starter-web'\n}\n\ndependencyManagement {\n    imports {\n        mavenBom \"org.springframework.ai:spring-ai-bom:1.0.0-M1\" \u002F\u002F 사용 시점의 최신 안정 버전 지정\n    }\n}\n","groovy","",[317,408,409,418,435,447,453,460,466,472,490,496],{"__ignoreMap":406},[410,411,414],"span",{"class":412,"line":413},"line",1,[410,415,417],{"class":416},"sTEyZ","dependencies {\n",[410,419,421,424,428,432],{"class":412,"line":420},2,[410,422,423],{"class":416},"    implementation ",[410,425,427],{"class":426},"sMK4o","'",[410,429,431],{"class":430},"sfazB","org.springframework.ai:spring-ai-openai-spring-boot-starter",[410,433,434],{"class":426},"'\n",[410,436,438,440,442,445],{"class":412,"line":437},3,[410,439,423],{"class":416},[410,441,427],{"class":426},[410,443,444],{"class":430},"org.springframework.boot:spring-boot-starter-web",[410,446,434],{"class":426},[410,448,450],{"class":412,"line":449},4,[410,451,452],{"class":416},"}\n",[410,454,456],{"class":412,"line":455},5,[410,457,459],{"emptyLinePlaceholder":458},true,"\n",[410,461,463],{"class":412,"line":462},6,[410,464,465],{"class":416},"dependencyManagement {\n",[410,467,469],{"class":412,"line":468},7,[410,470,471],{"class":416},"    imports {\n",[410,473,475,478,481,484,486],{"class":412,"line":474},8,[410,476,477],{"class":416},"        mavenBom ",[410,479,480],{"class":426},"\"",[410,482,483],{"class":430},"org.springframework.ai:spring-ai-bom:1.0.0-M1",[410,485,480],{"class":426},[410,487,489],{"class":488},"sHwdD"," \u002F\u002F 사용 시점의 최신 안정 버전 지정\n",[410,491,493],{"class":412,"line":492},9,[410,494,495],{"class":416},"    }\n",[410,497,499],{"class":412,"line":498},10,[410,500,452],{"class":416},[389,502,504,505,507],{"id":503},"동기식-대화형-서비스-구조-chatmodel-주입","② 동기식 대화형 서비스 구조 (",[317,506,340],{}," 주입)",[292,509,510,511,513],{},"가장 표준적인 형태의 컴포넌트 모델이다. 별도의 수동 초기화 없이 빈(Bean) 스코프에서 주입된 ",[317,512,340],{},"을 통해 텍스트 생성을 즉시 처리한다.",[401,515,520],{"className":516,"code":517,"filename":518,"language":519,"meta":406,"style":406},"language-java shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import org.springframework.ai.chat.model.ChatModel;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.RestController;\n\n@RestController\npublic class AiChatController {\n\n    private final ChatModel chatModel;\n\n    \u002F\u002F Spring AI 오토구조가 인프라 설정을 읽어 자동으로 주입\n    public AiChatController(ChatModel chatModel) {\n        this.chatModel = chatModel;\n    }\n\n    @GetMapping(\"\u002Fapi\u002Fv1\u002Fai\u002Fgenerate\")\n    public String generate(@RequestParam(value = \"message\", defaultValue = \"오늘 서울 날씨 알려줘\") String message) {\n        return chatModel.call(message);\n    }\n}\n","AiChatController","java",[317,521,522,560,592,621,650,654,662,677,681,697,701,707,728,744,749,754,774,832,853,858],{"__ignoreMap":406},[410,523,524,528,532,535,538,540,543,545,548,550,553,555,557],{"class":412,"line":413},[410,525,527],{"class":526},"sbssI","import",[410,529,531],{"class":530},"spNyl"," org",[410,533,534],{"class":426},".",[410,536,537],{"class":530},"springframework",[410,539,534],{"class":426},[410,541,542],{"class":530},"ai",[410,544,534],{"class":426},[410,546,547],{"class":530},"chat",[410,549,534],{"class":426},[410,551,552],{"class":530},"model",[410,554,534],{"class":426},[410,556,340],{"class":530},[410,558,559],{"class":426},";\n",[410,561,562,564,566,568,570,572,575,577,580,582,585,587,590],{"class":412,"line":420},[410,563,527],{"class":526},[410,565,531],{"class":530},[410,567,534],{"class":426},[410,569,537],{"class":530},[410,571,534],{"class":426},[410,573,574],{"class":530},"web",[410,576,534],{"class":426},[410,578,579],{"class":530},"bind",[410,581,534],{"class":426},[410,583,584],{"class":530},"annotation",[410,586,534],{"class":426},[410,588,589],{"class":530},"GetMapping",[410,591,559],{"class":426},[410,593,594,596,598,600,602,604,606,608,610,612,614,616,619],{"class":412,"line":437},[410,595,527],{"class":526},[410,597,531],{"class":530},[410,599,534],{"class":426},[410,601,537],{"class":530},[410,603,534],{"class":426},[410,605,574],{"class":530},[410,607,534],{"class":426},[410,609,579],{"class":530},[410,611,534],{"class":426},[410,613,584],{"class":530},[410,615,534],{"class":426},[410,617,618],{"class":530},"RequestParam",[410,620,559],{"class":426},[410,622,623,625,627,629,631,633,635,637,639,641,643,645,648],{"class":412,"line":449},[410,624,527],{"class":526},[410,626,531],{"class":530},[410,628,534],{"class":426},[410,630,537],{"class":530},[410,632,534],{"class":426},[410,634,574],{"class":530},[410,636,534],{"class":426},[410,638,579],{"class":530},[410,640,534],{"class":426},[410,642,584],{"class":530},[410,644,534],{"class":426},[410,646,647],{"class":530},"RestController",[410,649,559],{"class":426},[410,651,652],{"class":412,"line":455},[410,653,459],{"emptyLinePlaceholder":458},[410,655,656,659],{"class":412,"line":462},[410,657,658],{"class":426},"@",[410,660,661],{"class":530},"RestController\n",[410,663,664,667,670,674],{"class":412,"line":468},[410,665,666],{"class":530},"public",[410,668,669],{"class":530}," class",[410,671,673],{"class":672},"sBMFI"," AiChatController",[410,675,676],{"class":426}," {\n",[410,678,679],{"class":412,"line":474},[410,680,459],{"emptyLinePlaceholder":458},[410,682,683,686,689,692,695],{"class":412,"line":492},[410,684,685],{"class":530},"    private",[410,687,688],{"class":530}," final",[410,690,691],{"class":530}," ChatModel",[410,693,694],{"class":416}," chatModel",[410,696,559],{"class":426},[410,698,699],{"class":412,"line":498},[410,700,459],{"emptyLinePlaceholder":458},[410,702,704],{"class":412,"line":703},11,[410,705,706],{"class":488},"    \u002F\u002F Spring AI 오토구조가 인프라 설정을 읽어 자동으로 주입\n",[410,708,710,713,716,719,721,724,726],{"class":412,"line":709},12,[410,711,712],{"class":530},"    public",[410,714,673],{"class":715},"s2Zo4",[410,717,718],{"class":426},"(",[410,720,340],{"class":530},[410,722,694],{"class":723},"sHdIc",[410,725,396],{"class":426},[410,727,676],{"class":426},[410,729,731,734,737,740,742],{"class":412,"line":730},13,[410,732,733],{"class":426},"        this.",[410,735,736],{"class":416},"chatModel ",[410,738,739],{"class":426},"=",[410,741,694],{"class":416},[410,743,559],{"class":426},[410,745,747],{"class":412,"line":746},14,[410,748,495],{"class":426},[410,750,752],{"class":412,"line":751},15,[410,753,459],{"emptyLinePlaceholder":458},[410,755,757,760,762,764,766,769,771],{"class":412,"line":756},16,[410,758,759],{"class":426},"    @",[410,761,589],{"class":530},[410,763,718],{"class":426},[410,765,480],{"class":426},[410,767,768],{"class":430},"\u002Fapi\u002Fv1\u002Fai\u002Fgenerate",[410,770,480],{"class":426},[410,772,773],{"class":426},")\n",[410,775,777,779,782,785,788,790,792,795,798,801,804,806,809,812,814,816,819,821,823,825,828,830],{"class":412,"line":776},17,[410,778,712],{"class":530},[410,780,781],{"class":530}," String",[410,783,784],{"class":715}," generate",[410,786,787],{"class":426},"(@",[410,789,618],{"class":530},[410,791,718],{"class":426},[410,793,794],{"class":672},"value",[410,796,797],{"class":426}," =",[410,799,800],{"class":426}," \"",[410,802,803],{"class":430},"message",[410,805,480],{"class":426},[410,807,808],{"class":426},",",[410,810,811],{"class":672}," defaultValue",[410,813,797],{"class":426},[410,815,800],{"class":426},[410,817,818],{"class":430},"오늘 서울 날씨 알려줘",[410,820,480],{"class":426},[410,822,396],{"class":426},[410,824,781],{"class":530},[410,826,827],{"class":723}," message",[410,829,396],{"class":426},[410,831,676],{"class":426},[410,833,835,839,841,843,846,848,850],{"class":412,"line":834},18,[410,836,838],{"class":837},"s7zQu","        return",[410,840,694],{"class":416},[410,842,534],{"class":426},[410,844,845],{"class":715},"call",[410,847,718],{"class":426},[410,849,803],{"class":416},[410,851,852],{"class":426},");\n",[410,854,856],{"class":412,"line":855},19,[410,857,495],{"class":426},[410,859,861],{"class":412,"line":860},20,[410,862,452],{"class":426},[389,864,866,867,870],{"id":865},"구조화된-json-응답-역직렬화-structured-output-패턴","③ 구조화된 JSON 응답 역직렬화 (",[317,868,869],{},"Structured Output"," 패턴)",[292,872,873,874,877],{},"AI가 뱉는 자유 형식의 텍스트 응답을 백엔드 비즈니스 로직 내부에서 유효한 DTO나 객체 구조로 파싱하는 실무 핵심 패턴이다. ",[317,875,876],{},"ChatClient","의 Fluent API를 활용하면 매우 직관적으로 구현된다.",[401,879,882],{"className":516,"code":880,"filename":881,"language":519,"meta":406,"style":406},"import org.springframework.ai.chat.client.ChatClient;\nimport org.springframework.core.ParameterizedTypeReference;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RestController;\nimport java.util.List;\n\n@RestController\npublic class TechBlogController {\n\n    private final ChatClient chatClient;\n\n    public TechBlogController(ChatClient.Builder builder) {\n        this.chatClient = builder.build();\n    }\n\n    \u002F\u002F 결과 매핑용 불변 구조체 정의\n    public record BlogIdea(String title, String description, List\u003CString> tags) {}\n\n    @GetMapping(\"\u002Fapi\u002Fv1\u002Fai\u002Fblog-ideas\")\n    public List\u003CBlogIdea> getBlogIdeas() {\n        return chatClient.prompt()\n                .user(\"요즘 개발 트렌드 알려줘\")\n                .call()\n                .entity(new ParameterizedTypeReference\u003CList\u003CBlogIdea>>() {}); \n                \u002F\u002F 👈 LLM의 가변 응답 데이터 구조를 List\u003CBlogIdea> 객체 배열로 정밀 역직렬화한다.\n    }\n}\n","TechBlogController",[317,883,884,913,935,963,991,1010,1014,1020,1031,1035,1049,1053,1075,1094,1098,1102,1107,1153,1157,1174,1195,1210,1230,1239,1272,1278,1283],{"__ignoreMap":406},[410,885,886,888,890,892,894,896,898,900,902,904,907,909,911],{"class":412,"line":413},[410,887,527],{"class":526},[410,889,531],{"class":530},[410,891,534],{"class":426},[410,893,537],{"class":530},[410,895,534],{"class":426},[410,897,542],{"class":530},[410,899,534],{"class":426},[410,901,547],{"class":530},[410,903,534],{"class":426},[410,905,906],{"class":530},"client",[410,908,534],{"class":426},[410,910,876],{"class":530},[410,912,559],{"class":426},[410,914,915,917,919,921,923,925,928,930,933],{"class":412,"line":420},[410,916,527],{"class":526},[410,918,531],{"class":530},[410,920,534],{"class":426},[410,922,537],{"class":530},[410,924,534],{"class":426},[410,926,927],{"class":530},"core",[410,929,534],{"class":426},[410,931,932],{"class":530},"ParameterizedTypeReference",[410,934,559],{"class":426},[410,936,937,939,941,943,945,947,949,951,953,955,957,959,961],{"class":412,"line":437},[410,938,527],{"class":526},[410,940,531],{"class":530},[410,942,534],{"class":426},[410,944,537],{"class":530},[410,946,534],{"class":426},[410,948,574],{"class":530},[410,950,534],{"class":426},[410,952,579],{"class":530},[410,954,534],{"class":426},[410,956,584],{"class":530},[410,958,534],{"class":426},[410,960,589],{"class":530},[410,962,559],{"class":426},[410,964,965,967,969,971,973,975,977,979,981,983,985,987,989],{"class":412,"line":449},[410,966,527],{"class":526},[410,968,531],{"class":530},[410,970,534],{"class":426},[410,972,537],{"class":530},[410,974,534],{"class":426},[410,976,574],{"class":530},[410,978,534],{"class":426},[410,980,579],{"class":530},[410,982,534],{"class":426},[410,984,584],{"class":530},[410,986,534],{"class":426},[410,988,647],{"class":530},[410,990,559],{"class":426},[410,992,993,995,998,1000,1003,1005,1008],{"class":412,"line":455},[410,994,527],{"class":526},[410,996,997],{"class":530}," java",[410,999,534],{"class":426},[410,1001,1002],{"class":530},"util",[410,1004,534],{"class":426},[410,1006,1007],{"class":530},"List",[410,1009,559],{"class":426},[410,1011,1012],{"class":412,"line":462},[410,1013,459],{"emptyLinePlaceholder":458},[410,1015,1016,1018],{"class":412,"line":468},[410,1017,658],{"class":426},[410,1019,661],{"class":530},[410,1021,1022,1024,1026,1029],{"class":412,"line":474},[410,1023,666],{"class":530},[410,1025,669],{"class":530},[410,1027,1028],{"class":672}," TechBlogController",[410,1030,676],{"class":426},[410,1032,1033],{"class":412,"line":492},[410,1034,459],{"emptyLinePlaceholder":458},[410,1036,1037,1039,1041,1044,1047],{"class":412,"line":498},[410,1038,685],{"class":530},[410,1040,688],{"class":530},[410,1042,1043],{"class":530}," ChatClient",[410,1045,1046],{"class":416}," chatClient",[410,1048,559],{"class":426},[410,1050,1051],{"class":412,"line":703},[410,1052,459],{"emptyLinePlaceholder":458},[410,1054,1055,1057,1059,1061,1063,1065,1068,1071,1073],{"class":412,"line":709},[410,1056,712],{"class":530},[410,1058,1028],{"class":715},[410,1060,718],{"class":426},[410,1062,876],{"class":530},[410,1064,534],{"class":426},[410,1066,1067],{"class":530},"Builder",[410,1069,1070],{"class":723}," builder",[410,1072,396],{"class":426},[410,1074,676],{"class":426},[410,1076,1077,1079,1082,1084,1086,1088,1091],{"class":412,"line":730},[410,1078,733],{"class":426},[410,1080,1081],{"class":416},"chatClient ",[410,1083,739],{"class":426},[410,1085,1070],{"class":416},[410,1087,534],{"class":426},[410,1089,1090],{"class":715},"build",[410,1092,1093],{"class":426},"();\n",[410,1095,1096],{"class":412,"line":746},[410,1097,495],{"class":426},[410,1099,1100],{"class":412,"line":751},[410,1101,459],{"emptyLinePlaceholder":458},[410,1103,1104],{"class":412,"line":756},[410,1105,1106],{"class":488},"    \u002F\u002F 결과 매핑용 불변 구조체 정의\n",[410,1108,1109,1111,1114,1117,1119,1122,1125,1127,1129,1132,1134,1137,1140,1142,1145,1148,1150],{"class":412,"line":776},[410,1110,712],{"class":530},[410,1112,1113],{"class":530}," record",[410,1115,1116],{"class":672}," BlogIdea",[410,1118,718],{"class":426},[410,1120,1121],{"class":530},"String",[410,1123,1124],{"class":416}," title",[410,1126,808],{"class":426},[410,1128,781],{"class":530},[410,1130,1131],{"class":416}," description",[410,1133,808],{"class":426},[410,1135,1136],{"class":530}," List",[410,1138,1139],{"class":426},"\u003C",[410,1141,1121],{"class":416},[410,1143,1144],{"class":426},">",[410,1146,1147],{"class":416}," tags",[410,1149,396],{"class":426},[410,1151,1152],{"class":426}," {}\n",[410,1154,1155],{"class":412,"line":834},[410,1156,459],{"emptyLinePlaceholder":458},[410,1158,1159,1161,1163,1165,1167,1170,1172],{"class":412,"line":855},[410,1160,759],{"class":426},[410,1162,589],{"class":530},[410,1164,718],{"class":426},[410,1166,480],{"class":426},[410,1168,1169],{"class":430},"\u002Fapi\u002Fv1\u002Fai\u002Fblog-ideas",[410,1171,480],{"class":426},[410,1173,773],{"class":426},[410,1175,1176,1178,1180,1182,1185,1187,1190,1193],{"class":412,"line":860},[410,1177,712],{"class":530},[410,1179,1136],{"class":530},[410,1181,1139],{"class":426},[410,1183,1184],{"class":530},"BlogIdea",[410,1186,1144],{"class":426},[410,1188,1189],{"class":715}," getBlogIdeas",[410,1191,1192],{"class":426},"()",[410,1194,676],{"class":426},[410,1196,1198,1200,1202,1204,1207],{"class":412,"line":1197},21,[410,1199,838],{"class":837},[410,1201,1046],{"class":416},[410,1203,534],{"class":426},[410,1205,1206],{"class":715},"prompt",[410,1208,1209],{"class":426},"()\n",[410,1211,1213,1216,1219,1221,1223,1226,1228],{"class":412,"line":1212},22,[410,1214,1215],{"class":426},"                .",[410,1217,1218],{"class":715},"user",[410,1220,718],{"class":426},[410,1222,480],{"class":426},[410,1224,1225],{"class":430},"요즘 개발 트렌드 알려줘",[410,1227,480],{"class":426},[410,1229,773],{"class":426},[410,1231,1233,1235,1237],{"class":412,"line":1232},23,[410,1234,1215],{"class":426},[410,1236,845],{"class":715},[410,1238,1209],{"class":426},[410,1240,1242,1244,1247,1249,1252,1255,1257,1259,1261,1263,1266,1269],{"class":412,"line":1241},24,[410,1243,1215],{"class":426},[410,1245,1246],{"class":715},"entity",[410,1248,718],{"class":426},[410,1250,1251],{"class":837},"new",[410,1253,1254],{"class":530}," ParameterizedTypeReference",[410,1256,1139],{"class":426},[410,1258,1007],{"class":530},[410,1260,1139],{"class":426},[410,1262,1184],{"class":530},[410,1264,1265],{"class":426},">>()",[410,1267,1268],{"class":426}," {});",[410,1270,1271],{"class":416}," \n",[410,1273,1275],{"class":412,"line":1274},25,[410,1276,1277],{"class":488},"                \u002F\u002F 👈 LLM의 가변 응답 데이터 구조를 List\u003CBlogIdea> 객체 배열로 정밀 역직렬화한다.\n",[410,1279,1281],{"class":412,"line":1280},26,[410,1282,495],{"class":426},[410,1284,1286],{"class":412,"line":1285},27,[410,1287,452],{"class":426},[296,1289],{},[301,1291,1293],{"id":1292},"_4-에이전틱-워크플로우를-위한-function-calling-인터페이스","4. 에이전틱 워크플로우를 위한 Function Calling 인터페이스",[292,1295,1296,1297,1300],{},"Spring AI의 가장 독보적인 기능 중 하나는 LLM이 필요에 따라 실제 시스템의 자바 메서드를 동적으로 호출할 수 있는 ",[309,1298,1299],{},"Function Calling(도구 사용)"," 인프라다.",[292,1302,1303,1304,1307],{},"\"내 블로그 오늘 총 조회수 알려줘\" 같은 실시간성 질문이 들어오면, LLM이 텍스트 생성을 멈추고 미리 등록해 둔 조회용 자바 함수(",[317,1305,1306],{},"Function\u003CR, T>",")를 명세에 따라 가상으로 호출하여 결합된 데이터를 반환하게 유도할 수 있다.",[401,1309,1312],{"className":516,"code":1310,"filename":1311,"language":519,"meta":406,"style":406},"@Configuration\npublic class AiConfig {\n\n    @Bean\n    @Description(\"데이터베이스에서 실시간 특정 날짜의 블로그 방문 통계 수치를 조회합니다.\")\n    public Function\u003CDailyStatusRequest, DailyStatusResponse> fetchBlogMetrics() {\n        return request -> {\n            \u002F\u002F 실제 영속성 레이어(JPA\u002FMyBatis) 엔드포인트 연동 스코프\n            return new DailyStatusResponse(14500); \n        };\n    }\n}\n","AiConfig",[317,1313,1314,1321,1332,1336,1343,1361,1387,1399,1404,1424,1429,1433],{"__ignoreMap":406},[410,1315,1316,1318],{"class":412,"line":413},[410,1317,658],{"class":426},[410,1319,1320],{"class":530},"Configuration\n",[410,1322,1323,1325,1327,1330],{"class":412,"line":420},[410,1324,666],{"class":530},[410,1326,669],{"class":530},[410,1328,1329],{"class":672}," AiConfig",[410,1331,676],{"class":426},[410,1333,1334],{"class":412,"line":437},[410,1335,459],{"emptyLinePlaceholder":458},[410,1337,1338,1340],{"class":412,"line":449},[410,1339,759],{"class":426},[410,1341,1342],{"class":530},"Bean\n",[410,1344,1345,1347,1350,1352,1354,1357,1359],{"class":412,"line":455},[410,1346,759],{"class":426},[410,1348,1349],{"class":530},"Description",[410,1351,718],{"class":426},[410,1353,480],{"class":426},[410,1355,1356],{"class":430},"데이터베이스에서 실시간 특정 날짜의 블로그 방문 통계 수치를 조회합니다.",[410,1358,480],{"class":426},[410,1360,773],{"class":426},[410,1362,1363,1365,1368,1370,1373,1375,1378,1380,1383,1385],{"class":412,"line":462},[410,1364,712],{"class":530},[410,1366,1367],{"class":530}," Function",[410,1369,1139],{"class":426},[410,1371,1372],{"class":530},"DailyStatusRequest",[410,1374,808],{"class":426},[410,1376,1377],{"class":530}," DailyStatusResponse",[410,1379,1144],{"class":426},[410,1381,1382],{"class":715}," fetchBlogMetrics",[410,1384,1192],{"class":426},[410,1386,676],{"class":426},[410,1388,1389,1391,1394,1397],{"class":412,"line":468},[410,1390,838],{"class":837},[410,1392,1393],{"class":416}," request ",[410,1395,1396],{"class":530},"->",[410,1398,676],{"class":426},[410,1400,1401],{"class":412,"line":474},[410,1402,1403],{"class":488},"            \u002F\u002F 실제 영속성 레이어(JPA\u002FMyBatis) 엔드포인트 연동 스코프\n",[410,1405,1406,1409,1412,1414,1416,1419,1422],{"class":412,"line":492},[410,1407,1408],{"class":837},"            return",[410,1410,1411],{"class":837}," new",[410,1413,1377],{"class":715},[410,1415,718],{"class":426},[410,1417,1418],{"class":526},"14500",[410,1420,1421],{"class":426},");",[410,1423,1271],{"class":416},[410,1425,1426],{"class":412,"line":498},[410,1427,1428],{"class":426},"        };\n",[410,1430,1431],{"class":412,"line":703},[410,1432,495],{"class":426},[410,1434,1435],{"class":412,"line":709},[410,1436,452],{"class":426},[296,1438],{},[301,1440,1442],{"id":1441},"_5-결론-및-실무-체크리스트","5. 결론 및 실무 체크리스트",[292,1444,1445],{},"Spring AI를 엔터프라이즈 실무 아키텍처에 안착시키기 위해서는 다음 세 가지 기술적 지향점을 유지해야 한다.",[1447,1448,1449,1458,1470],"ol",{},[334,1450,1451,1454,1455,1457],{},[309,1452,1453],{},"인프라 결합도 낮추기:"," 비즈니스 서비스 레이어가 특정 벤더(예: OpenAI)의 고유 상속 프롭 객체에 결합하지 않도록, ",[317,1456,876],{},"나 추상화 클래스를 적극 활용해야 마이그레이션 유연성이 확보된다.",[334,1459,1460,1463,1464,1466,1467,1469],{},[309,1461,1462],{},"리액티브 파이프라인 대응:"," 트래픽 오버헤드와 모델 응답 지연에 대응하기 위해, 무거운 단일 블로킹 콜보다는 ",[317,1465,344],{},"을 이용한 ",[317,1468,349],{}," 기반의 논블로킹 웹플럭스(WebFlux) 구조를 우선적으로 고려해야 한다.",[334,1471,1472,1475],{},[309,1473,1474],{},"데이터 경량 토큰 최적화:"," RAG 구성 시 무조건적인 고차원 임베딩 서치보다는 적절한 크기의 덩어리(Chunk) 슬라이싱 and 메타데이터 필터링을 병행하여 자바 메모리 누수와 API 토큰 비용 낭비를 예방해야 한다.",[1477,1478,1479],"style",{},"html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":406,"searchDepth":420,"depth":420,"links":1481},[1482,1483,1484,1492,1493],{"id":303,"depth":420,"text":304},{"id":325,"depth":420,"text":326},{"id":386,"depth":420,"text":387,"children":1485},[1486,1488,1490],{"id":391,"depth":437,"text":1487},"① 의존성 관리 및 BOM 설정 (build.gradle)",{"id":503,"depth":437,"text":1489},"② 동기식 대화형 서비스 구조 (ChatModel 주입)",{"id":865,"depth":437,"text":1491},"③ 구조화된 JSON 응답 역직렬화 (Structured Output 패턴)",{"id":1292,"depth":420,"text":1293},{"id":1441,"depth":420,"text":1442},"2026-07-07","Spring AI가 제공하는 AI 서비스 추상화 구조와 LLM, Vector DB 연동 방식, 실무에서 활용되는 주요 개발 패턴 정리","md",null,{"tags":1499,"image":290,"imageClass":288},[1500,1501,1502,1503],"Spring AI","Spring Boot 3","LLM","Java",{"icon":20},{"title":1506,"description":1507},"Spring AI 핵심 개념과 LLM 연동 방법 정리","Spring AI의 동작 원리와 ChatModel, VectorStore, Function Calling 기반의 LLM 애플리케이션 구성 방법을 설명합니다.","ejAyKbnKl_Dl_-UFWG55CpjMRS85shrXZcpmJoqLJVg",[1497,1510],{"title":22,"path":23,"stem":24,"description":1511,"icon":20,"children":-1},"Java의 record 객체가 등장한 이유와 불변 데이터 객체의 특징, 장단점, DTO 활용법부터 JPA의 @Version을 이용한 낙관적 락과 동시성 제어 원리까지 정리합니다.",1789947615939]