【Magicoder】ChatGPTとGeminiを超えていると噂の小規模言語モデルを比較レビューしてみた

Magicoder ChatGPT Gemini 小規模言語モデル

WEELメディア事業部テックリサーチャーの藤崎です。

2023年12月4日、Magicoderという言語モデルが発表されました。

Magicoderは7Bと小規模な言語モデルながら、コード生成のタスクにおいてChatGPTなどの大規模言語モデルを上回る性能を発揮しているとのこと!

今までもコードを生成することができるLLMというのはいくつも発表されてきましたが、それらが生成するコードデータには偏りがあり、多様性や現実性にやや欠いていました。
そこで、より実用性の高いコードを生成するフレームワークとして開発されたのが「Magicoder」なんです!

この記事ではMagicoderの概要から実際に使ってみた感想、そして他のコード生成ができるLLMとの比較などをお伝えします。
今までAIでコード生成してきたけど、もっと他のAIも試してみたい!と考えているプログラマーの方は必読です!

ぜひ最後までご覧ください!

なお弊社では、生成AIツール開発についての無料相談を承っています。こちらからお気軽にご相談ください。
→無料相談で話を聞いてみる

目次

Magicoderの概要

Magicoderは2023年12月4日に発表されたパラメータ数が7Bという比較的小規模な言語モデルです。

なんと、MagicoderはHumanEvalというコード生成にベンチマークにおいて、175Bのパラメータ数を持つChatGPT(GPT-3.5)を上回る性能を記録したとのこと!


引用:https://github.com/ise-uiuc/magicoder

しかも、7Bのパラメータ数を持つ他のコード生成ができるLLMと比べてみてもMagicoderの性能の高さはダントツですね。
さらに最新の情報では、いま話題のGemini Ultra をも上回る性能だとか!

Magicoder-S-DS-6.7B outperforms gpt-3.5-turbo-1106 and Gemini Ultra on HumanEval (76.8 vs. [72.6 and 74.4])!

引用:https://github.com/ise-uiuc/magicoder

そんな。Magicoderの性能が高い秘密の一つには新しいアプローチ方法が挙げられるそうです。

Magicoderは、オープンソースのコードスニペットを用いた新しいアプローチ、OSS-Instructによって強化されているようです。
これにより、低バイアスで高品質なコードデータを生成することができるとのこと。

引用:https://github.com/ise-uiuc/magicoder

つまり、ゼロからプログラミングをしているわけではなく、すでに公開されているオープンソースのコードスニペットを利用しこちらの指示に合うように組み替えて生成しているということですね!

たしかにオープンソースのコードは様々なプログラマーがブラッシュアップし、公開されているものが多いので有用かもしれませんね。
ただ、その反面セキュリティ的な部分が心配にはなりますが。。。

Magicoderの料金体系

MagicoderはMIT licenseで公開されているので、利用料金は無料です。

なお、Metaが開発したコード生成AIについて知りたい方はこちらの記事をご覧ください。
【CodeLlama-70B】700億パラメーターコード生成AIをGPT-4と比較してみた

Magicoderの使い方

では、実際にMagicoderの使い方を解説します。
今回、GoogleColab上でテストしました。

Magicodeを動かすのに必要なスペック

■使用したGPU
・T4

■Pythonのバージョン
・Python 3.10.12

■必要なパッケージ/ライブラリ
・transformers
・accelerate

なんと、今回のサンプルコードを実行するだけなら無料のGoogleColabでも動作確認ができました!

ただし、GPUが14GBでほぼ張り付きであったこと、ディスク使用量が50GBを超えていたのでギリギリのラインで動作していたように感じます。
もし、他のGoogleColabで他の作業もしているなどがあれば、有料のGoogleColabPro以上をおすすめします。

Magicoderの導入方法

まず、追加で必要なライブラリである「transformers」と「accelerate」をインストールします。

!pip install transformers accelerate

次に、パイプラインを使う準備を行います。

from transformers import pipeline
import torch

MAGICODER_PROMPT = """You are an exceptionally intelligent coding assistant that consistently delivers accurate and reliable responses to user instructions.

@@ Instruction
{instruction}

@@ Response
"""
generator = pipeline(
    model="ise-uiuc/Magicoder-S-DS-6.7B",
    task="text-generation",
    torch_dtype=torch.bfloat16,
    device_map="auto",
)

