Please provide the bounding box coordinate of the region this sentence describes: ミャクミャク.
「ミャクミャク」は認識していませんが、「大きな青い『ミスター・ポテトヘッド』風の彫刻」を囲む矩形の座標が返されています。視覚的な情報から、このアート作品は、大きな青い胴体を持ち、一般的に「ミスター・ポテトヘッド」として知られるキャラクターに似ていることで明確に識別できます。青い体と赤い風船が組み合わさっており、視覚的に非常に印象的で記憶に残るデザインとなっています。さらに、足元、もしくは階段のように見える部分も青く縁取られており、彫刻全体のデザインをより際立たせています。
このタスクでは、画像内でこのアート作品を特定し、その位置を見つけ出す必要があります。作品は黒と赤のプラットフォーム上に目立って配置されており、地面とのコントラストがはっきりしているため、その存在を明確に認識できます。彫刻自体はかなり高い台座の上に置かれており、独特で目を引くデザインによって、見る者の視線を上方へと導きます。
鮮やかな色使い、精緻なデザイン、台座上の配置といった視覚的手がかりを踏まえると、このアート作品は大きな青い「ミスター・ポテトヘッド」風のフィギュアを中心に構成されていることは明らかです。この分析は、指定された座標情報とも一致しており、この彫刻がシーンの中心的存在であることを裏付けています。したがって、視覚的観察と空間的推論に基づいた結論として、このアート作品は「大きな青い『ミスター・ポテトヘッド』風の彫刻」であると断定できます。
実際には、temperatureとして0.7を指定しているせいか、または、FastAPIでSimpleInferenceを呼び出しているせいか、レスポンスが安定しません。レスポンスに含まれる推論や座標が途中で切れたり、推論中に例外が発生したりします。そのため実用性はまったくありませんが、「こんなレスポンスを返すLLMがある」ということを知ることはできます。
% uv init -p 3.10 RoboBrain
Initialized project `robobrain` at `/Users/ynakakoshi/Documents/RoboBrain`
% cd RoboBrain
RoboBrain %
inputs = inputs.to("cuda")
本記事はmacOSで作業をしているためNVIDIA GPUの指定であるcudaを、以下のようにApple Siliconの指定であるmpsに変更しています。
inputs = inputs.to("mps")
cudaの代わりにcpuを指定することにより、NVIDIA GPUおよびApple Silicon以外でも動作するかもしれませんが、確認はできていません(Apple SiliconのMacでcpuを指定すると、mpsを使ってください、というエラーが発生するため)。
import os | |
import base64 | |
import tempfile | |
from typing import Optional, Union, List | |
from contextlib import asynccontextmanager | |
from fastapi import FastAPI, HTTPException | |
from pydantic import BaseModel, Field | |
import uvicorn | |
# inference.pyからSimpleInferenceクラスをインポート | |
from inference import SimpleInference | |
# グローバルにモデルインスタンスを保持 | |
model_instance = None | |
@asynccontextmanager | |
async def lifespan(app: FastAPI): | |
# スタートアップ | |
global model_instance | |
print("Initializing model...") | |
try: | |
model_instance = SimpleInference("BAAI/RoboBrain2.0-7B") | |
print("Model initialized successfully") | |
except Exception as e: | |
print(f"Failed to initialize model: {str(e)}") | |
raise e | |
yield | |
# シャットダウン(必要に応じて) | |
# モデルのクリーンアップなどをここに記述 | |
# FastAPIアプリケーションの初期化 | |
app = FastAPI( | |
title="SimpleInference API", | |
version="1.0.0", | |
lifespan=lifespan | |
) | |
class InferenceRequest(BaseModel): | |
text: str | |
image: Union[str, List[str]] = Field(..., description="base64エンコードされた画像文字列") | |
task: str = "general" | |
enable_thinking: bool = True | |
do_sample: bool = True | |
temperature: float = 0.7 | |
class InferenceResponse(BaseModel): | |
thinking: str | |
answer: str | |
def base64_to_temp_file(base64_string: str, suffix: str = ".jpg") -> str: | |
""" | |
base64文字列を一時ファイルに変換 | |
Args: | |
base64_string: base64エンコードされた画像文字列 | |
suffix: ファイル拡張子 | |
Returns: | |
一時ファイルのパス | |
""" | |
try: | |
# base64文字列にdata:image/jpeg;base64,のようなプレフィックスがある場合は削除 | |
if "," in base64_string: | |
base64_string = base64_string.split(",")[1] | |
# base64文字列の長さをチェック(デバッグ用) | |
print(f"Processing base64 string of length: {len(base64_string)}") | |
# base64をデコード | |
try: | |
image_data = base64.b64decode(base64_string) | |
print(f"Decoded image data size: {len(image_data)} bytes") | |
except Exception as decode_error: | |
raise HTTPException( | |
status_code=400, | |
detail=f"Invalid base64 encoding: {str(decode_error)}" | |
) | |
# 一時ファイルを作成 | |
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=suffix) | |
temp_file.write(image_data) | |
temp_file.close() | |
return temp_file.name | |
except HTTPException: | |
raise | |
except Exception as e: | |
raise HTTPException(status_code=400, detail=f"Failed to process image data: {str(e)}") | |
@app.get("/") | |
async def root(): | |
""" | |
ヘルスチェック用エンドポイント | |
""" | |
return {"message": "SimpleInference API is running", "status": "healthy"} | |
@app.post("/inference", response_model=InferenceResponse) | |
async def inference_endpoint(request: InferenceRequest): | |
""" | |
推論実行エンドポイント | |
Args: | |
request: 推論リクエスト | |
Returns: | |
推論結果 | |
""" | |
if model_instance is None: | |
raise HTTPException(status_code=503, detail="Model not initialized") | |
temp_files = [] | |
try: | |
# リクエストサイズのログ出力 | |
if isinstance(request.image, str): | |
print(f"Received single image, base64 length: {len(request.image)}") | |
image_paths = [base64_to_temp_file(request.image)] | |
else: | |
total_length = sum(len(img) for img in request.image) | |
print(f"Received {len(request.image)} images, total base64 length: {total_length}") | |
image_paths = [base64_to_temp_file(img) for img in request.image] | |
temp_files.extend(image_paths) | |
# 推論実行 | |
result = model_instance.inference( | |
text=request.text, | |
image=image_paths, | |
task=request.task, | |
plot=False, # 常にFalseに設定 | |
enable_thinking=request.enable_thinking, | |
do_sample=request.do_sample, | |
temperature=request.temperature | |
) | |
return InferenceResponse( | |
thinking=result["thinking"], | |
answer=result["answer"] | |
) | |
except HTTPException: | |
raise | |
except Exception as e: | |
raise HTTPException(status_code=500, detail=f"Inference failed: {str(e)}") | |
finally: | |
# 一時ファイルをクリーンアップ | |
for temp_file in temp_files: | |
try: | |
if os.path.exists(temp_file): | |
os.unlink(temp_file) | |
except Exception as e: | |
print(f"Failed to delete temp file {temp_file}: {str(e)}") | |
if __name__ == "__main__": | |
import uvicorn | |
# サーバー起動(大きなリクエストに対応する設定) | |
uvicorn.run( | |
app, | |
host="0.0.0.0", | |
port=8000, | |
reload=False, # プロダクション環境ではFalseに設定 | |
# 大きなリクエストボディを許可する設定 | |
limit_max_requests=1000, | |
limit_concurrency=100, | |
# HTTPリクエストのサイズ制限を大幅に増加 | |
h11_max_incomplete_event_size=100 * 1024 * 1024, # 100MB | |
) |
[project] | |
name = "robobrain" | |
version = "0.1.0" | |
description = "Add your description here" | |
readme = "README.md" | |
requires-python = ">=3.10" | |
dependencies = [ | |
"accelerate==1.7.0", | |
"annotated-types==0.7.0", | |
"anyio==4.9.0", | |
"av==14.4.0", | |
"certifi==2025.6.15", | |
"charset-normalizer==3.4.2", | |
"click==8.2.1", | |
"exceptiongroup==1.3.0", | |
"fastapi==0.115.12", | |
"filelock==3.18.0", | |
"fsspec==2025.5.1", | |
"h11==0.16.0", | |
"hf-xet==1.1.4", | |
"huggingface-hub==0.33.0", | |
"idna==3.10", | |
"jinja2==3.1.6", | |
"markupsafe==3.0.2", | |
"mpmath==1.3.0", | |
"networkx==3.4.2", | |
"numpy==2.2.6", | |
"opencv-python==4.11.0.86", | |
"packaging==25.0", | |
"pillow==11.2.1", | |
"psutil==7.0.0", | |
"pydantic==2.11.7", | |
"pydantic-core==2.33.2", | |
"pyyaml==6.0.2", | |
"qwen-vl-utils==0.0.11", | |
"regex==2024.11.6", | |
"requests==2.32.4", | |
"safetensors==0.5.3", | |
"setuptools==80.9.0", | |
"sniffio==1.3.1", | |
"starlette==0.46.2", | |
"sympy==1.14.0", | |
"tokenizers==0.21.1", | |
"torch==2.7.1", | |
"torchvision==0.22.1", | |
"tqdm==4.67.1", | |
"transformers==4.52.4", | |
"typing-extensions==4.14.0", | |
"typing-inspection==0.4.1", | |
"urllib3==2.4.0", | |
"uvicorn==0.34.3", | |
] |
RoboBrain % uv sync
Using CPython 3.10.15 interpreter at: /opt/homebrew/opt/python@3.10/bin/python3.10
Creating virtual environment at: .venv
Resolved 61 packages in 162ms
Installed 44 packages in 372ms
+ accelerate==1.7.0
+ annotated-types==0.7.0
+ anyio==4.9.0
+ av==14.4.0
+ certifi==2025.6.15
+ charset-normalizer==3.4.2
+ click==8.2.1
+ exceptiongroup==1.3.0
+ fastapi==0.115.12
+ filelock==3.18.0
+ fsspec==2025.5.1
+ h11==0.16.0
+ hf-xet==1.1.4
+ huggingface-hub==0.33.0
+ idna==3.10
+ jinja2==3.1.6
+ markupsafe==3.0.2
+ mpmath==1.3.0
+ networkx==3.4.2
+ numpy==2.2.6
+ opencv-python==4.11.0.86
+ packaging==25.0
+ pillow==11.2.1
+ psutil==7.0.0
+ pydantic==2.11.7
+ pydantic-core==2.33.2
+ pyyaml==6.0.2
+ qwen-vl-utils==0.0.11
+ regex==2024.11.6
+ requests==2.32.4
+ safetensors==0.5.3
+ setuptools==80.9.0
+ sniffio==1.3.1
+ starlette==0.46.2
+ sympy==1.14.0
+ tokenizers==0.21.1
+ torch==2.7.1
+ torchvision==0.22.1
+ tqdm==4.67.1
+ transformers==4.52.4
+ typing-extensions==4.14.0
+ typing-inspection==0.4.1
+ urllib3==2.4.0
+ uvicorn==0.34.3
RoboBrain %
RoboBrain % uv run main.py
INFO: Started server process [29096]
INFO: Waiting for application startup.
Initializing model...
Loading Checkpoint ...
Loading checkpoint shards: 100%|█████████████████████| 4/4 [00:07<00:00, 1.96s/it]
Using a slow image processor as `use_fast` is unset and a slow processor was saved with this model. `use_fast=True` will be the default behavior in v4.52, even if the model was saved with a slow processor. This will result in minor differences in outputs. You'll still be able to use a slow processor with `use_fast=False`.
You have video processor config saved in `preprocessor.json` file which is deprecated. Video processor configs should be saved in their own `video_preprocessor.json` file. You can rename the file or load and save the processor back which renames it automatically. Loading from `preprocessor.json` will be removed in v5.0.
Model initialized successfully
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
Uvicornがhttp://0.0.0.0:8000で実行されているとメッセージが表示されれば、RoboBrain2.0を呼び出せる状態です。
declare | |
l_blob blob; | |
l_mime_type varchar2(4000); | |
begin | |
select blob_content, mime_type into l_blob, l_mime_type | |
from apex_application_temp_files where name = :P1_IMAGE_FILE; | |
/* | |
* アップロードしたイメージはつねにN001=1で取得できる。 | |
* これがページ・アイテムP1_IMAGEのソースとなる。 | |
*/ | |
apex_collection.create_or_truncate_collection('IMAGE'); | |
apex_collection.add_member( | |
p_collection_name => 'IMAGE' | |
,p_n001 => 1 | |
,p_c001 => l_mime_type | |
,p_blob001 => l_blob | |
); | |
end; |
def inference(self, text:str, image: Union[list,str], task="general", plot=False, enable_thinking=True, do_sample=True, temperature=0.7):
declare | |
/* Local LM Studio */ | |
C_ENDPOINT constant varchar2(4000) := 'http://host.containers.internal:8000/inference'; | |
/* | |
* /inferenceは以下のリクエストを受け付ける。 | |
{ | |
"text": "What is shown in this image?", | |
"image": image_base64, | |
"task": "general", | |
"plot": False, | |
"enable_thinking": True, | |
"do_sample": True, | |
"temperature": 0.7 | |
} | |
*/ | |
/* 送信するメッセージ */ | |
l_request json_object_t; | |
l_request_clob clob; | |
l_mime_type apex_application_temp_files.mime_type%type; | |
l_blob_content apex_application_temp_files.blob_content%type; | |
l_text clob; | |
l_image clob; | |
/* 受信するメッセージ */ | |
l_response clob; | |
l_response_json json_object_t; | |
e_call_api_failed exception; | |
l_choices json_array_t; | |
l_choice json_object_t; | |
l_response_message clob; | |
begin | |
/* 写真の取り出し */ | |
select blob001, c001 into l_blob_content, l_mime_type | |
from apex_collections where collection_name = 'IMAGE' and n001 = 1; | |
/* リクエストの準備 */ | |
l_request := json_object_t(); | |
l_text := :P1_PROMPT; | |
l_image := 'data:' || l_mime_type || ';base64,' | |
|| apex_web_service.blob2clobbase64(l_blob_content, 'N', 'N'); | |
l_request.put('text', l_text); | |
l_request.put('image', l_image); | |
l_request.put('task', :P1_TASK); | |
l_request.put('plot', false); | |
l_request.put('enable_thinking', (:P1_ENABLE_THINKING = 'Y')); | |
l_request.put('do_sample', true); | |
l_request.put('temperature', 0.7); | |
l_request_clob := l_request.to_clob(); | |
/* | |
* FastAPIサーバーの呼び出し。 | |
*/ | |
apex_web_service.clear_request_headers(); | |
apex_web_service.set_request_headers('Content-Type', 'application/json'); | |
l_response := apex_web_service.make_rest_request( | |
p_url => C_ENDPOINT | |
,p_http_method => 'POST' | |
,p_body => l_request_clob | |
); | |
/* process response */ | |
if not apex_web_service.g_status_code between 200 and 300 then | |
raise e_call_api_failed; | |
end if; | |
:P1_RESPONSE := l_response; | |
end; |
意図した結果ではないかもしれませんが、thinkingに推論の過程が説明されているため、なぜそうなったのかを確認できます。
/* | |
* 応答のJSONからanswer属性として返されている座標を取り出す。 | |
*/ | |
const response = JSON.parse(apex.item("P1_RESPONSE").getValue()); | |
let positions = response.answer; | |
if (! positions ) { | |
console.log('No position found.'); | |
return; | |
}; | |
apex.debug.info(positions); | |
/* | |
* 画像が表示されているIMG要素を取り出す。 | |
*/ | |
const img = document.getElementById("P1_IMAGE"); | |
/* | |
* 選択されているタスクを取り出す。 | |
*/ | |
const task = apex.item("P1_TASK").getValue(); | |
render(positions, img, task); |
ファンクションrenderは静的アプリケーション・ファイルとして作成したapp.jsに記述しています。Taskがpointingであれば点、trajectoryであれば折れ線、affordanceまたはgroundingであれば矩形を表示します。それぞれの図形は、画像にDIV要素を重ね合わせて表示しています。
function render(positions, img, task) { | |
/* | |
* IMG要素の親要素に当たるDIV要素を取り出す。 | |
* | |
* これは選択しているアイテム・テンプレートより <div class="t-Form-itemWrapper"></div>になることを | |
* 想定している。 | |
* APEXが生成するHTMLの内部構造を直接操作しているため、推奨される方法ではない。 | |
*/ | |
const parentDiv = img.parentElement; | |
parentDiv.style.position = 'relative'; | |
/* | |
* IMG要素の表示されている画像の元々のサイズを取り出す。 | |
*/ | |
const naturalWidth = img.naturalWidth; | |
const naturalHeight = img.naturalHeight; | |
apex.debug.info(`actual image size: naturalWidth: ${naturalWidth}, naturalHeight: ${naturalHeight}`); | |
/* | |
* taskに応じた描画を行う。 | |
* | |
* pointingは点を描画。 | |
* trajectoryは折れ線。 | |
* grounding, affordanceは矩形。 | |
*/ | |
if ( task === "pointing" ) { | |
/* | |
* (x, y)の配列を取り出す。 | |
*/ | |
const jsonStr = positions.replace(/\(/g, '[').replace(/\)/g, ']'); | |
const points = JSON.parse(jsonStr); | |
// 点の描画を繰り返す。 | |
for (let i = 0; i < points.length; i++ ) { | |
const [x, y] = points[i]; | |
const percentX = 100 * ( x / naturalWidth ); | |
const percentY = 100 * ( y / naturalHeight ); | |
const pointElement = document.createElement("div"); | |
pointElement.className = "point"; | |
apex.debug.info(`point plotted on x: ${percentX}%, y: ${percentY}%`); | |
Object.assign(pointElement.style, { | |
left: percentX + '%', | |
top: percentY + '%' | |
}); | |
parentDiv.appendChild(pointElement); | |
} | |
} | |
if ( task === "trajectory" ) { | |
/* | |
* (x, y)の配列を取り出す。 | |
*/ | |
const jsonStr = positions.replace(/\(/g, '[').replace(/\)/g, ']'); | |
const points = JSON.parse(jsonStr); | |
// 連続する点の間に線を描画する。 | |
for (let i = 0; i < points.length - 1; i++) { | |
const [x0, y0] = points[i]; | |
const [x1, y1] = points[i + 1]; | |
// 画像のサイズに対する百分率で座標を計算 | |
const percentX0 = 100 * ( x0 / naturalWidth ); | |
const percentY0 = 100 * ( y0 / naturalHeight ); | |
const deltaX = (x1 - x0); | |
const deltaY = (y1 - y0); | |
// 線の長さを計算 | |
const lengthPx = Math.sqrt(deltaX * deltaX + deltaY * deltaY); | |
const length = 100 * (lengthPx / naturalWidth); | |
// 線の角度を計算(ラジアンから度に変換) | |
const angle = Math.atan2( deltaY, deltaX ) * 180 / Math.PI; | |
// DIV要素で線を作成 | |
const lineElement = document.createElement("div"); | |
lineElement.className = "line-div"; | |
apex.debug.info(`line plotted on left: ${percentX0}%, top: ${percentY0}%, width: ${length}%, transform: rotate${angle}deg`); | |
Object.assign(lineElement.style, { | |
left: percentX0 + '%', | |
top: percentY0 + '%', | |
width: length + '%', | |
transform: `rotate(${angle}deg)`, // 角度を適用 | |
}); | |
parentDiv.appendChild(lineElement); | |
} | |
} | |
if ( task === "affordance" || task === "grounding" ) { | |
/* | |
* [x0, y0, x1, y1]の形式の矩形を取り出す。 | |
*/ | |
const coords = JSON.parse(positions); | |
apex.debug.info(`bounding box in px left: ${coords[0]}px, top: ${coords[1]}px, right: ${coords[2]}px, bottom: ${coords[3]}px`); | |
const percentLeft = 100 * (coords[0] / naturalWidth); | |
const percentTop = 100 * (coords[1] / naturalHeight); | |
const percentWidth = 100 * ((coords[2] - coords[0]) / naturalWidth); | |
const percentHeight = 100 * ((coords[3] - coords[1]) / naturalHeight); | |
// 描画する矩形を定義する。 | |
const boxElement = document.createElement("div"); | |
boxElement.className = "bounding-box"; | |
apex.debug.info(`bounding box plotted on left: ${percentLeft}%, top: ${percentTop}%, width: ${percentWidth}%, height: ${percentHeight}%` ); | |
Object.assign(boxElement.style, { | |
left: percentLeft + '%', | |
top: percentTop + '%', | |
width: percentWidth + '%', | |
height: percentHeight + '%', | |
}); | |
parentDiv.appendChild(boxElement); | |
} | |
} |
APEXアプリケーションの実装についての説明は以上です。
declare | |
/* Local LM Studio */ | |
C_ENDPOINT constant varchar2(4000) := 'http://host.containers.internal:8080/v1/chat/completions'; | |
/* LLM model name */ | |
C_MODEL constant varchar2(200) := 'baai_robobrain2.0-7b@q6_k_l'; | |
/* 写真に関する変数 */ | |
l_image json_object_t; | |
l_image_url json_object_t; | |
l_mime_type apex_application_temp_files.mime_type%type; | |
l_blob_content apex_application_temp_files.blob_content%type; | |
/* 送信するメッセージ */ | |
l_prompt json_object_t; | |
l_text clob; | |
l_content json_array_t; | |
l_message json_object_t; | |
l_messages json_array_t; | |
l_request json_object_t; | |
l_request_clob clob; | |
/* 受信するメッセージ */ | |
l_response clob; | |
l_response_json json_object_t; | |
e_call_api_failed exception; | |
l_choices json_array_t; | |
l_choice json_object_t; | |
l_response_message clob; | |
/* PROMPT */ | |
PROMPT_POINTING constant varchar2(4000) := q'~ | |
%s. | |
Your answer should be formatted as a list of tuples, i.e. [(x1, y1), (x2, y2), ...], | |
where each tuple contains the x and y coordinates of a point satisfying the conditions above. | |
The coordinates should indicate the normalized pixel locations of the points in the image. | |
The response should be in JSON format as follows: | |
```json | |
{ | |
"thinking": "the reasoning behind your answer.", | |
"answer": "a list of tuples, i.e. [(x1, y1), (x2, y2), ...], where each tuple contains the x and y coordinates of a point." | |
} | |
``` | |
~'; | |
PROMPT_AFFORDANCE constant varchar2(4000) := q'~ | |
You are a robot using the joint control. The task is "%s". | |
Please predict a possible affordance area of the end effector. | |
The response should be in JSON format as follows: | |
```json | |
{ | |
"thinking": "the reasoning behind your answer.", | |
"answer": "i.e. [x0, y0, x1, y1], where x0 is left, y0 is top, x1 is bottom, y1 is right coordinates of a bounding box." | |
} | |
``` | |
~'; | |
PROMPT_TRAJECTORY constant varchar2(4000) := q'~ | |
You are a robot using the joint control. The task is "%s". | |
Please predict up to 10 key trajectory points to complete the task. | |
The response should be in JSON format as follows: | |
```json | |
{ | |
"thinking": "the reasoning behind your answer.", | |
"answer": "a list of tuples, i.e. [(x1, y1), (x2, y2), ...], where each tuple contains the x and y coordinates of a point." | |
} | |
``` | |
~'; | |
PROMPT_GROUNDING constant varchar2(4000) := q'~ | |
Please provide the bounding box coordinate of the region this sentence describes: %s. | |
The response should be in JSON format as follows: | |
```json | |
{ | |
"thinking": "the reasoning behind your answer.", | |
"answer": "i.e. [x0, y0, x1, y1], where x0 is left, y0 is top, x1 is bottom, y1 is right coordinates of a bounding box." | |
} | |
``` | |
~'; | |
begin | |
/* 写真の取り出し */ | |
select blob001, c001 into l_blob_content, l_mime_type | |
from apex_collections where collection_name = 'IMAGE' and n001 = 1; | |
/* イメージの準備 - base64で送信する */ | |
l_image_url := json_object_t(); | |
/* | |
* LM Studioではimage_urlオブジェクトに直接base64の文字列を入れるのは不可で、 | |
* urlオブジェクトにbase64の文字列を入れて、image_urlに与える必要がある。 | |
*/ | |
apex_debug.info('mime_type = %s', l_mime_type); | |
l_image_url.put('url', 'data:' || l_mime_type || ';base64,' | |
|| apex_web_service.blob2clobbase64(l_blob_content, 'N', 'N') | |
); | |
l_image := json_object_t(); | |
l_image.put('type', 'image_url'); | |
l_image.put('image_url', l_image_url); | |
/* プロンプトの準備 */ | |
l_prompt := json_object_t(); | |
l_prompt.put('type', 'text'); | |
/* タスクの選択に従って、プロントプトを拡張する。 */ | |
if :P2_TASK = 'pointing' then | |
l_text := apex_string.format(PROMPT_POINTING, :P2_PROMPT); | |
elsif :P2_TASK = 'affordance' then | |
l_text := apex_string.format(PROMPT_AFFORDANCE, :P2_PROMPT); | |
elsif :P2_TASK = 'trajectory' then | |
l_text := apex_string.format(PROMPT_TRAJECTORY, :P2_PROMPT); | |
elsif :P2_TASK = 'grounding' then | |
l_text := apex_string.format(PROMPT_GROUNDING, :P2_PROMPT); | |
else | |
apex_debug.info('General Task.'); | |
l_text := :P2_PROMPT; | |
end if; | |
/* | |
* Reasoningを有効にする。 | |
*/ | |
if :P2_ENABLE_THINKING = 'Y' then | |
l_text := '# Reasoning ' || CHR(10) || l_text; | |
end if; | |
apex_debug.info('Prompt: %s', l_text); | |
l_prompt.put('text', l_text); | |
/* contentの作成 */ | |
l_content := json_array_t(); | |
l_content.append(l_prompt); | |
l_content.append(l_image); | |
/* messageの作成 - systemプロンプトは無し, userの単発のみ */ | |
l_message := json_object_t(); | |
l_message.put('role', 'user'); | |
l_message.put('content', l_content); | |
/* messagesの作成 */ | |
l_messages := json_array_t(); | |
l_messages.append(l_message); | |
/* requestの作成 */ | |
l_request := json_object_t(); | |
l_request.put('model', C_MODEL); | |
l_request.put('messages', l_messages); | |
-- l_request.put('max_tokens', p_max_tokens); | |
/* call OpenAI chat completions api */ | |
l_request_clob := l_request.to_clob(); | |
apex_web_service.clear_request_headers(); | |
apex_web_service.set_request_headers('Content-Type', 'application/json', p_reset => false); | |
l_response := apex_web_service.make_rest_request( | |
p_url => C_ENDPOINT | |
,p_http_method => 'POST' | |
,p_body => l_request_clob | |
); | |
apex_debug.info('Response: %s', l_response); | |
/* process response */ | |
if not apex_web_service.g_status_code between 200 and 300 then | |
raise e_call_api_failed; | |
end if; | |
l_response_json := json_object_t(l_response); | |
l_choices := l_response_json.get_array('choices'); | |
l_response_message := ''; | |
for i in 1..l_choices.get_size() | |
loop | |
l_choice := treat(l_choices.get(i-1) as json_object_t); | |
l_message := l_choice.get_object('message'); | |
l_response_message := l_response_message || l_message.get_clob('content'); | |
end loop; | |
:P2_RESPONSE := l_response_message; | |
end; |
視覚入力には、2人の人物が互いに近くに座っている様子が映し出されています。1人は赤い帽子をかぶっており、バナナを食べているもう1人の人物と何らかの活動に関わっているように見えます。赤い帽子はその鮮やかな色のために目立ち、衣服の一部として容易に識別できます。このタスクでは、その人物が身につけている特定のアイテムを識別することが求められています。シーンを観察すると、私は赤い帽子をかぶった人物に注目し、視野内ではっきりと帽子をかぶっていることを確認します。この分析により、赤い帽子が識別対象の物体であると結論づけられます。