社内コードベース向けAI(RAG)を最速で構築する手順

目的

社内アプリのソースコードを対象としたAI(RAG)を最速(30分以内)で構築します。
  • 質問→ベクトル検索→LLM→回答(+シーケンス図作成)

前提条件

  • VM(Ubuntu 24.04)
  • Docker が動作
  • 環境変数 OPENAI_API_KEY.env に設定
echo OPENAI_API_KEY=sk-… > ~/.env

1. Python 仮想環境&ライブラリ整備

python3 -m venv venv && source venv/bin/activate && pip install chromadb==0.6.3 langchain-chroma==0.2.3 langchain-openai==0.3.14 fastapi uvicorn python-dotenv streamlit requests

2. ベクトルインデックス生成

ソースコードリポジトリをファイル走査して 1,000~1,200文字チャンクに分割し、OpenAI で埋め込み→Chroma に保存。
対象が数千ファイルであれば、実行時間は十数分、コストは$1.5程度でした。
実行例:python create_index.py --repo ~/repo1 --persist ~/chroma-data --col repo1
#!/usr/bin/env python3
"""
create_index.py — リポジトリを Chroma インデックス化
"""
import argparse,os,pathlib,shutil,sys
from dotenv import load_dotenv
import openai
from langchain.text_splitter import RecursiveCharacterTextSplitter,Language
from langchain.docstore.document import Document
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma

p=argparse.ArgumentParser()
p.add_argument('--repo',required=True)
p.add_argument('--persist',default='~/chroma-data')
p.add_argument('--col',default='repo1')
p.add_argument('--chunk',type=int,default=1200)
p.add_argument('--overlap',type=int,default=100)
args=p.parse_args()

ROOT=pathlib.Path(args.repo).expanduser().resolve()
PERSIST=pathlib.Path(args.persist).expanduser().resolve()/args.col
load_dotenv()
openai.api_key=os.getenv('OPENAI_API_KEY') or sys.exit('Missing API key')

EXTS={'.php':Language.PHP,'.ts':Language.TS,'.js':Language.JS,
      '.vue':Language.JS,'.twig':Language.HTML,
      '.scss':Language.CPP,'.css':Language.CPP}
files=[f for f in ROOT.rglob('*') if f.suffix in EXTS]
docs=[]
for f in files:
    try: txt=f.read_text(errors='ignore')
    except: continue
    docs.append(Document(page_content=txt,metadata={'path':str(f.relative_to(ROOT))}))
splits=[]
for d in docs:
    lang=EXTS[pathlib.Path(d.metadata['path']).suffix]
    splits+=RecursiveCharacterTextSplitter.from_language(
        language=lang,chunk_size=args.chunk,chunk_overlap=args.overlap
    ).split_documents([d])

if PERSIST.exists(): shutil.rmtree(PERSIST)
emb=OpenAIEmbeddings(model='text-embedding-3-large')
Chroma.from_documents(documents=splits,embedding=emb,
    persist_directory=str(PERSIST),collection_name=args.col)
print('Indexed at',PERSIST)

3. 質問応答 API 整備

FastAPI で /ask(QA)と /render_mermaid(Mermaid→PNG)を提供。
クエリパラメータでリポジトリ(repo1/repo2)とモデル(gpt-4.1/gpt-4.1-mini/gpt-4o-mini/o4-mini)を切替可能です。
起動例:uvicorn qa_api:app --host 127.0.0.1 --port 8600 --log-level debug
#!/usr/bin/env python3
"""
qa_api.py — QA(+Mermaid→PNG) API(repo/model切替可)
"""
import pathlib,uuid,subprocess,uvicorn
from fastapi import FastAPI,HTTPException,Query
from fastapi.responses import FileResponse
from pydantic import BaseModel
from dotenv import load_dotenv
from langchain_openai import OpenAIEmbeddings,ChatOpenAI
from langchain_chroma import Chroma
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate

