Skip to main content

Deterministic, hallucination-resistant multi-agent framework with structured output, source grounding, confidence gates, and an independent critic loop.

Project description

hallguard

ハルシネーションを極限まで抑え、決定論的な出力に特化した 軽量 Python マルチエージェントフレームワーク。 PyPI 上の distribution 名は hallguard、Python の import 名は hallucination_guard

LangChain / CrewAI のように「できることを増やす」のではなく、 「信頼性を保証する」 ことに振り切った逆張りポジションを取る。

⚠️ 本リポジトリは現在 初期実装フェーズ です。 State / DomainConfig / Retry 層 / 全ノード / Graph(LangGraph 組み立て)/ OpenAI アダプタ / ベンチマーク CLI まで完成しており、 Graph(...).run("query") でエンドツーエンドに動きます。 詳細は 実装ステータス を参照。


コアコンセプト(変えてはいけない設計思想)

# 仕組み 目的
1 型強制 (Structured Output) LLM の出力を Pydantic スキーマで拘束する
2 出典の強制 (Source Grounding) RAG で渡したドキュメントに無い内容は答えさせない
3 確信度ゲート (FactCheckGate) 閾値未満の出力をパイプライン下流に流さない
4 批評エージェント (CriticNode) 生成 LLM とは独立した第三者で矛盾検出
5 ドメイン注入 (DomainConfig) フレームワークは特定ドメインを知らない。Strategy で外部注入

アーキテクチャ

Input
  │
  ▼
StructuredNode         ← DomainConfig.output_schema() で型強制 / temperature=0
  │
  ▼
FactCheckGate          ← DomainConfig.confidence_threshold / is_valid_source
  ├─ FAIL ──▶ RetryNode ──▶ retry_count >= max_retries → ErrorOutput
  │               └──────────────────────────────────▶ StructuredNode(再試行)
  └─ PASS
        ▼
     CriticNode         ← DomainConfig.critic_prompt()
        ├─ FAIL ──▶ RetryNode(同上)
        └─ PASS ──▶ FinalOutput

ディレクトリ構成

hallguard/
├── hallucination_guard/
│   ├── __init__.py
│   ├── state.py            ← GraphState / FailReason(イミュータブル)
│   ├── exceptions.py       ← GraphError 階層
│   ├── schemas.py          ← Claim / GroundedOutput / CriticVerdict
│   ├── graph.py            ← Graph(LangGraph 組み立て)
│   ├── llm/
│   │   ├── protocols.py    ← StructuredLLM / JudgeLLM プロトコル
│   │   └── openai_adapter.py ← OpenAI Structured Outputs アダプタ
│   ├── nodes/
│   │   ├── structured_node.py  ← schema 強制 + retry directive 注入
│   │   ├── factcheck_gate.py   ← confidence / source 検証
│   │   ├── critic_node.py      ← 独立判定 + final_output 確定
│   │   ├── retry_node.py       ← retry_count++ / 信号リセット
│   │   └── error_output.py     ← max_retries 超過時の終端
│   ├── domain/
│   │   ├── base.py         ← DomainConfig 抽象クラス
│   │   ├── general.py      ← GeneralDomain(許容的なデフォルト)
│   │   └── medical.py      ← MedicalDomain(厳格なデモ)
│   └── retry/
│       ├── directive.py    ← RetryDirective(注入型を frozen で制限)
│       └── hint_builder.py ← RetryHintBuilder(プロンプト汚染防止)
├── tests/
│   ├── test_state.py
│   ├── test_hint_builder.py
│   ├── test_general_domain.py
│   ├── test_medical_domain.py
│   ├── test_protocols.py
│   ├── test_structured_node.py
│   ├── test_factcheck_gate.py
│   ├── test_critic_node.py
│   ├── test_retry_node.py
│   ├── test_error_output.py
│   └── test_graph_integration.py
├── examples/
│   └── research_agent.py   ← Graph をモック LLM で回すデモ
├── benchmarks/
│   ├── hallucination_rate.py ← 成功率 / 再試行数 / 失敗種別を集計する CLI
│   └── datasets/
│       ├── synthetic_qa.json ← デフォルトのベンチマーク用 QA セット
│       └── medical_qa.json   ← MedicalDomain 用 QA セット
├── CHANGELOG.md
├── Makefile                ← build / release / quality-gate ターゲット
├── pyproject.toml
└── README.md

