1 line
7.3 KiB
JSON
1 line
7.3 KiB
JSON
{"repo": "baiyu858/phyagentos-rekep-real-plugin", "files": {"runtime/vlm_client.py": {"c532a28ddd69809bbc63ac0604d72866582e047f": {"t": 1785768916.0084245, "n": 6872, "h": "21ef7dad67d01f23", "c": "import base64\nimport os\nimport time\nimport requests\n\n\nDEFAULT_VLM_MODEL = \"gpt-5.4\"\n\n\ndef _read_env(name):\n value = os.environ.get(name)\n return value.strip() if isinstance(value, str) else \"\"\n\n\ndef _read_int_env(name, default=0, lower=0, upper=10):\n raw = _read_env(name)\n if not raw:\n return int(default)\n try:\n value = int(raw)\n except Exception:\n value = int(default)\n return max(int(lower), min(int(upper), value))\n\n\ndef _read_float_env(name, default=1.0, lower=0.0, upper=30.0):\n raw = _read_env(name)\n if not raw:\n return float(default)\n try:\n value = float(raw)\n except Exception:\n value = float(default)\n return max(float(lower), min(float(upper), value))\n\n\ndef resolve_vlm_config(default_model=None):\n api_key = _read_env(\"REKEP_VLM_API_KEY\")\n api_key_env = \"\"\n\n if not api_key:\n configured_env = _read_env(\"REKEP_VLM_API_KEY_ENV\")\n if configured_env:\n api_key_env = configured_env\n api_key = _read_env(configured_env)\n\n if not api_key:\n for fallback_env in (\"DMXAPI_API_KEY\", \"OPENAI_API_KEY\"):\n fallback_value = _read_env(fallback_env)\n if fallback_value:\n api_key_env = fallback_env\n api_key = fallback_value\n break\n\n return {\n \"api_key\": api_key,\n \"api_key_env\": api_key_env,\n \"base_url\": _read_env(\"REKEP_VLM_BASE_URL\"),\n \"model\": _read_env(\"REKEP_VLM_MODEL\") or default_model or DEFAULT_VLM_MODEL,\n }\n\n\ndef vlm_ready(default_model=None):\n return bool(resolve_vlm_config(default_model=default_model)[\"api_key\"])\n\n\ndef get_vlm_request_config(default_model=None):\n config = resolve_vlm_config(default_model=default_model)\n if not config[\"api_key\"]:\n source = config[\"api_key_env\"] or \"REKEP_VLM_API_KEY / REKEP_VLM_API_KEY_ENV / DMXAPI_API_KEY / OPENAI_API_KEY\"\n raise RuntimeError(f\"VLM API key is required ({source})\")\n return config\n\n\ndef encode_image_bytes(image_bytes):\n return base64.b64encode(image_bytes).decode(\"utf-8\")\n\n\ndef build_image_messages(question, image_bytes, system_prompt=None):\n contents = [\n {\n \"type\": \"text\",\n \"text\": question,\n },\n {\n \"type\": \"image_url\",\n \"image_url\": {\n \"url\": f\"data:image/png;base64,{encode_image_bytes(image_bytes)}\",\n },\n },\n ]\n messages = []\n if system_prompt:\n messages.append({\"role\": \"system\", \"content\": system_prompt})\n messages.append({\"role\": \"user\", \"content\": contents})\n return messages\n\n\ndef _resolve_chat_completions_url(config):\n base_url = config[\"base_url\"] or \"https://api.openai.com/v1\"\n return f\"{base_url.rstrip('/')}/chat/completions\"\n\n\ndef _extract_message_text(payload):\n choices = payload.get(\"choices\", [])\n if not choices:\n return \"\"\n message = choices[0].get(\"message\", {})\n content = message.get(\"content\", \"\")\n if isinstance(content, str):\n return content.strip()\n if isinstance(content, list):\n text_parts = []\n for item in content:\n if isinstance(item, dict) and item.get(\"type\") == \"text\":\n text_parts.append(str(item.get(\"text\", \"\")))\n return \"\\n\".join(part for part in text_parts if part).strip()\n return str(content).strip()\n\n\ndef request_chat_completion(\n *,\n messages,\n default_model=None,\n temperature=0.0,\n max_tokens=512,\n timeout=300,\n):\n config = get_vlm_request_config(default_model=default_model)\n max_retries = _read_int_env(\"REKEP_VLM_MAX_RETRIES\", default=2, lower=0, upper=10)\n retry_backoff_s = _read_float_env(\"REKEP_VLM_RETRY_BACKOFF_S\", default=1.0, lower=0.0, upper=60.0)\n retry_status_codes = {429, 500, 502, 503, 504}\n payload = {\n \"model\": config[\"model\"],\n \"messages\": messages,\n }\n if temperature is not None:\n payload[\"temperature\"] = float(temperature)\n if max_tokens is not None:\n payload[\"max_tokens\"] = int(max_tokens)\n\n url = _resolve_chat_completions_url(config)\n headers = {\n \"Authorization\": f\"Bearer {config['api_key']}\",\n \"Content-Type\": \"application/json\",\n }\n\n last_exc = None\n for attempt in range(max_retries + 1):\n try:\n response = requests.post(\n url,\n headers=headers,\n json=payload,\n timeout=timeout,\n )\n except requests.RequestException as exc:\n last_exc = exc\n if attempt < max_retries:\n sleep_s = retry_backoff_s * (2**attempt)\n if sleep_s > 0:\n time.sleep(sleep_s)\n continue\n raise RuntimeError(\n f\"VLM request failed after {max_retries + 1} attempts \"\n f\"(model={config['model']}, base_url={config['base_url'] or 'https://api.openai.com/v1'}): {exc}\"\n ) from exc\n\n status_code = int(response.status_code)\n if status_code in retry_status_codes and attempt < max_retries:\n sleep_s = retry_backoff_s * (2**attempt)\n if sleep_s > 0:\n time.sleep(sleep_s)\n continue\n\n if status_code >= 400:\n body = response.text.strip().replace(\"\\n\", \" \")\n if len(body) > 400:\n body = f\"{body[:400]}...(truncated)\"\n raise requests.HTTPError(\n f\"{status_code} error from VLM endpoint \"\n f\"(model={config['model']}, base_url={config['base_url'] or 'https://api.openai.com/v1'}): {body}\",\n response=response,\n )\n\n try:\n return response.json(), config\n except Exception as exc:\n body = response.text.strip().replace(\"\\n\", \" \")\n if len(body) > 400:\n body = f\"{body[:400]}...(truncated)\"\n raise RuntimeError(\n f\"VLM response JSON parse failed \"\n f\"(model={config['model']}, base_url={config['base_url'] or 'https://api.openai.com/v1'}): {body}\"\n ) from exc\n\n raise RuntimeError(\n f\"VLM request failed after {max_retries + 1} attempts \"\n f\"(model={config['model']}, base_url={config['base_url'] or 'https://api.openai.com/v1'})\"\n ) from last_exc\n\n\ndef ask_image_question(\n *,\n image_bytes,\n question,\n default_model=None,\n system_prompt=None,\n temperature=0.0,\n max_tokens=512,\n):\n messages = build_image_messages(question, image_bytes, system_prompt=system_prompt)\n response, config = request_chat_completion(\n messages=messages,\n default_model=default_model,\n temperature=temperature,\n max_tokens=max_tokens,\n )\n answer = _extract_message_text(response)\n return answer, config\n"}}}} |