😷
ChatGPTで使うためにコードをマスクしたり戻したりする
やりたいこと
-
業務でChatGPTを使うとき、業務での用語をChatGPTに渡さないようにマスクしたい
- Pythonスクリプトを使って、マスクしたり戻せるように
- ホワイトリストを作って、一般的な単語は残す
- 残さないと、ChatGPTが理解できない
- suffix, prefixで指定した部分を残す
- 自分が理解しやすいように
-
注:ロジックそのものは隠せないので変数名を隠すという意味
書いてないこと
- マスクする是非、何をマスクすべきか、など
Code
- 全部同じフォルダにある想定
- result...はスクリプトで作成されるファイル
ファイル | 内容 |
---|---|
whitelist.txt | ホワイトリスト |
input_mask.txt | マスク用入力 |
do_mask.py | マスク操作 |
result_mask.txt | マスクした結果 |
result_mapping.txt | マスクのマッピング保存 |
input_unmask.txt | 戻す用入力 |
do_unmask.py | 戻す操作 |
result_unmask.txt | 戻した結果 |
マスク処理でやっていること
- デフォルトでは見つけた単語のを順にa01, a02, ...と置き換える
- result_mapping.txtに下記のように保存する
a01,something1
a02,something2
whitelistの記法
- 単語ごとの完全一致
- Pythonの予約語
- suffix:_id と書くと、語尾にある_idの部分は残す
- 例:
some_id -> a01_id
- 例:
- prefix:some_ と書くと、語頭にあるsome_の部分は残す
- 例:
some_id -> some_a01
- 例:
Code
クリックして開く
do_mask.py
import io
import os
import re
import token
import tokenize
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
WHITELIST_FILE = os.path.join(ROOT_DIR, 'whitelist.txt')
INPUT_FILE = os.path.join(ROOT_DIR, 'input_mask.txt')
RESULT_MASK_FILE = os.path.join(ROOT_DIR, 'result_mask.txt')
RESULT_MAPPING_FILE = os.path.join(ROOT_DIR, 'result_mapping.txt')
MASK_PREFIX = 'a'
ZERO_PAD = 2
def remove_comments_and_docstrings(source):
io_obj = io.StringIO(source)
out = []
prev_toktype = token.INDENT
last_lineno = -1
last_col = 0
for tok in tokenize.generate_tokens(io_obj.readline):
toktype, ttext, (slineno, scol), (elineno, ecol), _ = tok
if is_comment_or_string(toktype, prev_toktype):
continue
last_col = update_col(slineno, last_lineno, last_col)
if need_space(scol, last_col):
out.append(" " * (scol - last_col))
out.append(ttext)
prev_toktype = toktype
last_lineno = elineno
last_col = ecol
return "".join(out)
def is_comment_or_string(toktype, prev_toktype):
return toktype in (tokenize.COMMENT, token.STRING) and prev_toktype == token.INDENT
def update_col(slineno, last_lineno, last_col):
return 0 if slineno > last_lineno else last_col
def need_space(scol, last_col):
return scol > last_col
def load_whitelist(whitelist_file):
"""Loads whitelist from file and splits it into words, prefixes and suffixes."""
with open(whitelist_file) as file:
whitelist = set(line.strip() for line in file if line.strip() != '')
prefixes = {w[7:] for w in whitelist if w.startswith('prefix:')}
suffixes = {w[7:] for w in whitelist if w.startswith('suffix:')}
words = whitelist - prefixes - suffixes
return list(words), list(prefixes), list(suffixes)
def create_replacement(word, counter, prefix=None, suffix=None):
"""Creates a replacement for a given word with an optional prefix or suffix."""
replacement = f"{prefix if prefix else ''}{MASK_PREFIX}{counter:0{ZERO_PAD}d}{suffix if suffix else ''}"
return replacement
def mask_text(input_file, words, prefixes, suffixes, mask_numbers=True):
"""Masks words in text according to whitelist of words, prefixes and suffixes."""
with open(input_file, 'r') as f:
source = f.read()
text = remove_comments_and_docstrings(source)
masked_text = text
mapping = []
counter = 1
words_in_text = set(re.findall(r'\b\w+\b', text))
for word in words_in_text:
if word in words or (not mask_numbers and re.match(r'^\d+(\.\d+)?$', word)):
continue
replacement = None
for prefix in prefixes:
if word.startswith(prefix):
replacement = create_replacement(word, counter, prefix=prefix)
break
if not replacement:
for suffix in suffixes:
if word.endswith(suffix):
replacement = create_replacement(word, counter, suffix=suffix)
break
if not replacement:
replacement = create_replacement(word, counter)
masked_text = re.sub(rf'\b{re.escape(word)}\b', replacement, masked_text)
mapping.append(f'{replacement},{word}')
counter += 1
with open(RESULT_MASK_FILE, 'w') as f:
f.write(masked_text)
with open(RESULT_MAPPING_FILE, 'w') as f:
f.write('\n'.join(mapping))
print(RESULT_MASK_FILE)
def main():
"""Main execution function."""
words, prefixes, suffixes = load_whitelist(WHITELIST_FILE)
mask_text(INPUT_FILE, words, prefixes, suffixes, mask_numbers=False)
if __name__ == "__main__":
main()
do_unmask.py
import os
import re
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
def load_mapping(mapping_file):
with open(mapping_file, 'r') as f:
return dict(line.strip().split(',') for line in f)
def unmask_text(mapping, result_file, output_file):
with open(result_file, 'r') as f:
masked_text = f.read()
unmasked_text = masked_text
for masked_var, word in mapping.items():
unmasked_text = re.sub(rf'\b{re.escape(masked_var)}\b', word, unmasked_text)
with open(output_file, 'w') as f:
f.write(unmasked_text)
print(output_file)
if __name__ == "__main__":
mapping = load_mapping(os.path.join(ROOT_DIR, 'result_mapping.txt'))
unmask_text(mapping, os.path.join(ROOT_DIR, 'input_unmask.txt'), os.path.join(ROOT_DIR, 'result_unmask.txt'))
- 以下にはPythonの予約語、ビルトイン関数、標準ライブラリが入っている
- 空白行は無視される
whitelist.txt
and
as
assert
async
await
break
class
continue
def
del
elif
else
except
False
finally
for
from
global
if
import
in
is
lambda
None
nonlocal
not
or
pass
raise
return
True
try
while
with
yield
abs
all
any
ascii
bin
bool
breakpoint
bytearray
bytes
callable
chr
classmethod
compile
complex
delattr
dict
dir
divmod
enumerate
eval
exec
filter
float
format
frozenset
getattr
globals
hasattr
hash
help
hex
id
input
int
isinstance
issubclass
iter
len
list
locals
map
max
memoryview
min
next
object
oct
open
ord
pow
print
property
range
repr
reversed
round
set
setattr
slice
sorted
staticmethod
str
sum
super
tuple
type
vars
zip
import
abc
argparse
array
asyncio
binascii
bisect
builtins
bz2
calendar
cmath
collections
contextlib
copy
csv
datetime
decimal
dis
doctest
enum
functools
gc
glob
gzip
hashlib
heapq
io
itertools
json
locale
logging
math
mmap
multiprocessing
operator
os
pathlib
pickle
pprint
queue
random
re
select
shutil
signal
socket
sqlite3
ssl
statistics
string
struct
subprocess
sys
tempfile
threading
time
timeit
tkinter
traceback
types
typing
unicodedata
unittest
urllib
uuid
venv
warnings
xml
zipfile
zlib
使い方
1.whitelist.txt
を確認/修正する
2. input_mask.txt
にマスクしたい部分を貼り付け
3. do_mask.py
を実行
4. result_mask.txt
を確認
5. 修正する場合(1.)に戻る。OKなら(6.)へ
6. ChatGPTに投げる
7. ChatGPTの出力をinput_unmask.txt
に貼り付け
8. do_unmask.py
を実行
9. result_unmask.txt
を確認する
デモ
このコードを使います(特に理由ないです)。引用 https://docs.pytest.org/en/7.4.x/
# content of test_sample.py
def inc(x):
return x + 1
def test_answer():
assert inc(3) == 5
これを上記のwhitelistを使ってマスクします。例えば
python3 do_mask.py
このような結果になります。コメントは削除されます。
result_mask.txt
def a02(a03):
return a03 + 1
def a01():
assert a02(3) == 5
result_mapping.txt
a01,test_answer
a02,inc
a03,x
prefix:を使う
上記の続きで、「test_
の部分はマスクしない」ようにします。その場合、prefix:test_
をwhitelist.txt
に追加します。以下のようになるはずです。
result_mask.txt
def a01(a02):
return a02 + 1
def test_a03():
assert a01(3) == 5
result_mapping.txt
a01,inc
a02,x
test_a03,test_answer
あとはこれを使ってChatGPTに聞けばよいです。
まとめ
- ChatGPTに業務情報を入れないようにするためにマスクするPythonスクリプトを作りました
Discussion