Portkey provides a robust and secure gateway to facilitate the integration of various Large Language Models (LLMs), and embedding models into your apps, including Google Vertex AI.With Portkey, you can take advantage of features like fast AI gateway access, observability, prompt management, and more, all while ensuring the secure management of your Vertex auth through Model Catalog.
from portkey_ai import Portkey# 1. Install: pip install portkey-ai# 2. Add @vertex-ai provider in Model Catalog with Service Account JSON# 3. Use it:portkey = Portkey(api_key="PORTKEY_API_KEY")response = portkey.chat.completions.create( model="@vertex-ai/gemini-3-pro-preview", messages=[{"role": "user", "content": "Say this is a test"}])print(response.choices[0].message.content)
Authentication Note: When you configure Vertex AI in Model Catalog with Service Account JSON (recommended), authentication is handled automatically. If you only configure with Project ID and Region, you’ll need to pass an OAuth2 access token with each request using the Authorization header. See the Making Requests Without Model Catalog section for details.
Find and select Google Vertex AI from the provider list.
3
Configure Authentication
You’ll need your Vertex Project ID and Vertex Region. You can authenticate using either:Option 1: Service Account JSON (Recommended for self-deployed models)
Upload your Google Cloud service account JSON file
Specify the Vertex Region
Required for custom endpoints (must have aiplatform.endpoints.predict permission)
Save your configuration. Your provider slug will be @vertex-ai (or a custom name you specify).
To use Anthropic models on Vertex AI, prepend anthropic. to the model name.
Example: @vertex-ai/anthropic.claude-3-5-sonnet@20240620Similarly, for Meta models, prepend meta. to the model name.
Example: @vertex-ai/meta.llama-3-8b-8192
Anthropic Beta Header Support: When using Anthropic models on Vertex AI, you can pass the anthropic-beta header (or x-portkey-anthropic-beta) to enable beta features. This header is forwarded to the underlying Anthropic API.
Vertex AI supports context caching to reduce costs and latency for repeated prompts with large amounts of context. You can explicitly create a cache and then reference it in subsequent inference requests.
Use the Vertex AI cachedContents endpoint through Portkey to create a cache:
cURL
Copy
Ask AI
curl --location 'https://api.portkey.ai/v1/projects/{{YOUR_PROJECT_ID}}/locations/{{LOCATION}}/cachedContents' \--header 'x-portkey-provider: {{@my-vertex-ai-provider}}' \--header 'Content-Type: application/json' \--header 'x-portkey-api-key: {{your_api_key}}' \--header 'x-portkey-custom-host: https://aiplatform.googleapis.com/v1' \--data '{ "model": "projects/{{YOUR_PROJECT_ID}}/locations/{{LOCATION}}/publishers/google/models/{{MODEL_ID}}", "displayName": "{{my-cache-display-name}}", "contents": [{ "role": "user", "parts": [{ "text": "This is sample text to demonstrate explicit caching. (you need a minimum of 1024 tokens)" }] }, { "role": "model", "parts": [{ "text": "thankyou I am your helpful assistant" }] }]}'
Request variables:
Variable
Description
YOUR_PROJECT_ID
Your Google Cloud project ID.
LOCATION
The region where your model is deployed (e.g., us-central1).
MODEL_ID
The model identifier (e.g., gemini-1.5-pro-001).
my-cache-display-name
A unique name to identify your cache.
your_api_key
Your Portkey API key.
@my-vertex-ai-provider
Your Vertex AI provider slug from Portkey’s Model Catalog.
Context caching requires a minimum of 1024 tokens in the cached content. The cache has a default TTL (time-to-live) which you can configure using the ttl parameter.
Using Self-Deployed Models on Vertex AI (Hugging Face, Custom Models)
Portkey supports connecting to self-deployed models on Vertex AI, including models from Hugging Face or any custom models you’ve deployed to a Vertex AI endpoint.Requirements for Self-Deployed ModelsTo use self-deployed models on Vertex AI through Portkey:
Model Naming Convention: When making requests to your self-deployed model, you must prefix the model name with endpoints.
Copy
Ask AI
endpoints.my_endpoint_name
Required Permissions: The Google Cloud service account used in your Portkey Model Catalog must have the aiplatform.endpoints.predict permission.
NodeJS SDK
Python SDK
Copy
Ask AI
const chatCompletion = await portkey.chat.completions.create({ messages: [{ role: 'user', content: 'Say this is a test' }], model: '@vertex-ai/endpoints.my_custom_llm', // Use Model Catalog slug with 'endpoints.' prefix});console.log(chatCompletion.choices);
Copy
Ask AI
completion = portkey.chat.completions.create( messages= [{ "role": 'user', "content": 'Say this is a test' }], model= '@vertex-ai/endpoints.my_huggingface_model' # Use Model Catalog slug with 'endpoints.' prefix)print(completion)
Why the prefix? Vertex AI’s product offering for self-deployed models is called “Endpoints.” This naming convention indicates to Portkey that it should route requests to your custom endpoint rather than a standard Vertex AI model.This approach works for all models you can self-deploy on Vertex AI Model Garden, including Hugging Face models and your own custom models.
Vertex AI supports attaching various file types to your Gemini messages including documents (pdf), images (jpg, png), videos (webm, mp4), and audio files.
Method 1: Sending a Document via Google Files URLUpload your PDF using the Files API to get a Google Files URL.
Copy
Ask AI
const chatCompletion = await portkey.chat.completions.create({ model: '@vertex-ai/gemini-3-pro-preview', messages: [{ role: 'user', content: [ { type: 'image_url', image_url: { url: 'https://generativelanguage.googleapis.com/v1beta/files/your-pdf-file-id' } }, { type: 'text', text: 'Summarize the key findings of this research paper.' } ] }],});console.log(chatCompletion.choices[0].message.content);
Method 2: Sending a Local Document as Base64 DataThis is suitable for smaller, local PDF files.
Copy
Ask AI
import fs from 'fs';const pdfBytes = fs.readFileSync('whitepaper.pdf');const base64Pdf = pdfBytes.toString('base64');const pdfUri = `data:application/pdf;base64,${base64Pdf}`;const chatCompletion = await portkey.chat.completions.create({ model: '@VERTEX_PROVIDER/MODEL_NAME', messages: [{ role: 'user', content: [ { type: 'image_url', image_url: { url: pdfUri }}, { type: 'text', text: 'What is the main conclusion of this document?' } ] }],});console.log(chatCompletion.choices[0].message.content);
While you can send other document types like .txt or .html, they will be treated as plain text. Gemini’s native document vision capabilities are optimized for the application/pdf MIME type.
The media_resolution parameter allows you to control token allocation for media inputs (images, videos, PDFs) when using Gemini models on Vertex AI. This helps balance between processing detail and cost/speed.
For Gemini 3 models, you can specify media resolution on individual media parts. Per-part settings take precedence over global settings when both are specified.
The assistants thinking response is returned in the response_chunk.choices[0].delta.content_blocks array, not the response.choices[0].message.content string.Gemini models do not support plugging back the reasoning into multi turn conversations, so you don’t need to send the thinking message back to the model.
Models like google.gemini-2.5-flash-preview-04-17anthropic.claude-3-7-sonnet@20250219 support extended thinking.
This is similar to openai thinking, but you get the model’s reasoning as it processes the request as well.Note that you will have to set strict_open_ai_compliance=False in the headers to use this feature.
from portkey_ai import Portkey# Initialize the Portkey clienportkey = Portkey( api_key="PORTKEY_API_KEY", # Replace with your Portkey API key strict_open_ai_compliance=False)# Create the requestresponse = portkey.chat.completions.create( model="@VERTEX_PROVIDER/anthropic.claude-3-7-sonnet@20250219", # your model slug from Portkey's Model Catalog max_tokens=3000, thinking={ "type": "enabled", "budget_tokens": 2030 }, stream=True, messages=[ { "role": "user", "content": [ { "type": "text", "text": "when does the flight from new york to bengaluru land tomorrow, what time, what is its flight number, and what is its baggage belt?" } ] } ])print(response)# in case of streaming responses you'd have to parse the response_chunk.choices[0].delta.content_blocks array# response = portkey.chat.completions.create(# ...same config as above but with stream: true# )# for chunk in response:# if chunk.choices[0].delta:# content_blocks = chunk.choices[0].delta.get("content_blocks")# if content_blocks is not None:# for content_block in content_blocks:# print(content_block)
To disable thinking for gemini models like google.gemini-2.5-flash-preview-04-17, you are required to explicitly set budget_tokens to 0.
from portkey_ai import Portkey# Initialize the Portkey clientportkey = Portkey( api_key="PORTKEY_API_KEY", # Replace with your Portkey API key strict_open_ai_compliance=False)# Create the requestresponse = portkey.chat.completions.create( model="@VERTEX_PROVIDER/anthropic.claude-3-7-sonnet@20250219", # your model slug from Portkey's Model Catalog max_tokens=3000, thinking={ "type": "enabled", "budget_tokens": 2030 }, stream=True, messages=[ { "role": "user", "content": [ { "type": "text", "text": "when does the flight from baroda to bangalore land tomorrow, what time, what is its flight number, and what is its baggage belt?" } ] }, { "role": "assistant", "content": [ { "type": "thinking", "thinking": "The user is asking several questions about a flight from Baroda (also known as Vadodara) to Bangalore:\n1. When does the flight land tomorrow\n2. What time does it land\n3. What is the flight number\n4. What is the baggage belt number at the arrival airport\n\nTo properly answer these questions, I would need access to airline flight schedules and airport information systems. However, I don't have:\n- Real-time or scheduled flight information\n- Access to airport baggage claim allocation systems\n- Information about specific flights between these cities\n- The ability to look up tomorrow's specific flight schedules\n\nThis question requires current, specific flight information that I don't have access to. Instead of guessing or providing potentially incorrect information, I should explain this limitation and suggest ways the user could find this information.", "signature": "EqoBCkgIARABGAIiQBVA7FBNLRtWarDSy9TAjwtOpcTSYHJ+2GYEoaorq3V+d3eapde04bvEfykD/66xZXjJ5yyqogJ8DEkNMotspRsSDKzuUJ9FKhSNt/3PdxoMaFZuH+1z1aLF8OeQIjCrA1+T2lsErrbgrve6eDWeMvP+1sqVqv/JcIn1jOmuzrPi2tNz5M0oqkOO9txJf7QqEPPw6RG3JLO2h7nV1BMN6wE=" } ] }, { "role": "user", "content": "thanks that's good to know, how about to chennai?" } ])print(response)
This same message format also works for all other media types — just send your media file in the url field, like "url": "gs://cloud-samples-data/video/animals.mp4" for google cloud urls and "url":"https://download.samplelib.com/mp3/sample-3s.mp3" for public urlsYour URL should have the file extension, this is used for inferring MIME_TYPE which is a required parameter for prompting Gemini models with files
You can use any of Vertex AI’s English and Multilingual models through Portkey, in the familar OpenAI-schema.
The Gemini-specific parameter task_type is also supported on Portkey.
NodeJS
Python
cURL
Copy
Ask AI
import Portkey from 'portkey-ai';const portkey = new Portkey({ apiKey: "PORTKEY_API_KEY",});// Generate embeddingsasync function getEmbeddings() { const embeddings = await portkey.embeddings.create({ input: "embed this", model: "@VERTEX_PROVIDER/text-multilingual-embedding-002", // your model slug from Portkey's Model Catalog // @ts-ignore (if using typescript) task_type: "CLASSIFICATION", // Optional }); console.log(embeddings);}await getEmbeddings();
Copy
Ask AI
from portkey_ai import Portkey# Initialize the Portkey clientportkey = Portkey( api_key="PORTKEY_API_KEY", # Replace with your Portkey API key)# Generate embeddingsdef get_embeddings(): embeddings = portkey.embeddings.create( input='The vector representation for this text', model='@VERTEX_PROVIDER/text-embedding-004', # your model slug from Portkey's Model Catalog task_type="CLASSIFICATION" # Optional ) print(embeddings)get_embeddings()
Copy
Ask AI
curl 'https://api.portkey.ai/v1/embeddings' \ -H 'Content-Type: application/json' \ -H 'x-portkey-api-key: PORTKEY_API_KEY' \ --data-raw '{ "model": "@VERTEX_PROVIDER/text-embedding-004", # your model slug from Portkey's Model Catalog "input": "A HTTP 246 code is used to signify an AI response containing hallucinations or other inaccuracies", "task_type": "CLASSIFICATION" }'
You can manage all prompts to Google Gemini in the Prompt Library. All the models in the model garden are supported and you can easily start testing different prompts.Once you’re ready with your prompt, you can use the portkey.prompts.completions.create interface to use the prompt in your application.
Portkey supports Veo on Vertex AI for video generation from text or image prompts. Veo uses a two-step long-running flow: you first submit a generation request and receive an operation name, then poll until the operation completes and the video is ready.
Unlike single-call video APIs (for example, OpenAI’s Sora API, where you create a job and then check status or download separately), Vertex AI’s Veo video API is explicitly built as a long-running operation:
Step 1 – Start generation: Send a predictLongRunning request with your prompt (and optional image/video inputs). The API returns immediately with an operation name (no video yet).
Step 2 – Poll until done: Call fetchPredictOperation with that operation name repeatedly (e.g., every 30–60 seconds) until done is true. The final response contains the generated video(s), either as URIs (if you set storageUri) or as base64-encoded bytes.
Request script – Calls predictLongRunning with your prompt and parameters, then writes the returned operation name (and related data) to a file such as veo_operation.json.
Poll script – Reads that file, calls fetchPredictOperation in a loop until the operation is done, then saves the first generated video (e.g., to ./videos/dialogue_example.mp4).
Run the request script first; after it finishes, run the poll script (possibly in another terminal). The poll script will wait between attempts (e.g., 50 seconds) until the job completes.
Node.js
Python
1. Request script (veo_request.js) – The script sends your text prompt and options (duration, resolution, etc.) to Veo’s predictLongRunning endpoint. The API starts the job on Google’s side and returns right away with an operation ID. The script writes that ID plus project/location/model info to veo_operation.json so the poll script can use it.
Copy
Ask AI
// veo_request.jsimport { Portkey } from 'portkey-ai';import fs from 'fs';const projectId = 'YOUR_GCP_PROJECT_ID';const location = 'us-central1';const modelId = 'veo-3.1-generate-preview';const client = new Portkey({ apiKey: process.env.PORTKEY_API_KEY, baseURL: 'https://api.portkey.dev/v1', customHost: 'https://us-central1-aiplatform.googleapis.com/v1', provider: '@vertex-json',});async function main() { const urlPath = `/projects/${projectId}/locations/${location}/publishers/google/models/${modelId}:predictLongRunning`; const body = { instances: [ { prompt: "A close up of two people staring at a cryptic drawing on a wall, torchlight flickering. ...", }, ], parameters: { durationSeconds: 4, generateAudio: false, resolution: '720p', }, }; const operation = await client.post(urlPath, body); const opDict = typeof operation?.toJSON === 'function' ? operation.toJSON() : operation; const operationName = opDict.name || opDict.data?.name; if (!operationName) { throw new Error(`Could not find operation name in: ${JSON.stringify(opDict)}`); } const payload = { project_id: projectId, location, model_id: modelId, operation_name: operationName, operation: opDict, }; const outputPath = 'veo_operation.json'; fs.writeFileSync(outputPath, JSON.stringify(payload, null, 2), 'utf8'); console.log(`Saved operation info to ${outputPath}. Run the poll script next.`);}main().catch((err) => { console.error(err); process.exit(1);});
2. Poll script (veo_poll.js) – This script reads veo_operation.json, then calls fetchPredictOperation in a loop with the saved operation name. Each call returns the current job status; when done is true, the response includes the generated video(s). The script then decodes the first video from base64 and writes it to ./videos/dialogue_example.mp4.
Copy
Ask AI
// veo_poll.jsimport { Portkey } from 'portkey-ai';import fs from 'fs';import path from 'path';const operationPath = 'veo_operation.json';if (!fs.existsSync(operationPath)) { throw new Error(`${operationPath} not found. Run veo_request.js first to create it.`);}const opInfo = JSON.parse(fs.readFileSync(operationPath, 'utf8'));const projectId = opInfo.project_id;const location = opInfo.location;const modelId = opInfo.model_id;let operationName = opInfo.operation_name;if (!operationName) { const raw = opInfo.operation || {}; operationName = raw.name || raw.data?.name;}if (!operationName) { throw new Error(`Could not determine operation name from: ${JSON.stringify(opInfo)}`);}const client = new Portkey({ apiKey: process.env.PORTKEY_API_KEY, baseURL: 'https://api.portkey.dev/v1', customHost: 'https://us-central1-aiplatform.googleapis.com/v1', provider: '@vertex-json',});async function main() { const pollPath = `/projects/${projectId}/locations/${location}/publishers/google/models/${modelId}:fetchPredictOperation`; const pollBody = { operationName }; let checkDict; while (true) { const checkOperation = await client.post(pollPath, pollBody); checkDict = typeof checkOperation?.toJSON === 'function' ? checkOperation.toJSON() : checkOperation; const done = checkDict.done || checkDict.data?.done; if (done) break; console.log('Still running, sleeping 50s...'); await new Promise((r) => setTimeout(r, 50_000)); } const body = checkDict.response || checkDict.data?.response || {}; const videos = body.videos || []; if (!videos.length) { throw new Error(`No videos found in response: ${JSON.stringify(body)}`); } const generatedVideo = videos[0].bytesBase64Encoded; const videosDir = './videos/'; fs.mkdirSync(videosDir, { recursive: true }); const videoBuffer = Buffer.from(generatedVideo, 'base64'); const filePath = path.join(videosDir, 'dialogue_example.mp4'); fs.writeFileSync(filePath, videoBuffer); console.log(`Video saved to: ${filePath}`);}main().catch((err) => { console.error(err); process.exit(1);});
Run: node veo_request.js, then node veo_poll.js.
1. Request script (veo_request.py) – The script sends your text prompt and options (duration, resolution, etc.) to Veo’s predictLongRunning endpoint. The API starts the job on Google’s side and returns immediately with an operation ID. The script writes that ID plus project/location/model info to veo_operation.json for the poll script to use.
Copy
Ask AI
# veo_request.pyfrom portkey_ai import Portkeyimport jsonimport osproject_id = os.environ.get("VERTEX_PROJECT_ID", "YOUR_GCP_PROJECT_ID")location = "us-central1"model_id = "veo-3.1-generate-preview"client = Portkey( api_key=os.environ.get("PORTKEY_API_KEY"), base_url="https://api.portkey.dev/v1", custom_host="https://us-central1-aiplatform.googleapis.com/v1", provider="@vertex-json",)body = { "instances": [ { "prompt": "A close up of two people staring at a cryptic drawing on a wall, torchlight flickering. ...", } ], "parameters": { "durationSeconds": 4, "generateAudio": False, "resolution": "720p", },}url = f"projects/{project_id}/locations/{location}/publishers/google/models/{model_id}:predictLongRunning"operation = client.post(url, body)op_dict = operation.model_dump() if hasattr(operation, "model_dump") else operationoperation_name = op_dict.get("name") or op_dict.get("data", {}).get("name")if not operation_name: raise RuntimeError(f"Could not find operation name in: {op_dict}")payload = { "project_id": project_id, "location": location, "model_id": model_id, "operation_name": operation_name, "operation": op_dict,}output_path = "veo_operation.json"with open(output_path, "w", encoding="utf-8") as f: json.dump(payload, f, indent=2)print(f"Saved operation info to {output_path}. Run the poll script next.")
2. Poll script (veo_poll.py) – This script reads veo_operation.json, then calls fetchPredictOperation in a loop with the saved operation name. Each call returns the current job status; when done is true, the response includes the generated video(s). The script decodes the first video from base64 and writes it to ./videos/dialogue_example.mp4.
Copy
Ask AI
# veo_poll.pyfrom portkey_ai import Portkeyimport base64import jsonimport osimport timeoperation_path = "veo_operation.json"if not os.path.exists(operation_path): raise FileNotFoundError( f"{operation_path} not found. Run veo_request.py first to create it." )with open(operation_path, "r", encoding="utf-8") as f: op_info = json.load(f)project_id = op_info["project_id"]location = op_info["location"]model_id = op_info["model_id"]operation_name = op_info.get("operation_name")if not operation_name: raw = op_info.get("operation", {}) operation_name = raw.get("name") or raw.get("data", {}).get("name")if not operation_name: raise RuntimeError(f"Could not determine operation name from: {op_info}")client = Portkey( api_key=os.environ.get("PORTKEY_API_KEY"), base_url="https://api.portkey.dev/v1", custom_host="https://us-central1-aiplatform.googleapis.com/v1", provider="@vertex-json",)poll_url = f"projects/{project_id}/locations/{location}/publishers/google/models/{model_id}:fetchPredictOperation"poll_body = {"operationName": operation_name}while True: check_operation = client.post(poll_url, poll_body) check_dict = check_operation.model_dump() if hasattr(check_operation, "model_dump") else check_operation done = check_dict.get("done") or check_dict.get("data", {}).get("done") if done: break print("Still running, sleeping 50s...") time.sleep(50)body = check_dict.get("response") or check_dict.get("data", {}).get("response", {})videos = body.get("videos") or []if not videos: raise RuntimeError(f"No videos found in response: {body}")generated_video = videos[0]["bytesBase64Encoded"]os.makedirs("./videos/", exist_ok=True)video_data = base64.b64decode(generated_video)file_path = os.path.join("./videos/", "dialogue_example.mp4")with open(file_path, "wb") as video_file: video_file.write(video_data)print(f"Video saved to: {file_path}")
Run: python veo_request.py, then python veo_poll.py.
Ensure PORTKEY_API_KEY is set (and, for Python, optionally VERTEX_PROJECT_ID). Configure Vertex AI and project access in the Portkey dashboard or via the client so the gateway can call the Vertex Veo API.
The recommended way to attribute costs and track usage is to use metadata which allows your workflows to be vendor agnostic
Vertex AI supports adding custom labels to your API calls for attribution.
Pass labels in your request body or configure them in your gateway config using override_params.
Python
NodeJS
Config
Copy
Ask AI
completion = portkey.chat.completions.create( messages=[{"role": "user", "content": "Say this is a test"}], model="@VERTEX_PROVIDER/gemini-3-pro-preview", labels={"service_id": "backend-api", "environment": "production"})
Copy
Ask AI
const completion = await portkey.chat.completions.create({ messages: [{ role: 'user', content: 'Say this is a test' }], model: '@VERTEX_PROVIDER/gemini-3-pro-preview', labels: { service_id: "backend-api", environment: "production" }});
Vertex AI supports grounding with Google Search. This is a feature that allows you to ground your LLM responses with real-time search results.
Grounding is invoked by passing the google_search tool (for newer models like gemini-2.0-flash-001), and google_search_retrieval (for older models like gemini-1.5-flash) in the tools array.
Copy
Ask AI
"tools": [ { "type": "function", "function": { "name": "google_search" // or google_search_retrieval for older models } }]
If you mix regular tools with grounding tools, vertex might throw an error saying only one tool can be used at a time.
Vertex AI supports grounding with Google Maps for location-based queries — places, directions, ratings, and geographic information.Pass the google_maps tool in the tools array:
Copy
Ask AI
curl --location 'https://api.portkey.ai/v1/chat/completions' \--header 'x-portkey-api-key: YOUR_PORTKEY_API_KEY' \--header 'Content-Type: application/json' \--data '{ "model": "@YOUR_VERTEX_PROVIDER/gemini-2.5-pro", "messages": [{"role": "user", "content": "What are the best Italian restaurants near Times Square?"}], "tools": [{"type": "function", "function": {"name": "google_maps"}}]}'
gemini-2.0-flash-thinking-exp and other thinking/reasoning models
gemini-2.0-flash-thinking-exp models return a Chain of Thought response along with the actual inference text,
this is not openai compatible, however, Portkey supports this by adding a \r\n\r\n and appending the two responses together.
You can split the response along this pattern to get the Chain of Thought response and the actual inference text.If you require the Chain of Thought response along with the actual inference text, pass the strict open ai compliance flag as false in the request.If you want to get the inference text only, pass the strict open ai compliance flag as true in the request.
from portkey_ai import Portkey# Initialize the Portkey clienportkey = Portkey( api_key="PORTKEY_API_KEY", # Replace with your Portkey API key strict_open_ai_compliance=False)# Create the requestresponse = portkey.chat.completions.create( model="gemini-2.5-flash-image-preview", # your model slug from Portkey's Model Catalog max_tokens=32768, stream=False, modalities=["text", "image"], messages= [ { "role": "system", "content": "You are a helpful assistant" }, { "role": "user", "content": [ { "type": "text", "text": "Add some chocolate drizzle to the croissants. Include text across the top of the image that says \"Made Fresh Daily\"." }, { "type": "image_url", "image_url": { "url": "gs://cloud-samples-data/generative-ai/image/croissant.jpeg" } } ] } ])print(response)# in case of streaming responses you'd have to parse the response_chunk.choices[0].delta.content_blocks array# response = portkey.chat.completions.create(# ...same config as above but with stream: true# )# for chunk in response:# if chunk.choices[0].delta:# content_blocks = chunk.choices[0].delta.get("content_blocks")# if content_blocks is not None:# for content_block in content_blocks:# print(content_block)
Set x-portkey-strict-open-ai-compliance to false to receive the thought_signature in the response. This header must be included in all requests when using thought signatures.
Google’s Gemini 3 Pro model requires passing a thought_signature parameter in tool calling conversations for verifying the payload. This signature is returned by the model in the assistant’s tool call response and must be included when continuing multi-turn conversations.
In multi-turn conversations, you must include the thought_signature field in the assistant’s tool call when continuing the conversation.
Copy
Ask AI
curl --location 'https://api.portkey.ai/v1/chat/completions' \--header 'x-portkey-provider: @my-vertex-ai-provider' \--header 'Content-Type: application/json' \--header 'x-portkey-api-key: your-api-key' \--header 'x-portkey-strict-open-ai-compliance: false' \--data '{ "model": "gemini-3-pro-preview", "max_tokens": 1000, "stream": true, "messages": [ { "role": "system", "content": [ { "type": "text", "text": "You are a helpful assistant" } ] }, { "role": "user", "content": "Check the time in Chennai and if it is later than 9Pm get the temperature" }, { "role": "assistant", "tool_calls": [ { "id": "portkey-1dcd51a0-a20a-482d-b244-2d4aff5aebdb", "type": "function", "function": { "name": "get_current_time", "arguments": "{\"location\":\"Chennai, India\"}", "thought_signature": "CtQBAePx/17ARdotHH1RN31zOtCF+YpuOFTpU//tJRF4dEvegfDKLUaZnuG38II1POmVFdzBbzt87cTDr0TsEKHyHScN9PURHrhRer7liusjRrLR5QF4n1ZYJJYF3C+3bgC9YJsJyQhY/HAgVZQ53gq7n4I63CgXhYA+tzNN3CnHqdStgY0wLK0mCu/tb1kReSrXYMbre27SB5t2eRA7Wl+OKasKCOk7sYCJ8VkT+NaD+s6+NVTX2Au3RmUGVxYdjapo0vc7nnjvfmpTJHviyGJZIGIdXWw=" } } ] }, { "role": "tool", "content": "{ '\''time'\'': '\''10PM'\'' }", "tool_call_id": "toolu_014jEfKqGbfFvRaKfiauxgPv" } ], "tools": [ { "type": "function", "function": { "name": "get_current_time", "description": "Get the current time for a specific location", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "The city and state, e.g., San Francisco, CA" } }, "required": [ "location" ] } } }, { "type": "function", "function": { "name": "get_current_temperature", "description": "Get the current temperature for a specific location", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "The city and state, e.g., San Francisco, CA" }, "unit": { "type": "string", "enum": [ "Celsius", "Fahrenheit" ], "description": "The temperature unit to use. Infer this from the user'\''s location." } }, "required": [ "location", "unit" ] } } } ]}'
The thought_signature is automatically generated by the model and returned in the tool call response. You must preserve this signature when including the assistant’s message in subsequent requests.
from portkey_ai import Portkey# Initialize the Portkey clienportkey = Portkey( api_key="PORTKEY_API_KEY", # Replace with your Portkey API key strict_open_ai_compliance=False)# Create the requestresponse = portkey.chat.completions.create( model="gemini-2.5-flash-image-preview", # your model slug from Portkey's Model Catalog max_tokens=32768, stream=False, modalities=["text", "image"], messages= [ { "role": "system", "content": "You are a helpful assistant" }, { "role": "user", "content": [ { "type": "text", "text": "Add some chocolate drizzle to the croissants. Include text across the top of the image that says \"Made Fresh Daily\"." }, { "type": "image_url", "image_url": { "url": "gs://cloud-samples-data/generative-ai/image/croissant.jpeg" } } ] }, { "role": "assistant", "content": [ { "type": "text", "text": "Here are the croissants with chocolate drizzle and the requested text: " }, { "type": "image_url", "image_url": { "url": "data:image/jpeg;base64,UKDhasdhj....." } } ] }, { "role": "user", "content": "looking good, thanks fam" } ])print(response)# in case of streaming responses you'd have to parse the response_chunk.choices[0].delta.content_blocks array# response = portkey.chat.completions.create(# ...same config as above but with stream: true# )# for chunk in response:# if chunk.choices[0].delta:# content_blocks = chunk.choices[0].delta.get("content_blocks")# if content_blocks is not None:# for content_block in content_blocks:# print(content_block)
Gemini models support configuring safety settings to control how potentially harmful content is handled.Pass safety_settings as an array with category and threshold values:
You can also pass your Vertex AI details & secrets directly without using the Portkey’s Model Catalog.Vertex AI expects a region, a project ID and the access token in the request for a successful completion request. This is how you can specify these fields directly in your requests:
When selecting Service Account File as your authentication method, you’ll need to:
Upload your Google Cloud service account JSON file
Specify the Vertex Region
This method is particularly important for using self-deployed models, as your service account must have the aiplatform.endpoints.predict permission to access custom endpoints.Learn more about permission on your Vertex IAM key here.
For Self-Deployed Models: Your service account must have the aiplatform.endpoints.predict permission in Google Cloud IAM. Without this specific permission, requests to custom endpoints will fail.
For environments where Portkey runs on Google Cloud infrastructure (e.g., GKE), you can use Workload Identity Federation to authenticate without managing service account keys. This method uses the attached service account identity of the workload environment.To use this method, configure your integration with:
Set the auth type to Workload Identity Federation
Provide your Vertex Project ID
This is the recommended approach for GKE or Cloud Run deployments as it eliminates the need to manage and rotate service account key files.