prompter
prompter는 XML 프롬프트 계약을 기준으로 LLM 요청을 실행하는 모듈입니다. OpenAI, Claude, Gemini, Ollama, LM Studio 같은 제공자를 LLMSource로 등록하고, transact에서는 CommandType=P로 라우팅합니다.
책임 범위
- 프롬프트 계약을 읽어 provider, model, 메시지, body, tool 사용 범위를 구성합니다.
- 요청 파라미터를 프롬프트 본문과 헤더, 인증 정보, body 속성에 바인딩합니다.
- KernelPlugin, MCP 서버, CLI 도구, body 파일 경로 사용을 allowlist로 제한합니다.
- LLM 응답을 HandStack 거래 응답 형식으로 변환합니다.
- 요청/응답 로그와 채팅 이력 출력 여부 를 설정으로 제어합니다.
주요 API
| 메서드 | 경로 | 용도 |
|---|---|---|
GET | /prompter/api/query/has | 프롬프트 계약 존재 여부를 확인합니다. |
GET | /prompter/api/query/refresh | 프롬프트 계약 캐시를 갱신합니다. |
GET | /prompter/api/query/retrieve | 계약 원문 또는 상세 정보를 조회합니다. |
GET | /prompter/api/query/meta | 입력/출력 메타 정보를 반환합니다. |
GET | /prompter/api/query/reports | 계약 기반 보고용 정보를 조회합니다. |
POST | /prompter/api/query | 프롬프트 계약을 실행합니다. |
핵심 구현
Areas/prompter/Controllers/QueryController.cs: query API 진입점입니다.DataClient/PromptClient.cs: 프롬프트 계약 실행과 LLM provider 호출을 담당합니다.Extensions/PromptMapper.cs: XML 계약 로드와 refresh를 담당합니다.Entity/ModuleConfigJson.cs: LLMSource와 도구 allowlist 설정 스키마입니다.Events/PrompterRequestHandler.cs: 모듈 내부 MediatR 실행 요청(prompter.Events.PrompterRequest)을 처리합니다.
계약과 식별자
계약은 기본적으로 다음 위치에 둡니다.
contracts/prompter/{ApplicationID}/{ProjectID}/{TransactionID}.xml
프롬프트 ID는 다음 형식으로 조회됩니다.
ApplicationID|ProjectID|TransactionID|PromptID
프롬프트 본문은 ${ParameterName} 형태로 요청 값을 치환합니다. authorization, headers, body 속성처럼 값 전체를 파라미터로 받을 때는 @ParameterName 형식을 사용합니다. statement의 role 기본값은 user이며, 시스템 지시문으로 전달할 계약만 role="system"을 명시합니다. <message>의 role을 생략하면 statement role을 상속합니다.
이미지·오디오 입력
계약의 <media>는 QueryObject.Parameters에 담긴 Base64 원문을 텍스트와 분리하여 provider별 멀티모달 메시지로 전달합니다.
<statement id="GP02" seq="0" role="system">
<message role="user"><![CDATA[
${UserMessage}
]]></message>
<param id="@UserMessage" type="String" length="-1" value="" />
<media id="@Image1" type="Image" mimeType="image/png" required="N" />
<media id="@Audio1" type="Audio" mimeType="audio/wav" required="N" />
</statement>
id는<param>과 중복될 수 없으며 대소문자를 구분하지 않습니다.type은Image,Audio만 허용합니다.mimeType은image/png,image/jpeg,audio/wav,audio/mpeg같은 구체적인 값이어야 하며 wildcard는 허용하지 않습니다.required="Y"인 값이 없거나 값이 유효한 Base64가 아니면 provider 호출 전에 실패합니다.required="N"의 빈 값은 생략합니다.- 파라미터 값에는 URL이나
data:...;base64,접두사를 넣지 않고 Base64 데이터만 넣습니다. - media 파라미터는 프롬프트, authorization, headers, body, pretransaction 텍스트 치환에서 제외되고 현재 statement가 만든 마지막
user메시지에 첨부됩니다.user메시지가 없으면 media 전용 메시지를 만듭니다.
| LLMProvider | Image | Audio | provider 요청 형식 |
|---|---|---|---|
| OpenAI | 지원 | 지원 (audio/wav, audio/mpeg) | Chat Completions content parts |
| Claude | 지원 | 미지원 | Messages base64 image source |
| Gemini | 지원 | 지원 | GenerateContent inlineData |
| Ollama | 지원 | 미지원 | Chat message images |
| LM Studio | 지원 | 미지원 | OpenAI 호환 image_url content part |
provider뿐 아니라 선택한 모델도 해당 modality를 지원해야 합니다. 미지원 media는 네트워크 요청 전에 오류로 처리됩 니다. prompter 내부 로그는 media 값을 [media:유형;base64-length:길이]로 마스킹하지만, 앞단 transact와 프록시 로그에도 원문을 저장하지 않도록 별도 정책을 적용해야 합니다.
실행 가능한 예시는 CLS010.xml과 request.json에서 확인할 수 있습니다. 계약을 contracts/prompter/HDS/LLM/CLS010.xml에 배치하고 LLM1을 OpenAI 또는 Gemini의 멀티모달 모델로 설정한 뒤 request.json을 /prompter/api/query에 POST합니다.
주요 설정
| 설정 | 설명 |
|---|---|
LLMSource | provider, model, endpoint, API Key, stream/think 옵션을 정의합니다. |
AllowedKernelPlugins | 계약에서 호출 가능한 커널 플러그인과 함수 allowlist입니다. |
AllowedMcpServers | 계약에서 사용할 수 있는 MCP 서버 allowlist입니다. |
AllowedCliTools | 프롬프트 실행 중 사용할 수 있는 CLI 도구 allowlist입니다. |
AllowedBodyFileBasePaths | body 파일을 읽을 수 있는 기준 경로입니다. |
IsChatHistoryConsoleShow | 채팅 이력을 콘솔에 출력할지 결정합니다. |
EventAction | 기본값은 prompter.Events.ManagedRequest입니다. |
LLMSource 예시는 다음과 같습니다.
{
"ApplicationID": "HDS",
"ProjectID": "*",
"DataSourceID": "LLM1",
"LLMProvider": "OpenAI",
"ApiKey": "<api-key>",
"ModelID": "gpt-5.4-mini",
"Endpoint": "",
"Think": false,
"Stream": false
}
ack 또는 rdy의 AppSettings:IsConfigurationWatching이 true이면 실행 중 실제로 로드된 module.json의 LLMSource 변경을 감지해 PromptMapper.DataSourceMappings를 다시 구성합니다. 새 목록 전체가 유효할 때만 기존 캐시를 교체하며, provider 변환이나 API Key 복호화 오류가 있으면 이전 설정과 캐시를 유지합니다. 교체 완료 후 시작하는 요청부터 새 LLM 설정을 사용하고 빈 배열은 캐시 삭제로 반영됩니다.
LLMProvider 별로 사용 가능한 ModelID 목록
-
OpenAI: gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna 공식 모델 목록 (https://developers.openai.com/api/docs/models)
-
Claude: claude-fable-5, claude-opus-5, claude-sonnet-5, claude-haiku-4-5-20251001 공식 모델 목록 (https://platform.claude.com/docs/en/models/overview)
-
Gemma4: gemma-4-26b-a4b-it, gemma-4-31b-it
-
Gemini: gemini-3.7-flash, gemini-3.6-flash, gemini-3.5-flash, gemini-3.5-flash-lite, gemini-3.1-flash-lite, gemini-3.1-pro-preview, gemini-3-flash-preview, gemini-2.5-pro, gemini-2.5-flash, gemini-2.5-flash-lite 공식 모델 목록 (https://ai.google.dev/gemini-api/docs/models)
-
Ollama: 설치한 모델의 정확한 태그를 써야 합니다. 예: gemma-4-e2b, gemma-4-e4b, gemma-4-12b, gemma-4-26b, gemma-4-31b, gpt-oss:20b, qwen3.8:27b 실제 사용 가능 목록은 ollama list 결과를 사용합니다.
-
LM Studio: 설치·로드한 모델의 서버 반환 ID를 그대로 써야 합니다. 예: openai/gpt-oss-20b, qwen/qwen3-4b-2507 정확한 목록은 http://localhost:1234/v1/models의 id 값으로 확인합니다. LM Studio 모델 목록 API (https://beta.lmstudio.ai/docs/developer/openai-compat/models)
transact 연동
transact 거래 계약에서는 CommandType을 P로 둡니다.
{
"ServiceID": "GPT010",
"CommandType": "P",
"ReturnType": "Json"
}
라우팅 예시는 다음과 같습니다.
{
"HDS|*|P|D": "http://localhost:8421/prompter/api/query",
"HDS|*|P|P": "http://localhost:8421/prompter/api/query",
"HDS|*|P|T": "http://localhost:8421/prompter/api/query"
}
운영 주의사항
- API Key, endpoint, 로컬 모델 경로는 실제 값으로 계약이나 공개 문서에 남기지 않습니다.
- Ollama의 일부 qwen3 계열 모델은 비어 있지 않은
userrole이 없는 요청을 거부합니다. prompter는<message>가 없는 기존 system-only 계약을 Ollama로 호출할 때 마지막 유효한system메시지를user로 정규화하며, 메시지 본문은 복제하지 않습니다. - 도구 호출은 계약 선언과
module.jsonallowlist가 모두 일치할 때만 허용되도록 관리합니다. - MCP, CLI, 파일 body 기능은 외부 시스템이나 파일 시스템에 접근할 수 있으므로 운영 환경에서는 최소 권한 원칙을 적용합니다.
- LLM 응답은 비결정적일 수 있으므로 업무 거래에서는 후속 검증, 스키마 검사, 실패 시 fallback 전략을 함께 설계합니다.