こんにちは、サイオステクノロジーの佐藤です。 今回は、前回のブログでも触れたFoundry IQについて、もう一歩踏み込み、「実際に内部でどのような処理が行われているのか?」という構造面を解説します。 Foundry IQ便利そうだけどどういう仕組みなの? 内部的な動きってどうなっているの? という方は是非最後までご覧ください! なお、Foundry IQの概要やコンセプトに関しては前回の記事をご覧ください。 Foundry IQ は RAGにとっての銀の弾丸となりえるか?【Microsoft Ignite 2025 Recap】 Foundry IQの技術基盤 Foundry IQはMicrosoft Foundry上にて作成できるサービスとなっています。 ただし完全に新規で独立したサービスというわけでもなく、内部的な仕組みとしてはAzure AI Searchのサービスが基盤となっています。 またそれと並行してLLMやEmbeddingsといったAIモデルも必要となってきます。 この Azure AI Search と AIのモデル の2つがFoundry IQを構成する主な要素となります。 そのため、Microsoft Foundry上でFoundry IQを作成しようとすると、まずAzure AI Searchとの接続を求められます。 また、後述するナレッジベースを構築する上では、AIモデルのデプロイメントが必須となります。 AI Searchの役割 AI Searchの役割は高度な検索サービスであり、AI Search上に格納されたデータに対して様々な検索を行えるサービスです。 特に、近年実現されたAgentic Retrievalの検索手法は、今回のFoundry IQの肝となる検索手法と言っても過言ではありません。 また検索手法だけではなく、データのインポート技術にも優れています。 BlobStorageなどを対象として、データの読みとりから、チャンク化・ベクトル化といった加工までを実施可能で、検索に適した形でデータをインポートすることが可能です。 こういったことから、Azure AI SearchはFoundry IQの根幹となるプラットフォームとなっています。 AIモデルの役割 先ほど紹介したAI Searchの検索機能に対してより付加価値を高めるのが、各AIモデルの役割です。 単純にデータのインポート、検索を行うだけであればAI Searchだけで完結しますが ユーザーからの質問の意図の解釈や、質問の分解などに使われ、より検索精度・回答精度を高めるための手段としてLLMが利用されます。 また、LLMによる「思考」だけでなく、Embeddingモデルも非常に重要な役割を果たします。 先ほど述べたように、Foundry IQがデータを自動でインポートする際の「ベクトル化」や、ユーザーの質問をデータベースと照合する際の「意味的マッチング」において、このEmbeddingモデルが裏側で活躍しています。 Foundry IQの構成 実際にFoundry IQの内容についてみていきたいと思います。 Foundry IQのざっくりコンポーネント図としては以下のような形です。 ここで重要なのが以下の2つの要素です。 ナレッジソース ナレッジベース ナレッジソース ナレッジソースとは、実際に検索に使われるファイルなどのコンテンツを指定するための概念となります。 上の図でいうと、Storage AccountとSharePoint Onlineといったリソースと連携している形となります。 各ナレッジソースがこれらのリソースと紐づくことで、紐づいたリソースのコンテンツを検索対象とすることができます。 ナレッジソースの対象となっているものは以下の6種類です。 ナレッジソースと紐づけ可能なリソース タイプ AI Search Index インデックス型 Azure Blob インデックス型 OneLake インデックス型 SharePointOnline インデックス型 SharePointOnline リモート型 Web リモート型 なおここで記載しているタイプについては、以下のような違いがあります。 インデックス型:事前にデータを取り込んでおき、内部にインデックスを作成・保持しておく リモート型:検索が行われた時点で外部のシステムを直接見に行き、常に最新のデータを見に行く ナレッジベース ナレッジベースは、複数のナレッジソースを束ねて、エージェントに対する窓口となる概念です。 エージェントからのクエリに対して、どのナレッジソースを利用するべきか、どういった検索を行うか、本当にこのまま検索結果を返してよいかなどを判断するオーケストレーターとしての役割を担います。 また、ナレッジベースは最低一つ以上のナレッジソースから構築されます。 Agentic Retrieval Foundry IQの重要な機能に、 Agentic Retrieval があります。 とはいいつつ、これはFoundry IQというよりもAzure AI Searchの機能となります。 先ほども述べた通り、実際にエージェントからFoundry IQを呼び出したとしても、検索が行われるのはAI Searchです。 従来の検索手法では、ユーザーからの質問をベクトル化して一度検索を行い、類似度が高いものを結果に返すといった流れでした。(いわゆるSingle-shot) 一方で、Agentic RetrievalではLLMの機能を用いて、 クエリの内容はどのような内容なのか クエリに適したナレッジソースはどれなのか 得られた結果が本当に期待する回答なのか 必要に応じて再検索を実施 などを自律的に考えた検索が行われます。 そのため、従来のSingle-shotの検索よりも、期待に近い回答が得られやすくなります。 Agentic Retrievalの例 このAgentic Retrievalの例をひとつ見てみたいと思います。 例えばユーザーから以下のようなクエリが飛んできたとします。 サイオス病院の健康診断でB判定となりました。上の血圧が150でした。 今40歳なのですが、病院からは運動することを推奨されました。 どういった運動をしたらいいですか? この質問には、「B判定の基準」「血圧150の意味」「40歳の適切な運動量」という複数の要素が混ざり合っています。 これを従来のRAGでそのまま検索にかけても、全ての情報が網羅された完璧なドキュメントを引き当てることは困難です。 一方でAgentic Retrievalを利用することで、こちらが解決できる可能性があります。 Foundry IQ(Agentic Retrieval)では、この質問を受け取ると以下の4つのステップを自律的に実行します。 Step 1: クエリの計画と分解 まず、AIが質問の意図を解釈し、回答に必要な情報を集めるために質問を複数の「サブクエリ」に分解します。 「サイオス病院のB判定の基準とは?」 「血圧150の危険度は?」 「40歳に推奨される1日あたりの運動量はどれくらいか?」 このように、システムが自ら「何を調べるべきか」の計画を立てます。 Step 2: ソースの動的選択 次に、分解したサブクエリごとに、接続されている複数のナレッジソースの中から「どこへ探しに行くべきか」を動的に選択して検索を実行します。 B判定の基準 ➔ 病院内部資料である SharePoint を検索 血圧の危険度 ➔ 調査資料が格納された Blob Storage を検索 推奨される運動量 ➔ 自社データにない一般知識のため Web(Bing)検索 を実行 このように各データソースの特徴をAIが判断し、最適な場所へ同時に情報を探しに行きます。 Step 3: 自己評価と反復検索 ここがAgentic Retrievalの最も強力なポイントです。 取得した情報をそのまま返すのではなく、システム内部にいる小規模言語モデル(SLM)が「この情報でユーザーの質問に答えられるか?」を自己評価します。 もし「まだ情報が足りない(NG)」と判断した場合は、もう一度ステップ1に戻り、クエリを調整して再検索(反復)を行います。 Step 4: 回答の合成 SLMの評価が「OK」となり、十分な情報が揃ったと判断された段階で、複数のソースからかき集めた情報を統合し、自然な回答文として合成してユーザーに返却します。 retrieval Reasoning Effort このように自律的に動作するAgentic Retrievalですが、「毎回そんなに深く考えて反復検索しなくていいから、早く答えが欲しい」というケースもあるはずです。 Agentic Retrievalにおいては、この「検索の深さ(レイテンシと品質のトレードオフ)」を開発者がコントロールできるように、「検索推論努力(retrieval Reasoning Effort)」というパラメータが用意されています。 レベル 説明 minimal LLM 処理を行いいません。 low 質問の計画とソースの選択は行いますが、反復検索は行いません。 medium 自己評価と反復検索をフル活用し、徹底的に情報を集めます。 まとめ 今回は、Foundry IQの内部構造や、Agentic Retrievalの自律的なプロセスについて解説しました。 こうして内部の動きを見ると、開発者がこれまで自前で実装する必要があった複雑な検索処理を、Foundry IQがうまく担ってくれていることが分かっていただけたかと思います。 次回は実際にFoundry IQを活用してエージェントを構築する流れをご紹介したいと思います! ではまた! 余談 本ブログの内容に関しては、以下のYouTubeでもお話ししています。 もしよければこちらもご覧いただければと思います /* From extension marp-team.marp-vscode */ (()=>{var I=Object.defineProperty;var K=(f,u,_)=>u in f?I(f,u,{enumerable:!0,configurable:!0,writable:!0,value:_}):f[u]=_;var r=(f,u)=>I(f,"name",{value:u,configurable:!0});var w=(f,u,_)=>K(f,typeof u!="symbol"?u+"":u,_);(()=>{var f={32(S,E,h){S.exports=h(924)},924(S,E){"use strict";var h;h={value:!0};const b={h1:{proto:r(()=>HTMLHeadingElement,"proto"),attrs:{role:"heading","aria-level":"1"},style:"display: block; font-size: 2em; margin-block-start: 0.67em; margin-block-end: 0.67em; margin-inline-start: 0px; margin-inline-end: 0px; font-weight: bold;"},h2:{proto:r(()=>HTMLHeadingElement,"proto"),attrs:{role:"heading","aria-level":"2"},style:"display: block; font-size: 1.5em; margin-block-start: 0.83em; margin-block-end: 0.83em; margin-inline-start: 0px; margin-inline-end: 0px; font-weight: bold;"},h3:{proto:r(()=>HTMLHeadingElement,"proto"),attrs:{role:"heading","aria-level":"3"},style:"display: block; font-size: 1.17em; margin-block-start: 1em; margin-block-end: 1em; margin-inline-start: 0px; margin-inline-end: 0px; font-weight: bold;"},h4:{proto:r(()=>HTMLHeadingElement,"proto"),attrs:{role:"heading","aria-level":"4"},style:"display: block; margin-block-start: 1.33em; margin-block-end: 1.33em; margin-inline-start: 0px; margin-inline-end: 0px; font-weight: bold;"},h5:{proto:r(()=>HTMLHeadingElement,"proto"),attrs:{role:"heading","aria-level":"5"},style:"display: block; font-size: 0.83em; margin-block-start: 1.67em; margin-block-end: 1.67em; margin-inline-start: 0px; margin-inline-end: 0px; font-weight: bold;"},h6:{proto:r(()=>HTMLHeadingElement,"proto"),attrs:{role:"heading","aria-level":"6"},style:"display: block; font-size: 0.67em; margin-block-start: 2.33em; margin-block-end: 2.33em; margin-inline-start: 0px; margin-inline-end: 0px; font-weight: bold;"},span:{proto:r(()=>HTMLSpanElement,"proto")},pre:{proto:r(()=>HTMLElement,"proto"),style:"display: block; font-family: monospace; white-space: pre; margin: 1em 0; --marp-auto-scaling-white-space: pre;"}},q="data-marp-auto-scaling-wrapper",C="data-marp-auto-scaling-svg",O="data-marp-auto-scaling-container",j=class j extends HTMLElement{constructor(){super();w(this,"container");w(this,"containerSize");w(this,"containerObserver");w(this,"svg");w(this,"svgComputedStyle");w(this,"svgPreserveAspectRatio","xMinYMid meet");w(this,"wrapper");w(this,"wrapperSize");w(this,"wrapperObserver");const n=r(s=>([t])=>{const{width:i,height:o}=t.contentRect;this[s]={width:i,height:o},this.updateSVGRect()},"e");this.attachShadow({mode:"open"}),this.containerObserver=new ResizeObserver(n("containerSize")),this.wrapperObserver=new ResizeObserver((...s)=>{n("wrapperSize")(...s),this.flushSvgDisplay()})}static get observedAttributes(){return["data-downscale-only"]}connectedCallback(){this.shadowRoot.innerHTML=` svg[${C}] { display: block; width: 100%; height: auto; vertical-align: top; } span[${O}] { display: table; white-space: var(--marp-auto-scaling-white-space, nowrap); width: max-content; } `.split(/\n\s*/).join(""),this.wrapper=this.shadowRoot.querySelector(`div[${q}]`)??void 0;const n=this.svg;this.svg=this.wrapper?.querySelector(`svg[${C}]`)??void 0,this.svg!==n&&(this.svgComputedStyle=this.svg?window.getComputedStyle(this.svg):void 0),this.container=this.svg?.querySelector(`span[${O}]`)??void 0,this.observe()}disconnectedCallback(){this.svg=void 0,this.svgComputedStyle=void 0,this.wrapper=void 0,this.container=void 0,this.observe()}attributeChangedCallback(){this.observe()}flushSvgDisplay(){const{svg:n}=this;n&&(n.style.display="inline",requestAnimationFrame(()=>{n.style.display=""}))}observe(){this.containerObserver.disconnect(),this.wrapperObserver.disconnect(),this.wrapper&&this.wrapperObserver.observe(this.wrapper),this.container&&this.containerObserver.observe(this.container),this.svgComputedStyle&&this.observeSVGStyle(this.svgComputedStyle)}observeSVGStyle(n){const s=r(()=>{const t=(()=>{const i=n.getPropertyValue("--preserve-aspect-ratio");return i?i.trim():`x${(({textAlign:o,direction:d})=>{if(o.endsWith("left"))return"Min";if(o.endsWith("right"))return"Max";if(o==="start"||o==="end"){let m=d==="rtl";return o==="end"&&(m=!m),m?"Max":"Min"}return"Mid"})(n)}YMid meet`})();t!==this.svgPreserveAspectRatio&&(this.svgPreserveAspectRatio=t,this.updateSVGRect()),n===this.svgComputedStyle&&requestAnimationFrame(s)},"t");s()}updateSVGRect(){let n=Math.ceil(this.containerSize?.width??0);const s=Math.ceil(this.containerSize?.height??0);this.dataset.downscaleOnly!==void 0&&(n=Math.max(n,this.wrapperSize?.width??0));const t=this.svg?.querySelector(":scope > foreignObject");if(t?.setAttribute("width",`${n}`),t?.setAttribute("height",`${s}`),this.svg&&(this.svg.setAttribute("viewBox",`0 0 ${n} ${s}`),this.svg.setAttribute("preserveAspectRatio",this.svgPreserveAspectRatio),this.svg.style.height=n class extends c{constructor(...s){super(...s);for(const[t,i]of Object.entries(v))this.hasAttribute(t)||this.setAttribute(t,i);this._shadow()}static get observedAttributes(){return["data-auto-scaling"]}connectedCallback(){this._update()}attributeChangedCallback(){this._update()}_shadow(){if(!this.shadowRoot)try{this.attachShadow({mode:"open"})}catch(s){if(!(s instanceof Error&&s.name==="NotSupportedError"))throw s}return this.shadowRoot}_update(){const s=this._shadow();if(s){const t=n?` :host { ${n} } `:"";let i=" ";const{autoScaling:o}=this.dataset;o!==void 0&&(i=` ${i} `),s.innerHTML=t+i}}},"r");let L;const F=Symbol(),z=r(()=>L??(L=!!document.createElement("div",{is:"marp-auto-scaling"}).outerHTML.startsWith(" <div is"),L),"l");let A;const a="marpitSVGPolyfill:setZoomFactor,",l=Symbol(),e=Symbol(),p=r(()=>{const c=navigator.vendor==="Apple Computer, Inc.",v=c?[H]:[],n={then:r(s=>(c?(async()=>{if(A===void 0){const t=document.createElement("canvas");t.width=10,t.height=10;const i=t.getContext("2d"),o=new Image(10,10),d=new Promise(m=>{o.addEventListener("load",()=>m())});o.crossOrigin="anonymous",o.src="data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2210%22%20height%3D%2210%22%20viewBox%3D%220%200%201%201%22%3E%3CforeignObject%20width%3D%221%22%20height%3D%221%22%20requiredExtensions%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxhtml%22%3E%3Cdiv%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxhtml%22%20style%3D%22width%3A%201px%3B%20height%3A%201px%3B%20background%3A%20red%3B%20position%3A%20relative%22%3E%3C%2Fdiv%3E%3C%2FforeignObject%3E%3C%2Fsvg%3E",await d,i.drawImage(o,0,0),A=i.getImageData(5,5,1,1).data[3] {s?.(t?[H]:[])}):s?.([]),n),"then")};return Object.assign(v,n)},"p");let y,g;function H(c){const v=typeof c=="object"&&c.target||document,n=typeof c=="object"?c.zoom:c;window[e]||(Object.defineProperty(window,e,{configurable:!0,value:!0}),document.body.style.zoom=1.0001,document.body.offsetHeight,document.body.style.zoom=1,window.addEventListener("message",({data:t,origin:i})=>{if(i===window.origin)try{if(t&&typeof t=="string"&&t.startsWith(a)){const[,o]=t.split(","),d=Number.parseFloat(o);Number.isNaN(d)||(g=d)}}catch(o){console.error(o)}}));let s=!1;Array.from(v.querySelectorAll("svg[data-marpit-svg]"),t=>{var i,o,d,m;t.style.transform||(t.style.transform="translateZ(0)");const x=n||g||t.currentScale||1;y!==x&&(y=x,s=x);const W=t.getBoundingClientRect(),{length:Z}=t.children;for(let N=0;N {t?.postMessage(`${a}${s}`,window.origin==="null"?"*":window.origin)})}r(H,"v");function B({once:c=!1,target:v=document}={}){const n=(function(s=document){if(s[l])return s[l];let t=!0;const i=r(()=>{t=!1,delete s[l]},"i");Object.defineProperty(s,l,{configurable:!0,value:i});let o=[],d=!1;(async()=>{try{o=await p()}finally{d=!0}})();const m=r(()=>{for(const x of o)x({target:s});d&&o.length===0||t&&window.requestAnimationFrame(m)},"r");return m(),i})(v);return c?(n(),()=>{}):n}r(B,"w"),y=1,g=void 0;const Y=B,R=Symbol(),V=r((c=document)=>{if(typeof window>"u")throw new Error("Marp Core's browser script is valid only in browser context.");if(((t=document)=>{const i=window[F];i||customElements.define("marp-auto-scaling",k);for(const o of Object.keys(b)){const d=`marp-${o}`,m=b[o].proto();z()&&m!==HTMLElement?i||customElements.define(d,T(m,{style:b[o].style}),{extends:o}):(i||customElements.define(d,T(HTMLElement,b[o])),t.querySelectorAll(`${o}[is="${d}"]`).forEach(x=>{x.outerHTML=x.outerHTML.replace(new RegExp(`^ $`,"i"),` `)}))}window[F]=!0})(c),c[R])return c[R];const v=B({target:c}),n=r(()=>{v(),delete c[R]},"n"),s=Object.assign(n,{cleanup:n,update:r(()=>V(c),"update")});return Object.defineProperty(c,R,{configurable:!0,value:s}),s},"f");E.browser=V,h=V,h=Y}},u={};function _(S){var E=u[S];if(E!==void 0)return E.exports;var h=u[S]={exports:{}};return f[S](h,h.exports,_),h.exports}r(_,"__webpack_require__");var Q={};(()=>{"use strict";var S=_(32);const E="marp_vscode_content_section",h="data-marp-vscode-content-start-line",b="data-marp-vscode-content-end-line";function q(a){a.core.ruler.push(E,l=>{if(l.inlineMode)return;let e=null,p=0,y=0;for(const g of l.tokens)g.map&&(p=g.map[0],y=Math.max(y,...g.map)),g.type==="marpit_slide_open"&&(e&&(e.map&&e.attrSet(h,e.map[0]),e.attrSet(b,p-1)),e=g);e&&(e.map&&e.attrSet(h,e.map[0]),e.attrSet(b,y))})}r(q,"marpVSCodeContentSection");const C="marp-vscode.overflowTracker",O=r(a=>typeof a=="object"&&a!=null&&"type"in a&&a.type===C&&"overflowElements"in a&&Array.isArray(a.overflowElements),"isOverflowTrackerEvent"),A=class A{constructor(l){this.postMessage=l,this.delay=150,this.update()}update(){window.setTimeout(()=>this.detectOverflowElements(),this.delay)}cleanup(){}detectOverflowElements(){const l=[];for(const e of document.querySelectorAll(`section[${h}][${b}]`)){const p=e.scrollWidth-e.clientWidth,y=e.scrollHeight-e.clientHeight;if(p {const p=document.getElementById("__marp-vscode");!!a!=!!p?(document.body.classList.toggle("marp-vscode",!!p),p?a={browser:(0,S.browser)(),overflowTracker:l?new k(l):void 0}:(a?.browser.cleanup(),a?.overflowTracker?.cleanup(),a=void 0)):(a?.browser.update(),a?.overflowTracker?.update()),a?(p&&L(p),F()):z()},"updateCallback");window.addEventListener("load",()=>window.setTimeout(e,100)),window.addEventListener("vscode.markdown.updateContent",e),e()}r(T,"preview");const L=r(a=>{a.querySelectorAll("[is]").forEach(l=>{if(l.nodeName.includes("-")||document.createElement(l.nodeName).constructor!==l.constructor)return;const{outerHTML:p}=l;l.outerHTML=p,console.debug("[marp-vscode] Custom element has been upgraded forcibly:",p.slice(0,p.indexOf(">")+1||void 0))})},"forceUpgradeCustomElements"),F=r(()=>{const a=document.querySelectorAll("style:not(#__marp-vscode-style,#_defaultStyles,[data-marp-vscode-body])"),l=document.querySelectorAll('link[rel="stylesheet"][href]:not([href*="marp-vscode"])');a.forEach(e=>{e.closest("#__marp-vscode")||(e.dataset.marpVscodeBody=e.textContent??"",e.textContent="")}),l.forEach(e=>{if(e.closest("#__marp-vscode"))return;const{href:p}=e;e.dataset.marpVscodeHref=p,e.removeAttribute("href")})},"removeStyles"),z=r(()=>{const a=document.querySelectorAll("style[data-marp-vscode-body]"),l=document.querySelectorAll("link[data-marp-vscode-href]");a.forEach(e=>{e.textContent=e.dataset.marpVscodeBody||"",delete e.dataset.marpVscodeBody}),l.forEach(e=>{e.href=e.dataset.marpVscodeHref||"",delete e.dataset.marpVscodeHref})},"restoreStyles");T()})()})();})(); ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 2人がこの投稿は役に立ったと言っています。 The post Foundry IQはどう動いている?構成要素とAgentic Retrievalの仕組みを解説 first appeared on SIOS Tech Lab .