ChatGPT peut il m'assister ? - Partie 2
Dans le précédent billet, nous avons pu voir que chatGPT n’est pas si magique que cela. En effet, il ne suit pas forcément les bonnes pratiques et surtout ne propose pas toujours les solutions adaptées. La plus grosse difficulté est de trouver quelles informations lui fournir pour qu’il produise du contenu proche d’une solution acceptable.
Dans ce billet, je vais chercher des solutions pour fournir à chatGPT
un
template correspondant à mon besoin avec les options que j’attends et respectant
l’utilisation des FQCN.
Comment stocker les templates ?
L’idée est de décrire les taches avec des phrases proches du langage naturel. Ex: Copy a file /path/to/test to the path /path/to/ with mode 0666 owner test group tests.
J’ai constitué une base de données regroupant tous les modules Ansible indexés sur leur phrase de description. Pour constituer cette base, j’utilise mon outil qui génère les snippets vscode.
Exemple de template :
{ "execute command on remote node create remove lock": { "tasks": [ { "name": "Execute commands on targets", "ansible.builtin.command": { "chdir": "/tmp/test", "cmd": "/usr/bin/make_database.sh db_user db_name && touch /var/run/test.lock", "creates": "/var/run/test.lock", "removes": "/var/run/test.lock" } } ] },}
Et non ce n’est pas une base de données, mais un simple fichier json. Pour info mon fichier contient 650 templates regroupant les collections ansible les plus courantes.
Comment identifier le bon template ?
Maintenant que j’ai constitué ma base de templates, je dois trouver une solution permettant de trouver une similarité entre la phrase décrivant la tache et celle des modules.
Je me suis tourné vers Gensim, une bibliothèque logicielle Python de topic modelling.
#!/usr/bin/env python3
import osimport reimport sysfrom rich import print_jsonfrom gensim import corpora, models, similaritiesimport jiebaimport json
with open("snippets.json") as json_file: snippets = json.load(json_file)keyword = sys.argv[1]analysed_snippets = [jieba.lcut(snippet.lower()) for snippet in snippets]dictionary = corpora.Dictionary(analysed_snippets)feature_cnt = len(dictionary.token2id)corpus = [dictionary.doc2bow(snippet) for snippet in analysed_snippets]tfidf = models.TfidfModel(corpus)kw_vector = dictionary.doc2bow(jieba.lcut(keyword))index = similarities.SparseMatrixSimilarity(tfidf[corpus], num_features=feature_cnt)sim = list(index[tfidf[kw_vector]])index = sim.index(max(sim))snippet = snippets[list(snippets)[index]]print_json (json.dumps(snippet))
On lance notre premier test en ciblant le module ansible ci-dessus.
./test.py "execute commande /usr/bin/start.sh and creates lock /var/run/test.lock"Building prefix dict from the default dictionary ...Loading model from cache /tmp/jieba.cacheLoading model cost 0.700 seconds.Prefix dict has been built successfully.{ "tasks": [ { "name": "Execute commands on targets", "ansible.builtin.command": { "chdir": "/tmp/test", "cmd": "/usr/bin/make_database.sh db_user db_name", "creates": "/var/run/test.lock", "removes": "/var/run/test.lock" } } ]}
Bingo c’est le résultat attendu. Le plus long va être de corriger les descriptions de tous mes modules.
On enrichit notre demande à ChatGPT
Maintenant le code minimal pour poser une question à chatGPT
:
#!/usr/bin/env python3
import osimport reimport sysfrom rich import print_json, printfrom gensim import corpora, models, similaritiesimport jiebaimport jsonimport ruamel.yamlfrom rich.console import Consolefrom rich.text import Textfrom rich.markdown import Markdownimport openai
with open("snippets.json") as json_file: snippets = json.load(json_file)keyword = sys.argv[1]analysed_snippets = [jieba.lcut(snippet.lower()) for snippet in snippets]dictionary = corpora.Dictionary(analysed_snippets)feature_cnt = len(dictionary.token2id)corpus = [dictionary.doc2bow(snippet) for snippet in analysed_snippets]tfidf = models.TfidfModel(corpus)kw_vector = dictionary.doc2bow(jieba.lcut(keyword))index = similarities.SparseMatrixSimilarity(tfidf[corpus], num_features=feature_cnt)sim = list(index[tfidf[kw_vector]])index = sim.index(max(sim))snippet = snippets[list(snippets)[index]]openai.api_key = os.getenv("OPENAI_KEY")system_message = "You are an ansible expert. Use FQCN. No comments. Json:" % (snippet)system_user = "You must write a ansible task ansible en with all template options to %s #template 1 %s. No comments. Json:" % (sys.argv[1],snippet)print("Demande envoyé à ChatGPT: \nsystem : %s\nuser: %s" % (system_message, system_user))response = openai.ChatCompletion.create( model="gpt-3.5-turbo-0301", messages=[ { "role": "system", "content": system_message, }, {"role": "user", "content": system_user}, ], temperature = 0.5)chatgpt_response = response["choices"][0]["message"]["content"]chatgpt_response = re.sub(r'\s(?={^\{\}}*}})', "_", chatgpt_response)tokens = response['usage']['total_tokens']console = Console()print("ChatGPT réponds:")yaml = ruamel.yaml.YAML()yaml.indent(sequence=4, offset=2)try: yaml.dump(json.loads(chatgpt_response)["tasks"], sys.stdout)except: print(chatgpt_response)print("Tokens utilisés %i" % tokens)
On lance notre premier test :
./test.py "execute commande /usr/bin/start.sh and creates lock /var/run/test.lock"Building prefix dict from the default dictionary ...Loading model from cache /tmp/jieba.cacheLoading model cost 0.700 seconds.Prefix dict has been built successfully.Demande envoyé à ChatGPT:system : You are an ansible expert. Use FQCN. No comments. Json:user: You must write a ansible task ansible en with all template options to execute commande /usr/bin/start.sh and creates lock /var/run/test.lock #template 1 {'tasks': [{'name': 'Execute commands on targets','ansible.builtin.command': {'chdir': '/tmp/test', 'cmd': '/usr/bin/make_database.sh db_user db_name', 'creates': '/var/run/test.lock', 'removes': '/var/run/test.lock'}}]}. No comments. Json:ChatGPT réponds: - name: Execute commands on targets ansible.builtin.command: chdir: /tmp/test cmd: /usr/bin/start.sh creates: /var/run/test.lockTokens utilisés 190
Nickel, il respecte les FQCN, fourni les options attendues.
Un autre exemple :
./test.py "Copy file on remote from source /path/to/test to /path/to/ mode 0666 owner test group tests"Building prefix dict from the default dictionary ...Loading model from cache /tmp/jieba.cacheLoading model cost 0.687 seconds.Prefix dict has been built successfully.Demande envoyé à ChatGPT:system : You are an ansible expert. Use FQCN. No comments. Json:user: You must write a ansible task ansible en with all template options to Copy file on remote from source /path/to/test to /path/to/ mode 0666 owner test group tests #template 1 {'tasks': [{'name': 'Copy files to remotelocations', 'ansible.builtin.copy': {'backup': False, 'dest': '/tmp/test', 'follow': False, 'force': True, 'group': 'group', 'mode': '0755', 'owner': 'user', 'remote_src': False, 'src': '/tmp/test', 'unsafe_writes': False}}]}. Nocomments. Json:ChatGPT réponds: - name: Copy files to remote locations ansible.builtin.copy: backup: false dest: /path/to/ follow: false force: true group: tests mode: '0666' owner: test remote_src: false src: /path/to/test unsafe_writes: falseTokens utilisés 262
La suite
Bon, j’obtiens des résultats satisfaisants. On va passer au développement d’une CLI prenant soit une tache, soit une liste de tâches via un fichier.
Ce fichier est une suite de taches et de blocks Ansible. J’utiliserai ce format :
- task: Install package htop, nginx and net-tools with generic module- task: Copy file from local file /tmp/toto to remote /tmp/titi set mode 0666 owner bob group www register: test- name: A block when: test.rc == 0 block: - task: wait for port 6300 on localhost timeout 25 rescue: - task: Execute command to start /opt/application/start.sh and creates /var/run/test.lock- task: Download file from https://tmp.io/test/ set mode 0640 and force true
J’ai déjà bien avancé et je publierai la cli d’ici à quelques jours. Le temps de corriger quelques bugs et renforcer le fonctionnement. A bientôt…