Android应急APP集成Gemini微调模型遇403权限错误的解决方法
解决Gemini微调模型在安卓APP中403权限错误及无响应问题
一、权限问题排查步骤
- 验证API Key权限:确保API Key所属的Google Cloud项目已启用Vertex AI API(微调模型托管在Vertex AI),且API Key关联的身份拥有
Vertex AI User或Vertex AI Predictor角色,允许访问目标微调模型。 - 核对模型归属:确认微调模型
tunedModels/first-aid-123example123与API Key所在项目为同一项目,跨项目使用需将模型共享给目标项目并配置对应权限。 - 检查模型ID拼写:确认ID完全匹配(注意大小写、特殊字符,无多余空格)。
二、代码核心调整
安卓Gemini SDK调用Vertex AI托管的微调模型时,需指定模型所在区域的Endpoint,不能直接使用基础模型的默认构造逻辑。同时需兼容微调模型的响应结构,避免序列化异常。
1. 正确初始化微调模型
替换原GenerativeModel初始化代码,添加ModelConfiguration指定Vertex AI区域和模型Endpoint:
String apiKey = "GeminiApiKey"; String tunedModelId = "tunedModels/first-aid-123example123"; String projectId = "你的Google Cloud项目ID"; // 替换为实际项目ID String vertexRegion = "us-central1"; // 替换为模型创建时的区域 ModelConfiguration config = new ModelConfiguration.Builder() .setEndpoint(String.format("https://%s-aiplatform.googleapis.com/v1/projects/%s/locations/%s/%s", vertexRegion, projectId, vertexRegion, tunedModelId)) .build(); GenerativeModel gm = new GenerativeModel(tunedModelId, apiKey, config); model = GenerativeModelFutures.from(gm);
2. 修复序列化异常
微调模型响应结构与基础模型略有差异,需遍历候选内容获取文本,避免直接依赖getText():
@Override public void onSuccess(GenerateContentResponse result) { StringBuilder resultText = new StringBuilder(); // 遍历所有候选响应及内容部分,兼容微调模型格式 for (GenerateContentResponse.Candidate candidate : result.getCandidates()) { for (Content.Part part : candidate.getContent().getParts()) { if (part instanceof TextPart) { resultText.append(((TextPart) part).getText()); } } } // 后续格式处理逻辑保持不变 }
三、完整修改后的代码
// 保留原imports,需确保导入com.google.ai.client.generativeai.type.ModelConfiguration、TextPart等类 public class AssistanceActivity extends AppCompatActivity { private EditText emergencyInput; private TextView responseView; private Button sendButton; private GenerativeModelFutures model; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); EdgeToEdge.enable(this); setContentView(R.layout.activity_assistance); ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); return insets; }); emergencyInput = findViewById(R.id.emergencyInput); responseView = findViewById(R.id.response); sendButton = findViewById(R.id.send); // 初始化微调模型 String apiKey = "GeminiApiKey"; String tunedModelId = "tunedModels/first-aid-123example123"; String projectId = "你的Google Cloud项目ID"; // 替换为实际项目ID String vertexRegion = "us-central1"; // 替换为模型所在区域 ModelConfiguration config = new ModelConfiguration.Builder() .setEndpoint(String.format("https://%s-aiplatform.googleapis.com/v1/projects/%s/locations/%s/%s", vertexRegion, projectId, vertexRegion, tunedModelId)) .build(); GenerativeModel gm = new GenerativeModel(tunedModelId, apiKey, config); model = GenerativeModelFutures.from(gm); sendButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String query = emergencyInput.getText().toString().trim(); if (!query.isEmpty()) { modelCall(query); } else { responseView.setText("Por favor ingrese una consulta."); } } }); } private void modelCall(String query) { Content content = new Content.Builder() .addText(query) .build(); ListenableFuture<GenerateContentResponse> response = model.generateContent(content); Futures.addCallback( response, new FutureCallback<GenerateContentResponse>() { @Override public void onSuccess(GenerateContentResponse result) { StringBuilder resultText = new StringBuilder(); // 遍历候选响应,兼容微调模型结构 for (GenerateContentResponse.Candidate candidate : result.getCandidates()) { for (Content.Part part : candidate.getContent().getParts()) { if (part instanceof TextPart) { resultText.append(((TextPart) part).getText()); } } } SpannableStringBuilder spannable = new SpannableStringBuilder(); String[] lines = resultText.toString().split("\\n"); for (String line : lines) { if (line.startsWith("**")) { String boldText = line.replace("**", ""); SpannableString spannableBold = new SpannableString(boldText + "\n"); spannableBold.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0, boldText.length(), 0); spannable.append(spannableBold); } else if (line.startsWith("*")) { String listItem = "• " + line.replace("*", "") + "\n"; spannable.append(listItem); } else { spannable.append(line + "\n"); } } runOnUiThread(() -> responseView.setText(spannable)); } @Override public void onFailure(Throwable t) { t.printStackTrace(); // 优化错误提示,便于调试 String errorMsg = "Error al obtener respuesta: " + t.getMessage(); if (t instanceof ApiException) { ApiException apiEx = (ApiException) t; errorMsg += " | 状态码: " + apiEx.getStatusCode(); } runOnUiThread(() -> responseView.setText(errorMsg)); } }, getMainExecutor()); } }
注意:生产环境请勿硬编码API Key,建议使用Android安全存储方案管理敏感信息。
内容的提问来源于stack exchange,提问作者NotFound4020
相关产品推荐
相关产品推荐

