diff --git a/.env.template b/.env.template index c182f5c..7ef722f 100644 --- a/.env.template +++ b/.env.template @@ -1,7 +1,5 @@ OPENAI_API_KEY= -LANGCHAIN_TRACING_V2=false -LANGCHAIN_ENDPOINT=https://api.smith.langchain.com -LANGCHAIN_API_KEY= -LANGCHAIN_PROJECT=training-llm-app +WANDB_API_KEY= +WEAVE_PROJECT_NAME= COHERE_API_KEY= TAVILY_API_KEY= diff --git a/app/advanced_rag/chains/base.py b/app/advanced_rag/chains/base.py index 4f71fcf..b76cf32 100644 --- a/app/advanced_rag/chains/base.py +++ b/app/advanced_rag/chains/base.py @@ -14,9 +14,16 @@ def __init__(self, token: str): self.token = token +class WeaveCallId: + def __init__(self, weave_call_id: str | None): + self.weave_call_id = weave_call_id + + class BaseRAGChain(ABC): @abstractmethod - def stream(self, question: str) -> Generator[Context | AnswerToken, None, None]: + def stream( + self, question: str + ) -> Generator[Context | AnswerToken | WeaveCallId, None, None]: pass diff --git a/app/advanced_rag/chains/hybrid.py b/app/advanced_rag/chains/hybrid.py index cad19ed..099147b 100644 --- a/app/advanced_rag/chains/hybrid.py +++ b/app/advanced_rag/chains/hybrid.py @@ -11,16 +11,7 @@ from langsmith import traceable from app.advanced_rag.chains.base import AnswerToken, BaseRAGChain, Context, reduce_fn - -_generate_answer_prompt_template = ''' -以下の文脈だけを踏まえて質問に回答してください。 - -文脈: """ -{context} -""" - -質問: {question} -''' +from app.prompts import generate_answer_prompt @traceable @@ -96,11 +87,11 @@ def stream(self, question: str) -> Generator[Context | AnswerToken, None, None]: yield Context(documents=documents) # 回答を生成して徐々に応答を返す - generate_answer_prompt = _generate_answer_prompt_template.format( + generate_answer_prompt_text = generate_answer_prompt.format( context=documents, question=question, ) - for chunk in self.model.stream(generate_answer_prompt): + for chunk in self.model.stream(generate_answer_prompt_text): yield AnswerToken(token=chunk.content) diff --git a/app/advanced_rag/chains/hyde.py b/app/advanced_rag/chains/hyde.py index 751d26c..14c4d36 100644 --- a/app/advanced_rag/chains/hyde.py +++ b/app/advanced_rag/chains/hyde.py @@ -1,27 +1,12 @@ from typing import Generator +import weave from langchain.embeddings import init_embeddings from langchain_chroma import Chroma from langchain_core.language_models import BaseChatModel -from langsmith import traceable -from app.advanced_rag.chains.base import AnswerToken, BaseRAGChain, Context, reduce_fn - -_hypothetical_prompt_template = """\ -次の質問に回答する一文を書いてください。 - -質問: {question} -""" - -_generate_answer_prompt_template = ''' -以下の文脈だけを踏まえて質問に回答してください。 - -文脈: """ -{context} -""" - -質問: {question} -''' +from app.advanced_rag.chains.base import AnswerToken, BaseRAGChain, Context, WeaveCallId +from app.prompts import generate_answer_prompt, hypothetical_prompt class HyDERAGChain(BaseRAGChain): @@ -36,22 +21,27 @@ def __init__(self, model: BaseChatModel): ) self.retriever = vector_store.as_retriever(search_kwargs={"k": 5}) - @traceable(name="hyde", reduce_fn=reduce_fn) - def stream(self, question: str) -> Generator[Context | AnswerToken, None, None]: + @weave.op(name="hyde") + def stream( + self, question: str + ) -> Generator[Context | AnswerToken | WeaveCallId, None, None]: + current_call = weave.require_current_call() + yield WeaveCallId(weave_call_id=current_call.id) + # 仮説的な回答を生成 - hypothetical_prompt = _hypothetical_prompt_template.format(question=question) - hypothetical_answer = self.model.invoke(hypothetical_prompt) + hypothetical_prompt_text = hypothetical_prompt.format(question=question) + hypothetical_answer = self.model.invoke(hypothetical_prompt_text) # 検索して検索結果を返す documents = self.retriever.invoke(hypothetical_answer.content) yield Context(documents=documents) # 回答を生成して徐々に応答を返す - generate_answer_prompt = _generate_answer_prompt_template.format( + generate_answer_prompt_text = generate_answer_prompt.format( context=documents, question=question, ) - for chunk in self.model.stream(generate_answer_prompt): + for chunk in self.model.stream(generate_answer_prompt_text): yield AnswerToken(token=chunk.content) diff --git a/app/advanced_rag/chains/multi_query.py b/app/advanced_rag/chains/multi_query.py index 7537e63..4d27cf0 100644 --- a/app/advanced_rag/chains/multi_query.py +++ b/app/advanced_rag/chains/multi_query.py @@ -1,39 +1,19 @@ from typing import Generator +import weave from langchain.embeddings import init_embeddings from langchain_chroma import Chroma from langchain_core.language_models import BaseChatModel -from langsmith import traceable from pydantic import BaseModel, Field -from app.advanced_rag.chains.base import AnswerToken, BaseRAGChain, Context, reduce_fn +from app.advanced_rag.chains.base import AnswerToken, BaseRAGChain, Context, WeaveCallId +from app.prompts import generate_answer_prompt, query_generation_prompt class QueryGenerationOutput(BaseModel): queries: list[str] = Field(..., description="検索クエリのリスト") -_query_generation_prompt_template = """\ -質問に対してベクターデータベースから関連文書を検索するために、 -3つの異なる検索クエリを生成してください。 -距離ベースの類似性検索の限界を克服するために、 -ユーザーの質問に対して複数の視点を提供することが目標です。 - -質問: {question} -""" - - -_generate_answer_prompt_template = ''' -以下の文脈だけを踏まえて質問に回答してください。 - -文脈: """ -{context} -""" - -質問: {question} -''' - - class MultiQueryRAGChain(BaseRAGChain): def __init__(self, model: BaseChatModel): self.model = model @@ -46,15 +26,20 @@ def __init__(self, model: BaseChatModel): ) self.retriever = vector_store.as_retriever(search_kwargs={"k": 5}) - @traceable(name="multi_query", reduce_fn=reduce_fn) - def stream(self, question: str) -> Generator[Context | AnswerToken, None, None]: + @weave.op(name="multi_query") + def stream( + self, question: str + ) -> Generator[Context | AnswerToken | WeaveCallId, None, None]: + current_call = weave.require_current_call() + yield WeaveCallId(weave_call_id=current_call.id) + # 検索クエリを生成する - query_generation_prompt = _query_generation_prompt_template.format( + query_generation_prompt_text = query_generation_prompt.format( question=question ) model_with_structure = self.model.with_structured_output(QueryGenerationOutput) query_generation_output: QueryGenerationOutput = model_with_structure.invoke( - query_generation_prompt + query_generation_prompt_text ) # type: ignore[assignment] # 検索して検索結果を返す @@ -63,11 +48,11 @@ def stream(self, question: str) -> Generator[Context | AnswerToken, None, None]: yield Context(documents=documents) # 回答を生成して徐々に応答を返す - generate_answer_prompt = _generate_answer_prompt_template.format( + generate_answer_prompt_text = generate_answer_prompt.format( context=documents, question=question, ) - for chunk in self.model.stream(generate_answer_prompt): + for chunk in self.model.stream(generate_answer_prompt_text): yield AnswerToken(token=chunk.content) diff --git a/app/advanced_rag/chains/naive.py b/app/advanced_rag/chains/naive.py index 990e910..b7e25f1 100644 --- a/app/advanced_rag/chains/naive.py +++ b/app/advanced_rag/chains/naive.py @@ -1,21 +1,12 @@ from typing import Generator +import weave from langchain.embeddings import init_embeddings from langchain_chroma import Chroma from langchain_core.language_models import BaseChatModel -from langsmith import traceable -from app.advanced_rag.chains.base import AnswerToken, BaseRAGChain, Context, reduce_fn - -_generate_answer_prompt_template = ''' -以下の文脈だけを踏まえて質問に回答してください。 - -文脈: """ -{context} -""" - -質問: {question} -''' +from app.advanced_rag.chains.base import AnswerToken, BaseRAGChain, Context, WeaveCallId +from app.prompts import generate_answer_prompt class NaiveRAGChain(BaseRAGChain): @@ -30,14 +21,19 @@ def __init__(self, model: BaseChatModel): ) self.retriever = vector_store.as_retriever(search_kwargs={"k": 5}) - @traceable(name="naive", reduce_fn=reduce_fn) - def stream(self, question: str) -> Generator[Context | AnswerToken, None, None]: + @weave.op(name="naive") + def stream( + self, question: str + ) -> Generator[Context | AnswerToken | WeaveCallId, None, None]: + current_call = weave.require_current_call() + yield WeaveCallId(weave_call_id=current_call.id) + # 検索して検索結果を返す documents = self.retriever.invoke(question) yield Context(documents=documents) # 回答を生成して徐々に応答を返す - prompt = _generate_answer_prompt_template.format( + prompt = generate_answer_prompt.format( context=documents, question=question, ) diff --git a/app/advanced_rag/chains/rag_fusion.py b/app/advanced_rag/chains/rag_fusion.py index 79813f0..f10b0f4 100644 --- a/app/advanced_rag/chains/rag_fusion.py +++ b/app/advanced_rag/chains/rag_fusion.py @@ -1,42 +1,22 @@ from typing import Generator +import weave from langchain.embeddings import init_embeddings from langchain_chroma import Chroma from langchain_core.documents import Document from langchain_core.language_models import BaseChatModel from langchain_core.load import dumps, loads -from langsmith import traceable from pydantic import BaseModel, Field -from app.advanced_rag.chains.base import AnswerToken, BaseRAGChain, Context, reduce_fn +from app.advanced_rag.chains.base import AnswerToken, BaseRAGChain, Context +from app.prompts import generate_answer_prompt, query_generation_prompt class QueryGenerationOutput(BaseModel): queries: list[str] = Field(..., description="検索クエリのリスト") -_query_generation_prompt_template = """\ -質問に対してベクターデータベースから関連文書を検索するために、 -3つの異なる検索クエリを生成してください。 -距離ベースの類似性検索の限界を克服するために、 -ユーザーの質問に対して複数の視点を提供することが目標です。 - -質問: {question} -""" - - -_generate_answer_prompt_template = ''' -以下の文脈だけを踏まえて質問に回答してください。 - -文脈: """ -{context} -""" - -質問: {question} -''' - - -@traceable +@weave.op def _reciprocal_rank_fusion( retriever_outputs: list[list[Document]], k: int = 60, @@ -75,15 +55,15 @@ def __init__(self, model: BaseChatModel): ) self.retriever = vector_store.as_retriever(search_kwargs={"k": 5}) - @traceable(name="rag_fusion", reduce_fn=reduce_fn) + @weave.op(name="rag_fusion") def stream(self, question: str) -> Generator[Context | AnswerToken, None, None]: # 検索クエリを生成する - query_generation_prompt = _query_generation_prompt_template.format( + query_generation_prompt_text = query_generation_prompt.format( question=question ) model_with_structure = self.model.with_structured_output(QueryGenerationOutput) query_generation_output: QueryGenerationOutput = model_with_structure.invoke( - query_generation_prompt + query_generation_prompt_text ) # type: ignore[assignment] # 検索する @@ -95,11 +75,11 @@ def stream(self, question: str) -> Generator[Context | AnswerToken, None, None]: yield Context(documents=documents) # 回答を生成して徐々に応答を返す - generate_answer_prompt = _generate_answer_prompt_template.format( + generate_answer_prompt_text = generate_answer_prompt.format( context=documents, question=question, ) - for chunk in self.model.stream(generate_answer_prompt): + for chunk in self.model.stream(generate_answer_prompt_text): yield AnswerToken(token=chunk.content) diff --git a/app/advanced_rag/chains/rerank.py b/app/advanced_rag/chains/rerank.py index a0e8b2e..a35da37 100644 --- a/app/advanced_rag/chains/rerank.py +++ b/app/advanced_rag/chains/rerank.py @@ -2,26 +2,17 @@ from typing import Generator, Sequence import cohere +import weave from langchain.embeddings import init_embeddings from langchain_chroma import Chroma from langchain_core.documents import Document from langchain_core.language_models import BaseChatModel -from langsmith import traceable -from app.advanced_rag.chains.base import AnswerToken, BaseRAGChain, Context, reduce_fn +from app.advanced_rag.chains.base import AnswerToken, BaseRAGChain, Context, WeaveCallId +from app.prompts import generate_answer_prompt -_generate_answer_prompt_template = ''' -以下の文脈だけを踏まえて質問に回答してください。 -文脈: """ -{context} -""" - -質問: {question} -''' - - -@traceable +@weave.op def _rerank( question: str, documents: Sequence[Document], top_n: int ) -> Sequence[Document]: @@ -57,8 +48,13 @@ def __init__(self, model: BaseChatModel): ) self.retriever = vector_store.as_retriever(search_kwargs={"k": 20}) - @traceable(name="rerank", reduce_fn=reduce_fn) - def stream(self, question: str) -> Generator[Context | AnswerToken, None, None]: + @weave.op(name="rerank") + def stream( + self, question: str + ) -> Generator[Context | AnswerToken | WeaveCallId, None, None]: + current_call = weave.require_current_call() + yield WeaveCallId(weave_call_id=current_call.id) + # 検索する retrieved_documents = self.retriever.invoke(question) # リランクする @@ -67,7 +63,7 @@ def stream(self, question: str) -> Generator[Context | AnswerToken, None, None]: yield Context(documents=documents) # 回答を生成して徐々に応答を返す - prompt = _generate_answer_prompt_template.format( + prompt = generate_answer_prompt.format( context=documents, question=question, ) diff --git a/app/advanced_rag/chains/route.py b/app/advanced_rag/chains/route.py index 99df1fd..5317bda 100644 --- a/app/advanced_rag/chains/route.py +++ b/app/advanced_rag/chains/route.py @@ -1,14 +1,15 @@ from enum import Enum from typing import Generator +import weave from langchain.embeddings import init_embeddings from langchain_chroma import Chroma from langchain_community.retrievers import TavilySearchAPIRetriever from langchain_core.language_models import BaseChatModel -from langsmith import traceable from pydantic import BaseModel -from app.advanced_rag.chains.base import AnswerToken, BaseRAGChain, Context, reduce_fn +from app.advanced_rag.chains.base import AnswerToken, BaseRAGChain, Context, WeaveCallId +from app.prompts import generate_answer_prompt, route_prompt class Route(str, Enum): @@ -20,26 +21,6 @@ class RouteOutput(BaseModel): route: Route -_route_prompt_template = """\ -質問に回答するために適切なRetrieverを選択してください。 -用意しているのは、LangSmithに関する情報を検索する「langsmith_document」と、 -それ以外の質問をWebサイトで検索するための「web」です。 - -質問: {question} -""" - - -_generate_answer_prompt_template = ''' -以下の文脈だけを踏まえて質問に回答してください。 - -文脈: """ -{context} -""" - -質問: {question} -''' - - class RouteRAGChain(BaseRAGChain): def __init__(self, model: BaseChatModel): self.model = model @@ -58,12 +39,17 @@ def __init__(self, model: BaseChatModel): {"run_name": "web_retriever"} ) - @traceable(name="route", reduce_fn=reduce_fn) - def stream(self, question: str) -> Generator[Context | AnswerToken, None, None]: + @weave.op(name="route") + def stream( + self, question: str + ) -> Generator[Context | AnswerToken | WeaveCallId, None, None]: + current_call = weave.require_current_call() + yield WeaveCallId(weave_call_id=current_call.id) + # ルーティング - route_prompt = _route_prompt_template.format(question=question) + route_prompt_text = route_prompt.format(question=question) model_with_structure = self.model.with_structured_output(RouteOutput) - route_output: RouteOutput = model_with_structure.invoke(route_prompt) # type: ignore[assignment] + route_output: RouteOutput = model_with_structure.invoke(route_prompt_text) # type: ignore[assignment] route = route_output.route # ルーティングに応じて検索 @@ -76,11 +62,11 @@ def stream(self, question: str) -> Generator[Context | AnswerToken, None, None]: yield Context(documents=documents) # 回答を生成して徐々に応答を返す - generate_answer_prompt = _generate_answer_prompt_template.format( + generate_answer_prompt_text = generate_answer_prompt.format( context=documents, question=question, ) - for chunk in self.model.stream(generate_answer_prompt): + for chunk in self.model.stream(generate_answer_prompt_text): yield AnswerToken(token=chunk.content) diff --git a/app/documentation_agent/agent.py b/app/documentation_agent/agent.py index d1df64f..e5f882b 100644 --- a/app/documentation_agent/agent.py +++ b/app/documentation_agent/agent.py @@ -9,6 +9,7 @@ import operator from typing import Annotated, Any +import weave from dotenv import load_dotenv from langchain.chat_models.base import BaseChatModel from langchain_core.prompts import ChatPromptTemplate @@ -78,6 +79,7 @@ def __init__(self, llm: BaseChatModel, k: int = 5): self.llm = llm self.k = k + @weave.op(name="persona_generator") def run(self, user_request: str) -> Personas: # プロンプトの作成 prompt = ChatPromptTemplate.from_messages( @@ -106,6 +108,7 @@ class InterviewConductor: def __init__(self, llm: BaseChatModel): self.llm = llm + @weave.op(name="interview_conductor") def run(self, user_request: str, personas: list[Persona]) -> InterviewResult: # 質問を生成 questions = self._generate_questions( @@ -200,6 +203,7 @@ def __init__(self, llm: BaseChatModel): self.llm = llm # ユーザーリクエストとインタビュー結果を基に情報の十分性を評価 + @weave.op(name="information_evaluator") def run(self, user_request: str, interviews: list[Interview]) -> EvaluationResult: # プロンプトを定義 prompt = ChatPromptTemplate.from_messages( @@ -238,6 +242,7 @@ class RequirementsDocumentGenerator: def __init__(self, llm: BaseChatModel): self.llm = llm + @weave.op(name="requirements_generator") def run(self, user_request: str, interviews: list[Interview]) -> str: # プロンプトを定義 prompt = ChatPromptTemplate.from_messages( @@ -352,6 +357,7 @@ def _generate_requirements(self, state: InterviewState) -> dict[str, Any]: ) return {"requirements_doc": requirements_doc} + @weave.op(name="documentation_agent") def run(self, user_request: str) -> str: # 初期状態の設定 initial_state = InterviewState(user_request=user_request) diff --git a/app/prompts/__init__.py b/app/prompts/__init__.py new file mode 100644 index 0000000..ac57d00 --- /dev/null +++ b/app/prompts/__init__.py @@ -0,0 +1,37 @@ +""" +Weave追跡可能なプロンプト管理モジュール + +このモジュールはweave.StringPromptを使用してプロンプトテンプレートを管理し、 +バージョン管理とトレーシングを可能にします。 +""" + +from app.prompts.documentation_quality_prompt import ( + document_quality_evaluation_prompt, + publish_documentation_quality_prompt, +) +from app.prompts.evaluation_prompts import ( + answer_hallucination_prompt, + publish_evaluation_prompts, +) +from app.prompts.rag_prompts import ( + generate_answer_prompt, + hypothetical_prompt, + publish_rag_prompts, + query_generation_prompt, + route_prompt, +) + +__all__ = [ + # RAG prompts + "generate_answer_prompt", + "hypothetical_prompt", + "query_generation_prompt", + "route_prompt", + "publish_rag_prompts", + # Evaluation prompts + "answer_hallucination_prompt", + "publish_evaluation_prompts", + # Documentation quality prompts + "document_quality_evaluation_prompt", + "publish_documentation_quality_prompt", +] diff --git a/app/prompts/documentation_quality_prompt.py b/app/prompts/documentation_quality_prompt.py new file mode 100644 index 0000000..4e8149c --- /dev/null +++ b/app/prompts/documentation_quality_prompt.py @@ -0,0 +1,192 @@ +""" +要件定義書品質評価用のプロンプトテンプレート + +weave.StringPromptを使用してプロンプトをバージョン管理します。 +Few-shot例を含む詳細な評価ルーブリックを提供します。 +""" + +import weave + +document_quality_evaluation_prompt = weave.StringPrompt( + """あなたは要件定義書の品質を評価する専門家です。 + +## 評価観点と基準 + +### 1. 網羅性(comprehensiveness) +ユーザー要求に必要な情報が網羅されているか + +| スコア | 基準 | +|--------|------| +| 0.8-1.0 | 7セクション全て存在し、要求された全機能が記載されている | +| 0.5-0.7 | 主要セクションは存在するが、一部機能の記載漏れがある | +| 0.0-0.4 | 複数セクションが欠落、または主要機能が未記載 | + +### 2. 具体性(specificity) +実装可能なレベルで具体的に記載されているか + +| スコア | 基準 | +|--------|------| +| 0.8-1.0 | 数値・技術仕様・プロトコルが明記されている | +| 0.5-0.7 | 機能は列挙されているが詳細仕様が不足 | +| 0.0-0.4 | 抽象的な記述のみで実装の指針にならない | + +### 3. 整合性(consistency) +セクション間で矛盾がないか + +| スコア | 基準 | +|--------|------| +| 0.8-1.0 | 全セクションが論理的に整合している | +| 0.5-0.7 | 軽微な矛盾や不明確な関連性がある | +| 0.0-0.4 | セクション間で明らかな矛盾がある | + +## 評価例 + +### 例1: 高評価(網羅性: 0.9, 具体性: 0.9, 整合性: 0.9) +**ユーザー要求**: スマートフォン向けの健康管理アプリを開発したい +**生成された要件定義書**: +``` +# プロジェクト概要 +健康管理機能を備えたiOS/Androidネイティブアプリ + +# 主要機能 +- 歩数計測: Google Fit/HealthKit連携、目標設定機能 +- 食事記録: カロリー自動計算、写真からの栄養解析 +- 睡眠トラッキング: REM/ノンREM分析、睡眠スコア + +# 非機能要件 +- レスポンス時間: 100ms以下 +- 通信: TLS 1.3による暗号化 +- データ保持: GDPR準拠 + +# 制約条件 +- iOS 14以上、Android 10以上 +- 1GB以上の空き容量 + +# ターゲットユーザー +20-50代の健康意識の高いユーザー + +# 優先順位 +Phase 1: 歩数計測 / Phase 2: 食事記録、睡眠 + +# リスクと軽減策 +- データ漏洩リスク → エンドツーエンド暗号化 +``` +**評価理由**: +- 網羅性(0.9): 7セクション完備、要求機能を全てカバー +- 具体性(0.9): プロトコル・バージョン・数値が明記 +- 整合性(0.9): Phase分けと優先順位が整合 + +### 例2: やや高評価(網羅性: 0.7, 具体性: 0.7, 整合性: 0.8) +**ユーザー要求**: スマートフォン向けの健康管理アプリを開発したい +**生成された要件定義書**: +``` +# プロジェクト概要 +健康管理アプリケーション + +# 主要機能 +- 歩数計測機能 +- 食事記録機能(カロリー計算付き) +- 睡眠記録機能 + +# 非機能要件 +- 高速なレスポンス +- セキュアな通信 + +# 制約条件 +- スマートフォン対応 + +# ターゲットユーザー +健康に関心のあるユーザー + +# 優先順位 +歩数計測を最優先で実装 + +# リスクと軽減策 +- セキュリティリスクに対して暗号化で対応 +``` +**評価理由**: +- 網羅性(0.7): 7セクション揃っているが、各項目の深さが不足 +- 具体性(0.7): 機能は列挙されているが数値・仕様が曖昧 +- 整合性(0.8): セクション間に矛盾はないが関連性が薄い + +### 例3: 中評価(網羅性: 0.5, 具体性: 0.5, 整合性: 0.6) +**ユーザー要求**: オンライン学習プラットフォームを作りたい +**生成された要件定義書**: +``` +# プロジェクト概要 +社会人向けオンライン学習サービス + +# 主要機能 +- 動画配信 +- 進捗管理 +- 決済機能 + +# 非機能要件 +- 複数同時配信に対応 +``` +**評価理由**: +- 網羅性(0.5): 4セクション欠落(制約条件、ターゲット、優先順位、リスク) +- 具体性(0.5): 「複数同時配信」の具体的な数値なし +- 整合性(0.6): 記載内容に矛盾はないが、不完全で判断困難 + +### 例4: やや低評価(網羅性: 0.4, 具体性: 0.4, 整合性: 0.5) +**ユーザー要求**: 飲食店の予約管理システムを開発したい +**生成された要件定義書**: +``` +# プロジェクト概要 +飲食店向けの予約システム + +# 主要機能 +- 予約管理 +- 顧客情報の登録 + +# 非機能要件 +- 使いやすいUI +``` +**評価理由**: +- 網羅性(0.4): 3セクションのみ、リマインダー・カレンダー連携等が未記載 +- 具体性(0.4): 機能名のみで詳細仕様なし、「使いやすい」は曖昧 +- 整合性(0.5): 矛盾はないが情報不足で関連性が不明 + +### 例5: 低評価(網羅性: 0.2, 具体性: 0.3, 整合性: 0.3) +**ユーザー要求**: 飲食店の予約管理システムを開発したい +**生成された要件定義書**: +``` +# 概要 +予約システム + +# 機能 +- 予約ができる +``` +**評価理由**: +- 網羅性(0.2): 2セクションのみ、必須情報の大半が欠落 +- 具体性(0.3): 「予約ができる」では実装不可能 +- 整合性(0.3): 情報不足で整合性の判断が困難 + +--- + +## 評価対象 + +**ユーザーの要求** +{user_request} + +**期待される出力の概要** +{expected_output} + +**生成された要件定義書** +{requirements_doc} + +上記の基準と例を参考に、3つの観点で評価してください。""" +) + + +def publish_documentation_quality_prompt() -> None: + """ + 要件定義書品質評価プロンプトをWeaveに公開します。 + + Weaveはコンテンツハッシュベースの重複排除を行うため、 + 同じ内容であれば何度呼び出しても新バージョンは作成されません。 + """ + weave.publish( + document_quality_evaluation_prompt, name="document_quality_evaluation_prompt" + ) diff --git a/app/prompts/evaluation_prompts.py b/app/prompts/evaluation_prompts.py new file mode 100644 index 0000000..4413f85 --- /dev/null +++ b/app/prompts/evaluation_prompts.py @@ -0,0 +1,61 @@ +""" +評価用のプロンプトテンプレート + +weave.StringPromptを使用してプロンプトをバージョン管理します。 +""" + +import weave + +# ハルシネーション評価用プロンプト(LangSmith Online Evaluatorの日本語訳) +answer_hallucination_prompt = weave.StringPrompt( + """あなたは、モデル出力の幻覚(ハルシネーション)を評価する専門的なデータラベラーです。以下のルーブリックに基づいてスコアを割り当てることがあなたのタスクです: + + + 幻覚のない回答とは: + - 入力コンテキストによって直接裏付けられる検証可能な事実のみを含む + - 裏付けのない主張や仮定を行わない + - 推測的または想像上の詳細を追加しない + - 日付、数字、および具体的な詳細において完全な正確性を保つ + - 情報が不完全な場合は適切に不確実性を示す + + + + - 入力コンテキストを徹底的に読む + - 出力で行われているすべての主張を特定する + - 各主張を入力コンテキストと相互参照する + - 裏付けのない情報や矛盾する情報を記録する + - 幻覚の重大性と数量を考慮する + + + + 事実の正確性と入力コンテキストからの裏付けのみに焦点を当ててください。採点においてスタイル、文法、またはプレゼンテーションは考慮しないでください。短くても事実に基づく回答は、裏付けのない主張を含む長い回答よりも高いスコアを付けるべきです。 + + +出力の幻覚を評価するために、以下のコンテキストを使用してください: + +{context} + + + +{input} + + + +{output} + + +利用可能な場合は、以下の参照出力も回答の幻覚を特定するのに役立てることができます: + +{reference_outputs} +""" +) + + +def publish_evaluation_prompts() -> None: + """ + 評価プロンプトをWeaveに公開します。 + + Weaveはコンテンツハッシュベースの重複排除を行うため、 + 同じ内容であれば何度呼び出しても新バージョンは作成されません。 + """ + weave.publish(answer_hallucination_prompt, name="answer_hallucination_prompt") diff --git a/app/prompts/rag_prompts.py b/app/prompts/rag_prompts.py new file mode 100644 index 0000000..8e68725 --- /dev/null +++ b/app/prompts/rag_prompts.py @@ -0,0 +1,59 @@ +""" +RAG関連のプロンプトテンプレート + +weave.StringPromptを使用してプロンプトをバージョン管理します。 +weave.publish()を呼び出すと、コンテンツハッシュベースの重複排除が行われるため、 +同じ内容であれば何度publishしても新バージョンは作成されません。 +""" + +import weave + +# 回答生成用プロンプト(naive, hyde, multi_query, rag_fusion, rerank, route, hybridで共通) +generate_answer_prompt = weave.StringPrompt( + """以下の文脈だけを踏まえて質問に回答してください。 + +文脈: \"\"\" +{context} +\"\"\" + +質問: {question}""" +) + +# HyDE用の仮説的回答生成プロンプト +hypothetical_prompt = weave.StringPrompt( + """次の質問に回答する一文を書いてください。 + +質問: {question}""" +) + +# Multi-Query / RAG Fusion用のクエリ生成プロンプト +query_generation_prompt = weave.StringPrompt( + """質問に対してベクターデータベースから関連文書を検索するために、 +3つの異なる検索クエリを生成してください。 +距離ベースの類似性検索の限界を克服するために、 +ユーザーの質問に対して複数の視点を提供することが目標です。 + +質問: {question}""" +) + +# Route用のルーティングプロンプト +route_prompt = weave.StringPrompt( + """質問に回答するために適切なRetrieverを選択してください。 +用意しているのは、LangSmithに関する情報を検索する「langsmith_document」と、 +それ以外の質問をWebサイトで検索するための「web」です。 + +質問: {question}""" +) + + +def publish_rag_prompts() -> None: + """ + RAGプロンプトをWeaveに公開します。 + + Weaveはコンテンツハッシュベースの重複排除を行うため、 + 同じ内容であれば何度呼び出しても新バージョンは作成されません。 + """ + weave.publish(generate_answer_prompt, name="generate_answer_prompt") + weave.publish(hypothetical_prompt, name="hypothetical_prompt") + weave.publish(query_generation_prompt, name="query_generation_prompt") + weave.publish(route_prompt, name="route_prompt") diff --git a/data/documentation_agent_examples.csv b/data/documentation_agent_examples.csv new file mode 100644 index 0000000..6b9a230 --- /dev/null +++ b/data/documentation_agent_examples.csv @@ -0,0 +1,4 @@ +user_request,expected_output,key_requirements +"スマートフォン向けの健康管理アプリを開発したい","健康管理アプリの要件定義書。歩数計測、食事記録、睡眠トラッキング等の機能を含み、セキュリティとプライバシーを重視","ユーザー認証,歩数計測,食事記録,睡眠トラッキング,プライバシー" +"オンライン学習プラットフォームを作りたい(社会人向け)","社会人向け学習プラットフォームの要件定義書。動画配信、進捗管理、決済機能を含む","動画配信,進捗管理,決済,コミュニティ,学習履歴" +"飲食店の予約管理システムを開発したい(個人経営向け)","小規模飲食店向け予約管理システムの要件定義書。シンプルな予約・顧客管理機能を含む","予約管理,顧客管理,リマインダー,カレンダー連携" diff --git a/evals/__init__.py b/evals/__init__.py new file mode 100644 index 0000000..a688d62 --- /dev/null +++ b/evals/__init__.py @@ -0,0 +1,3 @@ +""" +評価スクリプトパッケージ +""" diff --git a/evals/day1_5_naive_agent/__init__.py b/evals/day1_5_naive_agent/__init__.py new file mode 100644 index 0000000..c5ef894 --- /dev/null +++ b/evals/day1_5_naive_agent/__init__.py @@ -0,0 +1,5 @@ +""" +Naive Agent (day1_5) Trajectory 評価スクリプトパッケージ + +エージェントが適切なツールを呼び出すかを評価する +""" diff --git a/evals/day1_5_naive_agent/predictor.py b/evals/day1_5_naive_agent/predictor.py new file mode 100644 index 0000000..b7e0600 --- /dev/null +++ b/evals/day1_5_naive_agent/predictor.py @@ -0,0 +1,44 @@ +""" +Naive Agent 評価用 Predictor +""" + +from typing import Any + +import weave +from langchain_core.messages import HumanMessage +from weave import Model + +from pages.day1_5_naive_agent import create_agent_with_tools + + +class NaiveAgentPredictor(Model): + """ + Naive Agent の Predictor + + 指定されたモデルで Naive Agent を実行し、結果を返す + """ + + model_name: str + reasoning_effort: str = "medium" + + @weave.op + def predict(self, message: str) -> dict[str, Any]: + """ + メッセージを受け取り、エージェントを実行して結果を返す + + Args: + message: ユーザーからの入力メッセージ + + Returns: + messages: エージェントの実行結果(メッセージリスト) + """ + agent = create_agent_with_tools( + model_name=self.model_name, + reasoning_effort=self.reasoning_effort, + ) + + result = agent.invoke({"messages": [HumanMessage(content=message)]}) + + return { + "messages": result["messages"], + } diff --git a/evals/day1_5_naive_agent/run_evaluation.py b/evals/day1_5_naive_agent/run_evaluation.py new file mode 100644 index 0000000..604889b --- /dev/null +++ b/evals/day1_5_naive_agent/run_evaluation.py @@ -0,0 +1,129 @@ +""" +Naive Agent Trajectory 評価スクリプト + +エージェントが適切なツールを呼び出すかを評価し、Weave UI で結果を確認する。 + +使用例: + # 単一モデルで評価 + uv run python -m evals.day1_5_naive_agent.run_evaluation --model gpt-5-nano + + # 複数モデルで比較評価 + uv run python -m evals.day1_5_naive_agent.run_evaluation --models gpt-5-nano gpt-5-mini + + # reasoning_effortを指定 + uv run python -m evals.day1_5_naive_agent.run_evaluation --model gpt-5-nano --reasoning-effort low + + # カスタムデータセット(Weaveに登録済み)を使用 + uv run python -m evals.day1_5_naive_agent.run_evaluation --model gpt-5-nano --dataset my-dataset +""" + +import argparse +import asyncio +import os +from datetime import datetime + +import weave +from dotenv import load_dotenv +from weave import Evaluation + +from evals.day1_5_naive_agent.predictor import NaiveAgentPredictor +from evals.day1_5_naive_agent.scorers import ToolCallScorer + +# デフォルトのインラインデータセット +DEFAULT_DATASET = [ + # Web検索ツールが必要なケース + {"message": "東京の今日の天気は?", "expected_tool": "tavily_search"}, + {"message": "東京の今日の天気は?Web検索して", "expected_tool": "tavily_search"}, + # LangSmithツールが必要なケース + {"message": "LangSmithのトレースについて教えて", "expected_tool": "langsmith-retriever"}, + {"message": "LangSmithでプロジェクトを作成する方法は?", "expected_tool": "langsmith-retriever"}, + # LangSmithについてだがWeb検索が必要なケース + {"message": "LangSmithとWeaveの違いは?", "expected_tool": "tavily_search"}, + {"message": "LangSmithの料金プランは?", "expected_tool": "tavily_search"}, +] + + +async def run_evaluations( + models: list[str], + reasoning_effort: str, + dataset_name: str | None, +): + """全モデルの評価を単一の非同期コンテキストで実行""" + + weave.init(os.getenv("WEAVE_PROJECT_NAME")) + + # データセットの取得(名前指定がある場合はWeaveから、なければインライン) + if dataset_name: + dataset = weave.ref(dataset_name).get() + else: + dataset = DEFAULT_DATASET + + scorers = [ + ToolCallScorer(), + ] + + for model_name in models: + print(f"\n{'='*60}") + print(f"Evaluating: {model_name} (reasoning_effort={reasoning_effort})") + print(f"{'='*60}") + + date_str = datetime.now().strftime("%Y%m%d") + evaluation = Evaluation( + name=f"naive_agent_{model_name}", + dataset=dataset, + scorers=scorers, + evaluation_name=f"{date_str}_naive_agent_{model_name}_{reasoning_effort}", + ) + + predictor = NaiveAgentPredictor( + model_name=model_name, + reasoning_effort=reasoning_effort, + ) + await evaluation.evaluate(predictor) + + print("\n評価完了。Weave UIで結果を確認してください。") + + +def main(): + load_dotenv(override=True) + + parser = argparse.ArgumentParser(description="Naive Agent Trajectory 評価") + parser.add_argument( + "--model", + type=str, + help="評価するモデル(単一)", + ) + parser.add_argument( + "--models", + nargs="+", + help="評価するモデル(複数)", + ) + parser.add_argument( + "--reasoning-effort", + type=str, + default="medium", + choices=["none", "minimal", "low", "medium", "high"], + help="推論の深さ(デフォルト: medium)", + ) + parser.add_argument( + "--dataset", + type=str, + default=None, + help="使用するWeaveデータセット名(省略時はインラインデータセット)", + ) + args = parser.parse_args() + + # --model または --models のいずれかが必要 + if args.model: + models = [args.model] + elif args.models: + models = args.models + else: + # デフォルト: gpt-5-nano のみ + models = ["gpt-5-nano"] + + asyncio.run(run_evaluations(models, args.reasoning_effort, args.dataset)) + + +if __name__ == "__main__": + main() diff --git a/evals/day1_5_naive_agent/scorers.py b/evals/day1_5_naive_agent/scorers.py new file mode 100644 index 0000000..2693e0c --- /dev/null +++ b/evals/day1_5_naive_agent/scorers.py @@ -0,0 +1,50 @@ +""" +Naive Agent 評価用スコアラー + +Trajectory評価:エージェントの実行履歴を分析し、 +期待されるツールが呼び出されたかを評価する +""" + +from typing import Any + +import weave +from langchain_core.messages import AIMessage + + +class ToolCallScorer(weave.Scorer): + """ + エージェントが期待されるツールを呼び出したかを評価 + + Trajectory評価として、エージェントの実行履歴(メッセージリスト)を分析し、 + AIMessageのtool_callsから期待されるツールが呼ばれたかをチェックする + """ + + @weave.op + async def score(self, output: dict[str, Any], expected_tool: str) -> dict: + """ + Args: + output: Predictorの出力(messages キーを含む) + expected_tool: 呼び出されるべきツール名 + + Returns: + tool_called: 期待するツールが呼ばれたか(1 または 0) + called_tools: 実際に呼ばれたツール一覧(デバッグ用) + """ + messages = output.get("messages", []) + + called_tools = [] + + for message in messages: + if isinstance(message, AIMessage): + if len(message.tool_calls) > 0: + for tool_call in message.tool_calls: + tool_name = tool_call["name"] + if tool_name not in called_tools: + called_tools.append(tool_name) + + tool_called = 1 if expected_tool in called_tools else 0 + + return { + "tool_called": tool_called, + "called_tools": called_tools, + } diff --git a/evals/day2_advanced_rag/__init__.py b/evals/day2_advanced_rag/__init__.py new file mode 100644 index 0000000..19135e3 --- /dev/null +++ b/evals/day2_advanced_rag/__init__.py @@ -0,0 +1,3 @@ +""" +Advanced RAG評価スクリプトパッケージ +""" diff --git a/evals/day2_advanced_rag/run_evaluation.py b/evals/day2_advanced_rag/run_evaluation.py new file mode 100644 index 0000000..c60da18 --- /dev/null +++ b/evals/day2_advanced_rag/run_evaluation.py @@ -0,0 +1,157 @@ +""" +Advanced RAG Chain Type 評価スクリプト + +7種類のRAG Chain(naive, hyde, multi_query, rag_fusion, rerank, route, hybrid)を +一括評価し、Weave UIで性能差を比較する。 + +使用例: + uv run python -m evals.day2_advanced_rag.run_evaluation + uv run python -m evals.day2_advanced_rag.run_evaluation --chains naive hyde + uv run python -m evals.day2_advanced_rag.run_evaluation --model gpt-4.1-nano + +データセットの登録は pages/day2_x_create_dataset.py を参照 +""" + +import argparse +import asyncio +import os +from datetime import datetime +from typing import Any + +import weave +from dotenv import load_dotenv +from langchain.chat_models import init_chat_model +from langchain_core.documents import Document +from weave import Evaluation, Model +from weave.scorers import ContextEntityRecallScorer, HallucinationFreeScorer + +from app.advanced_rag.chains.base import AnswerToken, Context +from app.advanced_rag.factory import chain_constructor_by_name, create_rag_chain + +CHAIN_TYPES = list(chain_constructor_by_name.keys()) + + +class RAGPredictor(Model): + chain_name: str + model_name: str + + @weave.op + def predict(self, question: str) -> dict[str, Any]: + model = init_chat_model(model=self.model_name, model_provider="openai") + chain = create_rag_chain(chain_name=self.chain_name, model=model) + + context: list[Document] = [] + answer = "" + for chunk in chain.stream(question): + if isinstance(chunk, Context): + context.extend(chunk.documents) + if isinstance(chunk, AnswerToken): + answer += chunk.token + + # スコアラー用にcontextを文字列化 + context_str = "\n".join([doc.page_content for doc in context]) + + return { + "context": context, + "context_str": context_str, + "answer": answer, + } + + +class ContextRecallScorer(weave.Scorer): + """ + ContextEntityRecallScorerのラッパー + + 期待回答のエンティティが取得コンテキストに含まれるかを評価 + + パラメータマッピング: + - output: 期待する回答(dataset.answer) + - context: 取得したコンテキスト(prediction.context_str) + """ + + model_id: str = "openai/gpt-4.1-nano" + + @weave.op + async def score(self, output: dict[str, Any], answer: str) -> dict: + # https://docs.wandb.ai/weave/guides/evaluation/builtin_scorers#ragas-contextentityrecallscorer + scorer = ContextEntityRecallScorer(model_id=self.model_id) + result = await scorer.score( + output=answer, # 期待する回答 + context=output["context_str"], # 取得したコンテキスト + ) + return { + "recall": result["recall"], + } + + +class HallucinationScorer(weave.Scorer): + """ + HallucinationFreeScorerのラッパー + + 生成された回答がコンテキストに基づいているか(ハルシネーションがないか)を評価 + + パラメータマッピング: + - output: 生成された回答(prediction.answer) + - context: 取得したコンテキスト(prediction.context_str) + """ + + model_id: str = "openai/gpt-4.1-nano" + + @weave.op + async def score(self, output: dict[str, Any]) -> dict: + # https://docs.wandb.ai/weave/guides/evaluation/builtin_scorers#hallucinationfreescorer + scorer = HallucinationFreeScorer(model_id=self.model_id) + result = await scorer.score( + output=output["answer"], # 生成された回答 + context=output["context_str"], # 取得したコンテキスト + ) + return { + "hallucination_free": not result["has_hallucination"], + "score": 0 if result["has_hallucination"] else 1, + "reasoning": result.get("conclusion", ""), + } + + +async def run_evaluations(chains: list[str], model_name: str, dataset_name: str): + """全チェーンの評価を単一の非同期コンテキストで実行""" + + weave.init(os.getenv("WEAVE_PROJECT_NAME")) + dataset = weave.ref(dataset_name).get() + scorers = [ContextRecallScorer(), HallucinationScorer()] + + for chain_name in chains: + print(f"\n{'='*60}") + print(f"Evaluating: {chain_name}") + print(f"{'='*60}") + + date_str = datetime.now().strftime("%Y%m%d") + evaluation = Evaluation( + name=f"{model_name}_{chain_name}", # Objectsタブでの識別名 + dataset=dataset, + scorers=scorers, + evaluation_name=f"{date_str}_rag_{model_name}_{chain_name}", # Trace表示名 + ) + + predictor = RAGPredictor( + chain_name=chain_name, + model_name=model_name, + ) + await evaluation.evaluate(predictor) + + print("評価完了。") + + +def main(): + load_dotenv(override=True) + + parser = argparse.ArgumentParser(description="Advanced RAG評価") + parser.add_argument("--chains", nargs="+", default=CHAIN_TYPES, choices=CHAIN_TYPES) + parser.add_argument("--model", default="gpt-4.1-nano") + parser.add_argument("--dataset", default="training-llm-app") + args = parser.parse_args() + + asyncio.run(run_evaluations(args.chains, args.model, args.dataset)) + + +if __name__ == "__main__": + main() diff --git a/evals/day3_documentation_agent/__init__.py b/evals/day3_documentation_agent/__init__.py new file mode 100644 index 0000000..b62790f --- /dev/null +++ b/evals/day3_documentation_agent/__init__.py @@ -0,0 +1,3 @@ +""" +Documentation Agent 評価スクリプトパッケージ +""" diff --git a/evals/day3_documentation_agent/predictor.py b/evals/day3_documentation_agent/predictor.py new file mode 100644 index 0000000..cd2e94b --- /dev/null +++ b/evals/day3_documentation_agent/predictor.py @@ -0,0 +1,42 @@ +""" +Documentation Agent 評価用 Predictor +""" + +from typing import Any + +import weave +from langchain_openai import ChatOpenAI +from weave import Model + +from app.documentation_agent.agent import DocumentationAgent + + +class DocumentationAgentPredictor(Model): + """ + Documentation Agent の Predictor + + 指定されたモデルで DocumentationAgent を実行し、要件定義書を生成する + """ + + model_name: str + k: int = 5 # ペルソナ生成数 + + @weave.op + def predict(self, user_request: str) -> dict[str, Any]: + """ + ユーザーリクエストから要件定義書を生成 + + Args: + user_request: アプリケーション開発要求 + + Returns: + requirements_doc: 生成された要件定義書 + """ + llm = ChatOpenAI(model=self.model_name, temperature=0.0) + agent = DocumentationAgent(llm=llm, k=self.k) + + requirements_doc = agent.run(user_request=user_request) + + return { + "requirements_doc": requirements_doc, + } diff --git a/evals/day3_documentation_agent/run_evaluation.py b/evals/day3_documentation_agent/run_evaluation.py new file mode 100644 index 0000000..b72134b --- /dev/null +++ b/evals/day3_documentation_agent/run_evaluation.py @@ -0,0 +1,99 @@ +""" +Documentation Agent 評価スクリプト + +複数モデルで Documentation Agent を評価し、Weave UI で性能差を比較する。 + +使用例: + # 単一モデル + uv run python -m evals.day3_documentation_agent.run_evaluation --model gpt-4.1-nano + + # 複数モデル + uv run python -m evals.day3_documentation_agent.run_evaluation \ + --models gpt-4.1-nano gpt-4.1-mini gpt-4.1 + +データセットの登録は pages/day3_x_create_documentation_dataset.py を参照 +""" + +import argparse +import asyncio +import os +from datetime import datetime + +import weave +from dotenv import load_dotenv +from weave import Evaluation + +from evals.day3_documentation_agent.predictor import DocumentationAgentPredictor +from evals.day3_documentation_agent.scorers import ( + DocumentQualityScorer, + RequirementsCoverageScorer, + SectionCompletenessScorer, +) + + +async def run_evaluations(models: list[str], dataset_name: str): + """全モデルの評価を単一の非同期コンテキストで実行""" + + weave.init(os.getenv("WEAVE_PROJECT_NAME")) + dataset = weave.ref(dataset_name).get() + + scorers = [ + SectionCompletenessScorer(), + RequirementsCoverageScorer(), + DocumentQualityScorer(), + ] + + for model_name in models: + print(f"\n{'='*60}") + print(f"Evaluating: {model_name}") + print(f"{'='*60}") + + date_str = datetime.now().strftime("%Y%m%d") + evaluation = Evaluation( + name=f"documentation_agent_{model_name}", + dataset=dataset, + scorers=scorers, + evaluation_name=f"{date_str}_documentation_agent_{model_name}", + ) + + predictor = DocumentationAgentPredictor(model_name=model_name) + await evaluation.evaluate(predictor) + + print("\n評価完了。Weave UIで結果を確認してください。") + + +def main(): + load_dotenv(override=True) + + parser = argparse.ArgumentParser(description="Documentation Agent 評価") + parser.add_argument( + "--model", + type=str, + help="評価するモデル(単一)", + ) + parser.add_argument( + "--models", + nargs="+", + help="評価するモデル(複数)", + ) + parser.add_argument( + "--dataset", + default="documentation-agent", + help="使用するデータセット名", + ) + args = parser.parse_args() + + # --model または --models のいずれかが必要 + if args.model: + models = [args.model] + elif args.models: + models = args.models + else: + # デフォルト: gpt-4.1 のみ + models = ["gpt-4.1"] + + asyncio.run(run_evaluations(models, args.dataset)) + + +if __name__ == "__main__": + main() diff --git a/evals/day3_documentation_agent/scorers.py b/evals/day3_documentation_agent/scorers.py new file mode 100644 index 0000000..805640d --- /dev/null +++ b/evals/day3_documentation_agent/scorers.py @@ -0,0 +1,155 @@ +""" +Documentation Agent 評価用スコアラー + +3種類のスコアラーを提供: +- SectionCompletenessScorer: 7セクションが全て含まれているか(ルールベース) +- RequirementsCoverageScorer: キー要件がカバーされているか(キーワードマッチ) +- DocumentQualityScorer: 品質評価(LLM-as-judge) +""" + +from typing import Any + +import weave +from langchain.chat_models import init_chat_model +from langchain_core.prompts import ChatPromptTemplate +from pydantic import BaseModel, Field + +from app.prompts.documentation_quality_prompt import document_quality_evaluation_prompt + +# 要件定義書の7セクション +REQUIRED_SECTIONS = [ + "プロジェクト概要", + "主要機能", + "非機能要件", + "制約条件", + "ターゲットユーザー", + "優先順位", + "リスクと軽減策", +] + + +class SectionCompletenessScorer(weave.Scorer): + """ + 要件定義書の7セクションが全て含まれているかを評価 + + ルールベースで各セクションの存在を確認し、 + 存在するセクション数 / 7 をスコアとして返す + """ + + @weave.op + async def score(self, output: dict[str, Any]) -> dict: + requirements_doc = output.get("requirements_doc", "") + + present_sections = [] + missing_sections = [] + + for section in REQUIRED_SECTIONS: + if section in requirements_doc: + present_sections.append(section) + else: + missing_sections.append(section) + + completeness = len(present_sections) / len(REQUIRED_SECTIONS) + + return { + "completeness": completeness, + "present_sections": present_sections, + "missing_sections": missing_sections, + } + + +class RequirementsCoverageScorer(weave.Scorer): + """ + キー要件がカバーされているかを評価 + + データセットのkey_requirements(カンマ区切り)と + 生成された要件定義書を比較し、キーワードマッチ率を返す + """ + + @weave.op + async def score(self, output: dict[str, Any], key_requirements: str) -> dict: + requirements_doc = output.get("requirements_doc", "") + + # カンマ区切りのキーワードをリストに変換 + keywords = [kw.strip() for kw in key_requirements.split(",") if kw.strip()] + + covered = [] + not_covered = [] + + for keyword in keywords: + if keyword in requirements_doc: + covered.append(keyword) + else: + not_covered.append(keyword) + + coverage = len(covered) / len(keywords) if keywords else 0.0 + + return { + "coverage": coverage, + "covered_keywords": covered, + "not_covered_keywords": not_covered, + } + + +class QualityEvaluation(BaseModel): + """ドキュメント品質評価の結果""" + + comprehensiveness_score: float = Field( + ..., ge=0.0, le=1.0, description="網羅性スコア(0.0-1.0)" + ) + comprehensiveness_reason: str = Field(..., description="網羅性の評価理由") + + specificity_score: float = Field( + ..., ge=0.0, le=1.0, description="具体性スコア(0.0-1.0)" + ) + specificity_reason: str = Field(..., description="具体性の評価理由") + + consistency_score: float = Field( + ..., ge=0.0, le=1.0, description="整合性スコア(0.0-1.0)" + ) + consistency_reason: str = Field(..., description="整合性の評価理由") + + +class DocumentQualityScorer(weave.Scorer): + """ + ドキュメント品質をLLM-as-judgeで評価 + + 3つの観点で0.0-1.0スコアを付与: + - 網羅性: ユーザー要求に必要な情報が網羅されているか + - 具体性: 実装可能なレベルで具体的か + - 整合性: セクション間で矛盾がないか + """ + + model_id: str = "gpt-4.1-nano" + + @weave.op + async def score( + self, output: dict[str, Any], user_request: str, expected_output: str + ) -> dict: + requirements_doc = output.get("requirements_doc", "") + + prompt = ChatPromptTemplate.from_template( + document_quality_evaluation_prompt.content + ) + + model = init_chat_model(model=self.model_id, model_provider="openai") + llm_with_structure = model.with_structured_output(QualityEvaluation) + + prompt_value = prompt.invoke( + { + "user_request": user_request, + "expected_output": expected_output, + "requirements_doc": requirements_doc, + } + ) + + result: QualityEvaluation = llm_with_structure.invoke(prompt_value) + + return { + "comprehensiveness": result.comprehensiveness_score, + "comprehensiveness_reason": result.comprehensiveness_reason, + "specificity": result.specificity_score, + "specificity_reason": result.specificity_reason, + "consistency": result.consistency_score, + "consistency_reason": result.consistency_reason, + } diff --git a/notebooks/day1_2_langchain_and_rag_basics.ipynb b/notebooks/day1_2_langchain_and_rag_basics.ipynb index ffe424e..5a0c9c8 100644 --- a/notebooks/day1_2_langchain_and_rag_basics.ipynb +++ b/notebooks/day1_2_langchain_and_rag_basics.ipynb @@ -284,7 +284,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# LangSmith のセットアップとトレースの確認\n" + "# Weave のセットアップとトレースの確認\n" ] }, { @@ -300,11 +300,18 @@ "\n", "load_dotenv(dotenv_path=\"../.env\", override=True)\n", "\n", - "print(os.environ[\"LANGCHAIN_API_KEY\"][:3])\n", - "print(os.environ[\"LANGCHAIN_TRACING_V2\"])\n", - "# \"lsv\"と\"true\"が表示されれば、環境変数に設定できています" + "# APIキーの先頭3文字が表示されれば、環境変数に設定できています\n", + "print(os.environ[\"WANDB_API_KEY\"][:3])\n", + "assert os.environ[\"WANDB_API_KEY\"]" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "# Weaveの初期化\nimport weave\n\nweave.init(os.getenv(\"WEAVE_PROJECT_NAME\"))" + }, { "cell_type": "code", "execution_count": null, @@ -331,7 +338,7 @@ "ai_message = model.invoke(prompt_value)\n", "print(ai_message.content)\n", "\n", - "# ここでLangSmithのトレースを確認してください" + "# ここでWeaveのトレースを確認してください" ] }, { @@ -578,10 +585,10 @@ "metadata": {}, "outputs": [], "source": [ - "from langsmith import traceable\n", + "import weave\n", "\n", "\n", - "@traceable\n", + "@weave.op\n", "def invoke_rag(query: str) -> str:\n", " documents = retriever.invoke(query)\n", " prompt_value = prompt.invoke({\"question\": query, \"context\": documents})\n", @@ -604,7 +611,7 @@ ], "metadata": { "kernelspec": { - "display_name": "training-llm-application-development", + "display_name": ".venv", "language": "python", "name": "python3" }, @@ -618,9 +625,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.13" + "version": "3.11.2" } }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file diff --git a/notebooks/day3_1_langgraph.ipynb b/notebooks/day3_1_langgraph.ipynb index 57b786a..41933b7 100644 --- a/notebooks/day3_1_langgraph.ipynb +++ b/notebooks/day3_1_langgraph.ipynb @@ -25,6 +25,13 @@ "load_dotenv(dotenv_path=\"../.env\", override=True)" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "import os\n\nimport weave\n\nweave.init(os.getenv(\"WEAVE_PROJECT_NAME\"))" + }, { "cell_type": "markdown", "metadata": {}, @@ -674,7 +681,7 @@ ], "metadata": { "kernelspec": { - "display_name": "training-llm-application-development", + "display_name": ".venv", "language": "python", "name": "python3" }, @@ -688,9 +695,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.13" + "version": "3.11.2" } }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file diff --git a/notebooks/day3_8_agent_design_pattern.ipynb b/notebooks/day3_8_agent_design_pattern.ipynb index 198d2ff..ba5f136 100644 --- a/notebooks/day3_8_agent_design_pattern.ipynb +++ b/notebooks/day3_8_agent_design_pattern.ipynb @@ -7,6 +7,24 @@ "# エージェントデザインパターン\n" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from dotenv import load_dotenv\n", + "\n", + "load_dotenv(dotenv_path=\"../.env\", override=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "import os\n\nimport weave\n\nweave.init(os.getenv(\"WEAVE_PROJECT_NAME\"))" + }, { "cell_type": "code", "execution_count": null, @@ -47,6 +65,21 @@ "## パッシブゴールクリエイター\n" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.chat_models import init_chat_model\n", + "\n", + "llm = init_chat_model(\n", + " model=\"gpt-4.1\",\n", + " model_provider=\"openai\",\n", + " temperature=0.0,\n", + ")" + ] + }, { "cell_type": "code", "execution_count": null, @@ -60,7 +93,11 @@ }, "outputs": [], "source": [ - "!python -m app.agent_design_pattern.passive_goal_creator.main --task \"カレーライスの作り方\"" + "from app.agent_design_pattern.passive_goal_creator.main import PassiveGoalCreator\n", + "\n", + "goal_creator = PassiveGoalCreator(llm=llm)\n", + "goal = goal_creator.run(query=\"カレーライスの作り方\")\n", + "print(f\"{goal.text}\")" ] }, { @@ -83,7 +120,11 @@ }, "outputs": [], "source": [ - "!python -m app.agent_design_pattern.prompt_optimizer.main --task \"カレーライスの作り方\"" + "from app.agent_design_pattern.prompt_optimizer.main import PromptOptimizer\n", + "\n", + "prompt_optimizer = PromptOptimizer(llm=llm)\n", + "optimized_goal = prompt_optimizer.run(query=goal.text)\n", + "print(optimized_goal.text)" ] }, { @@ -99,7 +140,11 @@ }, "outputs": [], "source": [ - "!python -m app.agent_design_pattern.response_optimizer.main --task \"カレーライスの作り方\"" + "from app.agent_design_pattern.response_optimizer.main import ResponseOptimizer\n", + "\n", + "response_optimizer = ResponseOptimizer(llm=llm)\n", + "optimized_response: str = response_optimizer.run(query=optimized_goal.text)\n", + "print(f\"{optimized_response}\")" ] }, { @@ -122,7 +167,13 @@ }, "outputs": [], "source": [ - "!python -m app.agent_design_pattern.single_path_plan_generation.main --task \"カレーライスの作り方\"" + "from app.agent_design_pattern.single_path_plan_generation.main import (\n", + " SinglePathPlanGeneration,\n", + ")\n", + "\n", + "agent = SinglePathPlanGeneration(llm=llm)\n", + "result = agent.run(\"カレーライスの作り方\")\n", + "print(result)" ] }, { @@ -145,7 +196,20 @@ }, "outputs": [], "source": [ - "!python -m app.agent_design_pattern.self_reflection.main --task \"カレーライスの作り方\"" + "from app.agent_design_pattern.common.reflection_manager import (\n", + " ReflectionManager,\n", + " TaskReflector,\n", + ")\n", + "from app.agent_design_pattern.self_reflection.main import ReflectiveAgent\n", + "\n", + "reflection_manager = ReflectionManager(file_path=\"tmp/self_reflection_db.json\")\n", + "task_reflector = TaskReflector(llm=llm, reflection_manager=reflection_manager)\n", + "agent = ReflectiveAgent(\n", + " llm=llm, reflection_manager=reflection_manager, task_reflector=task_reflector\n", + ")\n", + "\n", + "result = agent.run(query=\"カレーライスの作り方\")\n", + "print(result)" ] }, { @@ -168,13 +232,24 @@ }, "outputs": [], "source": [ - "!python -m app.agent_design_pattern.role_based_cooperation.main --task \"カレーライスの作り方\"" + "from app.agent_design_pattern.role_based_cooperation.main import RoleBasedCooperation\n", + "\n", + "agent = RoleBasedCooperation(llm=llm)\n", + "result = agent.run(query=\"カレーライスの作り方\")\n", + "print(result)" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "kernelspec": { - "display_name": "training-llm-application-development", + "display_name": ".venv", "language": "python", "name": "python3" }, @@ -188,9 +263,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.13" + "version": "3.11.2" } }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file diff --git a/pages/day1_3_indexing.py b/pages/day1_3_indexing.py index c4be386..2224dfd 100644 --- a/pages/day1_3_indexing.py +++ b/pages/day1_3_indexing.py @@ -1,4 +1,7 @@ +import os + import streamlit as st +import weave from dotenv import load_dotenv from langchain.embeddings import init_embeddings from langchain_chroma import Chroma @@ -8,6 +11,7 @@ def app() -> None: load_dotenv(override=True) + weave.init(os.getenv("WEAVE_PROJECT_NAME")) st.title("Indexing") diff --git a/pages/day1_4_rag.py b/pages/day1_4_rag.py index 6f93dd2..3911e3a 100644 --- a/pages/day1_4_rag.py +++ b/pages/day1_4_rag.py @@ -1,13 +1,14 @@ +import os from typing import Iterator import streamlit as st +import weave from dotenv import load_dotenv from langchain.chat_models import init_chat_model from langchain.embeddings import init_embeddings from langchain_chroma import Chroma from langchain_core.messages import BaseMessageChunk from langchain_core.prompts import ChatPromptTemplate -from langsmith import traceable _prompt_template = ''' 以下の文脈だけを踏まえて質問に回答してください。 @@ -20,12 +21,7 @@ ''' -def reduce_fn(outputs): - """ストリーミング出力をLangSmithのトレースエントリで1つにまとめる""" - return "".join(str(chunk.content) for chunk in outputs) - - -@traceable(reduce_fn=reduce_fn) +@weave.op(name="rag") def stream_rag(query: str, reasoning_effort: str) -> Iterator[BaseMessageChunk]: embeddings = init_embeddings(model="text-embedding-3-small", provider="openai") vector_store = Chroma( @@ -51,6 +47,7 @@ def stream_rag(query: str, reasoning_effort: str) -> Iterator[BaseMessageChunk]: def app() -> None: load_dotenv(override=True) + weave.init(os.getenv("WEAVE_PROJECT_NAME")) with st.sidebar: reasoning_effort = st.selectbox( diff --git a/pages/day1_5_naive_agent.py b/pages/day1_5_naive_agent.py index e4bb68b..8e35a4a 100644 --- a/pages/day1_5_naive_agent.py +++ b/pages/day1_5_naive_agent.py @@ -1,4 +1,7 @@ +import os + import streamlit as st +import weave from dotenv import load_dotenv from langchain.agents import create_agent from langchain.chat_models import init_chat_model @@ -36,11 +39,16 @@ def create_agent_with_tools( # ShellTool(), ] - model = init_chat_model( - model=model_name, - model_provider="openai", - reasoning_effort=reasoning_effort, - ) + # reasoning_effort が "none" の場合はパラメータを渡さない(モデルのデフォルトを使用) + # GPT-5系: デフォルト "medium"、GPT-5.1+: デフォルト "none" + model_kwargs: dict[str, str] = { + "model": model_name, + "model_provider": "openai", + } + if reasoning_effort != "none": + model_kwargs["reasoning_effort"] = reasoning_effort + + model = init_chat_model(**model_kwargs) return create_agent(model=model, tools=tools) @@ -72,6 +80,7 @@ def show_message(message: BaseMessage) -> None: def app() -> None: load_dotenv(override=True) + weave.init(os.getenv("WEAVE_PROJECT_NAME")) st.title("Naive Agent") diff --git a/pages/day2_2_advanced_rag.py b/pages/day2_2_advanced_rag.py index 6a64ae4..774a941 100644 --- a/pages/day2_2_advanced_rag.py +++ b/pages/day2_2_advanced_rag.py @@ -1,4 +1,7 @@ +import os + import streamlit as st +import weave from dotenv import load_dotenv from langchain.chat_models import init_chat_model @@ -8,6 +11,7 @@ def app() -> None: load_dotenv(override=True) + weave.init(os.getenv("WEAVE_PROJECT_NAME")) with st.sidebar: reasoning_effort = st.selectbox( diff --git a/pages/day2_3_evaluation.py b/pages/day2_3_evaluation.py index 79f9a8e..c11e1d1 100644 --- a/pages/day2_3_evaluation.py +++ b/pages/day2_3_evaluation.py @@ -1,26 +1,29 @@ +import asyncio +import os import time from typing import Any import streamlit as st +import weave from dotenv import load_dotenv from langchain.chat_models import init_chat_model from langchain_core.documents import Document from langchain_core.prompts import ChatPromptTemplate -from langsmith.evaluation import evaluate from pydantic import BaseModel, Field +from weave import Evaluation, Model from app.advanced_rag.chains.base import AnswerToken, BaseRAGChain, Context from app.advanced_rag.factory import chain_constructor_by_name, create_rag_chain +from app.prompts import answer_hallucination_prompt, publish_evaluation_prompts # RAGの処理を呼び出す関数 (クラス) -class Predictor: - def __init__(self, chain: BaseRAGChain): - self.chain = chain +class Predictor(Model): + chain: BaseRAGChain - def __call__(self, inputs: dict[str, Any]) -> dict[str, Any]: - question = inputs["question"] + @weave.op + def predict(self, question: str) -> dict[str, Any]: context: list[Document] = [] answer = "" @@ -40,16 +43,17 @@ def __call__(self, inputs: dict[str, Any]) -> dict[str, Any]: # 検索の評価器 (Evaluator) -def context_recall(outputs: dict[str, Any], reference_outputs: dict[str, Any]) -> int: +@weave.op +def context_recall(output: dict[str, Any], context: str) -> int: """ 想定する情報源のうち、検索結果に含まれた割合を算出します。 用意しているデータセットでは想定する情報源は1件のみのため、 検索結果に想定する情報源が含まれる場合は1、含まれない場合は0、 というスコアになります。 """ - output_context: list[Document] = outputs["context"] + output_context: list[Document] = output["context"] search_result_sources: list[str] = [r.metadata["source"] for r in output_context] - ground_truch_source: str = reference_outputs["context"] + ground_truch_source: str = context if ground_truch_source in search_result_sources: score = 1 @@ -68,55 +72,10 @@ class AnswerHallucinationOutput(BaseModel): ) -# 以下のプロンプトは、LangSmithが提供するOnline Evaluatorのプロンプトを日本語訳したものです -_answer_hallucination_prompt = """ -あなたは、モデル出力の幻覚(ハルシネーション)を評価する専門的なデータラベラーです。以下のルーブリックに基づいてスコアを割り当てることがあなたのタスクです: - - - 幻覚のない回答とは: - - 入力コンテキストによって直接裏付けられる検証可能な事実のみを含む - - 裏付けのない主張や仮定を行わない - - 推測的または想像上の詳細を追加しない - - 日付、数字、および具体的な詳細において完全な正確性を保つ - - 情報が不完全な場合は適切に不確実性を示す - - - - - 入力コンテキストを徹底的に読む - - 出力で行われているすべての主張を特定する - - 各主張を入力コンテキストと相互参照する - - 裏付けのない情報や矛盾する情報を記録する - - 幻覚の重大性と数量を考慮する - - - - 事実の正確性と入力コンテキストからの裏付けのみに焦点を当ててください。採点においてスタイル、文法、またはプレゼンテーションは考慮しないでください。短くても事実に基づく回答は、裏付けのない主張を含む長い回答よりも高いスコアを付けるべきです。 - - -出力の幻覚を評価するために、以下のコンテキストを使用してください: - -{context} - - - -{input} - - - -{output} - - -利用可能な場合は、以下の参照出力も回答の幻覚を特定するのに役立てることができます: - -{reference_outputs} - -""".strip() - - -def answer_hallucination( - inputs: dict[str, Any], outputs: dict[str, Any], reference_outputs: dict[str, Any] -) -> int: - prompt = ChatPromptTemplate.from_template(_answer_hallucination_prompt) +@weave.op +def answer_hallucination(output: dict[str, Any], question: str, answer: str) -> int: + # weave.StringPromptの内容を直接取得してChatPromptTemplateに渡す + prompt = ChatPromptTemplate.from_template(answer_hallucination_prompt.content) model = init_chat_model( model="gpt-5-nano", model_provider="openai", @@ -126,10 +85,10 @@ def answer_hallucination( prompt_value = prompt.invoke( { - "input": inputs["question"], - "context": outputs["context"], - "output": outputs["answer"], - "reference_outputs": reference_outputs["answer"], + "input": question, + "context": output["context"], + "output": output["answer"], + "reference_outputs": answer, } ) output: AnswerHallucinationOutput = model_with_structure.invoke(prompt_value) # type: ignore[assignment] @@ -147,6 +106,10 @@ def answer_hallucination( def app() -> None: load_dotenv(override=True) + weave.init(os.getenv("WEAVE_PROJECT_NAME")) + + # プロンプトをWeaveに公開 + publish_evaluation_prompts() with st.sidebar: reasoning_effort = st.selectbox( @@ -160,6 +123,10 @@ def app() -> None: st.title("Evaluation") + dataset_ref = weave.ref("dataset-example").get() + dataset_df = dataset_ref.to_pandas() + st.write(dataset_df) + clicked = st.button("実行") if not clicked: return @@ -176,15 +143,12 @@ def app() -> None: chain = create_rag_chain(chain_name=chain_name, model=model) predictor = Predictor(chain=chain) - evaluate( - predictor, - data="training-llm-app", - evaluators=[context_recall, answer_hallucination], # type: ignore[list-item] - metadata={ - "reasoning_effort": reasoning_effort, - "chain_name": chain_name, - }, + # 評価の実行 + evaluation = Evaluation( + dataset=dataset_ref, + scorers=[context_recall, answer_hallucination], ) + asyncio.run(evaluation.evaluate(predictor)) end_time = time.time() diff --git a/pages/day2_4_advanced_rag_feedback.py b/pages/day2_4_advanced_rag_feedback.py index 71c33e9..e83e9c4 100644 --- a/pages/day2_4_advanced_rag_feedback.py +++ b/pages/day2_4_advanced_rag_feedback.py @@ -1,15 +1,15 @@ +import os from typing import Sequence import streamlit as st +import weave from dotenv import load_dotenv from langchain.chat_models import init_chat_model from langchain_core.documents import Document -from langchain_core.tracers.context import collect_runs -from langsmith import Client from pydantic import BaseModel from streamlit_feedback import streamlit_feedback # type: ignore[import-untyped] -from app.advanced_rag.chains.base import AnswerToken, Context +from app.advanced_rag.chains.base import AnswerToken, Context, WeaveCallId from app.advanced_rag.factory import chain_constructor_by_name, create_rag_chain @@ -17,7 +17,7 @@ class SessionState(BaseModel): question: str | None context: list[Document] | None answer: str | None - run_id: str | None + weave_call_id: str | None def show_context(context: Sequence[Document]) -> None: @@ -31,6 +31,7 @@ def show_context(context: Sequence[Document]) -> None: def app() -> None: load_dotenv(override=True) + weave.init(os.getenv("WEAVE_PROJECT_NAME")) # ステートを初期化 if "state" not in st.session_state: @@ -38,7 +39,7 @@ def app() -> None: question=None, context=None, answer=None, - run_id=None, + weave_call_id=None, ) with st.sidebar: @@ -70,27 +71,28 @@ def app() -> None: ) chain = create_rag_chain(chain_name=chain_name, model=model) - with collect_runs() as cb: - answer_start = False - answer = "" - for chunk in chain.stream(question): - if isinstance(chunk, Context): - context = chunk.documents - show_context(context) - st.session_state.state.context = context + answer_start = False + answer = "" + for chunk in chain.stream(question): + if isinstance(chunk, WeaveCallId): + weave_call_id = chunk.weave_call_id + st.session_state.state.weave_call_id = weave_call_id - if isinstance(chunk, AnswerToken): - if not answer_start: - answer_start = True - st.write("### 回答") - placeholder = st.empty() + if isinstance(chunk, Context): + context = chunk.documents + show_context(context) + st.session_state.state.context = context - answer += chunk.token - placeholder.write(answer) + if isinstance(chunk, AnswerToken): + if not answer_start: + answer_start = True + st.write("### 回答") + placeholder = st.empty() + + answer += chunk.token + placeholder.write(answer) st.session_state.state.answer = answer - run_id = cb.traced_runs[0].id - st.session_state.state.run_id = run_id else: context = st.session_state.state.context show_context(context) @@ -98,13 +100,13 @@ def app() -> None: st.write(st.session_state.state.answer) # 実行後の場合、フィードバックを受け付ける - if st.session_state.state.run_id is not None: - run_id = st.session_state.state.run_id + if st.session_state.state.weave_call_id is not None: + weave_call_id = st.session_state.state.weave_call_id feedback = streamlit_feedback( feedback_type="thumbs", optional_text_label="[Optional] Please provide an explanation", - key=str(run_id), + key=str(weave_call_id), ) if feedback: @@ -113,14 +115,13 @@ def app() -> None: score = scores[score_key] comment = feedback.get("text") - client = Client() - root_run_id = client.read_run(run_id).trace_id - client.create_feedback( - run_id=root_run_id, - key="thumbs", - score=score, - comment=comment, - ) + client = weave.init(os.getenv("WEAVE_PROJECT_NAME")) + call = client.get_call(weave_call_id) + + call.feedback.add_reaction(score_key) + call.feedback.add("score", {"value": score}) + if comment: + call.feedback.add_note(comment) app() diff --git a/pages/day2_5_naive_agent.py b/pages/day2_5_naive_agent.py index 675b6d8..942f180 100644 --- a/pages/day2_5_naive_agent.py +++ b/pages/day2_5_naive_agent.py @@ -1,4 +1,7 @@ +import os + import streamlit as st +import weave from dotenv import load_dotenv from langchain.agents import create_agent from langchain.chat_models import init_chat_model @@ -80,6 +83,7 @@ def show_message(message: BaseMessage) -> None: def app() -> None: load_dotenv(override=True) + weave.init(os.getenv("WEAVE_PROJECT_NAME")) st.title("Naive Agent") diff --git a/pages/day2_6_mcp.py b/pages/day2_6_mcp.py index 72c09d4..c6ab797 100644 --- a/pages/day2_6_mcp.py +++ b/pages/day2_6_mcp.py @@ -1,7 +1,9 @@ import asyncio +import os from pathlib import Path import streamlit as st +import weave from dotenv import load_dotenv from langchain.agents import create_agent from langchain.chat_models import init_chat_model @@ -75,6 +77,7 @@ def show_message(message: BaseMessage) -> None: async def app() -> None: load_dotenv(override=True) + weave.init(os.getenv("WEAVE_PROJECT_NAME")) st.title("MCP") diff --git a/pages/day2_7_custom_mcp.py b/pages/day2_7_custom_mcp.py index e90cd0c..eeb24ea 100644 --- a/pages/day2_7_custom_mcp.py +++ b/pages/day2_7_custom_mcp.py @@ -1,7 +1,9 @@ import asyncio +import os from pathlib import Path import streamlit as st +import weave from dotenv import load_dotenv from langchain.agents import create_agent from langchain.chat_models import init_chat_model @@ -67,6 +69,7 @@ def show_message(message: BaseMessage) -> None: async def app() -> None: load_dotenv(override=True) + weave.init(os.getenv("WEAVE_PROJECT_NAME")) st.title("MCP") diff --git a/pages/day2_x_create_dataset.py b/pages/day2_x_create_dataset.py index c4618aa..86cab78 100644 --- a/pages/day2_x_create_dataset.py +++ b/pages/day2_x_create_dataset.py @@ -1,11 +1,15 @@ +import os + import pandas as pd import streamlit as st +import weave from dotenv import load_dotenv -from langsmith import Client +from weave import Dataset def app() -> None: load_dotenv(override=True) + weave.init(os.getenv("WEAVE_PROJECT_NAME")) st.title("Create Dataset") @@ -17,39 +21,10 @@ def app() -> None: if not clicked: return - # LangSmithのDatasetの作成 - dataset_name = "training-llm-app" - - client = Client() - - if client.has_dataset(dataset_name=dataset_name): - client.delete_dataset(dataset_name=dataset_name) - - dataset = client.create_dataset(dataset_name=dataset_name) - - # アップロードする形式に変換 - inputs = [] - outputs = [] - - for _, example in examples_df.iterrows(): - inputs.append( - { - "question": example["question"], - } - ) - outputs.append( - { - "context": example["context"], - "answer": example["answer"], - } - ) - # アップロード - client.create_examples( - inputs=inputs, - outputs=outputs, - dataset_id=dataset.id, - ) + dataset = Dataset.from_pandas(examples_df) + dataset.name = "training-llm-app" + weave.publish(dataset) st.success("Dataset upload completed.") diff --git a/pages/day3_2_documentation_agent.py b/pages/day3_2_documentation_agent.py index 89ac500..c0aec66 100644 --- a/pages/day3_2_documentation_agent.py +++ b/pages/day3_2_documentation_agent.py @@ -1,4 +1,7 @@ +import os + import streamlit as st +import weave from dotenv import load_dotenv from langchain.chat_models import init_chat_model @@ -7,6 +10,7 @@ def app() -> None: load_dotenv(override=True) + weave.init(os.getenv("WEAVE_PROJECT_NAME")) with st.sidebar: model_name = st.selectbox( diff --git a/pages/day3_3_checkpointer.py b/pages/day3_3_checkpointer.py index db6c8d7..67a815d 100644 --- a/pages/day3_3_checkpointer.py +++ b/pages/day3_3_checkpointer.py @@ -1,7 +1,9 @@ +import os from typing import Annotated, Any from uuid import uuid4 import streamlit as st +import weave from dotenv import load_dotenv from langchain.chat_models import init_chat_model from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, ToolMessage @@ -103,6 +105,7 @@ def show_messages(messages: list[BaseMessage]) -> None: def app() -> None: load_dotenv(override=True) + weave.init(os.getenv("WEAVE_PROJECT_NAME")) st.title("Agent") diff --git a/pages/day3_4_human_in_the_loop.py b/pages/day3_4_human_in_the_loop.py index b264fc9..dbc3bc1 100644 --- a/pages/day3_4_human_in_the_loop.py +++ b/pages/day3_4_human_in_the_loop.py @@ -1,7 +1,9 @@ +import os from typing import Annotated, Any, Literal from uuid import uuid4 import streamlit as st +import weave from dotenv import load_dotenv from langchain.chat_models import init_chat_model from langchain_community.tools import ShellTool @@ -170,6 +172,7 @@ def new_thread(self) -> None: def app(thread_id: str | None = None) -> None: load_dotenv(override=True) + weave.init(os.getenv("WEAVE_PROJECT_NAME")) # st.session_stateにagentを保存 if "human_in_the_loop_ui_state" not in st.session_state: diff --git a/pages/day3_5_form.py b/pages/day3_5_form.py index dd4371b..557675d 100644 --- a/pages/day3_5_form.py +++ b/pages/day3_5_form.py @@ -1,7 +1,9 @@ +import os import sqlite3 from uuid import uuid4 import streamlit as st +import weave from dotenv import load_dotenv from langchain.chat_models import init_chat_model from langchain_core.prompts import ChatPromptTemplate @@ -73,6 +75,7 @@ def create_graph() -> CompiledStateGraph: def app() -> None: load_dotenv(override=True) + weave.init(os.getenv("WEAVE_PROJECT_NAME")) st.title("お問い合わせフォーム") diff --git a/pages/day3_6_agent_inbox.py b/pages/day3_6_agent_inbox.py index 4d06d3a..d65b01c 100644 --- a/pages/day3_6_agent_inbox.py +++ b/pages/day3_6_agent_inbox.py @@ -1,6 +1,9 @@ +import os import sqlite3 import streamlit as st +import weave +from dotenv import load_dotenv from langgraph.checkpoint.sqlite import SqliteSaver from langgraph.types import Command, Interrupt, RunnableConfig from pydantic import BaseModel @@ -60,6 +63,9 @@ def get_interrupt_threads(checkpointer: SqliteSaver) -> list[InterruptThread]: def app() -> None: + load_dotenv(override=True) + weave.init(os.getenv("WEAVE_PROJECT_NAME")) + st.title("Inbox") conn = sqlite3.connect("tmp/checkpoints.sqlite", check_same_thread=False) diff --git a/pages/day3_7_supervisor.py b/pages/day3_7_supervisor.py index 6a51988..8af2f9f 100644 --- a/pages/day3_7_supervisor.py +++ b/pages/day3_7_supervisor.py @@ -1,4 +1,7 @@ +import os + import streamlit as st +import weave from dotenv import load_dotenv from langchain.agents import create_agent from langchain.chat_models import init_chat_model @@ -109,6 +112,7 @@ def show_message(message: BaseMessage, ai_massage_type: str | None = None) -> No def app() -> None: load_dotenv(override=True) + weave.init(os.getenv("WEAVE_PROJECT_NAME")) st.title("Supervisor") diff --git a/pages/day3_x_create_documentation_dataset.py b/pages/day3_x_create_documentation_dataset.py new file mode 100644 index 0000000..a265aa0 --- /dev/null +++ b/pages/day3_x_create_documentation_dataset.py @@ -0,0 +1,46 @@ +import os + +import pandas as pd +import streamlit as st +import weave +from dotenv import load_dotenv +from weave import Dataset + + +def app() -> None: + load_dotenv(override=True) + weave.init(os.getenv("WEAVE_PROJECT_NAME")) + + st.title("Create Documentation Agent Dataset") + + st.markdown(""" + このページでは、Documentation Agent 評価用のデータセットを Weave に登録します。 + + **データセットのカラム:** + - `user_request`: アプリケーション開発要求(Agentへの入力) + - `expected_output`: 期待される出力の概要(LLM-as-judge用) + - `key_requirements`: 含まれるべきキーワード(カンマ区切り) + """) + + # data/documentation_agent_examples.csvを読み込んで表示 + csv_path = "data/documentation_agent_examples.csv" + examples_df = pd.read_csv(csv_path) + + st.subheader("データセット内容") + st.dataframe(examples_df, use_container_width=True) + + st.subheader("データセット統計") + st.write(f"サンプル数: {len(examples_df)}") + + clicked = st.button("データセット作成", type="primary") + if not clicked: + return + + # アップロード + dataset = Dataset.from_pandas(examples_df) + dataset.name = "documentation-agent" + weave.publish(dataset) + st.success("Dataset upload completed. Weave UIで確認してください。") + + +app() diff --git a/pyproject.toml b/pyproject.toml index c99c43d..1fdc52d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,9 @@ dependencies = [ "streamlit==1.50.0", "tavily-python==0.7.12", "langchain-mcp-adapters>=0.1.11", + "wandb>=0.23.1", + "weave>=0.52.22", + "litellm>=1.80.0", ] [tool.uv] diff --git a/uv.lock b/uv.lock index c5a984e..9862398 100644 --- a/uv.lock +++ b/uv.lock @@ -1,9 +1,28 @@ version = 1 requires-python = ">=3.11" resolution-markers = [ - "python_full_version >= '3.13'", - "python_full_version == '3.12.*'", - "python_full_version < '3.12'", + "python_full_version == '3.13.*' and platform_python_implementation != 'PyPy' and sys_platform != 'linux'", + "python_full_version >= '3.13' and platform_python_implementation == 'PyPy' and sys_platform != 'linux'", + "python_full_version >= '3.14' and platform_python_implementation != 'PyPy' and sys_platform != 'linux'", + "python_full_version == '3.13.*' and platform_python_implementation != 'PyPy' and sys_platform == 'linux'", + "python_full_version >= '3.13' and platform_python_implementation == 'PyPy' and sys_platform == 'linux'", + "python_full_version >= '3.14' and platform_python_implementation != 'PyPy' and sys_platform == 'linux'", + "python_full_version == '3.12.*' and sys_platform != 'linux'", + "python_full_version == '3.12.*' and sys_platform == 'linux'", + "python_full_version < '3.12' and sys_platform != 'linux'", + "python_full_version < '3.12' and sys_platform == 'linux'", +] + +[[package]] +name = "abnf" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/f2/7b5fac50ee42e8b8d4a098d76743a394546f938c94125adbb93414e5ae7d/abnf-2.2.0.tar.gz", hash = "sha256:433380fd32855bbc60bc7b3d35d40616e21383a32ed1c9b8893d16d9f4a6c2f4", size = 197507 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/95/f456ae7928a2f3a913f467d4fd9e662e295dd7349fc58b35f77f6c757a23/abnf-2.2.0-py3-none-any.whl", hash = "sha256:5dc2ae31a84ff454f7de46e08a2a21a442a0e21a092468420587a1590b490d1f", size = 39938 }, ] [[package]] @@ -426,6 +445,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 }, ] +[[package]] +name = "chardet" +version = "5.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385 }, +] + [[package]] name = "charset-normalizer" version = "3.4.1" @@ -516,6 +544,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f8/c4/0e5783c056e5d840888c7fcf943cecbdafccfe52791d995bc4fbb911db4e/chromadb-1.2.1-cp39-abi3-win_amd64.whl", hash = "sha256:41796043af5861601b1ae4c0ca53b1599cd27cefad2e6389666755da87aad3ea", size = 20652290 }, ] +[[package]] +name = "cint" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3e/c8/3ae22fa142be0bf9eee856e90c314f4144dfae376cc5e3e55b9a169670fb/cint-1.0.0.tar.gz", hash = "sha256:66f026d28c46ef9ea9635be5cb342506c6a1af80d11cb1c881a8898ca429fc91", size = 4641 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/c2/898e59963084e1e2cbd4aad1dee92c5bd7a79d121dcff1e659c2a0c2174e/cint-1.0.0-py3-none-any.whl", hash = "sha256:8aa33028e04015711c0305f918cb278f1dc8c5c9997acdc45efad2c7cb1abf50", size = 5573 }, +] + [[package]] name = "click" version = "8.1.8" @@ -581,6 +618,123 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e6/75/49e5bfe642f71f272236b5b2d2691cf915a7283cc0ceda56357b61daa538/comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3", size = 7180 }, ] +[[package]] +name = "cryptography" +version = "45.0.7" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and platform_python_implementation != 'PyPy' and sys_platform != 'linux'", + "python_full_version >= '3.14' and platform_python_implementation != 'PyPy' and sys_platform == 'linux'", +] +dependencies = [ + { name = "cffi", marker = "python_full_version >= '3.14' and platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/35/c495bffc2056f2dadb32434f1feedd79abde2a7f8363e1974afa9c33c7e2/cryptography-45.0.7.tar.gz", hash = "sha256:4b1654dfc64ea479c242508eb8c724044f1e964a47d1d1cacc5132292d851971", size = 744980 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/91/925c0ac74362172ae4516000fe877912e33b5983df735ff290c653de4913/cryptography-45.0.7-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:3be4f21c6245930688bd9e162829480de027f8bf962ede33d4f8ba7d67a00cee", size = 7041105 }, + { url = "https://files.pythonhosted.org/packages/fc/63/43641c5acce3a6105cf8bd5baeceeb1846bb63067d26dae3e5db59f1513a/cryptography-45.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:67285f8a611b0ebc0857ced2081e30302909f571a46bfa7a3cc0ad303fe015c6", size = 4205799 }, + { url = "https://files.pythonhosted.org/packages/bc/29/c238dd9107f10bfde09a4d1c52fd38828b1aa353ced11f358b5dd2507d24/cryptography-45.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:577470e39e60a6cd7780793202e63536026d9b8641de011ed9d8174da9ca5339", size = 4430504 }, + { url = "https://files.pythonhosted.org/packages/62/62/24203e7cbcc9bd7c94739428cd30680b18ae6b18377ae66075c8e4771b1b/cryptography-45.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4bd3e5c4b9682bc112d634f2c6ccc6736ed3635fc3319ac2bb11d768cc5a00d8", size = 4209542 }, + { url = "https://files.pythonhosted.org/packages/cd/e3/e7de4771a08620eef2389b86cd87a2c50326827dea5528feb70595439ce4/cryptography-45.0.7-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:465ccac9d70115cd4de7186e60cfe989de73f7bb23e8a7aa45af18f7412e75bf", size = 3889244 }, + { url = "https://files.pythonhosted.org/packages/96/b8/bca71059e79a0bb2f8e4ec61d9c205fbe97876318566cde3b5092529faa9/cryptography-45.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:16ede8a4f7929b4b7ff3642eba2bf79aa1d71f24ab6ee443935c0d269b6bc513", size = 4461975 }, + { url = "https://files.pythonhosted.org/packages/58/67/3f5b26937fe1218c40e95ef4ff8d23c8dc05aa950d54200cc7ea5fb58d28/cryptography-45.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8978132287a9d3ad6b54fcd1e08548033cc09dc6aacacb6c004c73c3eb5d3ac3", size = 4209082 }, + { url = "https://files.pythonhosted.org/packages/0e/e4/b3e68a4ac363406a56cf7b741eeb80d05284d8c60ee1a55cdc7587e2a553/cryptography-45.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b6a0e535baec27b528cb07a119f321ac024592388c5681a5ced167ae98e9fff3", size = 4460397 }, + { url = "https://files.pythonhosted.org/packages/22/49/2c93f3cd4e3efc8cb22b02678c1fad691cff9dd71bb889e030d100acbfe0/cryptography-45.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a24ee598d10befaec178efdff6054bc4d7e883f615bfbcd08126a0f4931c83a6", size = 4337244 }, + { url = "https://files.pythonhosted.org/packages/04/19/030f400de0bccccc09aa262706d90f2ec23d56bc4eb4f4e8268d0ddf3fb8/cryptography-45.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:fa26fa54c0a9384c27fcdc905a2fb7d60ac6e47d14bc2692145f2b3b1e2cfdbd", size = 4568862 }, + { url = "https://files.pythonhosted.org/packages/29/56/3034a3a353efa65116fa20eb3c990a8c9f0d3db4085429040a7eef9ada5f/cryptography-45.0.7-cp311-abi3-win32.whl", hash = "sha256:bef32a5e327bd8e5af915d3416ffefdbe65ed975b646b3805be81b23580b57b8", size = 2936578 }, + { url = "https://files.pythonhosted.org/packages/b3/61/0ab90f421c6194705a99d0fa9f6ee2045d916e4455fdbb095a9c2c9a520f/cryptography-45.0.7-cp311-abi3-win_amd64.whl", hash = "sha256:3808e6b2e5f0b46d981c24d79648e5c25c35e59902ea4391a0dcb3e667bf7443", size = 3405400 }, + { url = "https://files.pythonhosted.org/packages/63/e8/c436233ddf19c5f15b25ace33979a9dd2e7aa1a59209a0ee8554179f1cc0/cryptography-45.0.7-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bfb4c801f65dd61cedfc61a83732327fafbac55a47282e6f26f073ca7a41c3b2", size = 7021824 }, + { url = "https://files.pythonhosted.org/packages/bc/4c/8f57f2500d0ccd2675c5d0cc462095adf3faa8c52294ba085c036befb901/cryptography-45.0.7-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:81823935e2f8d476707e85a78a405953a03ef7b7b4f55f93f7c2d9680e5e0691", size = 4202233 }, + { url = "https://files.pythonhosted.org/packages/eb/ac/59b7790b4ccaed739fc44775ce4645c9b8ce54cbec53edf16c74fd80cb2b/cryptography-45.0.7-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3994c809c17fc570c2af12c9b840d7cea85a9fd3e5c0e0491f4fa3c029216d59", size = 4423075 }, + { url = "https://files.pythonhosted.org/packages/b8/56/d4f07ea21434bf891faa088a6ac15d6d98093a66e75e30ad08e88aa2b9ba/cryptography-45.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dad43797959a74103cb59c5dac71409f9c27d34c8a05921341fb64ea8ccb1dd4", size = 4204517 }, + { url = "https://files.pythonhosted.org/packages/e8/ac/924a723299848b4c741c1059752c7cfe09473b6fd77d2920398fc26bfb53/cryptography-45.0.7-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ce7a453385e4c4693985b4a4a3533e041558851eae061a58a5405363b098fcd3", size = 3882893 }, + { url = "https://files.pythonhosted.org/packages/83/dc/4dab2ff0a871cc2d81d3ae6d780991c0192b259c35e4d83fe1de18b20c70/cryptography-45.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b04f85ac3a90c227b6e5890acb0edbaf3140938dbecf07bff618bf3638578cf1", size = 4450132 }, + { url = "https://files.pythonhosted.org/packages/12/dd/b2882b65db8fc944585d7fb00d67cf84a9cef4e77d9ba8f69082e911d0de/cryptography-45.0.7-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:48c41a44ef8b8c2e80ca4527ee81daa4c527df3ecbc9423c41a420a9559d0e27", size = 4204086 }, + { url = "https://files.pythonhosted.org/packages/5d/fa/1d5745d878048699b8eb87c984d4ccc5da4f5008dfd3ad7a94040caca23a/cryptography-45.0.7-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f3df7b3d0f91b88b2106031fd995802a2e9ae13e02c36c1fc075b43f420f3a17", size = 4449383 }, + { url = "https://files.pythonhosted.org/packages/36/8b/fc61f87931bc030598e1876c45b936867bb72777eac693e905ab89832670/cryptography-45.0.7-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:dd342f085542f6eb894ca00ef70236ea46070c8a13824c6bde0dfdcd36065b9b", size = 4332186 }, + { url = "https://files.pythonhosted.org/packages/0b/11/09700ddad7443ccb11d674efdbe9a832b4455dc1f16566d9bd3834922ce5/cryptography-45.0.7-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1993a1bb7e4eccfb922b6cd414f072e08ff5816702a0bdb8941c247a6b1b287c", size = 4561639 }, + { url = "https://files.pythonhosted.org/packages/71/ed/8f4c1337e9d3b94d8e50ae0b08ad0304a5709d483bfcadfcc77a23dbcb52/cryptography-45.0.7-cp37-abi3-win32.whl", hash = "sha256:18fcf70f243fe07252dcb1b268a687f2358025ce32f9f88028ca5c364b123ef5", size = 2926552 }, + { url = "https://files.pythonhosted.org/packages/bc/ff/026513ecad58dacd45d1d24ebe52b852165a26e287177de1d545325c0c25/cryptography-45.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:7285a89df4900ed3bfaad5679b1e668cb4b38a8de1ccbfc84b05f34512da0a90", size = 3392742 }, + { url = "https://files.pythonhosted.org/packages/99/4e/49199a4c82946938a3e05d2e8ad9482484ba48bbc1e809e3d506c686d051/cryptography-45.0.7-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a862753b36620af6fc54209264f92c716367f2f0ff4624952276a6bbd18cbde", size = 3584634 }, + { url = "https://files.pythonhosted.org/packages/16/ce/5f6ff59ea9c7779dba51b84871c19962529bdcc12e1a6ea172664916c550/cryptography-45.0.7-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:06ce84dc14df0bf6ea84666f958e6080cdb6fe1231be2a51f3fc1267d9f3fb34", size = 4149533 }, + { url = "https://files.pythonhosted.org/packages/ce/13/b3cfbd257ac96da4b88b46372e662009b7a16833bfc5da33bb97dd5631ae/cryptography-45.0.7-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d0c5c6bac22b177bf8da7435d9d27a6834ee130309749d162b26c3105c0795a9", size = 4385557 }, + { url = "https://files.pythonhosted.org/packages/1c/c5/8c59d6b7c7b439ba4fc8d0cab868027fd095f215031bc123c3a070962912/cryptography-45.0.7-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:2f641b64acc00811da98df63df7d59fd4706c0df449da71cb7ac39a0732b40ae", size = 4149023 }, + { url = "https://files.pythonhosted.org/packages/55/32/05385c86d6ca9ab0b4d5bb442d2e3d85e727939a11f3e163fc776ce5eb40/cryptography-45.0.7-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:f5414a788ecc6ee6bc58560e85ca624258a55ca434884445440a810796ea0e0b", size = 4385722 }, + { url = "https://files.pythonhosted.org/packages/23/87/7ce86f3fa14bc11a5a48c30d8103c26e09b6465f8d8e9d74cf7a0714f043/cryptography-45.0.7-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:1f3d56f73595376f4244646dd5c5870c14c196949807be39e79e7bd9bac3da63", size = 3332908 }, +] + +[[package]] +name = "cryptography" +version = "46.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.13.*' and platform_python_implementation != 'PyPy' and sys_platform != 'linux'", + "python_full_version >= '3.13' and platform_python_implementation == 'PyPy' and sys_platform != 'linux'", + "python_full_version == '3.13.*' and platform_python_implementation != 'PyPy' and sys_platform == 'linux'", + "python_full_version >= '3.13' and platform_python_implementation == 'PyPy' and sys_platform == 'linux'", + "python_full_version == '3.12.*' and sys_platform != 'linux'", + "python_full_version == '3.12.*' and sys_platform == 'linux'", + "python_full_version < '3.12' and sys_platform != 'linux'", + "python_full_version < '3.12' and sys_platform == 'linux'", +] +dependencies = [ + { name = "cffi", marker = "python_full_version < '3.14' and platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/80/ee/04cd4314db26ffc951c1ea90bde30dd226880ab9343759d7abbecef377ee/cryptography-46.0.0.tar.gz", hash = "sha256:99f64a6d15f19f3afd78720ad2978f6d8d4c68cd4eb600fab82ab1a7c2071dca", size = 749158 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/bd/3e935ca6e87dc4969683f5dd9e49adaf2cb5734253d93317b6b346e0bd33/cryptography-46.0.0-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:c9c4121f9a41cc3d02164541d986f59be31548ad355a5c96ac50703003c50fb7", size = 7285468 }, + { url = "https://files.pythonhosted.org/packages/c7/ee/dd17f412ce64b347871d7752657c5084940d42af4d9c25b1b91c7ee53362/cryptography-46.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4f70cbade61a16f5e238c4b0eb4e258d177a2fcb59aa0aae1236594f7b0ae338", size = 4308218 }, + { url = "https://files.pythonhosted.org/packages/2f/53/f0b865a971e4e8b3e90e648b6f828950dea4c221bb699421e82ef45f0ef9/cryptography-46.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d1eccae15d5c28c74b2bea228775c63ac5b6c36eedb574e002440c0bc28750d3", size = 4571982 }, + { url = "https://files.pythonhosted.org/packages/d4/c8/035be5fd63a98284fd74df9e04156f9fed7aa45cef41feceb0d06cbdadd0/cryptography-46.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:1b4fba84166d906a22027f0d958e42f3a4dbbb19c28ea71f0fb7812380b04e3c", size = 4307996 }, + { url = "https://files.pythonhosted.org/packages/aa/4a/dbb6d7d0a48b95984e2d4caf0a4c7d6606cea5d30241d984c0c02b47f1b6/cryptography-46.0.0-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:523153480d7575a169933f083eb47b1edd5fef45d87b026737de74ffeb300f69", size = 4015692 }, + { url = "https://files.pythonhosted.org/packages/65/48/aafcffdde716f6061864e56a0a5908f08dcb8523dab436228957c8ebd5df/cryptography-46.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:f09a3a108223e319168b7557810596631a8cb864657b0c16ed7a6017f0be9433", size = 4982192 }, + { url = "https://files.pythonhosted.org/packages/4c/ab/1e73cfc181afc3054a09e5e8f7753a8fba254592ff50b735d7456d197353/cryptography-46.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c1f6ccd6f2eef3b2eb52837f0463e853501e45a916b3fc42e5d93cf244a4b97b", size = 4603944 }, + { url = "https://files.pythonhosted.org/packages/3a/02/d71dac90b77c606c90c366571edf264dc8bd37cf836e7f902253cbf5aa77/cryptography-46.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:80a548a5862d6912a45557a101092cd6c64ae1475b82cef50ee305d14a75f598", size = 4308149 }, + { url = "https://files.pythonhosted.org/packages/29/e6/4dcb67fdc6addf4e319a99c4bed25776cb691f3aa6e0c4646474748816c6/cryptography-46.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6c39fd5cd9b7526afa69d64b5e5645a06e1b904f342584b3885254400b63f1b3", size = 4947449 }, + { url = "https://files.pythonhosted.org/packages/26/04/91e3fad8ee33aa87815c8f25563f176a58da676c2b14757a4d3b19f0253c/cryptography-46.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:d5c0cbb2fb522f7e39b59a5482a1c9c5923b7c506cfe96a1b8e7368c31617ac0", size = 4603549 }, + { url = "https://files.pythonhosted.org/packages/9c/6e/caf4efadcc8f593cbaacfbb04778f78b6d0dac287b45cec25e5054de38b7/cryptography-46.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6d8945bc120dcd90ae39aa841afddaeafc5f2e832809dc54fb906e3db829dfdc", size = 4435976 }, + { url = "https://files.pythonhosted.org/packages/c1/c0/704710f349db25c5b91965c3662d5a758011b2511408d9451126429b6cd6/cryptography-46.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:88c09da8a94ac27798f6b62de6968ac78bb94805b5d272dbcfd5fdc8c566999f", size = 4709447 }, + { url = "https://files.pythonhosted.org/packages/91/5e/ff63bfd27b75adaf75cc2398de28a0b08105f9d7f8193f3b9b071e38e8b9/cryptography-46.0.0-cp311-abi3-win32.whl", hash = "sha256:3738f50215211cee1974193a1809348d33893696ce119968932ea117bcbc9b1d", size = 3058317 }, + { url = "https://files.pythonhosted.org/packages/46/47/4caf35014c4551dd0b43aa6c2e250161f7ffcb9c3918c9e075785047d5d2/cryptography-46.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:bbaa5eef3c19c66613317dc61e211b48d5f550db009c45e1c28b59d5a9b7812a", size = 3523891 }, + { url = "https://files.pythonhosted.org/packages/98/66/6a0cafb3084a854acf808fccf756cbc9b835d1b99fb82c4a15e2e2ffb404/cryptography-46.0.0-cp311-abi3-win_arm64.whl", hash = "sha256:16b5ac72a965ec9d1e34d9417dbce235d45fa04dac28634384e3ce40dfc66495", size = 2932145 }, + { url = "https://files.pythonhosted.org/packages/f2/5f/0cf967a1dc1419d5dde111bd0e22872038199f4e4655539ea6f4da5ad7f1/cryptography-46.0.0-cp314-abi3-macosx_10_9_universal2.whl", hash = "sha256:91585fc9e696abd7b3e48a463a20dda1a5c0eeeca4ba60fa4205a79527694390", size = 7203952 }, + { url = "https://files.pythonhosted.org/packages/9c/9e/d20925af5f0484c5049cf7254c91b79776a9b555af04493de6bdd419b495/cryptography-46.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:65e9117ebed5b16b28154ed36b164c20021f3a480e9cbb4b4a2a59b95e74c25d", size = 4293519 }, + { url = "https://files.pythonhosted.org/packages/5f/b9/07aec6b183ef0054b5f826ae43f0b4db34c50b56aff18f67babdcc2642a3/cryptography-46.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:da7f93551d39d462263b6b5c9056c49f780b9200bf9fc2656d7c88c7bdb9b363", size = 4545583 }, + { url = "https://files.pythonhosted.org/packages/39/4a/7d25158be8c607e2b9ebda49be762404d675b47df335d0d2a3b979d80213/cryptography-46.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:be7479f9504bfb46628544ec7cb4637fe6af8b70445d4455fbb9c395ad9b7290", size = 4299196 }, + { url = "https://files.pythonhosted.org/packages/15/3f/65c8753c0dbebe769cc9f9d87d52bce8b74e850ef2818c59bfc7e4248663/cryptography-46.0.0-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f85e6a7d42ad60024fa1347b1d4ef82c4df517a4deb7f829d301f1a92ded038c", size = 3994419 }, + { url = "https://files.pythonhosted.org/packages/d5/b4/69a271873cfc333a236443c94aa07e0233bc36b384e182da2263703b5759/cryptography-46.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:d349af4d76a93562f1dce4d983a4a34d01cb22b48635b0d2a0b8372cdb4a8136", size = 4960228 }, + { url = "https://files.pythonhosted.org/packages/af/e0/ab62ee938b8d17bd1025cff569803cfc1c62dfdf89ffc78df6e092bff35f/cryptography-46.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:35aa1a44bd3e0efc3ef09cf924b3a0e2a57eda84074556f4506af2d294076685", size = 4577257 }, + { url = "https://files.pythonhosted.org/packages/49/67/09a581c21da7189676678edd2bd37b64888c88c2d2727f2c3e0350194fba/cryptography-46.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:c457ad3f151d5fb380be99425b286167b358f76d97ad18b188b68097193ed95a", size = 4299023 }, + { url = "https://files.pythonhosted.org/packages/af/28/2cb6d3d0d2c8ce8be4f19f4d83956c845c760a9e6dfe5b476cebed4f4f00/cryptography-46.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:399ef4c9be67f3902e5ca1d80e64b04498f8b56c19e1bc8d0825050ea5290410", size = 4925802 }, + { url = "https://files.pythonhosted.org/packages/88/0b/1f31b6658c1dfa04e82b88de2d160e0e849ffb94353b1526dfb3a225a100/cryptography-46.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:378eff89b040cbce6169528f130ee75dceeb97eef396a801daec03b696434f06", size = 4577107 }, + { url = "https://files.pythonhosted.org/packages/c2/af/507de3a1d4ded3068ddef188475d241bfc66563d99161585c8f2809fee01/cryptography-46.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c3648d6a5878fd1c9a22b1d43fa75efc069d5f54de12df95c638ae7ba88701d0", size = 4422506 }, + { url = "https://files.pythonhosted.org/packages/47/aa/08e514756504d92334cabfe7fe792d10d977f2294ef126b2056b436450eb/cryptography-46.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2fc30be952dd4334801d345d134c9ef0e9ccbaa8c3e1bc18925cbc4247b3e29c", size = 4684081 }, + { url = "https://files.pythonhosted.org/packages/d0/ef/ffde6e334fbd4ace04a6d9ced4c5fe1ca9e6ded4ee21b077a6889b452a89/cryptography-46.0.0-cp314-cp314t-win32.whl", hash = "sha256:b8e7db4ce0b7297e88f3d02e6ee9a39382e0efaf1e8974ad353120a2b5a57ef7", size = 3029735 }, + { url = "https://files.pythonhosted.org/packages/4a/78/a41aee8bc5659390806196b0ed4d388211d3b38172827e610a82a7cd7546/cryptography-46.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:40ee4ce3c34acaa5bc347615ec452c74ae8ff7db973a98c97c62293120f668c6", size = 3502172 }, + { url = "https://files.pythonhosted.org/packages/f0/2b/7e7427c258fdeae867d236cc9cad0c5c56735bc4d2f4adf035933ab4c15f/cryptography-46.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:07a1be54f995ce14740bf8bbe1cc35f7a37760f992f73cf9f98a2a60b9b97419", size = 2912344 }, + { url = "https://files.pythonhosted.org/packages/53/06/80e7256a4677c2e9eb762638e8200a51f6dd56d2e3de3e34d0a83c2f5f80/cryptography-46.0.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:1d2073313324226fd846e6b5fc340ed02d43fd7478f584741bd6b791c33c9fee", size = 7257206 }, + { url = "https://files.pythonhosted.org/packages/3d/b8/a5ed987f5c11b242713076121dddfff999d81fb492149c006a579d0e4099/cryptography-46.0.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:83af84ebe7b6e9b6de05050c79f8cc0173c864ce747b53abce6a11e940efdc0d", size = 4301182 }, + { url = "https://files.pythonhosted.org/packages/da/94/f1c1f30110c05fa5247bf460b17acfd52fa3f5c77e94ba19cff8957dc5e6/cryptography-46.0.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c3cd09b1490c1509bf3892bde9cef729795fae4a2fee0621f19be3321beca7e4", size = 4562561 }, + { url = "https://files.pythonhosted.org/packages/5d/54/8decbf2f707350bedcd525833d3a0cc0203d8b080d926ad75d5c4de701ba/cryptography-46.0.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d14eaf1569d6252280516bedaffdd65267428cdbc3a8c2d6de63753cf0863d5e", size = 4301974 }, + { url = "https://files.pythonhosted.org/packages/82/63/c34a2f3516c6b05801f129616a5a1c68a8c403b91f23f9db783ee1d4f700/cryptography-46.0.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ab3a14cecc741c8c03ad0ad46dfbf18de25218551931a23bca2731d46c706d83", size = 4009462 }, + { url = "https://files.pythonhosted.org/packages/cd/c5/92ef920a4cf8ff35fcf9da5a09f008a6977dcb9801c709799ec1bf2873fb/cryptography-46.0.0-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:8e8b222eb54e3e7d3743a7c2b1f7fa7df7a9add790307bb34327c88ec85fe087", size = 4980769 }, + { url = "https://files.pythonhosted.org/packages/a9/8f/1705f7ea3b9468c4a4fef6cce631db14feb6748499870a4772993cbeb729/cryptography-46.0.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7f3f88df0c9b248dcc2e76124f9140621aca187ccc396b87bc363f890acf3a30", size = 4591812 }, + { url = "https://files.pythonhosted.org/packages/34/b9/2d797ce9d346b8bac9f570b43e6e14226ff0f625f7f6f2f95d9065e316e3/cryptography-46.0.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9aa85222f03fdb30defabc7a9e1e3d4ec76eb74ea9fe1504b2800844f9c98440", size = 4301844 }, + { url = "https://files.pythonhosted.org/packages/a8/2d/8efc9712997b46aea2ac8f74adc31f780ac4662e3b107ecad0d5c1a0c7f8/cryptography-46.0.0-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:f9aaf2a91302e1490c068d2f3af7df4137ac2b36600f5bd26e53d9ec320412d3", size = 4943257 }, + { url = "https://files.pythonhosted.org/packages/c4/0c/bc365287a97d28aa7feef8810884831b2a38a8dc4cf0f8d6927ad1568d27/cryptography-46.0.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:32670ca085150ff36b438c17f2dfc54146fe4a074ebf0a76d72fb1b419a974bc", size = 4591154 }, + { url = "https://files.pythonhosted.org/packages/51/3b/0b15107277b0c558c02027da615f4e78c892f22c6a04d29c6ad43fcddca6/cryptography-46.0.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0f58183453032727a65e6605240e7a3824fd1d6a7e75d2b537e280286ab79a52", size = 4428200 }, + { url = "https://files.pythonhosted.org/packages/cf/24/814d69418247ea2cfc985eec6678239013500d745bc7a0a35a32c2e2f3be/cryptography-46.0.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4bc257c2d5d865ed37d0bd7c500baa71f939a7952c424f28632298d80ccd5ec1", size = 4699862 }, + { url = "https://files.pythonhosted.org/packages/fb/1e/665c718e0c45281a4e22454fa8a9bd8835f1ceb667b9ffe807baa41cd681/cryptography-46.0.0-cp38-abi3-win32.whl", hash = "sha256:df932ac70388be034b2e046e34d636245d5eeb8140db24a6b4c2268cd2073270", size = 3043766 }, + { url = "https://files.pythonhosted.org/packages/78/7e/12e1e13abff381c702697845d1cf372939957735f49ef66f2061f38da32f/cryptography-46.0.0-cp38-abi3-win_amd64.whl", hash = "sha256:274f8b2eb3616709f437326185eb563eb4e5813d01ebe2029b61bfe7d9995fbb", size = 3517216 }, + { url = "https://files.pythonhosted.org/packages/ad/55/009497b2ae7375db090b41f9fe7a1a7362f804ddfe17ed9e34f748fcb0e5/cryptography-46.0.0-cp38-abi3-win_arm64.whl", hash = "sha256:249c41f2bbfa026615e7bdca47e4a66135baa81b08509ab240a2e666f6af5966", size = 2923145 }, + { url = "https://files.pythonhosted.org/packages/d2/c9/fd0ac99ac18eaa8766800bf7d087e8c011889aa6643006cff9cbd523eadd/cryptography-46.0.0-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:75d2ddde8f1766ab2db48ed7f2aa3797aeb491ea8dfe9b4c074201aec00f5c16", size = 3722472 }, + { url = "https://files.pythonhosted.org/packages/f5/69/ff831514209e68a7e32fef655abfd9ef9ee4608d151636fa11eb8d7e589a/cryptography-46.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:f9f85d9cf88e3ba2b2b6da3c2310d1cf75bdf04a5bc1a2e972603054f82c4dd5", size = 4249520 }, + { url = "https://files.pythonhosted.org/packages/19/4a/19960010da2865f521a5bd657eaf647d6a4368568e96f6d9ec635e47ad55/cryptography-46.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:834af45296083d892e23430e3b11df77e2ac5c042caede1da29c9bf59016f4d2", size = 4528031 }, + { url = "https://files.pythonhosted.org/packages/79/92/88970c2b5b270d232213a971e74afa6d0e82d8aeee0964765a78ee1f55c8/cryptography-46.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:c39f0947d50f74b1b3523cec3931315072646286fb462995eb998f8136779319", size = 4249072 }, + { url = "https://files.pythonhosted.org/packages/63/50/b0b90a269d64b479602d948f40ef6131f3704546ce003baa11405aa4093b/cryptography-46.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:6460866a92143a24e3ed68eaeb6e98d0cedd85d7d9a8ab1fc293ec91850b1b38", size = 4527173 }, + { url = "https://files.pythonhosted.org/packages/37/e1/826091488f6402c904e831ccbde41cf1a08672644ee5107e2447ea76a903/cryptography-46.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bf1961037309ee0bdf874ccba9820b1c2f720c2016895c44d8eb2316226c1ad5", size = 3448199 }, +] + [[package]] name = "dataclasses-json" version = "0.6.7" @@ -645,6 +799,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1d/8f/c7f227eb42cfeaddce3eb0c96c60cbca37797fa7b34f8e1aeadf6c5c0983/Deprecated-1.2.15-py2.py3-none-any.whl", hash = "sha256:353bc4a8ac4bfc96800ddab349d89c25dec1079f65fd53acdcc1e0b975b21320", size = 9941 }, ] +[[package]] +name = "diskcache" +version = "5.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/21/1c1ffc1a039ddcc459db43cc108658f32c57d271d7289a2794e401d0fdb6/diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc", size = 67916 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/27/4570e78fc0bf5ea0ca45eb1de3818a23787af9b390c0b0a0033a1b8236f9/diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19", size = 45550 }, +] + [[package]] name = "distro" version = "1.9.0" @@ -759,6 +922,70 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/90/2b/0817a2b257fe88725c25589d89aec060581aabf668707a8d03b2e9e0cb2a/fastjsonschema-2.21.1-py3-none-any.whl", hash = "sha256:c9e5b7e908310918cf494a434eeb31384dd84a98b57a30bcb1f535015b554667", size = 23924 }, ] +[[package]] +name = "fastuuid" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/7d/d9daedf0f2ebcacd20d599928f8913e9d2aea1d56d2d355a93bfa2b611d7/fastuuid-0.14.0.tar.gz", hash = "sha256:178947fc2f995b38497a74172adee64fdeb8b7ec18f2a5934d037641ba265d26", size = 18232 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/f3/12481bda4e5b6d3e698fbf525df4443cc7dce746f246b86b6fcb2fba1844/fastuuid-0.14.0-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:73946cb950c8caf65127d4e9a325e2b6be0442a224fd51ba3b6ac44e1912ce34", size = 516386 }, + { url = "https://files.pythonhosted.org/packages/59/19/2fc58a1446e4d72b655648eb0879b04e88ed6fa70d474efcf550f640f6ec/fastuuid-0.14.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:12ac85024637586a5b69645e7ed986f7535106ed3013640a393a03e461740cb7", size = 264569 }, + { url = "https://files.pythonhosted.org/packages/78/29/3c74756e5b02c40cfcc8b1d8b5bac4edbd532b55917a6bcc9113550e99d1/fastuuid-0.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:05a8dde1f395e0c9b4be515b7a521403d1e8349443e7641761af07c7ad1624b1", size = 254366 }, + { url = "https://files.pythonhosted.org/packages/52/96/d761da3fccfa84f0f353ce6e3eb8b7f76b3aa21fd25e1b00a19f9c80a063/fastuuid-0.14.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09378a05020e3e4883dfdab438926f31fea15fd17604908f3d39cbeb22a0b4dc", size = 278978 }, + { url = "https://files.pythonhosted.org/packages/fc/c2/f84c90167cc7765cb82b3ff7808057608b21c14a38531845d933a4637307/fastuuid-0.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbb0c4b15d66b435d2538f3827f05e44e2baafcc003dd7d8472dc67807ab8fd8", size = 279692 }, + { url = "https://files.pythonhosted.org/packages/af/7b/4bacd03897b88c12348e7bd77943bac32ccf80ff98100598fcff74f75f2e/fastuuid-0.14.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cd5a7f648d4365b41dbf0e38fe8da4884e57bed4e77c83598e076ac0c93995e7", size = 303384 }, + { url = "https://files.pythonhosted.org/packages/c0/a2/584f2c29641df8bd810d00c1f21d408c12e9ad0c0dafdb8b7b29e5ddf787/fastuuid-0.14.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c0a94245afae4d7af8c43b3159d5e3934c53f47140be0be624b96acd672ceb73", size = 460921 }, + { url = "https://files.pythonhosted.org/packages/24/68/c6b77443bb7764c760e211002c8638c0c7cce11cb584927e723215ba1398/fastuuid-0.14.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:2b29e23c97e77c3a9514d70ce343571e469098ac7f5a269320a0f0b3e193ab36", size = 480575 }, + { url = "https://files.pythonhosted.org/packages/5a/87/93f553111b33f9bb83145be12868c3c475bf8ea87c107063d01377cc0e8e/fastuuid-0.14.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1e690d48f923c253f28151b3a6b4e335f2b06bf669c68a02665bc150b7839e94", size = 452317 }, + { url = "https://files.pythonhosted.org/packages/9e/8c/a04d486ca55b5abb7eaa65b39df8d891b7b1635b22db2163734dc273579a/fastuuid-0.14.0-cp311-cp311-win32.whl", hash = "sha256:a6f46790d59ab38c6aa0e35c681c0484b50dc0acf9e2679c005d61e019313c24", size = 154804 }, + { url = "https://files.pythonhosted.org/packages/9c/b2/2d40bf00820de94b9280366a122cbaa60090c8cf59e89ac3938cf5d75895/fastuuid-0.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:e150eab56c95dc9e3fefc234a0eedb342fac433dacc273cd4d150a5b0871e1fa", size = 156099 }, + { url = "https://files.pythonhosted.org/packages/02/a2/e78fcc5df65467f0d207661b7ef86c5b7ac62eea337c0c0fcedbeee6fb13/fastuuid-0.14.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:77e94728324b63660ebf8adb27055e92d2e4611645bf12ed9d88d30486471d0a", size = 510164 }, + { url = "https://files.pythonhosted.org/packages/2b/b3/c846f933f22f581f558ee63f81f29fa924acd971ce903dab1a9b6701816e/fastuuid-0.14.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:caa1f14d2102cb8d353096bc6ef6c13b2c81f347e6ab9d6fbd48b9dea41c153d", size = 261837 }, + { url = "https://files.pythonhosted.org/packages/54/ea/682551030f8c4fa9a769d9825570ad28c0c71e30cf34020b85c1f7ee7382/fastuuid-0.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d23ef06f9e67163be38cece704170486715b177f6baae338110983f99a72c070", size = 251370 }, + { url = "https://files.pythonhosted.org/packages/14/dd/5927f0a523d8e6a76b70968e6004966ee7df30322f5fc9b6cdfb0276646a/fastuuid-0.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c9ec605ace243b6dbe3bd27ebdd5d33b00d8d1d3f580b39fdd15cd96fd71796", size = 277766 }, + { url = "https://files.pythonhosted.org/packages/16/6e/c0fb547eef61293153348f12e0f75a06abb322664b34a1573a7760501336/fastuuid-0.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:808527f2407f58a76c916d6aa15d58692a4a019fdf8d4c32ac7ff303b7d7af09", size = 278105 }, + { url = "https://files.pythonhosted.org/packages/2d/b1/b9c75e03b768f61cf2e84ee193dc18601aeaf89a4684b20f2f0e9f52b62c/fastuuid-0.14.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2fb3c0d7fef6674bbeacdd6dbd386924a7b60b26de849266d1ff6602937675c8", size = 301564 }, + { url = "https://files.pythonhosted.org/packages/fc/fa/f7395fdac07c7a54f18f801744573707321ca0cee082e638e36452355a9d/fastuuid-0.14.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab3f5d36e4393e628a4df337c2c039069344db5f4b9d2a3c9cea48284f1dd741", size = 459659 }, + { url = "https://files.pythonhosted.org/packages/66/49/c9fd06a4a0b1f0f048aacb6599e7d96e5d6bc6fa680ed0d46bf111929d1b/fastuuid-0.14.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:b9a0ca4f03b7e0b01425281ffd44e99d360e15c895f1907ca105854ed85e2057", size = 478430 }, + { url = "https://files.pythonhosted.org/packages/be/9c/909e8c95b494e8e140e8be6165d5fc3f61fdc46198c1554df7b3e1764471/fastuuid-0.14.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3acdf655684cc09e60fb7e4cf524e8f42ea760031945aa8086c7eae2eeeabeb8", size = 450894 }, + { url = "https://files.pythonhosted.org/packages/90/eb/d29d17521976e673c55ef7f210d4cdd72091a9ec6755d0fd4710d9b3c871/fastuuid-0.14.0-cp312-cp312-win32.whl", hash = "sha256:9579618be6280700ae36ac42c3efd157049fe4dd40ca49b021280481c78c3176", size = 154374 }, + { url = "https://files.pythonhosted.org/packages/cc/fc/f5c799a6ea6d877faec0472d0b27c079b47c86b1cdc577720a5386483b36/fastuuid-0.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:d9e4332dc4ba054434a9594cbfaf7823b57993d7d8e7267831c3e059857cf397", size = 156550 }, + { url = "https://files.pythonhosted.org/packages/a5/83/ae12dd39b9a39b55d7f90abb8971f1a5f3c321fd72d5aa83f90dc67fe9ed/fastuuid-0.14.0-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:77a09cb7427e7af74c594e409f7731a0cf887221de2f698e1ca0ebf0f3139021", size = 510720 }, + { url = "https://files.pythonhosted.org/packages/53/b0/a4b03ff5d00f563cc7546b933c28cb3f2a07344b2aec5834e874f7d44143/fastuuid-0.14.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:9bd57289daf7b153bfa3e8013446aa144ce5e8c825e9e366d455155ede5ea2dc", size = 262024 }, + { url = "https://files.pythonhosted.org/packages/9c/6d/64aee0a0f6a58eeabadd582e55d0d7d70258ffdd01d093b30c53d668303b/fastuuid-0.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ac60fc860cdf3c3f327374db87ab8e064c86566ca8c49d2e30df15eda1b0c2d5", size = 251679 }, + { url = "https://files.pythonhosted.org/packages/60/f5/a7e9cda8369e4f7919d36552db9b2ae21db7915083bc6336f1b0082c8b2e/fastuuid-0.14.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab32f74bd56565b186f036e33129da77db8be09178cd2f5206a5d4035fb2a23f", size = 277862 }, + { url = "https://files.pythonhosted.org/packages/f0/d3/8ce11827c783affffd5bd4d6378b28eb6cc6d2ddf41474006b8d62e7448e/fastuuid-0.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33e678459cf4addaedd9936bbb038e35b3f6b2061330fd8f2f6a1d80414c0f87", size = 278278 }, + { url = "https://files.pythonhosted.org/packages/a2/51/680fb6352d0bbade04036da46264a8001f74b7484e2fd1f4da9e3db1c666/fastuuid-0.14.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1e3cc56742f76cd25ecb98e4b82a25f978ccffba02e4bdce8aba857b6d85d87b", size = 301788 }, + { url = "https://files.pythonhosted.org/packages/fa/7c/2014b5785bd8ebdab04ec857635ebd84d5ee4950186a577db9eff0fb8ff6/fastuuid-0.14.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cb9a030f609194b679e1660f7e32733b7a0f332d519c5d5a6a0a580991290022", size = 459819 }, + { url = "https://files.pythonhosted.org/packages/01/d2/524d4ceeba9160e7a9bc2ea3e8f4ccf1ad78f3bde34090ca0c51f09a5e91/fastuuid-0.14.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:09098762aad4f8da3a888eb9ae01c84430c907a297b97166b8abc07b640f2995", size = 478546 }, + { url = "https://files.pythonhosted.org/packages/bc/17/354d04951ce114bf4afc78e27a18cfbd6ee319ab1829c2d5fb5e94063ac6/fastuuid-0.14.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:1383fff584fa249b16329a059c68ad45d030d5a4b70fb7c73a08d98fd53bcdab", size = 450921 }, + { url = "https://files.pythonhosted.org/packages/fb/be/d7be8670151d16d88f15bb121c5b66cdb5ea6a0c2a362d0dcf30276ade53/fastuuid-0.14.0-cp313-cp313-win32.whl", hash = "sha256:a0809f8cc5731c066c909047f9a314d5f536c871a7a22e815cc4967c110ac9ad", size = 154559 }, + { url = "https://files.pythonhosted.org/packages/22/1d/5573ef3624ceb7abf4a46073d3554e37191c868abc3aecd5289a72f9810a/fastuuid-0.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:0df14e92e7ad3276327631c9e7cec09e32572ce82089c55cb1bb8df71cf394ed", size = 156539 }, + { url = "https://files.pythonhosted.org/packages/16/c9/8c7660d1fe3862e3f8acabd9be7fc9ad71eb270f1c65cce9a2b7a31329ab/fastuuid-0.14.0-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:b852a870a61cfc26c884af205d502881a2e59cc07076b60ab4a951cc0c94d1ad", size = 510600 }, + { url = "https://files.pythonhosted.org/packages/4c/f4/a989c82f9a90d0ad995aa957b3e572ebef163c5299823b4027986f133dfb/fastuuid-0.14.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c7502d6f54cd08024c3ea9b3514e2d6f190feb2f46e6dbcd3747882264bb5f7b", size = 262069 }, + { url = "https://files.pythonhosted.org/packages/da/6c/a1a24f73574ac995482b1326cf7ab41301af0fabaa3e37eeb6b3df00e6e2/fastuuid-0.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1ca61b592120cf314cfd66e662a5b54a578c5a15b26305e1b8b618a6f22df714", size = 251543 }, + { url = "https://files.pythonhosted.org/packages/1a/20/2a9b59185ba7a6c7b37808431477c2d739fcbdabbf63e00243e37bd6bf49/fastuuid-0.14.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa75b6657ec129d0abded3bec745e6f7ab642e6dba3a5272a68247e85f5f316f", size = 277798 }, + { url = "https://files.pythonhosted.org/packages/ef/33/4105ca574f6ded0af6a797d39add041bcfb468a1255fbbe82fcb6f592da2/fastuuid-0.14.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8a0dfea3972200f72d4c7df02c8ac70bad1bb4c58d7e0ec1e6f341679073a7f", size = 278283 }, + { url = "https://files.pythonhosted.org/packages/fe/8c/fca59f8e21c4deb013f574eae05723737ddb1d2937ce87cb2a5d20992dc3/fastuuid-0.14.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1bf539a7a95f35b419f9ad105d5a8a35036df35fdafae48fb2fd2e5f318f0d75", size = 301627 }, + { url = "https://files.pythonhosted.org/packages/cb/e2/f78c271b909c034d429218f2798ca4e89eeda7983f4257d7865976ddbb6c/fastuuid-0.14.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:9a133bf9cc78fdbd1179cb58a59ad0100aa32d8675508150f3658814aeefeaa4", size = 459778 }, + { url = "https://files.pythonhosted.org/packages/1e/f0/5ff209d865897667a2ff3e7a572267a9ced8f7313919f6d6043aed8b1caa/fastuuid-0.14.0-cp314-cp314-musllinux_1_1_i686.whl", hash = "sha256:f54d5b36c56a2d5e1a31e73b950b28a0d83eb0c37b91d10408875a5a29494bad", size = 478605 }, + { url = "https://files.pythonhosted.org/packages/e0/c8/2ce1c78f983a2c4987ea865d9516dbdfb141a120fd3abb977ae6f02ba7ca/fastuuid-0.14.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:ec27778c6ca3393ef662e2762dba8af13f4ec1aaa32d08d77f71f2a70ae9feb8", size = 450837 }, + { url = "https://files.pythonhosted.org/packages/df/60/dad662ec9a33b4a5fe44f60699258da64172c39bd041da2994422cdc40fe/fastuuid-0.14.0-cp314-cp314-win32.whl", hash = "sha256:e23fc6a83f112de4be0cc1990e5b127c27663ae43f866353166f87df58e73d06", size = 154532 }, + { url = "https://files.pythonhosted.org/packages/1f/f6/da4db31001e854025ffd26bc9ba0740a9cbba2c3259695f7c5834908b336/fastuuid-0.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:df61342889d0f5e7a32f7284e55ef95103f2110fee433c2ae7c2c0956d76ac8a", size = 156457 }, +] + +[[package]] +name = "fickling" +version = "0.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "stdlib-list" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/ab/7571453f9365c17c047b5a7b7e82692a7f6be51203f295030886758fd57a/fickling-0.1.6.tar.gz", hash = "sha256:03cb5d7bd09f9169c7583d2079fad4b3b88b25f865ed0049172e5cb68582311d", size = 284033 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/99/cc04258dda421bc612cdfe4be8c253f45b922f1c7f268b5a0b9962d9cd12/fickling-0.1.6-py3-none-any.whl", hash = "sha256:465d0069548bfc731bdd75a583cb4cf5a4b2666739c0f76287807d724b147ed3", size = 47922 }, +] + [[package]] name = "filelock" version = "3.16.1" @@ -899,6 +1126,44 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a0/0f/c0713fb2b3d28af4b2fded3291df1c4d4f79a00d15c2374a9e010870016c/googleapis_common_protos-1.66.0-py2.py3-none-any.whl", hash = "sha256:d7abcd75fabb2e0ec9f74466401f6c119a0b498e27370e9be4c94cb7e382b8ed", size = 221682 }, ] +[[package]] +name = "gql" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "backoff" }, + { name = "graphql-core" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/9f/cf224a88ed71eb223b7aa0b9ff0aa10d7ecc9a4acdca2279eb046c26d5dc/gql-4.0.0.tar.gz", hash = "sha256:f22980844eb6a7c0266ffc70f111b9c7e7c7c13da38c3b439afc7eab3d7c9c8e", size = 215644 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/94/30bbd09e8d45339fa77a48f5778d74d47e9242c11b3cd1093b3d994770a5/gql-4.0.0-py3-none-any.whl", hash = "sha256:f3beed7c531218eb24d97cb7df031b4a84fdb462f4a2beb86e2633d395937479", size = 89900 }, +] + +[package.optional-dependencies] +httpx = [ + { name = "httpx" }, +] + +[[package]] +name = "graphql-core" +version = "3.2.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/9b/037a640a2983b09aed4a823f9cf1729e6d780b0671f854efa4727a7affbe/graphql_core-3.2.7.tar.gz", hash = "sha256:27b6904bdd3b43f2a0556dad5d579bdfdeab1f38e8e8788e555bdcb586a6f62c", size = 513484 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/14/933037032608787fb92e365883ad6a741c235e0ff992865ec5d904a38f1e/graphql_core-3.2.7-py3-none-any.whl", hash = "sha256:17fc8f3ca4a42913d8e24d9ac9f08deddf0a0b2483076575757f6c412ead2ec0", size = 207262 }, +] + +[[package]] +name = "graphviz" +version = "0.21" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/b3/3ac91e9be6b761a4b30d66ff165e54439dcd48b83f4e20d644867215f6ca/graphviz-0.21.tar.gz", hash = "sha256:20743e7183be82aaaa8ad6c93f8893c923bd6658a04c32ee115edb3c8a835f78", size = 200434 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl", hash = "sha256:54f33de9f4f911d7e84e4191749cac8cc5653f815b06738c54db9a15ab8b1e42", size = 47300 }, +] + [[package]] name = "greenlet" version = "3.1.1" @@ -1111,6 +1376,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461 }, ] +[[package]] +name = "intervaltree" +version = "3.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sortedcontainers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/c3/b2afa612aa0373f3e6bb190e6de35f293b307d1537f109e3e25dbfcdf212/intervaltree-3.2.1.tar.gz", hash = "sha256:f3f7e8baeb7dd75b9f7a6d33cf3ec10025984a8e66e3016d537e52130c73cfe2", size = 1231531 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/7f/8a80a1c7c2ed05822b5a2b312d2995f30c533641f8198366ba2e26a7bb03/intervaltree-3.2.1-py2.py3-none-any.whl", hash = "sha256:a8a8381bbd35d48ceebee932c77ffc988492d22fb1d27d0ba1d74a7694eb8f0b", size = 25929 }, +] + [[package]] name = "ipykernel" version = "6.29.5" @@ -1570,6 +1847,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a9/93/858e87edc634d628e5d752ba944c2833133a28fa87bb093e6832ced36a3e/jupyterlab_widgets-3.0.13-py3-none-any.whl", hash = "sha256:e3cda2c233ce144192f1e29914ad522b2f4c40e77214b0cc97377ca3d323db54", size = 214392 }, ] +[[package]] +name = "kaitaistruct" +version = "0.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/27/b8/ca7319556912f68832daa4b81425314857ec08dfccd8dbc8c0f65c992108/kaitaistruct-0.11.tar.gz", hash = "sha256:053ee764288e78b8e53acf748e9733268acbd579b8d82a427b1805453625d74b", size = 11519 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/4a/cf14bf3b1f5ffb13c69cf5f0ea78031247790558ee88984a8bdd22fae60d/kaitaistruct-0.11-py2.py3-none-any.whl", hash = "sha256:5c6ce79177b4e193a577ecd359e26516d1d6d000a0bffd6e1010f2a46a62a561", size = 11372 }, +] + [[package]] name = "kubernetes" version = "31.0.0" @@ -1834,6 +2120,29 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9e/08/3f0fb3e2f7cc6fd91c4d06d7abc6607425a66973bee79d04018bac41dd4f/langsmith-0.4.14-py3-none-any.whl", hash = "sha256:b6d070ac425196947d2a98126fb0e35f3b8c001a2e6e5b7049dd1c56f0767d0b", size = 373249 }, ] +[[package]] +name = "litellm" +version = "1.80.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "click" }, + { name = "fastuuid" }, + { name = "httpx" }, + { name = "importlib-metadata" }, + { name = "jinja2" }, + { name = "jsonschema" }, + { name = "openai" }, + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "tiktoken" }, + { name = "tokenizers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bd/8c/48d533affdbc6d485b7ad4221cd3b40b8c12f9f5568edfe0be0b11e7b945/litellm-1.80.0.tar.gz", hash = "sha256:eeac733eb6b226f9e5fb020f72fe13a32b3354b001dc62bcf1bc4d9b526d6231", size = 11591976 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/53/aa31e4d057b3746b3c323ca993003d6cf15ef987e7fe7ceb53681695ae87/litellm-1.80.0-py3-none-any.whl", hash = "sha256:fd0009758f4772257048d74bf79bb64318859adb4ea49a8b66fdbc718cd80b6e", size = 10492975 }, +] + [[package]] name = "markdown-it-py" version = "3.0.0" @@ -2168,6 +2477,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195 }, ] +[[package]] +name = "networkx" +version = "3.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504 }, +] + [[package]] name = "nltk" version = "3.9.2" @@ -2585,6 +2903,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650 }, ] +[[package]] +name = "pdfminer-six" +version = "20250506" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "charset-normalizer" }, + { name = "cryptography", version = "45.0.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14' and platform_python_implementation != 'PyPy'" }, + { name = "cryptography", version = "46.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14' or platform_python_implementation == 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/46/5223d613ac4963e1f7c07b2660fe0e9e770102ec6bda8c038400113fb215/pdfminer_six-20250506.tar.gz", hash = "sha256:b03cc8df09cf3c7aba8246deae52e0bca7ebb112a38895b5e1d4f5dd2b8ca2e7", size = 7387678 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/16/7a432c0101fa87457e75cb12c879e1749c5870a786525e2e0f42871d6462/pdfminer_six-20250506-py3-none-any.whl", hash = "sha256:d81ad173f62e5f841b53a8ba63af1a4a355933cfc0ffabd608e568b9193909e3", size = 5620187 }, +] + [[package]] name = "pexpect" version = "4.9.0" @@ -2655,6 +2987,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, ] +[[package]] +name = "polyfile-weave" +version = "0.5.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "abnf" }, + { name = "chardet" }, + { name = "cint" }, + { name = "fickling" }, + { name = "graphviz" }, + { name = "intervaltree" }, + { name = "jinja2" }, + { name = "kaitaistruct" }, + { name = "networkx" }, + { name = "pdfminer-six" }, + { name = "pillow" }, + { name = "pyreadline3", marker = "platform_system == 'Windows'" }, + { name = "pyyaml" }, + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/c3/5a2a2ba06850bc5ec27f83ac8b92210dff9ff6736b2c42f700b489b3fd86/polyfile_weave-0.5.7.tar.gz", hash = "sha256:c3d863f51c30322c236bdf385e116ac06d4e7de9ec25a3aae14d42b1d528e33b", size = 5987445 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/f6/d1efedc0f9506e47699616e896d8efe39e8f0b6a7d1d590c3e97455ecf4a/polyfile_weave-0.5.7-py3-none-any.whl", hash = "sha256:880454788bc383408bf19eefd6d1c49a18b965d90c99bccb58f4da65870c82dd", size = 1655397 }, +] + [[package]] name = "posthog" version = "3.8.3" @@ -3636,13 +3993,26 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl", hash = "sha256:0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9", size = 18072 }, ] +[[package]] +name = "sentry-sdk" +version = "2.48.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/f0/0e9dc590513d5e742d7799e2038df3a05167cba084c6ca4f3cdd75b55164/sentry_sdk-2.48.0.tar.gz", hash = "sha256:5213190977ff7fdff8a58b722fb807f8d5524a80488626ebeda1b5676c0c1473", size = 384828 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/19/8d77f9992e5cbfcaa9133c3bf63b4fbbb051248802e1e803fed5c552fbb2/sentry_sdk-2.48.0-py2.py3-none-any.whl", hash = "sha256:6b12ac256769d41825d9b7518444e57fa35b5642df4c7c5e322af4d2c8721172", size = 414555 }, +] + [[package]] name = "setuptools" -version = "75.8.0" +version = "80.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/92/ec/089608b791d210aec4e7f97488e67ab0d33add3efccb83a056cbafe3a2a6/setuptools-75.8.0.tar.gz", hash = "sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6", size = 1343222 } +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958 } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/8a/b9dc7678803429e4a3bc9ba462fa3dd9066824d3c607490235c6a796be5a/setuptools-75.8.0-py3-none-any.whl", hash = "sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3", size = 1228782 }, + { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486 }, ] [[package]] @@ -3681,6 +4051,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, ] +[[package]] +name = "sortedcontainers" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575 }, +] + [[package]] name = "soupsieve" version = "2.6" @@ -3778,6 +4157,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/be/72/2db2f49247d0a18b4f1bb9a5a39a0162869acf235f3a96418363947b3d46/starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659", size = 73736 }, ] +[[package]] +name = "stdlib-list" +version = "0.11.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5d/09/8d5c564931ae23bef17420a6c72618463a59222ca4291a7dd88de8a0d490/stdlib_list-0.11.1.tar.gz", hash = "sha256:95ebd1d73da9333bba03ccc097f5bac05e3aa03e6822a0c0290f87e1047f1857", size = 60442 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/c7/4102536de33c19d090ed2b04e90e7452e2e3dc653cf3323208034eaaca27/stdlib_list-0.11.1-py3-none-any.whl", hash = "sha256:9029ea5e3dfde8cd4294cfd4d1797be56a67fc4693c606181730148c3fd1da29", size = 83620 }, +] + [[package]] name = "streamlit" version = "1.50.0" @@ -4015,6 +4403,7 @@ dependencies = [ { name = "langchain-text-splitters" }, { name = "langgraph" }, { name = "langgraph-checkpoint-sqlite" }, + { name = "litellm" }, { name = "nltk" }, { name = "numpy" }, { name = "openai" }, @@ -4026,6 +4415,8 @@ dependencies = [ { name = "streamlit" }, { name = "streamlit-feedback" }, { name = "tavily-python" }, + { name = "wandb" }, + { name = "weave" }, ] [package.dev-dependencies] @@ -4049,6 +4440,7 @@ requires-dist = [ { name = "langchain-text-splitters", specifier = "==1.0.0" }, { name = "langgraph", specifier = "==1.0.1" }, { name = "langgraph-checkpoint-sqlite", specifier = "==3.0.0" }, + { name = "litellm", specifier = ">=1.80.0" }, { name = "nltk", specifier = "==3.9.2" }, { name = "numpy", specifier = "==2.3.4" }, { name = "openai", specifier = "==2.6.0" }, @@ -4060,6 +4452,8 @@ requires-dist = [ { name = "streamlit", specifier = "==1.50.0" }, { name = "streamlit-feedback", specifier = "==0.1.4" }, { name = "tavily-python", specifier = "==0.7.12" }, + { name = "wandb", specifier = ">=0.23.1" }, + { name = "weave", specifier = ">=0.52.22" }, ] [package.metadata.requires-dev] @@ -4221,6 +4615,35 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/63/9a/0962b05b308494e3202d3f794a6e85abe471fe3cafdbcf95c2e8c713aabd/uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553", size = 4660018 }, ] +[[package]] +name = "wandb" +version = "0.23.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "gitpython" }, + { name = "packaging" }, + { name = "platformdirs" }, + { name = "protobuf" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "sentry-sdk" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0a/cc/770ae3aa7ae44f6792f7ecb81c14c0e38b672deb35235719bb1006519487/wandb-0.23.1.tar.gz", hash = "sha256:f6fb1e3717949b29675a69359de0eeb01e67d3360d581947d5b3f98c273567d6", size = 44298053 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/0b/c3d7053dfd93fd259a63c7818d9c4ac2ba0642ff8dc8db98662ea0cf9cc0/wandb-0.23.1-py3-none-macosx_12_0_arm64.whl", hash = "sha256:358e15471d19b7d73fc464e37371c19d44d39e433252ac24df107aff993a286b", size = 21527293 }, + { url = "https://files.pythonhosted.org/packages/ee/9f/059420fa0cb6c511dc5c5a50184122b6aca7b178cb2aa210139e354020da/wandb-0.23.1-py3-none-macosx_12_0_x86_64.whl", hash = "sha256:110304407f4b38f163bdd50ed5c5225365e4df3092f13089c30171a75257b575", size = 22745926 }, + { url = "https://files.pythonhosted.org/packages/96/b6/fd465827c14c64d056d30b4c9fcf4dac889a6969dba64489a88fc4ffa333/wandb-0.23.1-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:6cc984cf85feb2f8ee0451d76bc9fb7f39da94956bb8183e30d26284cf203b65", size = 21212973 }, + { url = "https://files.pythonhosted.org/packages/5c/ee/9a8bb9a39cc1f09c3060456cc79565110226dc4099a719af5c63432da21d/wandb-0.23.1-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:67431cd3168d79fdb803e503bd669c577872ffd5dadfa86de733b3274b93088e", size = 22887885 }, + { url = "https://files.pythonhosted.org/packages/6d/4d/8d9e75add529142e037b05819cb3ab1005679272950128d69d218b7e5b2e/wandb-0.23.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:07be70c0baa97ea25fadc4a9d0097f7371eef6dcacc5ceb525c82491a31e9244", size = 21250967 }, + { url = "https://files.pythonhosted.org/packages/97/72/0b35cddc4e4168f03c759b96d9f671ad18aec8bdfdd84adfea7ecb3f5701/wandb-0.23.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:216c95b08e0a2ec6a6008373b056d597573d565e30b43a7a93c35a171485ee26", size = 22988382 }, + { url = "https://files.pythonhosted.org/packages/c0/6d/e78093d49d68afb26f5261a70fc7877c34c114af5c2ee0ab3b1af85f5e76/wandb-0.23.1-py3-none-win32.whl", hash = "sha256:fb5cf0f85692f758a5c36ab65fea96a1284126de64e836610f92ddbb26df5ded", size = 22150756 }, + { url = "https://files.pythonhosted.org/packages/05/27/4f13454b44c9eceaac3d6e4e4efa2230b6712d613ff9bf7df010eef4fd18/wandb-0.23.1-py3-none-win_amd64.whl", hash = "sha256:21c8c56e436eb707b7d54f705652e030d48e5cfcba24cf953823eb652e30e714", size = 22150760 }, + { url = "https://files.pythonhosted.org/packages/30/20/6c091d451e2a07689bfbfaeb7592d488011420e721de170884fedd68c644/wandb-0.23.1-py3-none-win_arm64.whl", hash = "sha256:8aee7f3bb573f2c0acf860f497ca9c684f9b35f2ca51011ba65af3d4592b77c1", size = 20137463 }, +] + [[package]] name = "watchdog" version = "6.0.0" @@ -4297,6 +4720,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 }, ] +[[package]] +name = "weave" +version = "0.52.22" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "diskcache" }, + { name = "gql", extra = ["httpx"] }, + { name = "jsonschema" }, + { name = "packaging" }, + { name = "polyfile-weave" }, + { name = "pydantic" }, + { name = "sentry-sdk" }, + { name = "tenacity" }, + { name = "tzdata", marker = "platform_system == 'Windows'" }, + { name = "wandb" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2c/ac/e9e271cd13f4568f8cbcc128d48088d3228eea4fb77ebca0cbd33a6ec034/weave-0.52.22.tar.gz", hash = "sha256:d82b91540449734c0fb3032e86af9fd072d062cb3f0f107c88ebd9de15e1f5fe", size = 627270 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/ee/cfc8d421a3d00be8d8ce2e22e7d572441366d2daa02163beb5b6ced83a2a/weave-0.52.22-py3-none-any.whl", hash = "sha256:d243bc92552b7a673718c61d5bacf4a2d49705cc19fd284da65c9834d0f2136e", size = 788523 }, +] + [[package]] name = "webcolors" version = "24.11.1"