PERSIST_BASE=pathlib.Path.home()/'chroma-data'
REPOS=['repo1','repo2']
MODELS=['gpt-4.1','gpt-4.1-mini','gpt-4o-mini','o4-mini']
TMP=pathlib.Path('/tmp/mermaid_pngs');TMP.mkdir(mode=0o777,parents=True,exist_ok=True)
DOCKER_IMG='minlag/mermaid-cli';SCALE='3'
EMB='text-embedding-3-large';TOP_K=8
load_dotenv()

emb=OpenAIEmbeddings(model=EMB)
RETRIEVERS={r:Chroma(persist_directory=str(PERSIST_BASE/r),
    collection_name=r,embedding_function=emb
).as_retriever(search_kwargs={'k':TOP_K}) for r in REPOS}

prompt=PromptTemplate(input_variables=['context','question'],template="""
You are a code expert. Context:
{context}

Question:
{question}

1. Answer concisely.
2. If sequence diagram is requested, include a Mermaid sequenceDiagram block.
3. Otherwise, no Mermaid.
""")

app=FastAPI()
class QAReq(BaseModel):question:str

@app.post('/ask')
def ask(req:QAReq,
        repo:str=Query('repo1',enum=REPOS),
        model:str=Query('gpt-4.1',enum=MODELS)):
    retr=RETRIEVERS.get(repo)
    if not retr: raise HTTPException(400,'Unknown repo')
    if model in ['gpt-4.1','gpt-4.1-mini','o4-mini']:
        llm=ChatOpenAI(model_name=model)
    else:
        llm=ChatOpenAI(model_name=model,temperature=0.0)
    chain=RetrievalQA.from_chain_type(llm=llm,chain_type='stuff',
        retriever=retr,chain_type_kwargs={'prompt':prompt},
        return_source_documents=True)
    res=chain(req.question)
    return {'answer':res['result'],
            'sources':[d.metadata['path'] for d in res['source_documents']]}

class RenderReq(BaseModel):mermaid:str

@app.post('/render_mermaid')
def render(req:RenderReq):
    k=uuid.uuid4().hex
    m=TMP/f'{k}.mmd';p=TMP/f'{k}.png'
    m.write_text(req.mermaid,encoding='utf-8')
    try:
        subprocess.run(['docker','run','--rm','-v',
            f'{TMP.absolute()}:/data',DOCKER_IMG,
            '-i',f'/data/{k}.mmd','-o',f'/data/{k}.png',
            '-s',SCALE],check=True,stdout=subprocess.PIPE,
            stderr=subprocess.PIPE)
    except subprocess.CalledProcessError as e:
        err=(e.stderr or e.stdout).decode().strip()
        raise HTTPException(500,detail=f'Mermaid error: {err}')
    return FileResponse(str(p),media_type='image/png')

@app.get('/health')
def health(): return {'status':'ok'}

if __name__=='__main__':
    uvicorn.run(app,host='0.0.0.0',port=8600)

4. Web UI 整備

起動例:streamlit run ui.py --server.port 8501
#!/usr/bin/env python3
"""
ui.py — Streamlit チャット UI(repo/model選択+PNG表示)
"""
import os,re,requests,streamlit as st
from dotenv import load_dotenv

st.set_page_config(page_title='コードアシスタント',layout='wide')
load_dotenv()
API_ASK=os.getenv('API_ASK','http://localhost:8600/ask')
API_RDR=os.getenv('API_RENDER','http://localhost:8600/render_mermaid')

repo=st.sidebar.selectbox('リポジトリ',['repo1','repo2'],index=0)
model=st.sidebar.selectbox('モデル',['gpt-4.1','gpt-4.1-mini','gpt-4o-mini','o4-mini'],index=0)
st.sidebar.markdown(f'ask: `{API_ASK}`')
st.sidebar.markdown(f'render: `{API_RDR}`')

MERMAID_RE=re.compile(r'```mermaid\n(.*?)```',re.S)
st.title('🛠️ コード QA')

if 'chat' not in st.session_state: st.session_state.chat=[]

