Skip to content

Instantly share code, notes, and snippets.

@laiso
Last active November 26, 2025 07:14
Show Gist options
  • Select an option

  • Save laiso/c5e1bb2f8a230d218b9e75e08b97a3aa to your computer and use it in GitHub Desktop.

Select an option

Save laiso/c5e1bb2f8a230d218b9e75e08b97a3aa to your computer and use it in GitHub Desktop.
PTC Demo
"""
Tools package for PTC token comparison demo
"""
from .count_words import count_words
from .filter_results import filter_results
from .save_summary import save_summary
__all__ = ['count_words', 'filter_results', 'save_summary']
"""
count_wordsツール: テキストファイルから単語頻度を分析する
要件 3.1, 3.2, 3.3 を実装
"""
import os
import MeCab
from collections import Counter
from typing import List, Dict
# ストップワード(助詞、助動詞、一般的な動詞など)
STOP_WORDS = {
# 助詞・助動詞
'の', 'に', 'は', 'を', 'た', 'が', 'で', 'て', 'と', 'し', 'れ', 'さ',
'ある', 'いる', 'も', 'する', 'から', 'な', 'こと', 'として', 'い',
'や', 'れる', 'など', 'なっ', 'ない', 'この', 'ため', 'その', 'あっ',
'よう', 'また', 'もの', 'という', 'あり', 'まで', 'られ', 'なる',
'へ', 'か', 'だ', 'これ', 'によって', 'により', 'おり', 'より', 'による',
'ず', 'なり', 'られる', 'において', 'ば', 'なかっ', 'なく', 'しかし',
'について', 'せ', 'だっ', 'その後', 'できる', 'それ', 'う', 'ので',
'なお', 'のみ', 'でき', 'き', 'つ', 'における', 'および', 'いう',
'さらに', 'でも', 'ら', 'たり', 'その他', 'に関する', 'たち', 'ます',
'ん', 'なら', 'に対して', '及び', 'これら', 'とき', 'では', 'にて',
# カタカナ表記の一般動詞(MeCabの基本形出力)
'スル', 'イル', 'アル', 'ナル', 'デキル', 'イウ', 'コト', 'モノ',
'ヨウ', 'トキ', 'トコロ', 'ワケ', 'タメ', 'ホウ', 'ツモリ', 'ハズ',
'ミル', 'オモウ', 'カンガエル', 'シマウ', 'オク', 'クル', 'イク',
'クレル', 'モラウ', 'アゲル', 'ヤル', 'サセル', 'レル', 'ラレル'
}
def count_words(filename: str) -> List[Dict[str, any]]:
"""
テキストファイルから単語頻度を分析する
Args:
filename: 分析対象のテキストファイル名(パスはツール側で解決)
Returns:
[
{"word": "単語", "count": 123},
...
]
頻度順にソートされた全単語リスト
Raises:
FileNotFoundError: ファイルが見つからない場合
Exception: MeCabの初期化や解析に失敗した場合
"""
try:
# パスを解決:カレントディレクトリまたは /workspace/ を試す
possible_paths = [
filename, # カレントディレクトリ
os.path.join(os.getcwd(), filename), # 明示的なカレントディレクトリ
os.path.join('/workspace', filename), # サンドボックス内のワークスペース
os.path.join('/tmp', filename), # サンドボックス内のtmp
]
path = None
for p in possible_paths:
if os.path.exists(p):
path = p
break
if path is None:
raise FileNotFoundError(f"ファイルが見つかりません: {filename}")
# ファイルを読み込む
with open(path, 'r', encoding='utf-8') as f:
text = f.read()
if not text.strip():
return []
# MeCabで形態素解析
tagger = MeCab.Tagger()
tagger.parse('') # 初期化のためのダミー呼び出し
node = tagger.parseToNode(text)
words = []
while node:
# 品詞情報を取得
features = node.feature.split(',')
pos = features[0] # 品詞
# 名詞、動詞、形容詞のみを抽出
if pos in ['名詞', '動詞', '形容詞']:
# 基本形を取得(利用可能な場合)
if len(features) >= 7 and features[6] != '*':
word = features[6]
else:
word = node.surface
# ストップワードと1文字の単語を除外
if word and len(word) > 1 and word not in STOP_WORDS:
words.append(word)
node = node.next
# 単語頻度をカウント
word_counts = Counter(words)
# 頻度順にソート(降順)
sorted_words = sorted(word_counts.items(), key=lambda x: x[1], reverse=True)
# JSON配列形式に変換
result = [
{"word": word, "count": count}
for word, count in sorted_words
]
return result
except FileNotFoundError as e:
raise e
except Exception as e:
raise Exception(f"単語頻度分析中にエラーが発生しました: {str(e)}")
if __name__ == "__main__":
# テスト用
import sys
if len(sys.argv) > 1:
result = count_words(sys.argv[1])
print(f"Total words: {len(result)}")
print("Top 10 words:")
for item in result[:10]:
print(f" {item['word']}: {item['count']}")
"""
filter_resultsツール: 単語頻度データをフィルタリングする
要件 4.1, 4.2, 4.3 を実装
"""
from typing import List, Dict
def filter_results(words: List[Dict[str, any]], min_freq: int) -> List[Dict[str, any]]:
"""
単語頻度データをフィルタリングする
Args:
words: 単語頻度データの配列
[{"word": str, "count": int}, ...]
min_freq: 最小頻度(この値以上の単語のみを返す)
Returns:
フィルタリングされた単語頻度データの配列
[{"word": str, "count": int}, ...]
Raises:
ValueError: 入力データの形式が不正な場合
"""
try:
# 入力検証
if not isinstance(words, list):
raise ValueError("words must be a list")
if not isinstance(min_freq, int):
raise ValueError("min_freq must be an integer")
# 各要素の形式を検証
for item in words:
if not isinstance(item, dict):
raise ValueError("Each item in words must be a dictionary")
if 'word' not in item or 'count' not in item:
raise ValueError("Each item must have 'word' and 'count' fields")
if not isinstance(item['count'], int):
raise ValueError("'count' field must be an integer")
# min_freq以上の単語のみをフィルタリング
filtered = [
item for item in words
if item['count'] >= min_freq
]
return filtered
except ValueError as e:
raise ValueError(f"入力データの検証エラー: {str(e)}")
except Exception as e:
raise Exception(f"フィルタリング中にエラーが発生しました: {str(e)}")
if __name__ == "__main__":
# テスト用
test_data = [
{"word": "テスト", "count": 50},
{"word": "データ", "count": 30},
{"word": "単語", "count": 15},
{"word": "頻度", "count": 8},
{"word": "分析", "count": 5},
]
print("Original data:")
for item in test_data:
print(f" {item['word']}: {item['count']}")
print("\nFiltered (min_freq=10):")
filtered = filter_results(test_data, 10)
for item in filtered:
print(f" {item['word']}: {item['count']}")
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
run_non_ptc.py
Non-PTC(従来のTool Use)実装
Claude APIの従来のツール呼び出し方式を使用して3つのツールを連鎖的に呼び出し、
トークン使用量を測定する。
"""
import os
import sys
import json
import time
from anthropic import Anthropic
from dotenv import load_dotenv
# 3つのツールをインポート
from tools.count_words import count_words
from tools.filter_results import filter_results
from tools.save_summary import save_summary
# 共通出力フォーマット関数をインポート
from output_formatter import format_token_usage, print_mode_header
def main():
"""Non-PTCスクリプトのメイン処理(3ツール連鎖)"""
# .envファイルから環境変数を読み込む
load_dotenv()
# 環境変数からAPI キーを読み込む
api_key = os.environ.get("ANTHROPIC_API_KEY")
if not api_key:
print("Error: ANTHROPIC_API_KEY環境変数が設定されていません")
print("以下のコマンドで設定してください:")
print("export ANTHROPIC_API_KEY='your-api-key'")
sys.exit(1)
# Anthropic クライアントを初期化
client = Anthropic(api_key=api_key)
# パラメータ設定
target_file = "sample_text.txt"
min_freq = 10 # フィルタリングの最小頻度
output_file = "result-non-ptc.txt" # 保存先ファイル
# ファイルの存在確認
if not os.path.exists(target_file):
print(f"Error: {target_file}が見つかりません")
sys.exit(1)
# 3つのツール定義(Non-PTC: allowed_callers=["direct"])
tools = [
{
"name": "count_words",
"description": "テキストファイルから単語頻度を分析し、JSON配列として返す。ファイル名のみを指定してください(パスはツール側で自動解決されます)。",
"input_schema": {
"type": "object",
"properties": {
"filename": {
"type": "string",
"description": "分析対象のテキストファイル名(例: 'sample_text.txt')"
}
},
"required": ["filename"]
},
"allowed_callers": ["direct"]
},
{
"name": "filter_results",
"description": "単語頻度データをフィルタリングし、指定された最小頻度以上の単語のみを返す",
"input_schema": {
"type": "object",
"properties": {
"words": {
"type": "array",
"description": "単語頻度データの配列",
"items": {
"type": "object",
"properties": {
"word": {"type": "string"},
"count": {"type": "integer"}
}
}
},
"min_freq": {
"type": "integer",
"description": "最小頻度(この値以上の単語のみを返す)"
}
},
"required": ["words", "min_freq"]
},
"allowed_callers": ["direct"]
},
{
"name": "save_summary",
"description": "フィルタリングされた単語頻度データをファイルに保存する。ファイル名のみを指定してください(パスはツール側で自動解決されます)。",
"input_schema": {
"type": "object",
"properties": {
"result": {
"type": "array",
"description": "保存する単語頻度データの配列",
"items": {
"type": "object",
"properties": {
"word": {"type": "string"},
"count": {"type": "integer"}
}
}
},
"output_filename": {
"type": "string",
"description": "保存先ファイル名(例: 'result-non-ptc.txt')"
}
},
"required": ["result", "output_filename"]
},
"allowed_callers": ["direct"]
}
]
# モード表示
print_mode_header("Non-PTC")
# 実行時間の計測開始
start_time = time.time()
# Claude API呼び出し(model: claude-sonnet-4-5)
print("Claude APIを呼び出しています...")
print(f"対象ファイル: {target_file}")
print(f"最小頻度: {min_freq}")
print(f"出力ファイル: {output_file}")
print()
# 会話履歴を保持
messages = [
{
"role": "user",
"content": f"""以下の3つのツールを順次呼び出して、単語頻度分析を実行してください:
1. count_words: ファイル名 "{target_file}" から単語頻度を分析(ファイル名のみ指定、パスは不要)
2. filter_results: 結果をフィルタリング(min_freq={min_freq})
3. save_summary: フィルタリング結果をファイル名 "{output_file}" に保存(ファイル名のみ指定、パスは不要)
注意:
- count_wordsとsave_summaryツールはファイル名のみを受け取ります(パスはツール側で自動解決されます)
- count_wordsツールはMeCabによる形態素解析を使用しているため、結果はカタカナ表記の基本形(例:「カイハツ」→「開発」、「ジッコウ」→「実行」)で返されます
最後に、以下の情報を報告してください:
- 保存が完了したこと
- 頻出単語トップ5とその出現回数(カタカナ表記と推測される漢字表記の両方を表示)
- 各頻出単語について、文章の多様性を高めるための類義語や置き換え表現の提案(日本語で3つずつ)
例:「カイハツ(開発)」→ 「構築」「実装」「作成」"""
}
]
# 初回メッセージ送信(betaヘッダーが必要)
message = client.beta.messages.create(
model="claude-sonnet-4-5",
betas=["advanced-tool-use-2025-11-20"],
max_tokens=8192,
tools=tools,
messages=messages
)
# ツール実行ループ(3ツール順次呼び出し)
while message.stop_reason == "tool_use":
# tool_useブロックを処理
tool_results = []
for content_block in message.content:
if content_block.type == "tool_use":
tool_name = content_block.name
tool_input = content_block.input
tool_use_id = content_block.id
print(f"ツール呼び出し: {tool_name}")
# パラメータを短縮表示
compact_input = {}
for key, value in tool_input.items():
if isinstance(value, list) and len(value) > 3:
compact_input[key] = f"[{len(value)} items]"
elif isinstance(value, str) and len(value) > 50:
compact_input[key] = value[:50] + "..."
else:
compact_input[key] = value
print(f" パラメータ: {json.dumps(compact_input, ensure_ascii=False)}")
try:
if tool_name == "count_words":
# count_wordsツールを実行
result = count_words(filename=tool_input["filename"])
# 完全なJSON結果をtool_resultとして返送
tool_results.append({
"type": "tool_result",
"tool_use_id": tool_use_id,
"content": json.dumps(result, ensure_ascii=False)
})
print(f" → {len(result)}件の単語を返しました")
elif tool_name == "filter_results":
# filter_resultsツールを実行
result = filter_results(
words=tool_input["words"],
min_freq=tool_input["min_freq"]
)
# 完全なJSON結果をtool_resultとして返送
tool_results.append({
"type": "tool_result",
"tool_use_id": tool_use_id,
"content": json.dumps(result, ensure_ascii=False)
})
print(f" → {len(result)}件の単語にフィルタリングしました")
elif tool_name == "save_summary":
# save_summaryツールを実行
result = save_summary(
result=tool_input["result"],
output_filename=tool_input["output_filename"]
)
# ステータスをtool_resultとして返送
tool_results.append({
"type": "tool_result",
"tool_use_id": tool_use_id,
"content": json.dumps(result, ensure_ascii=False)
})
print(f" → ファイルに保存しました: {result['path']}")
else:
# 未知のツール
tool_results.append({
"type": "tool_result",
"tool_use_id": tool_use_id,
"content": json.dumps({"error": f"Unknown tool: {tool_name}"}, ensure_ascii=False),
"is_error": True
})
print(f" → エラー: 未知のツール")
except Exception as e:
# エラーが発生した場合
tool_results.append({
"type": "tool_result",
"tool_use_id": tool_use_id,
"content": json.dumps({"error": str(e)}, ensure_ascii=False),
"is_error": True
})
print(f" → エラー: {e}")
# 会話履歴にassistantの応答を追加
messages.append({
"role": "assistant",
"content": message.content
})
# tool_resultを追加
messages.append({
"role": "user",
"content": tool_results
})
# tool_resultを返送(会話履歴を引き継ぐ)
message = client.beta.messages.create(
model="claude-sonnet-4-5",
betas=["advanced-tool-use-2025-11-20"],
max_tokens=8192,
tools=tools,
messages=messages
)
# 実行時間の計測終了
end_time = time.time()
execution_time = end_time - start_time
# 最終レスポンスを表示
print()
print("=" * 60)
print("Claude の回答:")
print("=" * 60)
for content_block in message.content:
if hasattr(content_block, "text"):
print(content_block.text)
print()
# トークン使用量の出力(input_tokens, output_tokens)
print(format_token_usage(
mode="Non-PTC",
min_freq=min_freq,
input_tokens=message.usage.input_tokens,
output_tokens=message.usage.output_tokens,
execution_time=execution_time
))
if __name__ == "__main__":
main()
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
run_ptc.py
PTC(Programmatic Tool Calling)実装
Claude APIのPTC機能を使用して3つのツールを連鎖的に呼び出し、
トークン使用量を測定する。
"""
import os
import sys
import json
import time
from anthropic import Anthropic
from dotenv import load_dotenv
# 3つのツールをインポート
from tools.count_words import count_words
from tools.filter_results import filter_results
from tools.save_summary import save_summary
# 共通出力フォーマット関数をインポート
from output_formatter import format_token_usage, print_mode_header
def main():
"""PTCスクリプトのメイン処理"""
# .envファイルから環境変数を読み込む
load_dotenv()
# 環境変数からAPI キーを読み込む
api_key = os.environ.get("ANTHROPIC_API_KEY")
if not api_key:
print("Error: ANTHROPIC_API_KEY環境変数が設定されていません")
print("以下のコマンドで設定してください:")
print("export ANTHROPIC_API_KEY='your-api-key'")
sys.exit(1)
# Anthropic クライアントを初期化
client = Anthropic(api_key=api_key)
# 入力ファイルとパラメータ
target_file = "sample_text.txt"
if not os.path.exists(target_file):
print(f"Error: {target_file}が見つかりません")
sys.exit(1)
min_freq = 10 # フィルタリングの最小頻度
output_file = "result-ptc.txt" # 保存先ファイル
# 3つのツール定義を実装(各allowed_callers=["code_execution_20250825"])
tools = [
{
"type": "code_execution_20250825",
"name": "code_execution"
},
{
"name": "count_words",
"description": "テキストファイルから単語頻度を分析し、頻度順にソートされた結果を返します。ファイル名のみを指定してください(パスはツール側で自動解決されます)。",
"input_schema": {
"type": "object",
"properties": {
"filename": {
"type": "string",
"description": "分析対象のテキストファイル名(例: 'sample_text.txt')"
}
},
"required": ["filename"]
},
"allowed_callers": ["code_execution_20250825"]
},
{
"name": "filter_results",
"description": "単語頻度データをフィルタリングし、指定された最小頻度以上の単語のみを返します。",
"input_schema": {
"type": "object",
"properties": {
"words": {
"type": "array",
"description": "単語頻度データの配列",
"items": {
"type": "object",
"properties": {
"word": {"type": "string"},
"count": {"type": "integer"}
}
}
},
"min_freq": {
"type": "integer",
"description": "最小頻度(この値以上の単語のみを返す)"
}
},
"required": ["words", "min_freq"]
},
"allowed_callers": ["code_execution_20250825"]
},
{
"name": "save_summary",
"description": "フィルタリングされた単語頻度データをファイルに保存します。ファイル名のみを指定してください(パスはツール側で自動解決されます)。",
"input_schema": {
"type": "object",
"properties": {
"result": {
"type": "array",
"description": "保存する単語頻度データの配列",
"items": {
"type": "object",
"properties": {
"word": {"type": "string"},
"count": {"type": "integer"}
}
}
},
"output_filename": {
"type": "string",
"description": "保存先ファイル名(例: 'result-ptc.txt')"
}
},
"required": ["result", "output_filename"]
},
"allowed_callers": ["code_execution_20250825"]
}
]
# モード表示("PTC")
print_mode_header("PTC")
# 実行時間の計測開始
start_time = time.time()
# Claude API呼び出しの設定(model: claude-sonnet-4-5, beta: advanced-tool-use-2025-11-20)
print("Claude APIを呼び出しています...")
# プロンプト:3つのツールを連鎖的に呼び出し、最後に類義語を提案
messages = [
{
"role": "user",
"content": f"""以下の3つのツールを順次呼び出して、単語頻度分析を実行してください:
1. count_words: ファイル名 "{target_file}" から単語頻度を分析(ファイル名のみ指定、パスは不要)
2. filter_results: 結果をフィルタリング(min_freq={min_freq})
3. save_summary: フィルタリング結果をファイル名 "{output_file}" に保存(ファイル名のみ指定、パスは不要)
注意:
- count_wordsとsave_summaryツールはファイル名のみを受け取ります(パスはツール側で自動解決されます)
- count_wordsツールはMeCabによる形態素解析を使用しているため、結果はカタカナ表記の基本形(例:「カイハツ」→「開発」、「ジッコウ」→「実行」)で返されます
最後に、以下の情報を報告してください:
- 保存が完了したこと
- 頻出単語トップ5とその出現回数(カタカナ表記と推測される漢字表記の両方を表示)
- 各頻出単語について、文章の多様性を高めるための類義語や置き換え表現の提案(日本語で3つずつ)
例:「カイハツ(開発)」→ 「構築」「実装」「作成」"""
}
]
# 初回メッセージ送信
message = client.beta.messages.create(
model="claude-sonnet-4-5",
betas=["advanced-tool-use-2025-11-20"],
max_tokens=8192,
tools=tools,
messages=messages
)
# container IDを保持(PTCのサンドボックス維持に必須)
container_id = None
if hasattr(message, 'container') and message.container:
container_id = message.container.id
# サーバーサイドツール実行の処理を実装
# stop_reasonが"tool_use"の間ループ(PTCの正しいライフサイクル)
iteration = 0
while message.stop_reason == "tool_use":
iteration += 1
print(f"\n--- イテレーション {iteration} ---")
# server_tool_useとtool_useを分離して処理
server_tool_use_blocks = []
tool_use_blocks = []
for content_block in message.content:
if content_block.type == "server_tool_use":
server_tool_use_blocks.append(content_block)
# Claudeが生成したPythonコードをログ出力(デバッグ用)
print(f"\n[server_tool_use] id={content_block.id}, name={content_block.name}")
if hasattr(content_block, 'input') and 'code' in content_block.input:
print("生成されたPythonコード:")
print("-" * 40)
print(content_block.input['code'])
print("-" * 40)
elif content_block.type == "server_tool_result":
# サンドボックスからの実行結果をログ出力
print(f"\n[server_tool_result] id={content_block.id}")
if hasattr(content_block, 'output'):
print("サンドボックス実行結果:")
print("-" * 40)
print(content_block.output)
print("-" * 40)
elif content_block.type == "tool_use":
tool_use_blocks.append(content_block)
# サーバーサイドツール実行ループを実装(3ツール連鎖)
tool_results = []
for tool_use_block in tool_use_blocks:
tool_name = tool_use_block.name
tool_input = tool_use_block.input
tool_use_id = tool_use_block.id
print(f"\n[tool_use] id={tool_use_id}, name={tool_name}")
# パラメータを短縮表示
compact_input = {}
for key, value in tool_input.items():
if isinstance(value, list) and len(value) > 3:
compact_input[key] = f"[{len(value)} items]"
elif isinstance(value, str) and len(value) > 50:
compact_input[key] = value[:50] + "..."
else:
compact_input[key] = value
print(f"パラメータ: {json.dumps(compact_input, ensure_ascii=False)}")
try:
if tool_name == "count_words":
# count_words関数を実行
result = count_words(filename=tool_input["filename"])
# 結果をtool_resultとして準備(サンドボックスへ)
tool_results.append({
"type": "tool_result",
"tool_use_id": tool_use_id,
"content": json.dumps(result, ensure_ascii=False)
})
print(f"結果: {len(result)}件の単語を返しました")
elif tool_name == "filter_results":
# filter_results関数を実行
result = filter_results(
words=tool_input["words"],
min_freq=tool_input["min_freq"]
)
# 結果をtool_resultとして準備(サンドボックスへ)
tool_results.append({
"type": "tool_result",
"tool_use_id": tool_use_id,
"content": json.dumps(result, ensure_ascii=False)
})
print(f"結果: {len(result)}件の単語にフィルタリングしました")
elif tool_name == "save_summary":
# save_summary関数を実行
result = save_summary(
result=tool_input["result"],
output_filename=tool_input["output_filename"]
)
# 簡潔なステータスのみをtool_resultとして準備(サンドボックスへ)
tool_results.append({
"type": "tool_result",
"tool_use_id": tool_use_id,
"content": json.dumps(result, ensure_ascii=False)
})
print(f"結果: ファイル保存完了 - {result['path']}")
except Exception as e:
# エラーが発生した場合
tool_results.append({
"type": "tool_result",
"tool_use_id": tool_use_id,
"content": json.dumps({"error": str(e)}, ensure_ascii=False),
"is_error": True
})
print(f"エラー: {e}")
# 会話履歴にassistantの応答を追加
messages.append({
"role": "assistant",
"content": message.content
})
# tool_resultを追加
messages.append({
"role": "user",
"content": tool_results
})
# 次のAPI呼び出し(会話履歴とcontainerを引き継ぐ)
request_params = {
"model": "claude-sonnet-4-5",
"betas": ["advanced-tool-use-2025-11-20"],
"max_tokens": 8192,
"tools": tools,
"messages": messages
}
# container IDがあれば引き継ぐ(サンドボックス維持に必須)
if container_id:
request_params["container"] = container_id
message = client.beta.messages.create(**request_params)
# container IDを更新
if hasattr(message, 'container') and message.container:
container_id = message.container.id
# 実行時間の計測終了
end_time = time.time()
execution_time = end_time - start_time
# 最終レスポンスを表示(サンドボックスからのprint出力のみ)
print()
print("=" * 60)
print("Claude の回答:")
print("=" * 60)
for content_block in message.content:
if hasattr(content_block, "text"):
print(content_block.text)
print()
# トークン使用量の出力処理を実装(input_tokens, output_tokens, total_tokens)
print(format_token_usage(
mode="PTC",
min_freq=min_freq,
input_tokens=message.usage.input_tokens,
output_tokens=message.usage.output_tokens,
execution_time=execution_time
))
if __name__ == "__main__":
main()
"""
save_summaryツール: フィルタリングされた結果をファイルに保存する
要件 5.1, 5.2, 5.3, 5.4 を実装
"""
import os
import json
from typing import List, Dict
def save_summary(result: List[Dict[str, any]], output_filename: str) -> Dict[str, str]:
"""
フィルタリングされた結果をファイルに保存する
Args:
result: 保存する単語頻度データの配列
[{"word": str, "count": int}, ...]
output_filename: 保存先ファイル名(パスはツール側で解決)
Returns:
{"status": "success", "path": 実際の保存先パス}
Raises:
ValueError: 入力データの形式が不正な場合
IOError: ファイル保存に失敗した場合
"""
try:
# 入力検証
if not isinstance(result, list):
raise ValueError("result must be a list")
if not isinstance(output_filename, str) or not output_filename:
raise ValueError("output_filename must be a non-empty string")
# 各要素の形式を検証
for item in result:
if not isinstance(item, dict):
raise ValueError("Each item in result must be a dictionary")
if 'word' not in item or 'count' not in item:
raise ValueError("Each item must have 'word' and 'count' fields")
# パスを解決:カレントディレクトリまたは /workspace/ を試す
possible_dirs = [
os.getcwd(), # カレントディレクトリ
'/workspace', # サンドボックス内のワークスペース
'/tmp', # サンドボックス内のtmp
]
output_path = None
for directory in possible_dirs:
try:
test_path = os.path.join(directory, output_filename)
# ディレクトリが書き込み可能かテスト
if os.path.isdir(directory) and os.access(directory, os.W_OK):
output_path = test_path
break
except:
continue
if output_path is None:
# フォールバック:カレントディレクトリに保存
output_path = output_filename
# 人間が読みやすい形式でファイルに保存
with open(output_path, 'w', encoding='utf-8') as f:
f.write("単語頻度分析結果\n")
f.write("=" * 50 + "\n\n")
f.write(f"総単語数: {len(result)}\n\n")
f.write("-" * 50 + "\n")
f.write(f"{'単語':<20} {'頻度':>10}\n")
f.write("-" * 50 + "\n")
for item in result:
word = item['word']
count = item['count']
f.write(f"{word:<20} {count:>10}\n")
f.write("-" * 50 + "\n")
return {
"status": "success",
"path": output_path
}
except ValueError as e:
raise ValueError(f"入力データの検証エラー: {str(e)}")
except IOError as e:
raise IOError(f"ファイル保存エラー: {str(e)}")
except Exception as e:
raise Exception(f"保存処理中にエラーが発生しました: {str(e)}")
if __name__ == "__main__":
# テスト用
test_data = [
{"word": "テスト", "count": 50},
{"word": "データ", "count": 30},
{"word": "単語", "count": 15},
{"word": "頻度", "count": 10},
]
result = save_summary(test_data, "test_summary.txt")
print(f"Status: {result['status']}")
print(f"Saved to: {result['path']}")
# 保存されたファイルを読み込んで表示
print("\nSaved content:")
with open(result['path'], 'r', encoding='utf-8') as f:
print(f.read())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment