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 回目は低 confidence で
FactCheckGateが差し戻し、リトライで成功 - 常に低 confidence を返す LLM で
max_retriesを使い切り、ErrorOutput経路 Graph.stream()でノード単位の進捗イベントを観測(同じシナリオ #1)Graph.astream()をasyncio.run()経由で消費(#3 と同じイベント列を非同期 API で)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 一式) |
実装の優先順位は以下のとおり:
state.py—GraphState/FailReasondomain/base.py—DomainConfig抽象クラスretry/directive.py+retry/hint_builder.py— プロンプト汚染防止層nodes/structured_node.py— schema 強制 /temperature=0nodes/factcheck_gate.py— confidence / source バリデーションnodes/critic_node.py— 矛盾検出nodes/retry_node.py— イミュータブル更新 / hint 注入graph.py— 全体組み立てtests/— 各ノード単体テスト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}"
RetryHintBuilder は INSTRUCTION_MAPS[locale] の 固定文言 からのみ
fix_instruction を生成します。動的な文字列は混入しません。locale は
DomainConfig.retry_locale() 経由で StructuredNode から渡され、デフォルト
は "en" です。
StructuredNode から見える retry プロンプトの組み立て窓口は
DomainConfig.format_retry_directive(base_prompt, directive) です。
セパレータの文言や禁止クレームの並べ方を変えたいときは、ドメイン側で
このメソッドだけを差し替えれば足ります(StructuredNode を継承する
必要はありません)。実装上、fail_history の生文字列を組み込んではいけない
という不変条件は引き続きこの境界で保たれます。
ビルトインの GeneralDomain / MedicalDomain は locale="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 のデフォルトは 3。Graph 初期化時に上書き可能にする予定。
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'
仮想環境を有効化し忘れているか、依存をインストールしていません。 セットアップ を参照。
pytest が tests/ を見つけない
ルートディレクトリ(pyproject.toml のある場所)で実行してください。
pyproject.toml の [tool.pytest.ini_options] で testpaths = ["tests"] を指定しています。
mypy が import エラーを出す
langgraph のような外部依存を pip install -e ".[dev]" で
入れていない可能性があります。hallucination_guard/graph.py は
langgraph を 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 へアップロード
リリースを切るときの手順:
pyproject.tomlのversionを bumpCHANGELOG.md先頭に新エントリを追記(Keep a Changelog 形式)make release-checkで artifacts を検証make release-testで TestPyPI に上げてpip install -i https://test.pypi.org/simple/ hallguard==<ver>を確認- 問題なければ
make release - Git タグを切って push
認証は ~/.pypirc か TWINE_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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
353867ff2b1d180bfe047f8cdb666c6abc053777baa65f4a93453958137b77d4
|
|
| MD5 |
95a2a9beed173a7618cf6882ca120794
|
|
| BLAKE2b-256 |
4841cd36937c24a9c0ff48f5483b85286c81ab9b1c540b3ce79ec037e14ec8bd
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9ba3a76929a5a603f734c1f9fa62d890cc4a8a5f2a4d250cd254f21310446a15
|
|
| MD5 |
d92670cb18bf9274529f8e90534b66d1
|
|
| BLAKE2b-256 |
e4394a5d82f3d4ea9ed55e2a945ae4da50f6e070f7a2408b53c612c210da0a5e
|