必要環境

  • Python 3.11 以上(開発・動作確認は 3.13 で実施)
  • macOS / Linux を想定(Windows は未検証)

セットアップ

コアの依存は pydantic のみ。LangGraph / OpenAI は extras で必要な分だけ 追加します。

A. State / Retry 層だけ使う(最軽量)

python3 -m venv .venv
.venv/bin/pip install --upgrade pip
.venv/bin/pip install -e .

Graph は不要だが GraphState / RetryHintBuilder 等は使う、という構成。

B. Graph を回す(LangGraph 込み)

.venv/bin/pip install -e ".[graph]"

C. OpenAI アダプタ込み

.venv/bin/pip install -e ".[openai]"
# graph と両方欲しければ:
.venv/bin/pip install -e ".[all]"

D. 開発用フルセット(テスト・mypy 含む)

.venv/bin/pip install -e ".[dev]"

仮想環境の有効化(任意)

source .venv/bin/activate
# 以降は `pytest` や `mypy` をそのまま打てる
deactivate    # 抜けるとき

動作確認

テスト

.venv/bin/python -m pytest tests/ -v

現状 193 件すべて通過 します(うち統合テスト 40 件は Graph を モック LLM で実行し、成功経路・リトライ・max_retries 超過・max_retries=0 エッジケース・checkpointer 経由の状態永続化・Graph.stream() / Graph.astream() の進捗イベント・Graph.arun() を含む async-native 経路と sync/async 混在時のフォールバック・asyncio.wait_for / 明示 キャンセル経由でのキャンセル経路・asyncio.Semaphore(32) 下の 高並列度 arun フェイク fan-out での state 分離 / fail_history 非共有 ストレステストを検証)。LLM プロトコルの @runtime_checkable 判定は 6 件で sync/async 双方および両方を実装した クラスの分岐を、OpenAI アダプタは in-memory fake で 9 件、ベンチマーク CLI は 23 件で JSON データセット読み込み・--domain 切替・suggested_domain 不一致 警告(および --strict-domain で非ゼロ終了)・--async --concurrency N での 並列実行と並列度上限・並列度=1 の直列退行検出 を含む CLI 引数を、 checkpointer 用シリアライザは 11 件で msgpack allow-list と Graph(auto_serialize=True) オプト機構を回帰チェックします。retry hint の ロケール対応(INSTRUCTION_MAPS["en" | "ja"]DomainConfig.retry_locale() の連動)は test_hint_builder.py(12 件、 英日両方の固定文言マップを parametrize)で固定します。ドメインごとの retry directive テンプレートは test_general_domain.py(19 件、locale="ja" の日本語版および retry_locale() 整合性含む)と test_medical_domain.py (22 件、_ALLOWED_HOSTS ↔ retry テンプレ整合性チェックと locale="ja" 日本語版および retry_locale() 整合性含む)が網羅します。

型チェック

.venv/bin/mypy hallucination_guard/

Success: no issues found in 24 source files が出れば OK。

デモ実行

.venv/bin/python -m examples.research_agent