最後に、プロンプトを渡してコードを生成してもらいます!

instruction = "ここにプロンプトを入力してください"

prompt = MAGICODER_PROMPT.format(instruction=instruction)
result = generator(prompt, max_length=2048, num_return_sequences=1, temperature=0.0)
print(result[0]["generated_text"])

とっても簡単ですね!

Magicoderを実際に使ってみた

早速、Magicoderを使っていきましょう!ここでは簡単なプログラムの生成と日本語対応の確認を行いました。

まずは簡単なpythonで簡単なクイックソートを行うプログラムを生成してもらいます。
入力したプロンプトは下記の通りです。

instruction = "Please create a program in Python that performs quicksort."

prompt = MAGICODER_PROMPT.format(instruction=instruction)
result = generator(prompt, max_length=2048, num_return_sequences=1, temperature=0.0)
print(result[0]["generated_text"])

「pythonでクイックソートを行うプログラムを作ってください」を英語に訳したものを使用しましたが果たして結果は・・・?

@@ Instruction
Please create a program in Python that performs quicksort.

@@ Response
Sure, here is a simple implementation of quicksort in Python:

```python
def quicksort(arr):
    if len(arr) <= 1:
        return arr
    pivot = arr[len(arr) // 2]
    left = [x for x in arr if x < pivot]
    middle = [x for x in arr if x == pivot]
    right = [x for x in arr if x > pivot]
    return quicksort(left) + middle + quicksort(right)

print(quicksort([3,6,8,10,1,2,1]))
```

This program works by selecting a 'pivot' element from the array and partitioning the other elements into two sub-arrays, according to whether they are less than or greater than the pivot. The sub-arrays are then recursively sorted.

Please note that quicksort is not a stable sort, meaning that equal elements may not retain their original order. If you need a stable sort, you should use a different algorithm, such as mergesort.

こちらの意図通りクイックソートを行うプログラムを解説付きで生成してくれました。
生成速度も55秒ほどだったので、まずまずではないでしょうか。

実際に吐き出されたプログラムを実行しても、正常に動いたので問題なさそうです!

次に同じプロンプトを日本語で入力するとどうなるでしょうか?
結果はこちらです!

なんと、日本語でも詳しい解説つきで正常に起動するプログラムを生成してくれました!

Magicoderはgpt-3.5-turbo-1106で生成されたデータを使用してトレーニングされているとのことだったので、日本語の解釈もバッチリということでしょうか?
プログラム解説の内容を見ても言い回しに不自然なところは見当たりませんでした。

なお、Googleが開発したコード生成AIについて知りたい方はこちらの記事をご覧ください。
【AlphaCodium】Googleを超える最強コード生成AIが競プロで優勝できるか試してみた

Magicoderの推しポイントであるコード生成が得意というのは本当なのか?

いよいよMagicoderの推しポイントである、コード生成が他のLLMよりも得意というのは本当なのか、比較検証していきます!

検証する内容は「クイックソートのプログラム作成」「フィボナッチ数列の計算に関するプログラム作成」そして、「TODOアプリのためのAPIを実装」の3本立て!
プロンプトはできるだけ精度が高い方がいいので、英語に訳したものを入力しています。

比較するのはMagicoder、GPT3.5、GPT4、CodeLlama-Python(7B)、そしてGemini Proの5つのLLMです。
なお、CodeLlama-PythonはLM Studioでの実行となります。

では、比較していきましょう!

クイックソートのプログラム作成を比較してみた

Magicoderはクイックソートのプログラムが作れるということは「使い方」の項目で試しているので、残り3つのLLMで試してみます。
プロンプトは下記になります。

Please create a program in Python that performs quicksort.
【日本語訳】
Pythonでクイックソートを行うプログラムを作ってください

では、比較検証スタート!

GPT-3.5