for r,m in st.session_state.chat: st.chat_message(r).write(m)
q=st.chat_input('質問を入力...')
if q:
    st.session_state.chat.append(('user',q));st.chat_message('user').write(q)
    with st.spinner('回答中…'):
        res=requests.post(f"{API_ASK}?repo={repo}&model={model}",
                          json={'question':q}).json()
        ans,sources=res['answer'],res['sources']
        st.session_state.chat.append(('assistant',ans))
        with st.chat_message('assistant'):
            parts=MERMAID_RE.split(ans)
            for i,p in enumerate(parts):
                if i%2==0 and p.strip(): st.markdown(p)
                if i%2==1:
                    img=requests.post(API_RDR,json={'mermaid':p}).content
                    st.image(img,use_container_width=True)
                    with st.expander('Mermaid ソース'): st.code(p)
            if sources:
                with st.expander(f'{len(sources)} 件の参照コード'):
                    for s in sources: st.markdown(f'- `{s}`')

5. Nginx 設定(抜粋)

location /api/ {
    proxy_pass http://127.0.0.1:8600/;
}
location / {
    proxy_pass http://127.0.0.1:8501;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

6. ブラウザで動作確認

ブラウザで / を開き、「ログイン処理の概要やシーケンス図を生成して」など質問すると、回答+シーケンス図が得られます。

今後の予定

自動コードレビューや、エディタ内でのコード補完など。

抜け漏れを防ぐには見方を変える。

はじめまして、本記事投稿時点では弊社開発部門最年少の中村です。(来月から新卒が来るので最年少卒業です。うれしいですね。)  

さて私は最近の業務で主に弊社の工場のWF改善設計業務をしています。SEなので新システムを使ったWFを想定するわけですが、あくまでもシステムは道具なのでより効率的に印刷が行えるようなWFもセットで考えることが私たちの業務なのです。 ただその設計がとても難しい。。。
システムだけじゃなくリアルな人や物の動きも想像しないといけないので考えることが爆増です。   でも実は難しくしちゃってるのは自分だなということもあります。特によく指摘されるのが思考の飛躍や、唐突に現れる新しい概念。
1.野菜と肉とカレールーを買う
2.野菜と肉を切る
3.野菜と肉に火を入れる
4.野菜と肉とカレールーを煮込む
5.煮込んだカレーとご飯をお皿に盛りつける
例えば上の例はカレーを作る処理です。この処理の中でどこにも定義されていない要素が唐突に登場しています。
何かわかりますか??


  。。。正解は「ごはん」です。
この工程の中にはごはんを炊く工程が書かれていません。上述のようにテキストベースだけで書いちゃうと私はこうした抜け漏れをよくやっちゃいます。(わかってて端折るのはいいですが、私の場合端折ってることにも気づかないです。)毎度毎度上から下まで抜け漏れがないか頑張って見直しても見つからないのです。。。

このカレー作り工程をコードっぽく書いてみるとあら不思議、ごはん要素が初期化されていないことがわかりやすくなりました。(少なくとも私はエラーに気づきやすくなりました。)  
【 野菜, 肉 , カレールー】 = 買い物();
cutted = 切る(野菜, 肉);
fired = 火を入れる(cutted);
stewed = 煮込む(fired, カレールー);
plated = 盛り付け(stewed, ごはん);
こんな風にコードっぽく書く以外にも、アクティビティ図やポンチ絵にしてみるとこういったミスに気づきやすくなります。   よく抜け漏れや誤字脱字をやっちゃうなって人はこんな風に書き方や、見方を変えてみるとミスに気づきやすくなると思います! 設計だけでなく資料やメールを書くときなんかにも役立つと思うのでぜひお試しあれ~

Konvaのグループ機能について

前回はStageとLayerを作成し、オブジェクトをLayerに追加して表示できるようにしました。今回は、オブジェクトのグループ化について説明します。
オブジェクトを別々のLayerに追加することもできますが、複数のオブジェクトを一緒に表示したり、コントロールしたりしたい場合があります。
例えば、くまの画像とその名前のテキストを一緒に表示したい時です。こういった場合、グループ化することで、必ず一緒に移動したり配置したりするオブジェクトを効率よく管理できます。
Konvaでグループを作成する方法は次の通りです。

const group = new Konva.Group({
                    x: 100,
                    y: 100,
                    rotation: 0,
                });
グループ化された場合、stage上の位置はグループのx、y設定によって決まります。
グループを作成したら、次に画像と文字のKonvaオブジェクトを作成します。

const bear = new Image();//こちらのはHTML Image element
bear.src = imageUrl;

bear.onload = () => {
    const bearImg = new Konva.Image({ //こちらのはKonvaのImage Object
        x: 0, //stageではなく、グループ中のX位置
        y: 0, //stageではなく、グループ中のY位置
        rotation: 0,
        width: 100,
        height: 100,
        image: bear,
    });

    //作成した画像オブジェクトをグループに追加する
    group.add(bearImg); 
};
注意点:グループに追加されるのは最初に作成したHTMLのImage要素ではなく、KonvaのImageオブジェクトです。そのため、onload内でこのオブジェクトをグループに追加します。
次に文字オブジェクトを作成します。画像がロードされた後、文字オブジェクトを作成し、グループに追加したいため、onload内で作成して、グループに追加します。

    const  name = new Konva.Text({
        x: 0,
        y: 110,  //くまの高さは100、くまの下にするため、110に設定する
        text: 'Bob',
        fontSize: 16,
        fill: 'black',
    });

    //作成した文字オブジェクトをグループに追加する
    group.add(name);
オブジェクトがすべて作成された後、グループをLayerに追加し、Layerを描画して追加内容を表示します。

this.layer.add(group);
this.layer.draw();
注意点: :画像と文字はすでにグループに追加されているので、個別にLayerに追加する必要はありません。グループのみを追加します。
画像や文字、グループを別々にLayerに追加すると、コントロールできない不具合が発生する可能性があるため、そのような操作は避けてください。
以上が、Konvaでのオブジェクトグループ化の紹介です。

開発本部の様子 歓迎会編

URAです。
明けましておめでとうございます!
今年は巳年ですね。
ヘビーな課題もガンガン解決していきたいです。ニョロしくお願いします。
(ダジャレで攻めたからって離脱しないで…)

さて、2025年のスタートを記念して
昨年末にあった飲み会 新メンバーの歓迎会の様子を公開します!

1枚目:おそらくけっこう飲んだあとの終盤

歓迎会の様子

楽しそうですね! 仕事の話もプライベートの話も何でもアリです!

2枚目:歓迎会終わりに駅まで歩いているところを盗撮されていた件

歓迎会帰り

後楽園駅の近くにオフィスがあるので、冬は会社から帰るついでに東京ドーム付近でイルミネーションが見られます!
キレイですね!毎日でも見に行きたいですね!
見に行きたい方、イメージ・マジックではエンジニアなど複数ポジションを募集しています!
ページ右上のバナーからぜひご応募ください!

ここまで読んで、今日はどんなテックに関する記事なんだろうと未だに思っている真面目な方へ。
申し訳ありません!今日はテックのテの字も出てきません!
捻りだそうかとも思いましたが私の力不足でクの字も出ません!
これからも開発本部の様子をたまに投稿していけたらと思っています!
テックに関する記事は他のメンバーの投稿をご期待ください!

SPF・DKIM・DMARCの簡単な解説

こんにちは岡野です。
時々分からなくなるためSPF・DKIM・DMARCについてまとめます。

前提知識

メールのFromアドレスには2種類ある。
エンベロープFrom:SMTP通信で使用するもの。メールヘッダ内ではReturn-Pathとして確認できる。
メールヘッダFrom:メール送信者が本文などとともに指定するもの。メールクライアントではこちらが表示されるため、なりすましを防ぐにはこちらが正しいことが重要。

SPF認証

エンベロープFromのドメインでDNSからSPF用IPアドレスリストを取得し、送信サーバのIPアドレスが存在することを確認する。

DKIM認証

メール署名時のドメインでDNSから公開鍵を取得し、ハッシュが一致することを確認する。

DMARC

SPF認証&SPFアライメント、もしくは、DKIM認証&DKIMアライメント、のいずれかが成り立つことを確認する。
SPFアライメント:エンベロープFromのドメインとメールヘッダFromのドメインが一致すること。
DKIMアライメント:DKIM署名のドメインとメールヘッダFromのドメインが一致すること。

結論

SPF・DKIM・DMARCすべて対応するのが望ましい。

初めてのkonva.jsインストール

こんにちは、陳です。
最近Konva.jsを使い始めたので、少しだけ共有したいと思います。


Konvaオフィシャルサイト
https://konvajs.org/index.html




Konva.jsとは何ですか?

Konva.jsは、ウェブ上で2Dグラフィックスを簡単に作成できるJavaScriptライブラリです。HTML5のcanvas要素の上に構築されており、図形、画像、テキストを描画したり、アニメーションやインタラクティブな機能を追加したりできます。シンプルな描画から複雑なアニメーションまで、Konva.jsはアイデアを実現するための強力で柔軟なツールセットを提供します。


Konva.jsのステージとレイヤーの概念

Konva.jsでは、ステージはキャンバス上のすべてを保持するメインのコンテナです。ステージは描画領域全体を表します。ステージの中には複数のレイヤーを持つことができます。各レイヤーは、図形、画像、テキストを描画できる透明な紙のシートのようなものです。レイヤーを使うことで、コンテンツを整理し、描画の異なる部分を独立して管理および更新することが容易になります。 (https://konvajs.org/docs/overview.html)


初めて行うのは、konva.jsのインストールです。
フロントエンドプログラムとしてVueやReactを考える場合、それに対応するvue-konvaやreact-konvaをインストールできます。それを使わずに、単純にkonva.jsをインストールすることも可能です。

ただ、インストールした際に、現在の環境でスムーズに動作するかどうかを考える必要があります。多少他のライブラリや設定を追加しないと動作しない場合があります。これらの判断や調整は少し面倒かもしれません。

今回、まだテスト段階ですが、指定したバージョンのkonva.min.jsをダウンロードして、このファイルをインポートする方法がかなり簡単です。既存の開発環境への影響が少なく、CDNリンクよりも安定しています。

Symfonyで運用する場合、使用したいTWIGページにダウンロードしたkonva.min.jsファイルをインポートし、通常のJavaScriptファイルと同様に、JavaScriptやVue、konvaを利用すればOKです。

ただし、vue-konvaを使用しないため、Vueのdataにパラメータを設定し、konvaのstage、layer、circleなどのコンポーネントの状態を保存、変更する必要があります。

例えば:
import Vue from 'vue'; new Vue({
     delimiters: ['${', '}'],
     el: '#editArea',
     mixins: [],
     data: {
             stage: null,
             layerCircle: null,
             circle: null,
           },
mounted() {
     this.showCircle();
          },
methods: {
     showCircle() {
         // 使用したい<div>を取る
         const container = this.$refs.container;
         const {width, height} = container.getBoundingClientRect();

         // stageを作成する
         this.stage = new Konva.Stage({
              container: container,
              width: width,
              height: height,
         });

         // layerを作成する
         this.layerCircle = new Konva.Layer();

         // 表示したいのサークルを設定する
         this.circle = new Konva.Circle({
              x: stage.width() / 2,
              y: stage.height() / 2,
              radius: 70,
              fill: 'yellow',
              draggable: true,
         });

         // layerをstageに追加する
         this.stage.add(this.layerCircle);
         // サークルをlayerに追加する
         this.layerCircle.add(this.circle);
         // 設定済みのlayerを描く(画面で表示する)
         this.layerCircle.draw();
                 },
         }
}) 


今回のシェアはここまでです。興味があれば、ぜひkonvaのサイトのDEMOを見てみてください。

チェックポイントファイルの拡張子について

くろはです。今日は画像生成AI関連でチェックポイント(モデルファイル)の拡張子について疑問に思って調べてみたことを共有したいと思います。 Googleなどで画像生成AIについて検索をするとおそらく目にするであろう「StableDiffusion」、このAIではチェックポイントという学習済データを指定することによって画風などをある程度決めることが可能です。そしてそのチェックポイントファイルは「Civitai」や「Hugging Face」といったウェブサイト上からダウンロードすることが可能です。 実際にCivitaiからチェックポイントファイルをDLしてみたところ、ファイルの拡張子が「.ckpt」と「.safetensors」というように複数あることに気づきました。今回はこの2種類の拡張子について軽くまとめます。

ckpt

「.ckpt」という拡張子は「pickle」というPythonモジュールを用いて直列化して保存されたデータに用いられる、Python固有のデータフォーマットです。pickleモジュールはPythonの各種オブジェクトをバイト列に変換したり、変換したりしたバイト列をファイルに保存したりする用途に使われます。 pickleの主な利点はPythonオブジェクトの状態を保存して後で再利用できることで、機械学習や大規模なデータ処理に非常に役立ちます。そんなpickleですがモジュールのトップレベルで定義されている組み込み関数などがPickle化できるため任意のコードが実行される可能性があり、脆弱性の問題を抱えています。この問題について、Pythonの公式ドキュメントでも注意喚起をしています。

safetensors

一方、「.safetensors」という拡張子は上で名前の出たHugging Faceによって開発された新しいシリアル化形式です。深層学習で重要なテンソルという呼ばれる大きくて複雑なデータの塊を保存、および取得するための特別な方法で、ファイルにはテンソルを安全に処理するためのアルゴリズムが保存されており悪意のあるコードに対して安全であると考えられています。 補足ですがテンソルとは、物理学やコンピューターサイエンスなどの様々な分野で使用される数学的な概念です。データを多次元配列として表現する方法です。ディープラーニングと人工知能の領域においてはテンソルはデータの編成と操作に使用される基本的なデータ構造として見られています。   以上、簡素ではありますが画像生成AIについて調べている過程で出会った2種類の拡張子についての共有でした。

scpコマンドでリモートからリモートにファイルを転送する

こんにちは。
今年はスマホで遊んでいたゲームが3本サービス終了して悲しいたにすぎです。
でも大丈夫、まだ舞台の予定がある……!
さて、3といえば最近SCPコマンドのオプションでリモートからリモートに転送できる方法があることに気づいたので共有します。

開発してるとたまに、テスト環境から○○のファイルをローカルに落として、ローカルから他のサーバにあげて動作確認するぞー……みたいな作業が発生して待ち時間とかちょっとだるかったりするのですが、なんとあるオプションを使うと1発で作業先のサーバまでファイルの転送が出来るんですなあ。 ■ 使い方
通常こんなふうに2回にわけて落として、上げてってやる必要がありますが。
scp user@src.example.com:~/sample.zip .
scp ./sample.zip user2@dist.example.com:~/sample.zip

なんと-3をつけるだけでこんな感じ1回で転送できます。
scp -3 user@src.example.com:~/sample.zip user2@dest.example.com
(portや鍵の指定は .ssh/config 辺りに接続設定書いておくと使う時にごちゃごちゃしなくてよい)

地味に便利だったので共有でした。へー(-3-)って思ってくれるとうれしいです。

高校の「ベクトル」に対して感じる違和感を見直してみる

こんにちは。こんばんば。イメージマジック三浦です。

Artificial Intelligence 略して「AI」という用語が一般に出るようになって、久しくなりました。この「AI」の裏側では「ベクトル空間」の世界の中で決められた規則に則って、大量の足し算と掛け算が実行されています。

私が「ベクトル」という言葉を初めて聞いたのは高校の数学(2022年度の学習指導要領では数学C)でした。当時「ベクトル」の定義は違和感を感じる表現で、そこから「ベクトル空間」へ飛び出すのはハードルが高かったと感じていました。

今回は高校の「ベクトル」の定義に対して感じた違和感を見直すことで、「ベクトル空間」への足掛かりになるような話を書いてみます。

ベクトルとは

高校の数学で出てくる「ベクトル」は、「大きさと向きを持つ量」という定義で出てきます(厳密な表現の違いはあるかもしれません)。
図では右のように矢印(有向線分)で表現します。
「大きさ」は点Aと点Bを結ぶ線の長さを測ればいいので、【量】と考えやすいです。

一方で「向き」はしっくりきません。右から左に向いているのは分かりますが、どうにも数値では表現しづらいです。 数値で表現できない概念を【量】と定義することに、高校生の時は違和感を持っていました。

「向き」を数値化する

「向き」を【量】とする違和感を「そういうもの」とやり過ごしていたら、大学の時に「ベクトル空間」を扱う線形代数学で苦労することになりました。この違和感を放っていたら、「ベクトル空間」へ飛び出す時の足枷になりそうです。
そこで「向き」を数値で表す方法を考えてみます。そのために、先ほどの矢印をXY座標平面に書き写してみます。話を簡単にするため、点Aは原点、点Bは整数値の座標で表現できたことにします(図1)。
右の図では、点Aから点Bへ移動するにはX軸方向に-3、Y軸方向に2移動すればよいです。 ここで、ベクトルのうち線分のみに注目して、原点から点Bまでの距離、X軸から線分ABまでの回転角が測れます。実際にやってみましょう。
図1
原点から点Bまでの距離:点A(原点)と点Bを結ぶ線分の長さとすれば、三平方の定理により√13≒3.606 と分かります。
X軸から線分ABまでの回転角:点Aを起点として、反時計周りに線分ABまで回転した角度を測ると、約146.31度という値がでます。
図2

言いたかったこと

XY座標平面を導入することで、「原点から点Bまでの距離」と「X軸から線分ABまでの回転角」が測れるようになりました。2つの測定値をベクトルの「大きさ」と「向き」とすれば、ベクトルが持つ2つの量を数値で表現できます。言い換えると、次の2つは同じ意味です。
  • XY座標の原点から点B(-3, 2)へ移動すること。
  • XY座標の原点を起点として、反時計周りに146.31°回転した方向へ、√13分の距離を移動すること。
    注意:原点を中心として回転するのは何週でもできますが、最終的に146.31°の位置で止まれば同じ方向です。ただし話を簡単にするため、回転角は0°~360°の間に限定して考えることを前提にします。
これで、高校当時抱いていた「向きは数値で表しづらい」という違和感に1つ答えを出せました。

ちなみに、単にベクトルというだけでは位置は関係しませんが、片方の点を固定することでもう一方の点が決まります。そうすると、ベクトルが固定した点からの位置を表現できるようになり、「位置ベクトル」という考え方が出てきます。詳しい定義は、教科書や解説サイトに譲ります。

次の話

ベクトルをXY座標平面に置くことで、ベクトルの「大きさ」と「向き」を数値で表せること、XY座標表現に置き換えられることが分かりました。ここでXY座標表現を使うと、ベクトルの「計算」を数値の計算に持ち込みやすくなります。次回はベクトルの「計算」について触れてみます。

OS Loginを有効にしたGCEにPuttyやWinSCPで接続する方法

こんにちは岡野です。
GCPのCompute EngineへPuttyやWinSCPで接続したいという要望があり検索したのですが、なかなか情報が無かったためここで共有します。

・GCEの状態

外部IPアドレス未設定
OS Login有効

・接続方法

SSHトンネルを利用する。

1. 以下のコマンドをWSL上などで実行。

gcloud compute ssh {{ gce name }} --ssh-flag="-L 10001:localhost:22 -N -f"
    1. PuttyやWinSCPなどでは以下の情報で接続する。
    ユーザ名:GCPで使用しているアカウント(通常はメアドの記号を_へ置換したもの、例:name_example_com)
    ホスト名:localhost
    ポート:10001
    秘密鍵:GCP用の鍵