モック LLM を使って 5 つのシナリオを順に流します:

  1. 1 回目は低 confidence で FactCheckGate が差し戻し、リトライで成功
  2. 常に低 confidence を返す LLM で max_retries を使い切り、ErrorOutput 経路
  3. Graph.stream() でノード単位の進捗イベントを観測(同じシナリオ #1)
  4. Graph.astream()asyncio.run() 経由で消費(#3 と同じイベント列を非同期 API で)
  5. AsyncStructuredLLM / AsyncJudgeLLM を直接実装したクライアントを Graph.arun() で駆動(ブリッジ不要のネイティブ async 経路)

ベンチマーク

.venv/bin/python -m benchmarks.hallucination_rate                       # 集計のみ
.venv/bin/python -m benchmarks.hallucination_rate --verbose             # 各クエリの結果も
.venv/bin/python -m benchmarks.hallucination_rate --dataset my_qa.json  # 任意 JSON で
.venv/bin/python -m benchmarks.hallucination_rate \
    --domain medical \
    --dataset benchmarks/datasets/medical_qa.json                       # MedicalDomain で
.venv/bin/python -m benchmarks.hallucination_rate --async --concurrency 8  # 非同期並列で

決定論的な疑似 LLM を使って is_success 率・平均 retry_count・ 失敗種別(low_confidence / no_source / critic_rejected)の内訳を出力します。 デフォルトは GeneralDomain + benchmarks/datasets/synthetic_qa.json。 タグ仕様は各データセット JSON 内に記述してあります。--dataset でファイル、 --domain {general,medical} でドメインを切り替えられ、ペアとなる JSON を 組み合わせると同じ集計値が再現可能です(疑似 LLM がドメインの is_valid_source を満たす引用 URL を自動で選ぶため)。 各データセット JSON は suggested_domain メタフィールドを持ち、 --domain がそれと食い違うと stderr に WARNING: 行が出ます (実行自体は止まりません)。CI で集計のミスマッチを失敗扱いにしたい 場合は --strict-domain を付けてください — WARNING:ERROR: に 変わり、集計を生成せず非ゼロ終了します。 実 LLM で測りたい場合は --real --model <openai-model-id> を付けて OPENAI_API_KEY を環境変数に設定してください。--async と組み合わせると AsyncOpenAI 系のアダプタが選ばれ、--concurrency N で並列度を制御できます (デフォルト 4、--async 無しでは無視)。集計結果は sync 版と一致することを 回帰テストで保証しています。


使い方

エンドツーエンドで回す(モック LLM)

from pydantic import BaseModel

from hallucination_guard.domain.general import GeneralDomain
from hallucination_guard.graph import Graph
from hallucination_guard.schemas import Claim, CriticVerdict, GroundedOutput


class MyStructuredLLM:
    def generate(self, *, system: str, user: str, schema: type[BaseModel]) -> BaseModel:
        return GroundedOutput(
            claims=[
                Claim(
                    text="The capital of France is Paris",
                    confidence=0.98,
                    sources=["https://en.wikipedia.org/wiki/Paris"],
                )
            ]
        )


class MyJudgeLLM:
    def judge(self, *, system: str, content: str) -> CriticVerdict:
        return CriticVerdict(verdict="PASS")


result = Graph(
    domain=GeneralDomain(),
    structured_llm=MyStructuredLLM(),
    judge_llm=MyJudgeLLM(),
).run("What is the capital of France?")

print(result.is_success)     # True
print(result.final_output)   # JSON 文字列
print(result.retry_count)    # 0

実 LLM を繋ぐ場合は、StructuredLLM / JudgeLLM プロトコル (hallucination_guard/llm/protocols.py)を満たすクラスを書けば差し替え可能です。 OpenAI 用のアダプタは同梱しています:

from hallucination_guard.llm.openai_adapter import (
    OpenAIJudgeAdapter,
    OpenAIStructuredAdapter,
)

graph = Graph(
    domain=GeneralDomain(),
    structured_llm=OpenAIStructuredAdapter(model="<openai-model-id>"),
    judge_llm=OpenAIJudgeAdapter(model="<openai-model-id>"),
)

OPENAI_API_KEY 環境変数からキーを読みます。model は明示指定が必須 (デフォルトを設けないことで、誤ったモデルが裏で使われる事故を防ぐ)。 Structured Outputs(chat.completions.parse + response_format=<Pydantic class>) を内部で使うため、対応モデルが必要です。

State を永続化する(checkpointer)

LangGraph の checkpointer を渡すと、各ノード境界でのスナップショットが 保存されます。スレッド ID で世帯を分け、後から get_state() で復元可能:

from langgraph.checkpoint.memory import InMemorySaver

graph = Graph(
    domain=GeneralDomain(),
    structured_llm=MyStructuredLLM(),
    judge_llm=MyJudgeLLM(),
    checkpointer=InMemorySaver(),
)

graph.run("first question",  thread_id="alice")
graph.run("second question", thread_id="bob")

snapshot = graph.get_state("alice")
print(snapshot.is_success, snapshot.retry_count)

checkpointer を渡した場合は run(..., thread_id=...) 必須です。

LangGraph 1.1+ は checkpoint に書き戻されない型を deserialize すると Deserializing unregistered type ... This will be blocked in a future version. という警告を出し、将来のリリースでハードエラーになります。 本フレームワークの Pydantic 型(GroundedOutput / Claim / CriticVerdict / GraphState / FailReason)を許可リストに登録した serializer を hallucination_guard.serde.build_serializer() から取得できます:

from langgraph.checkpoint.memory import InMemorySaver
from hallucination_guard.serde import build_serializer

saver = InMemorySaver(serde=build_serializer())
graph = Graph(
    domain=GeneralDomain(),
    structured_llm=MyStructuredLLM(),
    judge_llm=MyJudgeLLM(),
    checkpointer=saver,
)

DomainConfig.output_schema() を独自クラスで上書きしているなら build_serializer(MyCustomOutput) で追加もできます。

Graph(auto_serialize=True) を渡せば、Graph 側が checkpointer の デフォルト serializer をフレームワーク用 allow-list 付きに自動で 差し替えます(カスタム serializer がすでに設定されている場合は 誤って上書きしないよう ValueError を出します):

saver = InMemorySaver()
graph = Graph(
    domain=GeneralDomain(),
    structured_llm=MyStructuredLLM(),
    judge_llm=MyJudgeLLM(),
    checkpointer=saver,
    auto_serialize=True,    # saver.serde を build_serializer() に置換
)

ストリーミング(ノード単位の進捗)

Graph.stream() は各ノードの実行が終わるたびに StreamEvent を yield します。StreamEvent.state には その時点までの累積 GraphState が入っているため、最終イベントの state は run() の 戻り値と同じになります。

for event in graph.stream("What is the capital of France?"):
    s = event.state
    print(f"{event.node}: retry={s.retry_count} gate={s.gate_result}")

進行中の UI 表示や per-node テレメトリに使えます。thread_id の扱いは run() と同じ規約です(checkpointer がある場合は必須)。

async 文脈では Graph.astream() を使うと、LangGraph の astream を経由して同じ StreamEvent 列を非同期に受け取れます:

async for event in graph.astream("What is the capital of France?"):
    print(event.node, event.state.retry_count)

stream() と同じ累積方式で research_output の Pydantic インスタンスを 保持します。実 LLM を asyncio で叩く場合の進捗観測に使えます。

単発実行が欲しいだけなら Graph.arun()run() の async 版です:

result = await graph.arun("What is the capital of France?")

AsyncStructuredLLM / AsyncJudgeLLM を実装したクライアントを コンストラクタに渡せば、Graph は async-native 経路(LangGraph の async ノードラッパー)に切り替わります。この場合、誤って run() / stream() を呼んだら RuntimeError が即時に投げられます(LangGraph から coroutine が透過的に返るのを防ぐため)。

from hallucination_guard.llm.openai_adapter import (
    AsyncOpenAIJudgeAdapter,
    AsyncOpenAIStructuredAdapter,
)

graph = Graph(
    domain=GeneralDomain(),
    structured_llm=AsyncOpenAIStructuredAdapter(model="<your-model-id>"),
    judge_llm=AsyncOpenAIJudgeAdapter(model="<your-model-id>"),
)
result = await graph.arun("Who painted Guernica?")

混在モード(structured だけ async、judge は sync、など)も許容されます。 どちらか一方でも async-only なら graph.is_async is True になり、async エントリポイント(arun / astream)のみが利用できます。

低レベル API(State / Retry 層)

from hallucination_guard.state import GraphState, FailReason
from hallucination_guard.retry.hint_builder import RetryHintBuilder

# 1) イミュータブルな State
state = GraphState(user_query="緑茶は癌を予防しますか?")

# 2) 更新は必ず with_update() 経由 — 元は破壊されない
next_state = state.with_update(
    retry_count=state.retry_count + 1,
    fail_reason=FailReason.NO_SOURCE,
)
assert state.retry_count == 0
assert next_state.retry_count == 1

# 3) RetryDirective を組み立てる(プロンプト注入の唯一の入口)
directive = RetryHintBuilder.build(next_state)
print(directive.fix_instruction)
# → "主張ごとに出典URLを必ず添付してください"
print(directive.forbidden_claims)
# → []

CRITIC が前回否定した主張は fail_history"critic_rejected:<claim>" の形式で追記する規約です。

s = GraphState(
    user_query="...",
    fail_reason=FailReason.CRITIC_REJECTED,
    fail_history=["critic_rejected:緑茶は癌を治す"],
)
RetryHintBuilder.build(s).forbidden_claims
# → ['緑茶は癌を治す']

実装ステータス

ファイル 状態
state.py ✅ 完了
exceptions.py ✅ 完了
schemas.py ✅ 完了
llm/protocols.py ✅ 完了(StructuredLLM / JudgeLLM / AsyncStructuredLLM / AsyncJudgeLLM
domain/base.py ✅ 完了
domain/general.py ✅ 完了
retry/directive.py ✅ 完了
retry/hint_builder.py ✅ 完了
nodes/structured_node.py ✅ 完了(retry プロンプト組み立ては DomainConfig.format_retry_directive に委譲・acall() で async クライアント対応)
nodes/factcheck_gate.py ✅ 完了
nodes/critic_node.py ✅ 完了(acall() で async クライアント対応)
nodes/retry_node.py ✅ 完了
nodes/error_output.py ✅ 完了
tests/test_state.py ✅ 7 件通過
tests/test_hint_builder.py ✅ 12 件通過(INSTRUCTION_MAPS"en" / "ja" 両方を parametrize)
tests/test_general_domain.py ✅ 19 件通過(format_retry_directive / locale="ja" / retry_locale() 含む)
tests/test_retry_node.py ✅ 5 件通過
tests/test_error_output.py ✅ 5 件通過
tests/test_factcheck_gate.py ✅ 10 件通過
tests/test_structured_node.py ✅ 13 件通過(acall / 非同期クライアント / sync 呼び出し拒否 / retry_locale 連動 含む)
tests/test_critic_node.py ✅ 11 件通過(acall / 非同期クライアント / sync 呼び出し拒否 含む)
tests/test_protocols.py ✅ 6 件通過(@runtime_checkable の sync/async 判定)
tests/test_medical_domain.py ✅ 22 件通過(format_retry_directive / _ALLOWED_HOSTS 整合性 / locale="ja" / retry_locale() 含む)
tests/test_graph_integration.py ✅ 40 件通過(max_retries=0 / checkpointer / Graph.stream() / Graph.astream() / Graph.arun() + async-native 経路 / asyncio.wait_for / 明示キャンセル経由のキャンセル経路 / 高並列度 arun の state 分離・fail_history 非共有ストレス 含む)
tests/test_openai_adapter.py ✅ 9 件通過
tests/test_benchmark_smoke.py ✅ 23 件通過(CLI / データセット読込 / --domain 切替 / suggested_domain 不一致警告 / --strict-domain / --async --concurrency N 並列実行 / 並列度=1 の直列退行検出)
tests/test_serde.py ✅ 11 件通過(msgpack allow-list / install_framework_serializer / Graph(auto_serialize=True)
domain/medical.py ✅ 完了(デモ用厳格ドメイン)
graph.py ✅ 完了(LangGraph 組み立て・run() / arun() / stream() / astream() / auto_serialize 対応・sync/async クライアント自動判定)
serde.py ✅ 完了(LangGraph checkpoint 用 allow-list serializer)
examples/research_agent.py ✅ 完了(モック LLM デモ + ストリーミング + Graph.astream() 非同期デモ + Graph.arun() async-native デモ)
llm/openai_adapter.py ✅ 完了(Structured Outputs / 注入クライアント / sync + async アダプタ)
benchmarks/hallucination_rate.py ✅ 完了(--domain {general,medical} 切替 / --strict-domain / --async --concurrency N 並列実行 対応)
benchmarks/datasets/synthetic_qa.json ✅ デフォルト(General)データセット
benchmarks/datasets/medical_qa.json ✅ MedicalDomain 用データセット
Makefile ✅ 完了(build / release / quality-gate 一式)

実装の優先順位は以下のとおり:

  1. state.pyGraphState / FailReason
  2. domain/base.pyDomainConfig 抽象クラス
  3. retry/directive.py + retry/hint_builder.py — プロンプト汚染防止層
  4. nodes/structured_node.py — schema 強制 / temperature=0
  5. nodes/factcheck_gate.py — confidence / source バリデーション
  6. nodes/critic_node.py — 矛盾検出
  7. nodes/retry_node.py — イミュータブル更新 / hint 注入
  8. graph.py — 全体組み立て
  9. tests/ — 各ノード単体テスト
  10. examples/research_agent.py — デモアプリ

設計上の重要ルール(破ると思想が崩れる)

開発に参加する場合は、以下を必ず守ること。

1. State は必ずイミュータブル更新

# ✅ OK
new_state = state.with_update(retry_count=state.retry_count + 1)

# ❌ NG(直接ミューテーション禁止)
state.retry_count += 1

2. プロンプトに fail_history の生文字列を埋めない

fail_history にはユーザー入力や LLM の前回出力に由来する文字列が含まれるため、 プロンプトインジェクションの侵入経路 になります。 プロンプトに注入できるのは RetryDirective 型だけ、と決まっています。

# ✅ OK
directive = RetryHintBuilder.build(state)
prompt = build_prompt(directive)

# ❌ NG
prompt = f"前回の失敗: {state.fail_history}"

RetryHintBuilderINSTRUCTION_MAPS[locale]固定文言 からのみ fix_instruction を生成します。動的な文字列は混入しません。localeDomainConfig.retry_locale() 経由で StructuredNode から渡され、デフォルト は "en" です。

StructuredNode から見える retry プロンプトの組み立て窓口は DomainConfig.format_retry_directive(base_prompt, directive) です。 セパレータの文言や禁止クレームの並べ方を変えたいときは、ドメイン側で このメソッドだけを差し替えれば足ります(StructuredNode を継承する 必要はありません)。実装上、fail_history の生文字列を組み込んではいけない という不変条件は引き続きこの境界で保たれます。

ビルトインの GeneralDomain / MedicalDomainlocale="en"(既定)と locale="ja" を受け付け、system_prompt / critic_prompt / format_retry_directive / retry_locale の 4 つを同時に切り替えます。 これにより RetryHintBuilder が返す fix_instruction も locale に追従し、 英語プロンプトに日本語の指示文が混入しません。verdict=PASS / verdict=FAIL などの構造化出力マーカーや出典ブランド名(PubMed, WHO, CDC, Cochrane, NEJM)は、日本語版でも ASCII のまま保たれ、ホスト allow-list や CriticVerdict のパースを壊さない設計です。Locale 型 (Literal["en", "ja"])は hallucination_guard.domain.base.Locale から import できます。

3. ドメイン知識をフレームワーク本体に書かない

# ✅ OK
graph = Graph(domain=MedicalDomain())

# ❌ NG(フレームワーク本体の if 文に書かない)
if domain == "medical":
    threshold = 0.95

閾値・出典バリデーション・Critic プロンプト・出力スキーマは すべて DomainConfig サブクラスに閉じ込める

4. 無限ループ防止

ルーティング関数では 最初に retry_count >= max_retries をチェックすること。

def route_after_gate(state: GraphState) -> str:
    if state.retry_count >= state.max_retries:
        return "error_output"   # ← 必ず先頭
    if state.gate_result == "FAIL":
        return "retry"
    return "critic"

max_retries のデフォルトは 3Graph 初期化時に上書き可能にする予定。

5. LLM 呼び出しは nodes/ 以下にだけ書く

state.py / domain/ / retry/ から LLM API を直接叩かない。 テスト容易性と関心分離のため。


fail_history のエントリ形式

get_rejected_claims() が動くために、 fail_history の各エントリは次の形式で書きます:

"<FailReason.value>:<本文>"

例:

"low_confidence:確信度0.4 を返した"
"no_source:出典なしで断定した"
"critic_rejected:緑茶は癌を治す"

このうち critic_rejected: プレフィックスのものだけが get_rejected_claims() および RetryDirective.forbidden_claims に流れます。 プロンプトに到達するのは prefix を除いた本文のみ です(プレフィックス文字列は混入しません)。


トラブルシュート

ModuleNotFoundError: No module named 'pydantic'

仮想環境を有効化し忘れているか、依存をインストールしていません。 セットアップ を参照。

pytesttests/ を見つけない

ルートディレクトリ(pyproject.toml のある場所)で実行してください。 pyproject.toml[tool.pytest.ini_options]testpaths = ["tests"] を指定しています。

mypy が import エラーを出す

langgraph のような外部依存を pip install -e ".[dev]" で 入れていない可能性があります。hallucination_guard/graph.pylanggraph を import するため、未インストールだと mypy も失敗します。 extras を絞っている場合(例: .[openai] だけで graph を含めていない)も 同じ事象が起きるので、フル開発時は .[dev] または .[all] を使ってください。


リリース手順(PyPI)

すべて Makefile のターゲットに集約してあります。事前に make install-dev を済ませて build / twine を取り込んでおくこと。

make check          # pytest + mypy + ポリシー grep
make build          # dist/*.tar.gz と *.whl を作る
make release-check  # check + build + twine check
make release-test   # TestPyPI へアップロード(事前に ~/.pypirc を設定)
make release        # 本番 PyPI へアップロード

リリースを切るときの手順:

  1. pyproject.tomlversion を bump
  2. CHANGELOG.md 先頭に新エントリを追記(Keep a Changelog 形式)
  3. make release-check で artifacts を検証
  4. make release-test で TestPyPI に上げて pip install -i https://test.pypi.org/simple/ hallguard==<ver> を確認
  5. 問題なければ make release
  6. Git タグを切って push

認証は ~/.pypircTWINE_USERNAME / TWINE_PASSWORD 環境変数で渡します (API トークンを推奨)。


ライセンス

未定。

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

hallguard-0.8.2.tar.gz (61.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

hallguard-0.8.2-py3-none-any.whl (40.8 kB view details)

Uploaded Python 3

File details

Details for the file hallguard-0.8.2.tar.gz.

File metadata

  • Download URL: hallguard-0.8.2.tar.gz
  • Upload date:
  • Size: 61.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.0

File hashes

Hashes for hallguard-0.8.2.tar.gz
Algorithm Hash digest
SHA256 353867ff2b1d180bfe047f8cdb666c6abc053777baa65f4a93453958137b77d4
MD5 95a2a9beed173a7618cf6882ca120794
BLAKE2b-256 4841cd36937c24a9c0ff48f5483b85286c81ab9b1c540b3ce79ec037e14ec8bd

See more details on using hashes here.

File details

Details for the file hallguard-0.8.2-py3-none-any.whl.

File metadata

  • Download URL: hallguard-0.8.2-py3-none-any.whl
  • Upload date:
  • Size: 40.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.0

File hashes

Hashes for hallguard-0.8.2-py3-none-any.whl
Algorithm Hash digest
SHA256 9ba3a76929a5a603f734c1f9fa62d890cc4a8a5f2a4d250cd254f21310446a15
MD5 d92670cb18bf9274529f8e90534b66d1
BLAKE2b-256 e4394a5d82f3d4ea9ed55e2a945ae4da50f6e070f7a2408b53c612c210da0a5e

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page