saytest 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- saytest/__init__.py +11 -0
- saytest/__main__.py +31 -0
- saytest/config.toml.example +14 -0
- saytest/flow.py +114 -0
- saytest/mcp.py +43 -0
- saytest-0.1.0.dist-info/METADATA +9 -0
- saytest-0.1.0.dist-info/RECORD +10 -0
- saytest-0.1.0.dist-info/WHEEL +5 -0
- saytest-0.1.0.dist-info/entry_points.txt +3 -0
- saytest-0.1.0.dist-info/top_level.txt +1 -0
saytest/__init__.py
ADDED
saytest/__main__.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""__main__ — CLI entry point.
|
|
2
|
+
|
|
3
|
+
사용: python -m saytest "사용자 입력"
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
from xgen_harness import PipelineState
|
|
10
|
+
|
|
11
|
+
from .flow import build_pipeline
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
async def _run(user_input: str) -> str:
|
|
15
|
+
pipeline = build_pipeline()
|
|
16
|
+
state = PipelineState(user_input=user_input)
|
|
17
|
+
result = await pipeline.run(state)
|
|
18
|
+
return getattr(result, "final_text", "") or ""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def cli_main() -> None:
|
|
22
|
+
user_input = sys.argv[1] if len(sys.argv) > 1 else ""
|
|
23
|
+
if not user_input:
|
|
24
|
+
print("Usage: python -m " + __package__ + " \"사용자 입력\"", file=sys.stderr)
|
|
25
|
+
sys.exit(1)
|
|
26
|
+
output = asyncio.run(_run(user_input))
|
|
27
|
+
print(output)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
if __name__ == "__main__":
|
|
31
|
+
cli_main()
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# xgen-harness.toml — 외부 override 예시
|
|
2
|
+
# 5 단계 resolution chain 의 3 번째 우선순위 (env > toml > cluster origin > sdk default)
|
|
3
|
+
# 이 파일이 존재하면 자동 로드. 없으면 cluster origin 그대로 사용.
|
|
4
|
+
|
|
5
|
+
[stage_params.s04_tool]
|
|
6
|
+
# selected_tools = {"mcp-sessions": []}
|
|
7
|
+
|
|
8
|
+
[stage_params.s03_prompt]
|
|
9
|
+
# selected_prompt = "_default"
|
|
10
|
+
|
|
11
|
+
# 외부 인프라 wire 는 env 변수 사용:
|
|
12
|
+
# QDRANT_URL=http://localhost:6333
|
|
13
|
+
# QDRANT_API_KEY=...
|
|
14
|
+
# OPENAI_API_KEY=sk-...
|
saytest/flow.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""
|
|
2
|
+
flow.py — cluster origin 환경값 + Pipeline 구축
|
|
3
|
+
|
|
4
|
+
CLUSTER_DEFAULTS dict 가 cluster UI 에서 사용자가 선택한 값들.
|
|
5
|
+
IDE 에서 직접 편집 가능. 또는 env / toml 로 외부 override.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from xgen_harness import HarnessConfig, Pipeline, PipelineState
|
|
12
|
+
from xgen_harness.config import DictConfigSource, EnvConfigSource, FileConfigSource
|
|
13
|
+
from xgen_harness.adapters import create_provider
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# === Cluster origin default — cluster UI 에서 사용자가 선택한 환경값 ===
|
|
17
|
+
# IDE 에서 직접 편집 가능. env / toml 로 외부 override 도 가능.
|
|
18
|
+
CLUSTER_DEFAULTS = {
|
|
19
|
+
"provider": "vllm",
|
|
20
|
+
"model": "Qwen3.5-27b",
|
|
21
|
+
"temperature": 0.7,
|
|
22
|
+
"max_tokens": 8192,
|
|
23
|
+
"aux_max_tokens": 500,
|
|
24
|
+
"openai_model": "",
|
|
25
|
+
"anthropic_model": "",
|
|
26
|
+
"max_iterations": 10,
|
|
27
|
+
"max_tool_rounds": None,
|
|
28
|
+
"max_retries": 10,
|
|
29
|
+
"validation_threshold": 0.7,
|
|
30
|
+
"system_prompt": "You are a helpful AI assistant.",
|
|
31
|
+
"disabled_stages": [],
|
|
32
|
+
"artifacts": {},
|
|
33
|
+
"stage_params": {
|
|
34
|
+
"s04_tool": {
|
|
35
|
+
"selected_tools": {
|
|
36
|
+
"mcp-sessions": []
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"s03_prompt": {
|
|
40
|
+
"selected_prompt": "_default"
|
|
41
|
+
},
|
|
42
|
+
"s06_context": {}
|
|
43
|
+
},
|
|
44
|
+
"active_strategies": {
|
|
45
|
+
"s00_harness": "streaming",
|
|
46
|
+
"s02_history": "default",
|
|
47
|
+
"s06_context": "cascade",
|
|
48
|
+
"s08_decide": "threshold",
|
|
49
|
+
"s09_finalize": "default"
|
|
50
|
+
},
|
|
51
|
+
"strategy_variants": {},
|
|
52
|
+
"capabilities": [],
|
|
53
|
+
"capability_params": {},
|
|
54
|
+
"external_inputs": {},
|
|
55
|
+
"cost_budget_usd": None,
|
|
56
|
+
"context_window": 200000,
|
|
57
|
+
"thinking_enabled": False,
|
|
58
|
+
"thinking_budget_tokens": 10000,
|
|
59
|
+
"verbose_events": False,
|
|
60
|
+
"judge_provider": "",
|
|
61
|
+
"judge_model": "",
|
|
62
|
+
"judge_use_main": False,
|
|
63
|
+
"preset": "standard",
|
|
64
|
+
"_schema_version": 1
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def build_pipeline() -> Pipeline:
|
|
69
|
+
"""Pipeline 인스턴스 생성. 5 단계 resolution + 외부 인프라 wire."""
|
|
70
|
+
config_toml = Path("./xgen-harness.toml")
|
|
71
|
+
config = HarnessConfig.resolve(sources=[
|
|
72
|
+
EnvConfigSource(prefix="XGEN_HARNESS_"),
|
|
73
|
+
FileConfigSource(config_toml) if config_toml.exists() else None,
|
|
74
|
+
DictConfigSource(CLUSTER_DEFAULTS),
|
|
75
|
+
])
|
|
76
|
+
|
|
77
|
+
# === 외부 인프라 wire — env 변수에서 받음 ===
|
|
78
|
+
provider = create_provider(
|
|
79
|
+
os.environ.get("XGEN_HARNESS_PROVIDER", config.provider or "openai"),
|
|
80
|
+
api_key=_resolve_api_key(config.provider or "openai"),
|
|
81
|
+
model=config.model or None,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
doc_service = _build_doc_service()
|
|
85
|
+
|
|
86
|
+
return Pipeline.from_config(
|
|
87
|
+
config,
|
|
88
|
+
doc_service=doc_service,
|
|
89
|
+
provider=provider,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _resolve_api_key(provider_name: str) -> str:
|
|
94
|
+
"""provider 이름 → API key env 변수 lookup."""
|
|
95
|
+
env_map = {
|
|
96
|
+
"openai": "OPENAI_API_KEY",
|
|
97
|
+
"anthropic": "ANTHROPIC_API_KEY",
|
|
98
|
+
"google": "GEMINI_API_KEY",
|
|
99
|
+
"vllm": "VLLM_API_KEY",
|
|
100
|
+
}
|
|
101
|
+
env_key = env_map.get(provider_name, f"{provider_name.upper()}_API_KEY")
|
|
102
|
+
return os.environ.get(env_key, "")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _build_doc_service():
|
|
106
|
+
"""Qdrant URL 박혀있으면 QdrantDocService, 아니면 None."""
|
|
107
|
+
qdrant_url = os.environ.get("QDRANT_URL")
|
|
108
|
+
if not qdrant_url:
|
|
109
|
+
return None
|
|
110
|
+
from xgen_harness.adapters import QdrantDocService
|
|
111
|
+
return QdrantDocService(
|
|
112
|
+
url=qdrant_url,
|
|
113
|
+
api_key=os.environ.get("QDRANT_API_KEY"),
|
|
114
|
+
)
|
saytest/mcp.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""MCP 서버 entry — FastMCP stdio.
|
|
2
|
+
|
|
3
|
+
사용:
|
|
4
|
+
claude mcp add my-wf -- python -m saytest.mcp
|
|
5
|
+
|
|
6
|
+
FastMCP 의존성:
|
|
7
|
+
pip install saytest[mcp]
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from xgen_harness import PipelineState
|
|
11
|
+
|
|
12
|
+
from .flow import build_pipeline
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _build_server():
|
|
16
|
+
"""FastMCP 서버 인스턴스 구축 — 도구 1 개 등록."""
|
|
17
|
+
try:
|
|
18
|
+
from fastmcp import FastMCP
|
|
19
|
+
except ImportError as e:
|
|
20
|
+
raise ImportError(
|
|
21
|
+
"FastMCP 미설치. `pip install saytest[mcp]` 또는 `pip install fastmcp` 박으세요."
|
|
22
|
+
) from e
|
|
23
|
+
|
|
24
|
+
mcp = FastMCP("saytest")
|
|
25
|
+
|
|
26
|
+
@mcp.tool
|
|
27
|
+
async def run_workflow(user_input: str) -> str:
|
|
28
|
+
"""xgen 워크플로우 실행. user_input 받아 final_text 반환."""
|
|
29
|
+
pipeline = build_pipeline()
|
|
30
|
+
state = PipelineState(user_input=user_input)
|
|
31
|
+
result = await pipeline.run(state)
|
|
32
|
+
return getattr(result, "final_text", "") or ""
|
|
33
|
+
|
|
34
|
+
return mcp
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def main() -> None:
|
|
38
|
+
server = _build_server()
|
|
39
|
+
server.run()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
if __name__ == "__main__":
|
|
43
|
+
main()
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
saytest/__init__.py,sha256=FwAFCtvxN_oV_o4o4VOwK-HOPznMZBPg3hsHj8NHfxQ,264
|
|
2
|
+
saytest/__main__.py,sha256=wB3OJWngJtSeq3feHS3n5bTt-WHk7LTYD5ywkJBdojo,721
|
|
3
|
+
saytest/config.toml.example,sha256=9aEELyhoSlMnP9Jv9JQ1nQCIbM0UxyeWHPo5BTLS_mQ,489
|
|
4
|
+
saytest/flow.py,sha256=O2sFeiZ5kKua3GG1XtFbC4RFA4sJCHUS10RwSCXVyug,3442
|
|
5
|
+
saytest/mcp.py,sha256=Fdz6tjbMdiJVN3Jvd89DrtWu9f2d8nxpwwRlsYpS0Rg,1027
|
|
6
|
+
saytest-0.1.0.dist-info/METADATA,sha256=8NZkoIoT771m79DwInelUcOrGiKzKqI-YlZPtA1-NyE,244
|
|
7
|
+
saytest-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
8
|
+
saytest-0.1.0.dist-info/entry_points.txt,sha256=eZ44wLfHX7F3pfFoN_G6DX0iyqf_HRiCLT3z92kyX-A,85
|
|
9
|
+
saytest-0.1.0.dist-info/top_level.txt,sha256=WWkxkUnrFbNsmbRKvxhxjO1CKafzeyzaWW0Z0z7hjLU,8
|
|
10
|
+
saytest-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
saytest
|