無料でGithubプライベートリポジトリからOpenHands GitHub Issue Resolverを使う

# はじめに

生成AIを使って開発を体験をしてみたいと思い、何とか安上がりな方法を探していたところ、以下記事を見つけたので、やってみました。

https://zenn.dev/itaosan/articles/43742985318a21

無料のプライベートリポジトリではGitHub上のAPI上の制限からPR Draftの作成ができなかったので、Github Actionsに付け足しをしました。
それと、実際にやってみた上での補足など記載していきます。

# OpenRouterとは

## 企業情報

今回の手順では、OpenRouterのアカウントを作成します。
OpenRouterという名前はサービスでもあり、企業名でもあります。
https://openrouter.ai/

企業（OpenRouter, Inc）としては、Delaware C-corporationの一つです。

> デラウェア州の C 株式会社とは
C 株式会社 (C Corp と呼ばれることも多いです) は、オーナーまたは株主が事業体とは別に課税される企業の法的形態です。

特徴として、以下プライバシーの特徴があります。

> デラウェア州では、設立に関する書類で取締役や役員の名前を開示する必要がありません。これにより、ある程度の匿名性が確保されます。

引用: https://stripe.com/jp/resources/more/how-to-form-a-c-corp-in-delaware

そのためか、2023年から活動していることは以下ページからわかりましたが、どういった人々が関わっているかはわかりませんでした。。
https://x.com/openrouterai
https://www.bizprofile.net/ny/new-york/openrouter-inc

* 登録日: 04/17/2023
* 法人化: 2/25/2025

法人化してから1.5か月と、新進気鋭のスタートアップと想像されます。

## サービス、プライバシー、セキュリティ

サービス（openrouter.ai）としては、チャットやモデルの一元管理などがあります。
https://openrouter.ai/docs/quickstart

プライバシー、セキュリティについては以下記載されています。
https://openrouter.ai/privacy

以下のような個人情報（PII）を収集するとされています

> your email address or wallet address

今回は無料の設定と無料のモデルのみですので、支払いに関する情報は登録せずに進められます。

また、サービスの利用状況も収集するとされています。
こちらは無効化もできるようです。アカウント設定画面から設定変更できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1474054/4d6d9a89-18b5-4b63-a42d-21a06b7ecda5.png)

収集を有効にすると、1%の値下げが受けられるようです。
Model Trainingはデフォルトで有効になっているので、必要に応じて無効化しておくとよさそうです。

> We also collect information related to your use of our Services, such as the web application URL associated with model requests.

> Users have the ability to opt out of logging prompts and completions, which are used to improve anonymous analytics features like classification.

> Users can also request the deletion of prompts and completions by contacting us using our support@ email address.

個人情報の提供は3rdパーティには共有しないが、生成AIへの指示（プロンプト）はモデルのプロバイダーにより保持される場合があるとのこと。

> We do not share your personal information with third parties except for certain service providers, including analytics services (like Google Analytics) and third-party service providers (like Clerk).

> AI model providers generally store prompt data transiently or not at all, but a few provide lower pricing if they can collect prompts anonymously. 

例えば、以下DeepSeekのモデルでは性能向上のためプロンプトを提供する必要があると説明されています。
https://openrouter.ai/deepseek/deepseek-v3-base:free

> Note that this is a base model mostly meant for testing, you need to provide detailed prompts for the model to return useful responses.

## 利用規約

以下に記載されています
https://openrouter.ai/terms

以下気になったところを引用します。

> Applications that developers build can be shown publicly on OpenRouter.

> OpenRouter is in beta; we may change or discontinue any part of the Services at any time, including the availability of certain models.

> If a non-experimental model needs to be removed, you can expect the following:
We will provide a notification in the OpenRouter community Discord server.

細かく利用方法は制限されていない印象でした。
クレジットの期限やモデル停止時の挙動についても記載されていました。一度目を通しておくのがよさそうです。

# 実際に無料のプライベートリポジトリでやってみる

記事通りに以下、進めていきます。
https://zenn.dev/itaosan/articles/43742985318a21

> OpenRouterのアカウント開設とAPIキーの取得

やってみると、無料でアカウント開設ができました。
利用規約を読むと、1つのユーザーで使う場合にはモデル使用料のみでよさそうです。
https://openrouter.ai/terms#_4_-payment

ただし、1日に200リクエストの制限があります。

> All new users receive a very small free allowance to be able to test out OpenRouter. There are many free models available on OpenRouter, it is important to note that these models have low rate limits (200 requests per day total) and are usually not suitable for production use.

引用: https://openrouter.ai/docs/faq#what-free-tier-options-exist

リクエスト数＝何回Github Actionsを動かしたか、ではありません。
以下ページから1回のGithub Actionsで複数回リクエストを飛ばしていることが分かります。
https://openrouter.ai/activity

> Github ActionsとWorkflowの設定手順

あらかじめ用意しておいたGithubプライベートリポジトリに、手順通りGithub Actionsを設定します。

Github ActionsではOpenHandsを使います。

```yaml
All-Hands-AI/OpenHands/.github/workflows/openhands-resolver.yml@main
```

mainを指定しているので、OpenHands側の最新のmainブランチの状態を取ってくることができます。

設定が一通り終わった後で、fix-meラベルが付いたIssueを作ると、OpenHandsが動いている様子が確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1474054/eb68adee-e87d-4c53-9f5b-a555b980943b.png)

しかし、Github Actions内のCreate Draft PR or push branchにて以下エラーが出ます。

