From b2b684e515244ae4de08c268d96dc6b12990fa07 Mon Sep 17 00:00:00 2001 From: valtzu Date: Thu, 10 Jul 2025 22:51:52 +0300 Subject: [PATCH] feat: expose Gemini grounding metadata --- examples/google/grounding.php | 47 +++++++++++++++++++ .../Google/GroundingOutputProcessor.php | 41 ++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 examples/google/grounding.php create mode 100644 src/Platform/Bridge/Google/GroundingOutputProcessor.php diff --git a/examples/google/grounding.php b/examples/google/grounding.php new file mode 100644 index 00000000..6f16c0a9 --- /dev/null +++ b/examples/google/grounding.php @@ -0,0 +1,47 @@ +loadEnv(dirname(__DIR__, 2).'/.env'); + +if (!$_ENV['GEMINI_API_KEY']) { + echo 'Please set the GEMINI_API_KEY environment variable.'.\PHP_EOL; + exit(1); +} + +$platform = PlatformFactory::create($_ENV['GEMINI_API_KEY']); + +// Available server-side tools as of 2025-06-28: url_context, google_search, code_execution +$llm = new Gemini('gemini-2.5-pro-preview-03-25', ['server_tools' => ['google_search' => true], 'temperature' => 1.0]); + +$toolbox = Toolbox::create(new Clock()); +$processor = new ChainProcessor($toolbox); +$chain = new Chain($platform, $llm, outputProcessors: [new GroundingOutputProcessor()]); + +$messages = new MessageBag(Message::ofUser('Summarize what is Symfony framework in 3 sentences')); + +$response = $chain->call($messages); + +echo $response->getContent().\PHP_EOL; + +$metadata = $response->getMetadata()['grounding']; + +printf( + <<<'FORMAT' + Search queries: %s + Sources: %s + + FORMAT, + implode(', ', $metadata['webSearchQueries']), + implode(', ', array_unique(array_column(array_column($metadata['groundingChunks'], 'web'), 'title'))) +); diff --git a/src/Platform/Bridge/Google/GroundingOutputProcessor.php b/src/Platform/Bridge/Google/GroundingOutputProcessor.php new file mode 100644 index 00000000..92c00a3d --- /dev/null +++ b/src/Platform/Bridge/Google/GroundingOutputProcessor.php @@ -0,0 +1,41 @@ + + */ +final class GroundingOutputProcessor implements OutputProcessorInterface +{ + public function processOutput(Output $output): void + { + if ($output->response instanceof StreamResponse) { + // Streams have to be handled manually as the tokens are part of the streamed chunks + return; + } + + $rawResponse = $output->response->getRawResponse()?->getRawObject(); + if (!$rawResponse instanceof ResponseInterface) { + return; + } + + $metadata = $output->response->getMetadata(); + + $content = $rawResponse->toArray(false); + + if (!$grounding = $content['candidates'][0]['groundingMetadata'] ?? null) { + return; + } + + $metadata->add('grounding', $grounding); + } +}