Gemini 1.5 Pro
2Mコンテキストに対応しているというGemini 1.5 Pro。
どれだけの容量なのか・・・と調査してみるとOpenAIのHPが参考になりました。
You can think of tokens as pieces of words used for natural language processing. For English text, 1 token is approximately 4 characters or 0.75 words. As a point of reference, the collected works of Shakespeare are about 900,000 words or 1.2M tokens
To learn more about how tokens work and estimate your usage…
- Experiment with our interactive Tokenizer tool(opens in a new window).
- Log in to your account and enter text into the Playground. The counter in the footer will display how many tokens are in your text.
シェイクスピアの作品集で1.2M。それをさばけるのですから恐ろしい量です。
大量の文字の処理となると何が挙げられるか。
自分が思いつくものは、先日からプレビューで実行できているPower AppsのYAMLコードです。
コードで可視化できることは便利ですが、全スクリーンのコードを抜き出すと容量が多くなります。
今回はPower AppsのテンプレートであるService Deskをもとにドキュメント作成ができないか、PythonとGemini APIで試してみました。
Service Deskのトークン数
OpenAIのTokenizer ToolでPower AppsのテンプレートのService DeskのYAMLコードを入れてみると
- Tokens 28,325
- Characters 114,929
と出てきます。うーむ、案外ほかのモデルでも行けそうですね🧐
とりあえず今回はコチラの情報をGemini APIでさばいてみてみましょう。
Vertex AIでGemini APIを実行する
今回はGoogle Cloud PlatformでVertex AIを使用します。
Vertex AIでVertex AI Studio
をクリックすると、Gemini 1.5 Pro
はスグに使うことができます。
事前に検証した結果
ほかのアプリで依存関係を表にしてほしい!というプロンプトを送った場合でも、Gemini
は上手い具合に処理してくれていました。
Gemini 1.5 Proやるなー
— 出戻りガツオ🐟 Microsoft MVP (@DemodoriGatsuo) June 26, 2024
Power Appsのテンプレの相関関係も表にできる pic.twitter.com/sxexXNHYSB
ホントに恐るべしです。
これを更にAPIで実行出来たら夢が広がります。
Pythonで使用するために
Vertex AI Studio
の右上にPython
のサンプルコードもあるため、入口は入りやすいです。
しかし、コードを試すために、まずはGoogle Cloud CLI
が必要です。
cloud CLI をインストールするを参考にgcloud CLI
を準備しましょう。
- 環境変数に
gcloud CLI
が指定されない場合、コマンドからgcloud CLI
を実行できません
完了後にVisual Studio Codeでフォルダを指定し、環境を設定します。
python -m venv venv
環境を有効化して必要なライブラリのインストールと認証を実行します。
pip install --upgrade google-cloud-aiplatform
gcloud auth application-default login
こちらをVisual Studio Codeのターミナルで実行し、認証を完了させます。
完了させると画面のように認証が完了したことを示す画面に遷移します。
Power Appsのドキュメントを作成する
今回はYAML
を使うため、pyyaml
をインストールします。
pip install pyyaml
- 実行ファイルと同一階層にYAMLファイルを格納するフォルダを設ける
- 対象のフォルダのファイルをまとめて読み取る
- Gemini APIを実行
- 結果をMarkdownで返す
上記のシナリオで進めてみます。
コード
GPT-4o
の力を借りまくって作成しています。
事前に環境変数の設定をしておいてください。
import os
import yaml
import vertexai
from vertexai.generative_models import GenerativeModel
import vertexai.preview.generative_models as generative_models
from pathlib import Path
# 環境変数の設定
project = os.getenv('PROJECT')
location = os.getenv('LOCATION')
# YAMLファイルの読み込み
def load_yaml(file_path):
"""
指定されたファイルパスからYAMLファイルを読み込み、辞書として返す。
もしYAMLエラーが発生した場合、もしくは辞書形式でない場合は
ファイルの内容を単純なテキストとして辞書に格納して返す。
:param file_path: 読み込むYAMLファイルのパス
:return: YAML内容の辞書、もしくはテキスト内容を含む辞書
"""
try:
with open(file_path, 'r') as file:
content = yaml.safe_load(file)
if isinstance(content, dict):
return content
else:
print(f"Warning: {file_path} did not contain a valid YAML dictionary. Treating as plain text.")
return {'content': content}
except yaml.YAMLError as e:
print(f"YAML error in {file_path}: {e}")
with open(file_path, 'r') as file:
return {'content': file.read()}
# 複数のYAMLコンテンツを統合する関数
def merge_yaml_contents(yaml_contents):
"""
複数のYAML内容を統合し、1つの辞書として返す。
辞書でない内容は 'raw_content' キーに統合する。
:param yaml_contents: YAML内容のリスト
:return: 統合されたYAML内容の辞書
"""
merged_content = {}
for content in yaml_contents:
if isinstance(content, dict):
merged_content.update(content)
else:
merged_content['raw_content'] = merged_content.get('raw_content', '') + "\n" + content.get('content', '')
return merged_content
# 依存関係を可視化する関数
def visualize_dependencies(yaml_content):
"""
統合されたYAML内容をもとに依存関係を解析し、Markdown形式で返す。
:param yaml_content: 統合されたYAML内容の辞書
:return: 依存関係のMarkdown形式のテキスト
"""
prompt = f"""
Analyze the following Power Apps YAML configuration and visualize the dependencies between controls and functions. Highlight the dependencies clearly in Markdown format:
```yaml
{yaml.dump(yaml_content)}
```
Provide the dependencies in Markdown format.
"""
vertexai.init(project=project, location=location)
model = GenerativeModel("gemini-1.5-pro-001")
response = model.generate_content([prompt], generation_config=generation_config, safety_settings=safety_settings)
# `GenerationResponse`オブジェクトからテキストを取得
markdown_output = response.candidates[0].content.parts[0].text
return markdown_output
# Markdownをファイルに保存する関数
def save_markdown(file_path, content):
"""
指定されたファイルパスにコンテンツをMarkdown形式で保存する。
:param file_path: 保存するファイルのパス
:param content: 保存するMarkdown形式のテキスト
"""
with open(file_path, 'w') as file:
file.write(content)
# "YAML"フォルダ内のすべてのYAMLファイルを処理し統合する関数
def process_yaml_files():
"""
"YAML"フォルダ内のすべてのYAMLファイルを読み込み、統合し、依存関係を解析してMarkdownファイルに保存する。
"""
current_dir = Path(__file__).parent
yaml_folder = current_dir / 'YAML'
yaml_files = yaml_folder.glob('*.yaml')
# 各YAMLファイルを読み込み
yaml_contents = [load_yaml(yaml_file) for yaml_file in yaml_files]
# 読み込んだ内容を統合
merged_yaml_content = merge_yaml_contents(yaml_contents)
# 統合された内容から依存関係を可視化
markdown_output = visualize_dependencies(merged_yaml_content)
# Markdownファイルとして保存
markdown_file_path = yaml_folder / 'merged_dependencies.md'
save_markdown(markdown_file_path, markdown_output)
print(f"Dependencies for merged YAML files saved to {markdown_file_path}")
# 依存関係の生成設定
generation_config = {
"max_output_tokens": 8192,
"temperature": 1,
"top_p": 0.95,
}
# セーフティ設定
safety_settings = {
generative_models.HarmCategory.HARM_CATEGORY_HATE_SPEECH: generative_models.HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
generative_models.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: generative_models.HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
generative_models.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: generative_models.HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
generative_models.HarmCategory.HARM_CATEGORY_HARASSMENT: generative_models.HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
}
# メイン処理
if __name__ == "__main__":
process_yaml_files()
実行前にproject-id
やlocation
は一時的に環境変数として設定しています。
$env:PROJECT = "PROJECT_ID"
$env:LOCATION = "REGION"
単純にYAMLを読み込むとエラーが出るため、読み取れないファイルは、テキストとして読み取り、マージしています。
結果
非常に優秀な結果が出ました・・・(最後に記載しています)。
これもうAI使いまくって、本当にドキュメント作成を効率化したいですね。
依存関係の図式化の例もPPログさんが書かれています。
プロンプトを工夫すればテストケースもイケそう。
やってみるしかないですね。
## Power Apps Control & Function Dependencies Visualization
This Markdown document visualizes the dependencies between controls and functions within the provided Power Apps YAML configuration.
**Page:** TicketdetailsPage
**Controls and their Dependencies:**
- **TextBox5_57 (Label)**
- **Text:** `EditRecord.DateCreated` (Data Source: EditRecord)
- **TextBox5_51 (Label)**
- **TextBox3 (Label)**
- **Text:** `Today()` (Function)
- **Visible:** `false`
- **Group21 (Group)**
- **Children:**
- **icon2_7 (Icon)**
- **OnSelect:**
- `Navigate(PriorityPage, ScreenTransition.Fade, { priority: priority })` (Navigation, Data: priority)
- **TextBox5_60 (Label)**
- **Text:** `priority` (Data Source)
- **OnSelect:**
- `Navigate(PriorityPage, ScreenTransition.Fade, { priority: priority })` (Navigation, Data: priority)
- **Group20 (Group)**
- **Children:**
- **icon2_6 (Icon)**
- **OnSelect:**
- `Navigate(AreaPage, ScreenTransition.Fade, { Area: Area })` (Navigation, Data: Area)
- **TextBox5_59 (Label)**
- **Text:** `Area` (Data Source)
- **OnSelect:**
- `Navigate(AreaPage, ScreenTransition.Fade, { Area: Area })` (Navigation, Data: Area)
- **Group19 (Group)**
- **Children:**
- **icon2_5 (Icon)**
- **OnSelect:**
- `Navigate(AssigntoPage, ScreenTransition.Fade, { assign: assign })` (Navigation, Data: assign)
- **TextBox5_26 (Label)**
- **Text:** `If(IsBlank(assign), "None", assign)` (Function, Data: assign)
- **OnSelect:**
- `Navigate(AssigntoPage, ScreenTransition.Fade, { assign: assign })` (Navigation, Data: assign)
- **Group18 (Group)**
- **Children:**
- **icon2_4 (Icon)**
- **OnSelect:**
- `Navigate(CasestatusPage, ScreenTransition.Fade, { type: type })` (Navigation, Data: type)
- **TextBox5_25 (Label)**
- **Text:** `type` (Data Source)
- **OnSelect:**
- `Navigate(CasestatusPage, ScreenTransition.Fade, { type: type })` (Navigation, Data: type)
- **Rectangle7_14 (Rectangle)**
- **TextInput4 (TextInput)**
- **Default:** `EditRecord.Description` (Data Source: EditRecord)
- **DisplayMode:** `If(description_disabled, DisplayMode.Disabled, false, DisplayMode.View, DisplayMode.Edit)` (Function, Data: description_disabled)
- **Fill:** `description_fill` (Data Source)
- **Reset:** `textreset` (Data Source)
- **TextInput3 (TextInput)**
- **Default:** `EditRecord.Comment` (Data Source: EditRecord)
- **DisplayMode:** `If(commentdisabled, DisplayMode.Disabled, false, DisplayMode.View, DisplayMode.Edit)` (Function, Data: commentdisabled)
- **Fill:** `commentfill` (Data Source)
- **Reset:** `textreset` (Data Source)
- **TextInput2 (TextInput)**
- **Default:** `EditRecord.Subject` (Data Source: EditRecord)
- **DisplayMode:** `If(subjectdisabled, DisplayMode.Disabled, false, DisplayMode.View, DisplayMode.Edit)` (Function, Data: subjectdisabled)
- **Fill:** `subjectfill` (Data Source)
- **Reset:** `textreset` (Data Source)
- **Rectangle7_8 (Rectangle)**
- **TextBox5_56 (Label)**
- **Text:** `EditRecord.ID` (Data Source: EditRecord)
- **TextBox5_50 (Label)**
- **TextBox5_58 (Label)**
- **Text:** `EditRecord.Owner` (Data Source: EditRecord)
- **TextBox5_52 (Label)**
- **TextBox5_53 (Label)**
- **TextBox5_54 (Label)**
- **TextBox5_61 (Label)**
- **Text:** `If(IsBlank(EditRecord.DateClosed), "--", EditRecord.DateClosed)` (Function, Data: EditRecord.DateClosed)
- **TextBox5_55 (Label)**
- **Button1 (Button)**
- **DisplayMode:** `If(false, DisplayMode.View, DisplayMode.Edit)`
- **OnSelect:** (Complex function with dependencies on multiple controls & data)
- `UpdateIf(TicketsCollect1, ID = EditRecord.ID, { Status: TextBox5_25.Text, AssignedTo: assign, DateClosed: "", AccountName: TextBox5_59.Text, Priority: TextBox5_60.Text, Subject: TextInput2.Text, Description: TextInput4.Text, Comment: TextInput3.Text })`
- `UpdateIf(TicketsCollect1, ID = EditRecord.ID, { Status: TextBox5_25.Text, AssignedTo: assign, DateClosed: TextBox3.Text, AccountName: TextBox5_59.Text, Priority: TextBox5_60.Text, Subject: TextInput2.Text, Description: TextInput4.Text, Comment: TextInput3.Text })`
- `Navigate(DashboardPage, ScreenTransition.Fade)`
- **Visible:** `If(type = "Completed", false, true)` (Data: type)
- **TextBox5_24 (Label)**
- **TextBox5_23 (Label)**
- **Rectangle7_7 (Rectangle)**
- **Rectangle7_6 (Rectangle)**
- **Rectangle7_5 (Rectangle)**
- **TextBox5_21 (Label)**
- **Rectangle7_4 (Rectangle)**
- **TextBox5_20 (Label)**
- **Rectangle7_3 (Rectangle)**
- **TextBox5_17 (Label)**
- **TextBox4_1 (Label)**
- **Rectangle1_1 (Rectangle)**
- **Rectangle5_7 (Rectangle)**
- **Rectangle1_14 (Rectangle)**
- **OnSelect:** `If(type = "Completed", "", Navigate(AssigntoPage, ScreenTransition.Fade, { assign: assign }))` (Data: type, Navigation, Data: assign)
- **Group3 (Group)**
- **Children:**
- **icon4 (Icon)**
- **OnSelect:**
- `UpdateContext({ textreset: false })`
- `UpdateContext({ textreset: true })`
- `Navigate(DashboardPage, ScreenTransition.Fade)`
- **Rectangle1_3 (Rectangle)**
- **OnSelect:**
- `UpdateContext({ textreset: false })`
- `UpdateContext({ textreset: true })`
- `Navigate(DashboardPage, ScreenTransition.Fade)`
**Data Sources:**
- EditRecord
- priority
- Area
- assign
- type
- description_disabled
- description_fill
- commentdisabled
- commentfill
- subjectdisabled
- subjectfill
- textreset
**Functions:**
- Today()
- IsBlank()
- If()
- UpdateIf()
- Navigate()
This visualization shows the interconnectedness of controls and functions within the TicketdetailsPage. You can analyze how data is used, how user interaction (OnSelect events) triggers navigation or updates, and the overall flow of information within this screen.