```python
08:45:53 - openhands:INFO: send_pull_request.py:146 - Copied repository to output/patches/issue_5
08:45:53 - openhands:INFO: send_pull_request.py:122 - Patch applied successfully
08:45:53 - openhands:INFO: send_pull_request.py:188 - Git user configured as openhands
08:45:53 - openhands:INFO: github.py:179 - Checking if branch openhands-fix-issue-5 exists...
08:45:53 - openhands:INFO: github.py:184 - Branch openhands-fix-issue-5 exists: False
08:45:53 - openhands:INFO: send_pull_request.py:274 - Getting base branch...
08:45:53 - openhands:INFO: github.py:179 - Checking if branch main exists...
08:45:53 - openhands:INFO: github.py:184 - Branch main exists: True
08:45:53 - openhands:INFO: send_pull_request.py:282 - Base branch: main
08:45:53 - openhands:INFO: send_pull_request.py:285 - Creating new branch...
08:45:53 - openhands:INFO: send_pull_request.py:302 - Pushing changes...
ERROR:root:  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/opt/hostedtoolcache/Python/3.12.9/x64/lib/python3.12/site-packages/openhands/resolver/send_pull_request.py", line 690, in <module>
    main()
  File "/opt/hostedtoolcache/Python/3.12.9/x64/lib/python3.12/site-packages/openhands/resolver/send_pull_request.py", line 673, in main
    process_single_issue(
  File "/opt/hostedtoolcache/Python/3.12.9/x64/lib/python3.12/site-packages/openhands/resolver/send_pull_request.py", line 506, in process_single_issue
    send_pull_request(
  File "/opt/hostedtoolcache/Python/3.12.9/x64/lib/python3.12/site-packages/openhands/resolver/send_pull_request.py", line 336, in send_pull_request
    pr_data = handler.create_pull_request(data)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.12.9/x64/lib/python3.12/site-packages/openhands/resolver/interfaces/issue_definitions.py", line 314, in create_pull_request
    return self._strategy.create_pull_request(data)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/hostedtoolcache/Python/3.12.9/x64/lib/python3.12/site-packages/openhands/resolver/interfaces/github.py", line 240, in create_pull_request
    response.raise_for_status()
  File "/opt/hostedtoolcache/Python/3.12.9/x64/lib/python3.12/site-packages/httpx/_models.py", line 829, in raise_for_status
    raise HTTPStatusError(message, request=request, response=self)

ERROR:root:<class 'httpx.HTTPStatusError'>: Client error '422 Unprocessable Entity' for url 'https://api.github.com/repos/***/xxx/pulls'
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422
```

OpenHandsの作成したファイルが含まれるブランチは存在するものの、プルリクエストが作成されません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1474054/8ef9f25a-6c72-4110-a8a0-83190280f3c3.png)

## エラーの原因: 無料のプライベートリポジトリのDraft PRの制限

どうやら無料のGithubプライベートリポジトリではDraft PRは作れないようです。

> Draft pull requests are available in public repositories with GitHub Free and GitHub Free for organizations, GitHub Pro, and legacy per-repository billing plans, and in public and private repositories with GitHub Team and GitHub Enterprise Cloud.

https://docs.github.com/en/rest/pulls/pulls?apiVersion=2022-11-28#create-a-pull-request
https://qiita.com/yoneapp/items/cec823b28e77e9971687

今回は無料を貫きたいので、PRの作成を実装します。

# PRをghコマンドから作成するGithub Actionsを追加する

DraftでないPRを作成するGithub Actionsを実装します。
OpenHandsがブランチを作成したタイミングで実行される必要があります。
また、PRにIssue番号を含めることで、Issue画面からPRが分かるようにします。

以下、作ったものです。

```yaml:create-pr-from-openhands-resolver.yml
name: Create Pull Request From OpenHands Resolver

on: create

jobs:
  create-pr:
    if: ${{ contains(github.ref, 'refs/heads/openhands-fix-issue-') }}
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
      - name: Set environment variables
        run: |
          echo "ISSUE_NUMBER=$(echo ${{ github.ref }} | grep -Eo 'openhands-fix-issue-[0-9]+' | cut -d'-' -f4)" >> $GITHUB_ENV
          echo "REPO_VISIBILITY=$(gh repo view --json visibility --jq .visibility)" >> $GITHUB_ENV
      - name: Create Pull Request
        if: env.REPO_VISIBILITY == 'PRIVATE'
        run: |
          gh pr create --title "Fix issue #${{ env.ISSUE_NUMBER }}: Automated PR for feature branch" --body "Fix issue #${{ env.ISSUE_NUMBER }}: This PR is auto-generated by GitHub Actions." --base ${{ vars.TARGET_BRANCH || 'main' }} --head "${GITHUB_REF#refs/heads/}"
    env:
      GH_TOKEN: ${{ secrets.PAT_TOKEN }}
      GITHUB_USER: ${{ secrets.PAT_USERNAME }}
```

ポイントは以下の通りです。

* Github Actionsの実行タイミングは`openhands-fix-issue-`を含むブランチが作成されるときとしました
* Issue番号はOpenHandsが作成するブランチに含まれているので、そこから抽出しました
* リポジトリがプライベートであることを実行条件に入れました
* GH_TOKEN,GITHUB_USERはOpenHandsとそろえました

動いた様子は以下の通りです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1474054/a7e236be-58be-42cf-a86a-51944601b4ee.png)

# おわりに

生成AIは便利だと感じましたが、外部サービスを使うとなると、それらの仕様を理解しておくことが大事だと感じました。

今後は、生成AIに設計書を渡して、開発からテスト、リリースまでやってもらえるようなシステムを作りたいです。

これからも勉強します。