OllamaでローカルLLMにコード生成や処理内容を説明させてみる

[Ollama](https://github.com/ollama/ollama) は LLM を実行するためのツールで、ローカル上で手軽に LLM を実行できます。

今回は、比較的軽量で必要なメモリが少ない[^1]下記モデルを使ってコードの生成や処理内容の説明等をさせてみました。

[^1]: codellama の方は最低でも8GB程度のメモリは必要。メモリ不足の場合は ```model requires more system memory (8.4 GiB)``` というエラーが発生し実行できなかった

|モデル名|パラメータ数|サイズ|
|---|---|---|
|llama3.2|3B|2.0GB|
|codellama|7B|3.8GB|

確率的にそれっぽい文章を作っているだけで、どちらもリファクタリング等は全然ダメそうでしたが、過度に期待せず特徴を理解して使う分には有用そうです。

# はじめに

まず、Ollama の CLI をインストールしてモデルを pull しておきます。[^2]

[^2]: 事前に pull しなくても run 時に自動的に pull します。基本的に docker コマンドを参考にしたような作りでした。

```sh:llama3.2の取得例
$ ollama pull llama3.2
```

```sh:codellamaの取得例
$ ollama pull codellama
```

Linux 環境の場合、pull したモデルは ```/usr/share/ollama/.ollama/models``` へ保存されました。

スタンドアロンで実行するには次のようにします。

```sh:スタンドアロン実行
ollama run <model> "<prompt>"
```

例えば、このようになります。

```sh:llama3.2実行例
$ ollama run llama3.2 "Write a shopping cart domain model in Domain-Driven Design"
```

# (a) ドメインモデル生成

題材としてよくありそうなショッピングカートのドメインモデルを作成させてみます。

プロンプトはこのようにしてみました。

````markdown:プロンプト（prompt1.md）
Write a shopping cart domain model in Domain-Driven Design
````

```sh:llama3.2実行例
$ ollama run llama3.2 "$(cat prompt1.md)"
```

## llama3.2の場合

実行の度に内容が変わり、例えば次のような結果となりました。（長いので適度に省略）

````markdown:llama3.2結果例
In Domain-Driven Design (DDD), the Shopping Cart domain model is an essential part of the application's business logic. Here's a possible implementation:

**ShoppingCart**

```csharp
public class ShoppingCart : AggregateRoot
{
    private readonly HashSet<CartItem> _items = new HashSet<CartItem>();
    private decimal _total;

    public ShoppingCart() { }

    public ShoppingCart(Identity identity) : base(identity)
    {
        // Ensure the shopping cart is created with a unique identifier
    }

    public void AddItem(CartItem item)
    {
        if (_items.Contains(item))
        {
            throw new InvalidOperationException("Item already exists in the shopping cart.");
        }
        _items.Add(item);
        UpdateTotal();
    }

    public void RemoveItem(CartItem item)
    {
        if (!_items.Contains(item))
        {
            throw new InvalidOperationException("Item does not exist in the shopping cart.");
        }
        _items.Remove(item);
        UpdateTotal();
    }

    public CartItem GetItem(int index)
    {
        return _items.ElementAt(index).Value;
    }

    public decimal Total
    {
        get => _total;
        private set
        {
            // Validate total amount
            if (value < 0)
            {
                throw new InvalidOperationException("Total amount cannot be negative.");
            }
            _total = value;
        }
    }

    private void UpdateTotal()
    {
        decimal subtotal = 0;
        foreach (var item in _items)
        {
            subtotal += item.Price * item.Quantity;
        }
        _total = subtotal;
    }
}
```

...省略

In this implementation:

*   The `ShoppingCart` class represents the shopping cart domain model. It contains a set of `CartItem` objects and calculates the total amount.
*   The `CartItem` class represents a single item in the shopping cart. It has a product ID and quantity.
*   The `AggregateRoot` abstract class provides a basic structure for aggregate roots, which are used to define boundaries within an application's domain model. In this case, the `ShoppingCart` class implements the `AggregateRoot` interface.

...省略

**Conclusion**

This domain model provides a solid foundation for implementing business logic related to shopping carts. It uses aggregate roots, entities, and domain events to encapsulate complex domain behavior. By following DDD principles, you can create a scalable, maintainable, and loosely coupled system that is easier to understand and extend.
````

DDD の例題としてありそうな内容に仕上がっているように思いますが、ドメインモデルよりも実装例とその説明が中心になる傾向が強そうでした。

コードは Python になる事もありましたが、C# になるケースが多そうでした。[^3]

[^3]: [Design a DDD-oriented microservice](https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/ddd-oriented-microservice) 等の影響なのかも

## codellamaの場合

codellama の方は文章のみで、基本的にコードは記載されませんでした。

````markdown:codellama結果例
In Domain-Driven Design (DDD), a shopping cart is a complex domain that requires careful consideration of its business rules and data entities. Here's an example of how we might model a shopping cart using DDD principles:

1. Aggregate Roots:
	* Cart: This is the central entity in our shopping cart model, and it represents the entire shopping experience. It is the root of our aggregate, and all other entities in the model are related to it.
2. Value Objects:
	* Product: This is a value object that represents a specific product in the shopping cart. It contains information about the product, such as its ID, name, price, and quantity.
3. Entities:
	* LineItem: This entity represents a single line item in the shopping cart. It contains information about the product being purchased, such as its ID, name, and price. It also keeps track of the quantity of products being purchased.
...省略

By following these principles of Domain-Driven Design, we can create a robust and scalable shopping cart model that accurately reflects the business rules and requirements of our system.
````

# (b) 処理の実施

適当に作成した JSON Lines から条件に合致したものを抽出できるか、次のプロンプトで試してみました。

````markdown:プロンプト
Select "id" and "name" from the following JSON Lines where color is white.

```json
{"id":1,"name":"item1","price":1100,"attrs":{"code":"0001","category":"A1"},"variants":[{"color":"white", "size":"S"},{"color":"black","size":"F"}]}
{"id":2,"name":"item2","price":2300,"attrs":{"code":"0002"},"variants":[{"color":"red"},{"color":"blue"},{"color":"white"}]}
{"id":3,"name":"item3","price":360,"attrs":{"code":"0003"}}
{"id":4,"name":"item4","price":4700,"attrs":{"code":"0004","category":"A1"},"variants":[{"color":"green", "size":"S"}]}
{"id":5,"name":"item5","price":590,"attrs":{"code":"0005","category":"B2"},"variants":[{"color":"red"},{"color":"white","size":"F"},{"color":"white","size":"L"}]}
{"id":6,"name":"item6","price":6200,"attrs":{"code":"0006","category":"C3"},"variants":[{"color":"blue","size":"S"},{"color":"green","size":"L"}]}
```
````

## llama3.2の場合

コード生成を指示していないにも関わらず、jq のコードが記載されました。（Python のコードになるケースもあった）

````markdown:llama3.2結果例
You can use the `jq` command to select specific fields from JSON data. Here's how you can do it:

First, let's pipe the JSON lines into `jq`. We'll assume that they are in a file called `data.json`.

```bash
jq '.[] | select(.variants|has("color" == "white") and .attrs) | {"id": .id, "name": .name}' data.json
```

Let's break this down:

- `.[]` selects each element in the JSON array.
- `select(.variants|has("color" == "white") and .attrs)` filters out elements where there are no variants with a color of white, or if any of the attributes are missing. The filter is case-sensitive, so we use 'white' instead of White to match the exact value in your JSON.
- `{"id": .id, "name": .name}` selects only the 'id' and 'name' fields from each object.

This command will output:

```json
{"id": 1, "name": "item1"}
{"id": 2, "name": "item2"}
{"id": 5, "name": "item5"}
```

These are the lines where color is white.
````

出力結果は合っていますが、このコマンドでは正常に動作しません。

jq で実施するのなら、例えば次のようになります。（重複を許す場合）

```sh:jqコマンド例
$ jq 'select(.variants != null and .variants[].color == "white") | {id, name}' data.json
```

## codellamaの場合

基本的にコードはなく結果だけ提示されます。

````markdown:codellama結果例
The following is the output of selecting "id" and "name" from the JSON Lines where color is white:

* {"id": 2, "name": "item2"}
* {"id": 5, "name": "item5"}
````

ただ、このように正解が 3つある内の 1つを見逃すケースが多かったです。

# (c) コード生成

```(b)``` と同じ内容でコード生成を指示してみます。

````markdown:プロンプト
Write a code to select "id" and "name" from the following JSON Lines where color is white.

```json
{"id":1,"name":"item1","price":1100,"attrs":{"code":"0001","category":"A1"},"variants":[{"color":"white", "size":"S"},{"color":"black","size":"F"}]}
...省略
```
````

## llama3.2の場合

一応、正しく動作するコードが生成されました。
ただし、当然ながら正常に動作しないコードを生成する場合もあります。

コードは Python になるケースが多そうでした。[^4]

[^4]: pandasを使うケースもありました

````markdown:llama3.2結果例
Here is a Python code that uses the `json` module to parse the JSON lines and select rows where "color" is "white". The output will be a new JSON line containing only the "id" and "name" fields:

```python
import json

# Load the JSON lines into a list
data = []
for line in open('input.json', 'r'):
    data.append(json.loads(line))

# Filter rows where color is white
white_variants = [item for item in data if len([variant for variant in item.get('variants', []) if variant.get('color') == 'white']) > 0]

# Select only id and name fields
result = [{'id': item['id'], 'name': item['name']} for item in white_variants if 'name' in item]

# Print the result as JSON lines
for row in result:
    print(json.dumps(row))
```

This code assumes that your input is in a file named `input.json` and uses a simple filter to find items with at least one variant of color "white". The output will be a new JSON line for each item with only the "id" and "name" fields.

If you want to use a more efficient approach, you can use a regular expression or a database query depending on your specific requirements.
````

## codellamaの場合

非常に惜しい内容ですがコードは正常に動作せず、出力結果も ```item1``` が欠けています。

この他に jq や Elasticsearch のクエリを生成しましたが、どちらも微妙でした。

````markdown:codellama結果例
Here's an example of how you can select the "id" and "name" fields from the JSON data where the color is white:
```javascript
const jsonData = `{"id":1,"name":"item1","price":1100,"attrs":{"code":"0001","category":"A1"},"variants":[{"color":"white", "size":"S"},{"color":"black","size":"F"}]}
{"id":2,"name":"item2","price":2300,"attrs":{"code":"0002"},"variants":[{"color":"red"},{"color":"blue"},{"color":"white"}]}
{"id":3,"name":"item3","price":360,"attrs":{"code":"0003"}}
{"id":4,"name":"item4","price":4700,"attrs":{"code":"0004","category":"A1"},"variants":[{"color":"green", "size":"S"}]}
{"id":5,"name":"item5","price":590,"attrs":{"code":"0005","category":"B2"},"variants":[{"color":"red"},{"color":"white","size":"F"},{"color":"white","size":"L"}]}
{"id":6,"name":"item6","price":6200,"attrs":{"code":"0006","category":"C3"},"variants":[{"color":"blue","size":"S"},{"color":"green","size":"L"}]}`;

const json = JSON.parse(jsonData);
const whiteItems = json.filter((item) => item.variants.some((variant) => variant.color === 'white'));

console.log(whiteItems.map((item) => ({ id: item.id, name: item.name })));
```
This will output the following:
```json
[
  { "id": 2, "name": "item2" },
  { "id": 5, "name": "item5" }
]
```
This code uses the `filter()` method to filter out all items that do not have a white variant. Then it maps the remaining items to an object with only the `id` and `name` fields using the `map()` method.
````

# (d) 処理内容の説明1

単純なコードの処理内容を説明させてみます。

````markdown:プロンプト
What does the following code do?

```javascript
import fs from 'fs'
import readline from 'readline'

const reader = readline.createInterface({
    input: fs.createReadStream(process.argv[2])
})

const lines = await Array.fromAsync(reader)

const items = lines.map(JSON.parse)

const res = items
    .filter(x => x.variants?.some(v => v.color == 'white'))
    .map(({id, name}) => ({ id, name }))

console.log(res)
```
````

## llama3.2の場合

処理内容を説明できており、問題なさそうです。

````markdown:llama3.2結果例
This JavaScript code reads a file specified as an argument from the command line and processes its contents. Here's a step-by-step breakdown of what it does:

1. **Import necessary modules**: The code imports two modules:
   - `fs`: The File System module, which allows you to interact with files.
   - `readline`: A built-in Node.js module that provides a way to read input from the console line by line.

2. **Create an interface for reading the file**: 
   - It creates a new instance of `readline.createInterface`, passing in an object with `input` set to `fs.createReadStream(process.argv[2])`.
   - This sets up the interface to read the contents of the file specified as the second command-line argument (`process.argv[2]`).

3. **Read and parse all lines from the file**: 
   - The code uses `Array.fromAsync(reader)` to read all lines from the file asynchronously.
   - It then maps over these lines, parsing each one with `JSON.parse`, which converts the JSON data into JavaScript objects.

4. **Filter items by a specific color**: 
   - The filtered line is mapped using `items.filter(x => x.variants?.some(v => v.color == 'white'))`.
   - This filters out any items that do not have at least one variant with a white color.

5. **Map the result to only include id and name properties**: 
   - It maps over these filtered items using `map(({id, name}) => ({ id, name }))`, which selects only the 'id' and 'name' properties from each item.

6. **Log the processed items to the console**: 
   - Finally, it logs the result to the console with `console.log(res)`.

In summary, this code reads a JSON file specified as an argument, filters out items that don't have white variants, selects only the 'id' and 'name' properties from each item, and logs the resulting processed data to the console.
````

## codellamaの場合

こちらも問題なさそうです。

````markdown:codellama結果例
This code reads a JSON file from the command line using `process.argv[2]` and then uses `readline` to read the file line by line. It then converts each line into an object using `JSON.parse`.

The code then filters the array of objects using the `filter` method, keeping only those objects where the `variants` property contains at least one element with a `color` property equal to `'white'`. The resulting filtered array is then mapped to create a new array containing only the `id` and `name` properties of each object.

Finally, the code logs the resulting array to the console using `console.log`.
````

# (e) リファクタリング

(d) のコードをリファクタリングさせてみます。

````markdown:プロンプト
Refactor the following code.

```javascript
import fs from 'fs'
import readline from 'readline'

const reader = readline.createInterface({
    input: fs.createReadStream(process.argv[2])
})

const lines = await Array.fromAsync(reader)

const items = lines.map(JSON.parse)

const res = items
    .filter(x => x.variants?.some(v => v.color == 'white'))
    .map(({id, name}) => ({ id, name }))

console.log(res)
```
````

## llama3.2の場合

余計な事をして改悪するような結果となりました。

````markdown:llama3.2結果例
Here's a refactored version of your code with improvements in structure, readability, and performance:

```javascript
import fs from 'fs';
import readline from 'readline';

/**
 * Reads the contents of a file into an array of JSON objects.
 *
 * @param {string} filePath - The path to the file to read.
 * @returns {Promise<Object[]>} A promise resolving with an array of JSON objects.
 */
async function readJsonFromFile(filePath) {
    const interfaceReader = readline.createInterface({
        input: fs.createReadStream(filePath),
    });

    return (await new Promise(resolve => {
        interfaceReader.on('line', line => resolve(line));
        interfaceReader.on('close', () => resolve(null));
    })).map(JSON.parse);
}

...省略

async function main() {
    try {
        const filePath = process.argv[2];

        if (!filePath) {
            throw new Error('File path not provided');
        }

        const lines = await readJsonFromFile(filePath);

        const result = await Promise.all([filterItems(lines), transformItems(lines)]);

        console.log(result[0]);
    } catch (error) {
        console.error(error);
    }
}

main();
```

**Changes Made**

*   **Extracted Functions**: The code was refactored into three separate functions: `readJsonFromFile`, `filterItems`, and `transformItems`. Each function has a single responsibility, making the code easier to read, maintain, and test.
*   **Improved Error Handling**: Instead of using `await` with an async function that returns `Promise.resolve()`, we now use `Promise.all()` which allows us to handle both promises in parallel. This makes the code more efficient and easier to understand.
*   **Removed `async/await` with `Array.fromAsync`**: `Array.fromAsync` is not a standard method in JavaScript. We replaced it with `Promise.resolve().then(() => Array.from(reader))`.
*   **Improved Code Readability**: The code was refactored for better readability, making it easier to understand and maintain.
*   **Removed Unnecessary `const res = items...` Line**: Since we are no longer using the original array of objects (`items`), we can remove this line altogether.
````

## codellamaの場合

こちらも同様でした。

````markdown:codellama結果例
Here is a refactored version of the code that uses async/await syntax and a more concise syntax for working with streams:
```javascript
import fs from 'fs';
import readline from 'readline';

async function findWhiteItems() {
  const reader = readline.createInterface({
    input: fs.createReadStream(process.argv[2]),
  });

  let items = [];
  for await (const line of reader) {
    try {
      const item = JSON.parse(line);
      if (item.variants?.some((v) => v.color === 'white')) {
        items.push({ id: item.id, name: item.name });
      }
    } catch (error) {
      console.log('Error parsing JSON', error);
    }
  }

  return items;
}

const res = await findWhiteItems();
console.log(res);
```
Explanation:

* The `findWhiteItems` function is declared as an async function, which means that it returns a promise. This allows us to use the `await` keyword inside the function to wait for the asynchronous operation of reading lines from the file and parsing JSON.
* The `for await...of` loop is used to read each line from the stream and parse it as JSON. If an error occurs while parsing, it is caught and logged.
* The `items` array is declared outside the loop, so that it can be accessed after the loop has finished executing.
* Inside the loop, we check if the item has a `variants` property with at least one variant whose color is 'white'. If it does, we add an object to the `items` array with the `id` and `name` properties of the item.
* The function returns the `items` array after the loop has finished executing.
* The `await` keyword is used to wait for the promise returned by `findWhiteItems` to resolve before logging the result.
````

ついでに、コードレビューも両方で試してみましたが、あまり有用な結果は得られそうにありませんでした。

色々と試してみましたが、内容を理解せずにそれっぽい文章を組み立てているだけなのは明らかで、リファクタリングやコードレビューに関しては今のところ微妙そうでした。

# (f) 処理内容の説明2

最後に、「[ECサイトにおける販促（値引やセット価格）の処理モデルを考えてみる](https://qiita.com/fits/items/296f6716dd45e68ec2aa)」 で実装した models.rs の処理内容を ```What does the following code do?``` プロンプトで説明させてみました。

## llama3.2の場合

概要レベルの説明になるケースばかりでしたが、それなりに説明できているように思います。

改善点や改善例としてコードを指摘する場合もありましたが、改善点は主に典型的なものでコードは微妙なものばかりでした。

````markdown:llama3.2結果例
This is a Rust code that implements a discount system for an online shopping platform. Here's a high-level overview of the code:

**Data Structures**

* `GroupCondition`: represents a condition for applying a discount, consisting of a group of items and a condition (e.g., price threshold).
* `DiscountAction`: represents an action to take when a discount is applied, such as giving a certain percentage off or changing the price.
* `DiscountRule`: represents a rule for applying discounts, consisting of a condition and an action.

**Functions**

* `is_all_none`: checks if all elements in a vector are None.
* `is_full_count`: checks if a quantity is equal to or greater than a maximum count (if set).
* `action`: performs the discount action on a vector of items.
* `apply`: applies a discount rule to a list of items.

**Discount Logic**

The code implements the following discount logic:

1. **Group Discounts**: When all items in a group meet the condition, apply a group discount with a value set by the rule's action.
2. **Item Discounts**: For each item in the group that meets the condition, apply an individual discount based on the rule's action.
3. **Price Changes**: If the price of an item is less than or equal to the threshold set by the rule's action, consider it as a valid option.

**Code Style**

The code follows standard Rust naming conventions and uses clear, concise variable names. The code is organized into logical sections, making it easy to understand and maintain.

**Improvement Suggestions**

* Consider adding more documentation (e.g., comments, docstrings) to explain the purpose of each function and data structure.
* Some functions (e.g., `action`) are quite long and complex. Break them down into smaller functions with clear names to improve readability.
* There is no error handling in the code. Add checks for potential errors (e.g., division by zero, invalid quantities) to make the code more robust.

Overall, this is a well-structured and readable implementation of a discount system. With some minor improvements, it can become even more maintainable and efficient.
````

## codellamaの場合

こちらも同様でしたが、llama3.2 とは違って改善点を指摘するようなケースは見られませんでした。

````markdown:codellama結果例
  The provided code is a Rust implementation of a discount rule system, where the goal is to calculate the discount for a set of items based on the condition and action defined in the rules. Here's a breakdown of each part of the code:

1. `DiscountRule`: This struct represents a discount rule, which consists of a group condition and an action. The `condition` field is a vector of items that must be present in the order for the rule to apply, while the `action` field is a function that takes a vector of items as input and returns a `Reward` struct.
2. `GroupCondition`: This struct represents a group condition for discount rules. It defines a set of items that must be present in the order for the rule to apply.
3. `DiscountAction`: This enum represents the different types of actions that can be taken when applying a discount rule. Each variant has a corresponding function that calculates the discount based on the input vector of items.
4. `Reward`: This struct represents the reward for applying a discount rule. It contains a vector of tuples, where each tuple consists of an item and its corresponding discount amount.
5. `select`: This method takes a vector of items as input and returns a vector of items that satisfy the condition defined in the group condition. If no items satisfy the condition, it returns an empty vector.
6. `apply`: This method takes a vector of items as input and applies the discount rule to it. It returns a `Reward` struct containing the calculated discount for each item in the order.

Overall, this code provides a flexible and modular way to calculate discounts based on a set of rules, where the rules can be defined using different conditions and actions. The `DiscountRule` struct is the core building block of the system, and it allows for easy addition or modification of new rules without affecting existing code.
````