def quicksort(arr):
    if len(arr) <= 1:
        return arr

    pivot = arr[len(arr) // 2]
    left = [x for x in arr if x < pivot]
    middle = [x for x in arr if x == pivot]
    right = [x for x in arr if x > pivot]

    return quicksort(left) + middle + quicksort(right)

# Example usage:
if __name__ == "__main__":
    unsorted_list = [3, 6, 8, 10, 1, 2, 1]
    sorted_list = quicksort(unsorted_list)
    print("Unsorted list:", unsorted_list)
    print("Sorted list:", sorted_list)

【実行結果】
Unsorted list: [3, 6, 8, 10, 1, 2, 1]
Sorted list: [1, 1, 2, 3, 6, 8, 10]

GPT-3.5は問題なくプログラムの生成ができており、生成されたものを実行しても正しくソートされた結果が返ってきました!

GPT 4

def quicksort(arr):
    if len(arr) <= 1:
        return arr
    pivot = arr[len(arr) // 2]
    left = [x for x in arr if x < pivot]
    middle = [x for x in arr if x == pivot]
    right = [x for x in arr if x > pivot]
    return quicksort(left) + middle + quicksort(right)

# Example usage
arr = [3, 6, 8, 10, 1, 2, 1]
sorted_arr = quicksort(arr)
print(sorted_arr)

【実行結果】
[1, 1, 2, 3, 6, 8, 10]

GPT 4が生成したプログラムはGPT3.5と非常に似ていますね。
異なるのは実行部分ですが、間違いなく答えも表示されているので問題ありません。

CodeLlama-Python(7B)

def quick_sort(arr):
   if len(arr) <= 1:
       return arr
   else:
       pivot = arr[0]
       lesser = [i for i in arr[1:] if i < pivot]
       greater = [i for i in arr[1:] if i >= pivot]
       return quick_sort(lesser) + [pivot] + quick_sort(greater)
n=int(input())
arr=[int(x) for x in input().split()]
print(*quick_sort(arr))

【実行結果】
5 #ソートする数字の数
1 3 2 5 4 #ソートする数字
1 2 3 4 5 #ソート結果

CodeLlama-Pythonも正常に動作するプログラムを生成してくれました。
ただ、他と違うところは生成されたプログラムを実行すると、まずソートする数字の数を入力し、その後にソートする数字を入力するという対話形式のプログラムとなっていました。

Gemini Pro

def partition(data, low, high):
  """
  This function partitions the data around a pivot element.

  Args:
    data: The list of data to be partitioned.
    low: The starting index of the data to be partitioned.
    high: The ending index of the data to be partitioned.

  Returns:
    The index of the pivot element after partitioning.
  """
  pivot = data[high]
  i = low - 1
  for j in range(low, high):
    if data[j] <= pivot:
      i += 1
      data[i], data[j] = data[j], data[i]
  data[i + 1], data[high] = data[high], data[i + 1]
  return i + 1

def quicksort(data, low, high):
  """
  This function sorts the data using the quicksort algorithm.

  Args:
    data: The list of data to be sorted.
    low: The starting index of the data to be sorted.
    high: The ending index of the data to be sorted.
  """
  if low < high:
    pi = partition(data, low, high)
    quicksort(data, low, pi - 1)
    quicksort(data, pi + 1, high)

# Example usage
data = [8, 7, 2, 1, 0, 9, 6, 5, 3, 4]
quicksort(data, 0, len(data) - 1)
print(data)

【実行結果】
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Gemini Proはコード中にコメントも書いてくれていて親切ですね。
出力されたコードの動作も問題ありませんでした!

クイックソートのプログラム作成は、それぞれアプローチ方法は違えど、すべてのLLMで正常に動くことが確認できました!

フィボナッチ数列を出力するプログラム生成を試してみた

次にフィボナッチ数列を生成するプログラムを作ってもらいました!
入力したプロンプトは下記になります。

Please write Python code to output the Fibonacci sequence until a number greater than or equal to 100 is reached.
【日本語訳】
フィボナッチ数列を100以上の数値が出るまで出力するコードをPythonで書いてください。

Magicoder

def fibonacci():
    a, b = 0, 1
    while True:
        print(a)
        a, b = b, a + b
        if a > 100:
            break

fibonacci()

【実行結果】
0 1 1 2 3 5 8 13 21 34 55 89

問題は100以上の数値が出るまでということなので、次の144まで生成してほしかったのですが、89ということで100以下の数値しか出力されませんでした。惜しい!

GPT-3.5

# Function to generate the Fibonacci sequence
def generate_fibonacci_sequence():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

# Print Fibonacci numbers until reaching a number greater than 100
for num in generate_fibonacci_sequence():
    if num > 100:
        break
    print(num)

【実行結果】
0 1 1 2 3 5 8 13 21 34 55 89

GPT 4

def fibonacci_sequence_until_100():
    a, b = 0, 1
    while a <= 100:
        print(a, end=" ")
        a, b = b, a + b

# Running the function to print the Fibonacci sequence up to 100
fibonacci_sequence_until_100()

【実行結果】
0 1 1 2 3 5 8 13 21 34 55 89 

GPT-3.5とGPT 4はそれぞれアプローチが異なるのですが、出力されたのは89とこちらも惜しい結果になりました。

CodeLlama-Python

def fibo(n):
   if n == 0: return 0
   elif n == 1: return 1
   else: return fibo(n-1) + fibo(n-2)
n=int(input())
while True:
   print(fibo(n))
   n+=1

【実行結果】
エラー

CodeLlama-Pythonは実行してもループするだけで結果が得られませんでした。

Gemini Pro

def fibonacci():
  """
  This function generates the Fibonacci sequence.

  Yields:
    The next number in the Fibonacci sequence.
  """
  a, b = 0, 1
  while True:
    yield a
    a, b = b, a + b

# Print the Fibonacci sequence until a number greater than or equal to 100 is reached
for number in fibonacci():
  if number >= 100:
    break
  print(number)

【実行結果】
0 1 1 2 3 5 8 13 21 34 55 89

最新のGemini Proも残念ながら100以下の数値しか出力してくれませんでした。

TODOアプリのためのAPIを実装

最後の検証としてMagicoderのサンプルにもあったAPIの実装という問題。
プロンプトは次のとおりです。

Implement a high-level API for a TODO list application. 
The API takes as input an operation request and updates the TODO list in place. 
If the request is invalid, raise an exception.

【日本語訳】
TODOリストアプリケーションのための高水準APIを実装してください。
このAPIは操作リクエストを入力として受け取り、TODOリストをその場で更新します。
リクエストが無効であれば、例外を発生させてください。

Magicoder

class TodoList:
    def __init__(self):
        self.tasks = []

    def add_task(self, task):
        if not isinstance(task, str):
            raise ValueError("Task must be a string")
        self.tasks.append({"task": task, "completed": False})

    def remove_task(self, task):
        if not isinstance(task, str):
            raise ValueError("Task must be a string")
        self.tasks = [t for t in self.tasks if t["task"]!= task]

    def complete_task(self, task):
        if not isinstance(task, str):
            raise ValueError("Task must be a string")
        for t in self.tasks:
            if t["task"] == task:
                t["completed"] = True
                break
        else:
            raise ValueError("Task not found")

    def get_tasks(self):
        return [t for t in self.tasks if not t["completed"]]

    def get_completed_tasks(self):
        return [t for t in self.tasks if t["completed"]]

todo = TodoList()
todo.add_task("Buy groceries")
todo.add_task("Finish project")
todo.complete_task("Buy groceries")
print(todo.get_tasks())  # Output: ['Finish project']
print(todo.get_completed_tasks())  # Output: ['Buy groceries']

【実行結果】
[{'task': 'Buy groceries', 'completed': False}]
[{'task': 'Finish project', 'completed': True}]

コードの出力、及び実行後の結果も問題ないことが確認できました!
また、タスクの追加や完了作業を変更しても、結果に反映されているので実効性のあるコードが出力されましたね。

GPT-3.5

class TodoList:
    def __init__(self):
        self.tasks = []

    def add_task(self, task):
        if not task:
            raise ValueError("Task cannot be empty.")
        self.tasks.append(task)

    def remove_task(self, task):
        if task not in self.tasks:
            raise ValueError(f"Task '{task}' not found in the list.")
        self.tasks.remove(task)

    def update_task(self, old_task, new_task):
        if old_task not in self.tasks:
            raise ValueError(f"Task '{old_task}' not found in the list.")
        if not new_task:
            raise ValueError("New task cannot be empty.")
        index = self.tasks.index(old_task)
        self.tasks[index] = new_task

    def list_tasks(self):
        return self.tasks

# Example usage:
try:
    todo_list = TodoList()

    todo_list.add_task("Buy groceries")
    todo_list.add_task("Read a book")

    print("Initial TODO List:")
    print(todo_list.list_tasks())

    todo_list.remove_task("Buy groceries")
    print("TODO List after removing 'Buy groceries':")
    print(todo_list.list_tasks())

    todo_list.update_task("Read a book", "Finish project")
    print("TODO List after updating 'Read a book' to 'Finish project':")
    print(todo_list.list_tasks())

    # Invalid request, attempting to add an empty task
    todo_list.add_task("")
except ValueError as e:
    print(f"Invalid request: {e}")

【実行結果】
Initial TODO List:
['Buy groceries', 'Read a book']
TODO List after removing 'Buy groceries':
['Read a book']
TODO List after updating 'Read a book' to 'Finish project':
['Finish project']
Invalid request: Task cannot be empty.

GPT-3.5もプログラム、出力結果ともに問題ありませんでした!

GPT 4

class TodoList:
    def __init__(self):
        self.tasks = []

    def add_task(self, task):
        if task in self.tasks:
            raise ValueError("Task already exists.")
        self.tasks.append(task)

    def remove_task(self, task):
        if task not in self.tasks:
            raise ValueError("Task does not exist.")
        self.tasks.remove(task)

    def update_task(self, old_task, new_task):
        if old_task not in self.tasks:
            raise ValueError("Old task does not exist.")
        index = self.tasks.index(old_task)
        self.tasks[index] = new_task

    def view_tasks(self):
        return self.tasks

# Example usage
todo = TodoList()
todo.add_task("Buy groceries")
todo.add_task("Read a book")
print(todo.view_tasks())
todo.update_task("Read a book", "Finish reading a book")
todo.remove_task("Buy groceries")
print(todo.view_tasks())

【実行結果】
['Buy groceries', 'Read a book']
['Finish reading a book']

GPT 4も問題なしです!

CodeLlama-python

class TodoAPI:
   def __init__(self):
       self._items = []

   def add_item(self, text):
       self._items.append(text)
       return len(self._items) - 1

   def mark_as_done(self, index):
       try:
           self._items[index] += 'x'
       except IndexError as e:
           raise ValueError from e

   def list(self):
       return self._items[:]


todo = TodoAPI()
print(todo.add_item("Buy the milk"))  # => 0
print(todo.mark_as_done(2))  # => None
print(todo.list())  # => ["Buy the milk", "Make blog post"]

【実行結果】
途中、エラーが表示されて中断された。

コードは出力されましたが途中でエラーがでてしまい、実行結果は得られませんでした。
エラーが出た箇所を修正すれば、期待通りの結果が出たのですがそのまま使えるというわけではないので惜しいところです。

Gemini Pro

class TodoList:
  """
  Class representing a TODO list.
  """

  def __init__(self):
    self._tasks = []

  def add_task(self, description):
    """
    Adds a new task to the list.

    Args:
      description: The description of the task.

    Raises:
      ValueError: If the description is empty or already exists.
    """
    if not description:
      raise ValueError("Task description cannot be empty.")
    if description in [task.description for task in self._tasks]:
      raise ValueError("Task with this description already exists.")
    self._tasks.append(Task(description))

  def delete_task(self, description):
    """
    Deletes a task from the list.

    Args:
      description: The description of the task to delete.

    Raises:
      ValueError: If the task does not exist.
    """
    try:
      task_index = self._tasks.index(Task(description))
    except ValueError:
      raise ValueError("Task with this description does not exist.")
    del self._tasks[task_index]

  def mark_completed(self, description):
    """
    Marks a task as completed.

    Args:
      description: The description of the task to mark completed.

    Raises:
      ValueError: If the task does not exist.
    """
    try:
      task = self._tasks[self._tasks.index(Task(description))]
    except ValueError:
      raise ValueError("Task with this description does not exist.")
    task.is_completed = True

  def list_tasks(self, completed=False):
    """
    Lists all the tasks in the list.

    Args:
      completed: Whether to list only completed tasks.

    Returns:
      A list of task descriptions.
    """
    if completed:
      return [task.description for task in self._tasks if task.is_completed]
    return [task.description for task in self._tasks]


class Task:
  """
  Class representing a task.
  """

  def __init__(self, description):
    self.description = description
    self.is_completed = False

  def __eq__(self, other):
    return self.description == other.description

  def __str__(self):
    return f"{self.description} {'(completed)' if self.is_completed else ''}"

todo_list = TodoList()

todo_list.add_task("Buy groceries")
todo_list.add_task("Clean the house")
todo_list.mark_completed("Buy groceries")

for task in todo_list.list_tasks():
  print(task)

# Output:
# Clean the house
# Buy groceries (completed)

todo_list.delete_task("Clean the house")

for task in todo_list.list_tasks(completed=True):
  print(task)

# Output:
# Buy groceries (completed)

【実行結果】
Buy groceries #1回目のprint
Clean the house #1回目のprint
Buy groceries #2回目のprint

パッと見たところ、問題なく動いているように思えますが、1回目のprint文で「Buy groceries」を完了にしているため、正確な出力は「Buy groceries (completed)」となっていなければなりません。惜しいっ!

さて、結果を一覧にまとめました!

プロンプトMagicoderGPT-3.5GPT 4CodeLlama-Python(7B)Gemini Pro
pythonでクイックソートを行うプログラムを作ってください
フィボナッチ数列の計算をしてください。100以上の数値が出るまで、数字を出力し続けてください。
出力が89まで。

出力が89まで。

出力が89まで。

出力が89まで。
TODOリストアプリケーションのための高水準APIを実装してください。
このAPIは操作リクエストを入力として受け取り、TODOリストをその場で更新します。
リクエストが無効であれば、例外を発生させてください。


結果は出力されるが、一部正しく動作しない

結果的には、MagicoderとGPTシリーズが同点ということでした。最新のGemini Proは惜しかったですね〜・・・。
ただ、Gemini Proは唯一コード中にコメントが記載されており、とても検証しやすかったです。

それにしても、パラメータ数が小規模なMagicoderがGPT4と同じ結果を残せるというのは驚きました。
本当にコーディングに特化したモデルなんだなと実感していただける検証結果だったのではないでしょうか?

これはもっと難しいプログラミングをMagicoderに任せてみたいですね!

まとめ

Magicoderはコーディングに特化した、7Bという少ないパラメータの言語モデルです。

しかし、コード生成においてはChatGPTを超える性能を持つ小規模な言語モデルでGoogleColab上でも快適に動作することが確認できました。
また、日本語も理解して回答を生成することができるので、非常に使い勝手のいいモデルとなっています。

同じコード生成に特化したCodeLlamaは、今回テストで使用したモデル以上にパラメータ数を持つものがありますが、パラメータ数が大きくなるにつれてマシンスペックが必要だったりと使いにくくなってきます。

その反面、Magicoderは気軽に使える言語モデルだということで、プログラムを組む上ではジャービスのようにパートナーとなってくれるのではないでしょうか?

精度が高く、使い勝手のいいMagicoderをぜひお試しください!

サービス紹介資料

【無料】2023年2月版生成系AIの業務活用なら!

・生成系AIを活用したPoC開発

・生成系AIの業務活用コンサルティング

・システム間API連携

サービス紹介資料

生成系AIの業務活用なら!

・生成系AIを活用したPoC開発

・生成系AIのコンサルティング

・システム間API連携

最後に

いかがだったでしょうか?

弊社では

・マーケティングやエンジニアリングなどの専門知識を学習させたAI社員の開発
・要件定義・業務フロー作成を80%自動化できる自律型AIエージェントの開発
・生成AIとRPAを組み合わせた業務自動化ツールの開発
・社内人事業務を99%自動化できるAIツールの開発
ハルシネーション対策AIツールの開発
自社専用のAIチャットボットの開発

などの開発実績がございます。

まずは、「無料相談」にてご相談を承っておりますので、ご興味がある方はぜひご連絡ください。

➡︎生成AIを使った業務効率化、生成AIツールの開発について相談をしてみる。

生成AIを社内で活用していきたい方へ

「生成AIを社内で活用したい」「生成AIの事業をやっていきたい」という方に向けて、生成AI社内セミナー・勉強会をさせていただいております。

セミナー内容や料金については、ご相談ください。

また、弊社紹介資料もご用意しておりますので、併せてご確認ください。

投稿者

  • 藤崎

    情シスとして8年間勤務したあと、現在はWEBコンサルタントとして独立。 システム開発やマーケティング、ライター、プログラミング教室など幅広い分野で活動中。 と言えば聞こえはいいですが、実際のところはITの何でも屋さんみたいなことをしています。

  • URLをコピーしました!
  • URLをコピーしました!
目次