#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License.  You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

""" A sample pipeline using the RunInference API to classify text using an LLM.
This pipeline creates a set of prompts and sends it to a deployed Vertex AI
model endpoint, then returns the predictions from the classifier model. The
model can be generated by fine tuning the text-bison model or another similar
model (see
https://cloud.google.com/vertex-ai/docs/generative-ai/models/tune-models#supervised-fine-tuning)
"""

import argparse
import logging

import apache_beam as beam
from apache_beam.ml.inference.base import KeyedModelHandler
from apache_beam.ml.inference.base import RunInference
from apache_beam.ml.inference.vertex_ai_inference import VertexAIModelHandlerJSON
from apache_beam.options.pipeline_options import PipelineOptions
from apache_beam.options.pipeline_options import SetupOptions
from apache_beam.runners.runner import PipelineResult


def parse_known_args(argv):
  """Parses args for the workflow."""
  parser = argparse.ArgumentParser()
  parser.add_argument(
      '--output',
      dest='output',
      type=str,
      required=True,
      help='Path to save output predictions.')
  parser.add_argument(
      '--endpoint_id',
      dest='endpoint',
      type=str,
      required=True,
      help='Vertex AI Endpoint resource ID to query (string).')
  parser.add_argument(
      '--endpoint_project', dest='project', required=True, help='GCP Project')
  parser.add_argument(
      '--endpoint_region',
      dest='location',
      type=str,
      required=True,
      help='GCP location for the Endpoint')
  parser.add_argument(
      '--endpoint_network',
      dest='vpc_network',
      type=str,
      required=False,
      help='GCP network the endpoint is peered to')
  parser.add_argument(
      '--experiment',
      dest='experiment',
      type=str,
      required=False,
      help='Vertex AI experiment label to apply to queries')
  parser.add_argument(
      '--private',
      dest='private',
      type=bool,
      default=False,
      help="True if the Vertex AI endpoint is a private endpoint")
  return parser.parse_known_args(argv)


def run(
    argv=None, save_main_session=True, test_pipeline=None) -> PipelineResult:
  """
  Args:
    argv: Command line arguments defined for this example.
    save_main_session: Used for internal testing.
    test_pipeline: Used for internal testing.
  """
  known_args, pipeline_args = parse_known_args(argv)
  pipeline_options = PipelineOptions(pipeline_args)
  pipeline_options.view_as(SetupOptions).save_main_session = save_main_session

  model_handler = VertexAIModelHandlerJSON(
      endpoint_id=known_args.endpoint,
      project=known_args.project,
      location=known_args.location,
      experiment=known_args.experiment,
      network=known_args.vpc_network,
      private=known_args.private)

  pipeline = test_pipeline
  if not test_pipeline:
    pipeline = beam.Pipeline(options=pipeline_options)

  # For this example, use the default parameters from
  # https://cloud.google.com/vertex-ai/docs/generative-ai/start/quickstarts/api-quickstart#parameter_definitions
  parameters = {
      "temperature": 0.2, "maxOutputTokens": 256, "topK": 40, "topP": 0.95
  }
  prompts = [
      "What is 5+2?",
      "Who is the president?",
      "Write me a business plan for a cookie shop."
  ]

  read_prompts = pipeline | "Get prompt" >> beam.Create(prompts)
  preprocess = read_prompts | "Format prompt" >> beam.Map(
      lambda data: (data, {
          "prompt": data
      }))
  predictions = preprocess | "RunInference" >> RunInference(
      KeyedModelHandler(model_handler), inference_args=parameters)
  _ = predictions | "PrintOutput" >> beam.Map(print)
  _ = predictions | "WriteOutput" >> beam.io.WriteToText(
      known_args.output, shard_name_template='', append_trailing_newlines=True)

  result = pipeline.run()
  result.wait_until_finish()
  return result


if __name__ == '__main__':
  logging.getLogger().setLevel(logging.INFO)
  run()
