@langwatch/mcp-server 0.7.0 → 0.9.0
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.
- package/.release-please-shim +1 -0
- package/CHANGELOG.md +87 -0
- package/LICENSE +7 -0
- package/dist/archive-project-HZBRPR2S.js +19 -0
- package/dist/{archive-scenario-WJ2A7ETN.js → archive-scenario-P5RRITRM.js} +4 -4
- package/dist/{archive-suite-4JOM4SSL.js → archive-suite-NUUFD2EM.js} +4 -4
- package/dist/{assign-prompt-tag-GL7EAAF3.js → assign-prompt-tag-FKCCZPML.js} +3 -3
- package/dist/{chunk-2WPLXEKF.js → chunk-42ZHHHIJ.js} +20 -5
- package/dist/chunk-ADSJ2FDA.js +44 -0
- package/dist/{chunk-IE4M5OGE.js → chunk-BB7EOKHR.js} +2 -2
- package/dist/chunk-BBJ5WAJB.js +24 -0
- package/dist/{chunk-EGH5RPXM.js → chunk-BYWYM2RP.js} +2 -2
- package/dist/{chunk-2GT7IFWK.js → chunk-CCM4ZFGF.js} +2 -2
- package/dist/{chunk-LUMEZEHL.js → chunk-CHKCEZBP.js} +2 -2
- package/dist/{chunk-ZVS4UKN2.js → chunk-HKY3GW45.js} +2 -2
- package/dist/{chunk-EUQDVOSU.js → chunk-JHOSVOHT.js} +2 -2
- package/dist/{chunk-E5JYYU3J.js → chunk-JVLJKQJG.js} +669 -94
- package/dist/{chunk-BWWY5KEF.js → chunk-KOO5733S.js} +3 -2
- package/dist/{chunk-HYMIWO3U.js → chunk-R7C7OUVG.js} +2 -2
- package/dist/{chunk-WQG2DFHX.js → chunk-TROIWSEQ.js} +2 -2
- package/dist/{chunk-CCWCIJJ3.js → chunk-X2SEURT2.js} +2 -2
- package/dist/{chunk-4AUB2BIA.js → chunk-XC62KJIT.js} +3 -3
- package/dist/config.js +1 -1
- package/dist/{create-agent-MBOOT3XK.js → create-agent-K5Q7BFGZ.js} +4 -4
- package/dist/{create-annotation-XMV6A7ED.js → create-annotation-KN3GE3DK.js} +4 -4
- package/dist/create-api-key-OQI5PUNQ.js +26 -0
- package/dist/{create-dashboard-VRKLFVZJ.js → create-dashboard-G4QA5AXP.js} +4 -4
- package/dist/{create-dataset-7HQPZCRE.js → create-dataset-WQSC37N7.js} +4 -4
- package/dist/{create-dataset-records-2NIVFYTV.js → create-dataset-records-B4RL3FLC.js} +4 -4
- package/dist/{create-evaluator-WYFRFLRC.js → create-evaluator-YFFQIR7E.js} +4 -4
- package/dist/create-mcp-server.js +3 -2
- package/dist/create-project-CUVQ67VN.js +44 -0
- package/dist/{create-prompt-IEN7O6JT.js → create-prompt-NGXKSP4X.js} +3 -3
- package/dist/{create-prompt-tag-CAEZ45VO.js → create-prompt-tag-IUKYPVHH.js} +3 -3
- package/dist/{create-scenario-6YHSMGLP.js → create-scenario-FNBXALH3.js} +4 -4
- package/dist/{create-suite-H5XOFDE5.js → create-suite-DJEPD4ZB.js} +4 -4
- package/dist/{delete-agent-JKTNBWAA.js → delete-agent-TJI5XBBM.js} +4 -4
- package/dist/{delete-annotation-ACMDV6QO.js → delete-annotation-KRP53TUU.js} +4 -4
- package/dist/{delete-dashboard-VJSR7CRU.js → delete-dashboard-N5SZJIR3.js} +4 -4
- package/dist/{delete-dataset-RTTTWP4D.js → delete-dataset-KDUOHL2P.js} +4 -4
- package/dist/{delete-dataset-records-KNQTIPQK.js → delete-dataset-records-DM4W3PAW.js} +4 -4
- package/dist/{delete-evaluator-7FVC5WM4.js → delete-evaluator-7ZATLJOA.js} +4 -4
- package/dist/{delete-prompt-tag-ZMZUAGFW.js → delete-prompt-tag-I6HNMSVJ.js} +3 -3
- package/dist/{delete-workflow-EYQ3T4RQ.js → delete-workflow-QEO74JQR.js} +4 -4
- package/dist/{get-agent-E3QSTVKG.js → get-agent-3KDWCP5N.js} +4 -4
- package/dist/{get-analytics-2OHV763I.js → get-analytics-GPVJX7SF.js} +3 -3
- package/dist/{get-dashboard-PC7JDHH6.js → get-dashboard-YPTIE5YY.js} +4 -4
- package/dist/{get-dataset-H5IK5FVU.js → get-dataset-6DQFYITM.js} +4 -4
- package/dist/{get-evaluator-UWVUPYTM.js → get-evaluator-PCM6XQAU.js} +4 -4
- package/dist/get-project-JCMWXVLU.js +26 -0
- package/dist/{get-prompt-WZUOLLQC.js → get-prompt-5XAGWWBG.js} +3 -3
- package/dist/{get-scenario-Q6LCNUYN.js → get-scenario-7XBCDZWX.js} +4 -4
- package/dist/{get-simulation-run-Z2XKO256.js → get-simulation-run-AV24FJWI.js} +4 -4
- package/dist/{get-suite-BXK5TN7P.js → get-suite-4DBVHFEM.js} +4 -4
- package/dist/{get-trace-4EJAYVBG.js → get-trace-MRBDS3HJ.js} +3 -3
- package/dist/{get-workflow-O34QJLHD.js → get-workflow-UR4Y6UFX.js} +4 -4
- package/dist/{http-server-ISHF7NJX.js → http-server-DEGNV5EP.js} +4 -3
- package/dist/index.js +1453 -431
- package/dist/{langwatch-api-annotations-ERBG27RF.js → langwatch-api-annotations-ZJDRB4JH.js} +4 -4
- package/dist/{langwatch-api-dashboards-G6TR7VVO.js → langwatch-api-dashboards-WARLI6NF.js} +4 -4
- package/dist/{langwatch-api-monitors-WSXZLS2B.js → langwatch-api-monitors-4XKXHYCG.js} +3 -3
- package/dist/{langwatch-api-secrets-RR5DKG2X.js → langwatch-api-secrets-6SHK5G7Q.js} +3 -3
- package/dist/{langwatch-api-triggers-2RTNSWQY.js → langwatch-api-triggers-5SX6R2WU.js} +3 -3
- package/dist/langwatch-api-workflows-NLJENNXE.js +15 -0
- package/dist/{list-agents-AW3P6FJV.js → list-agents-NECO7MPI.js} +4 -4
- package/dist/{list-annotations-QNCGWPQ3.js → list-annotations-3OJEO774.js} +4 -4
- package/dist/list-api-keys-AWW7ROLB.js +37 -0
- package/dist/{list-dashboards-R7RK2IXL.js → list-dashboards-DVPVVVKW.js} +4 -4
- package/dist/{list-dataset-records-WVCSQP7P.js → list-dataset-records-I4BYORF5.js} +4 -4
- package/dist/{list-datasets-KEXRLDEU.js → list-datasets-WUAE5HGX.js} +4 -4
- package/dist/{list-evaluators-7R2LB4JH.js → list-evaluators-B6OSVAM2.js} +4 -4
- package/dist/{list-model-providers-OBYJCKBX.js → list-model-providers-NL6IP4UZ.js} +4 -4
- package/dist/list-projects-5FHMJ4PQ.js +42 -0
- package/dist/{list-prompt-tags-6LY3ZPYI.js → list-prompt-tags-Z2DD6FLQ.js} +3 -3
- package/dist/{list-prompts-VWEZ4CDE.js → list-prompts-VNXKHK7T.js} +3 -3
- package/dist/{list-scenarios-TXD4W2ZJ.js → list-scenarios-CFZHXL76.js} +4 -4
- package/dist/{list-simulation-runs-RHPOLMFI.js → list-simulation-runs-AM2BXLBW.js} +4 -4
- package/dist/{list-suites-FSGBX27Z.js → list-suites-ZDOHAAXF.js} +4 -4
- package/dist/{list-workflows-VRGEZ3EN.js → list-workflows-WBRBKLIT.js} +4 -4
- package/dist/{rename-prompt-tag-VOJJLX4M.js → rename-prompt-tag-A4RJUK7Z.js} +3 -3
- package/dist/revoke-api-key-ZSPSPF5G.js +15 -0
- package/dist/{run-agent-KTCC7TB7.js → run-agent-JFNDY7MR.js} +4 -4
- package/dist/{run-suite-BTMHGI2D.js → run-suite-TH7LO5TJ.js} +4 -4
- package/dist/{run-workflow-IGAYGKJL.js → run-workflow-CK3JP5U2.js} +4 -4
- package/dist/{search-traces-VYFUIMXH.js → search-traces-3BBVYPC3.js} +3 -3
- package/dist/{set-model-provider-W5IUI326.js → set-model-provider-WOC5FNMV.js} +4 -4
- package/dist/{update-agent-4BB7EYLR.js → update-agent-YMEV5MTL.js} +4 -4
- package/dist/{update-dataset-CXBKZ4W3.js → update-dataset-EYT5POFM.js} +4 -4
- package/dist/{update-dataset-record-HOHAMHXG.js → update-dataset-record-VFF5JTX4.js} +4 -4
- package/dist/{update-evaluator-IVOR6FTR.js → update-evaluator-6IJHFG2E.js} +4 -4
- package/dist/update-project-OIT4WPFI.js +24 -0
- package/dist/{update-prompt-OWQAKE7K.js → update-prompt-ZU2DD6KM.js} +3 -3
- package/dist/{update-scenario-GDFVILPN.js → update-scenario-3VUTPDRU.js} +4 -4
- package/dist/{update-suite-BXYUYSPT.js → update-suite-C6JFDQHL.js} +4 -4
- package/package.json +7 -7
- package/pnpm-workspace.yaml +2 -0
- package/pyproject.toml +3 -0
- package/src/__tests__/all-tools.integration.test.ts +9 -0
- package/src/__tests__/datasets.integration.test.ts +17 -0
- package/src/__tests__/datasets.unit.test.ts +4 -0
- package/src/__tests__/experiment-list-tools.unit.test.ts +157 -0
- package/src/__tests__/get-experiment-results.unit.test.ts +222 -0
- package/src/__tests__/project-api-key-tools.unit.test.ts +451 -0
- package/src/__tests__/scenario-tools.unit.test.ts +3 -0
- package/src/config.ts +2 -0
- package/src/create-mcp-server.ts +257 -13
- package/src/langwatch-api-api-keys.ts +54 -0
- package/src/langwatch-api-projects.ts +82 -0
- package/src/langwatch-api.ts +22 -4
- package/src/tools/archive-project.ts +14 -0
- package/src/tools/create-api-key.ts +29 -0
- package/src/tools/create-project.ts +45 -0
- package/src/tools/get-experiment-results.ts +273 -0
- package/src/tools/get-project.ts +20 -0
- package/src/tools/list-api-keys.ts +33 -0
- package/src/tools/list-experiment-runs.ts +142 -0
- package/src/tools/list-experiments.ts +95 -0
- package/src/tools/list-projects.ts +41 -0
- package/src/tools/revoke-api-key.ts +9 -0
- package/src/tools/{run-evaluation.ts → run-experiment.ts} +5 -5
- package/src/tools/update-project.ts +22 -0
- package/uv.lock +192 -530
- package/Dockerfile +0 -22
- package/build.js +0 -12
- package/dist/archive-scenario-WJ2A7ETN.js.map +0 -1
- package/dist/archive-suite-4JOM4SSL.js.map +0 -1
- package/dist/assign-prompt-tag-GL7EAAF3.js.map +0 -1
- package/dist/chunk-2GT7IFWK.js.map +0 -1
- package/dist/chunk-2WPLXEKF.js.map +0 -1
- package/dist/chunk-4AUB2BIA.js.map +0 -1
- package/dist/chunk-BWWY5KEF.js.map +0 -1
- package/dist/chunk-CCWCIJJ3.js.map +0 -1
- package/dist/chunk-E5JYYU3J.js.map +0 -1
- package/dist/chunk-EGH5RPXM.js.map +0 -1
- package/dist/chunk-EUQDVOSU.js.map +0 -1
- package/dist/chunk-HYMIWO3U.js.map +0 -1
- package/dist/chunk-IE4M5OGE.js.map +0 -1
- package/dist/chunk-LUMEZEHL.js.map +0 -1
- package/dist/chunk-NUFPTHKU.js.map +0 -1
- package/dist/chunk-WQG2DFHX.js.map +0 -1
- package/dist/chunk-WVI5CJ6S.js.map +0 -1
- package/dist/chunk-ZVS4UKN2.js.map +0 -1
- package/dist/chunk-ZXKLPC2E.js.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/create-agent-MBOOT3XK.js.map +0 -1
- package/dist/create-annotation-XMV6A7ED.js.map +0 -1
- package/dist/create-dashboard-VRKLFVZJ.js.map +0 -1
- package/dist/create-dataset-7HQPZCRE.js.map +0 -1
- package/dist/create-dataset-records-2NIVFYTV.js.map +0 -1
- package/dist/create-evaluator-WYFRFLRC.js.map +0 -1
- package/dist/create-mcp-server.js.map +0 -1
- package/dist/create-prompt-IEN7O6JT.js.map +0 -1
- package/dist/create-prompt-tag-CAEZ45VO.js.map +0 -1
- package/dist/create-scenario-6YHSMGLP.js.map +0 -1
- package/dist/create-suite-H5XOFDE5.js.map +0 -1
- package/dist/delete-agent-JKTNBWAA.js.map +0 -1
- package/dist/delete-annotation-ACMDV6QO.js.map +0 -1
- package/dist/delete-dashboard-VJSR7CRU.js.map +0 -1
- package/dist/delete-dataset-RTTTWP4D.js.map +0 -1
- package/dist/delete-dataset-records-KNQTIPQK.js.map +0 -1
- package/dist/delete-evaluator-7FVC5WM4.js.map +0 -1
- package/dist/delete-prompt-tag-ZMZUAGFW.js.map +0 -1
- package/dist/delete-workflow-EYQ3T4RQ.js.map +0 -1
- package/dist/discover-evaluator-schema-H23XCLNE.js.map +0 -1
- package/dist/discover-scenario-schema-MEEEVND7.js.map +0 -1
- package/dist/discover-schema-3T52ORPB.js.map +0 -1
- package/dist/get-agent-E3QSTVKG.js.map +0 -1
- package/dist/get-analytics-2OHV763I.js.map +0 -1
- package/dist/get-dashboard-PC7JDHH6.js.map +0 -1
- package/dist/get-dataset-H5IK5FVU.js.map +0 -1
- package/dist/get-evaluator-UWVUPYTM.js.map +0 -1
- package/dist/get-prompt-WZUOLLQC.js.map +0 -1
- package/dist/get-scenario-Q6LCNUYN.js.map +0 -1
- package/dist/get-simulation-run-Z2XKO256.js.map +0 -1
- package/dist/get-suite-BXK5TN7P.js.map +0 -1
- package/dist/get-trace-4EJAYVBG.js.map +0 -1
- package/dist/get-workflow-O34QJLHD.js.map +0 -1
- package/dist/http-server-ISHF7NJX.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/langwatch-api-annotations-ERBG27RF.js.map +0 -1
- package/dist/langwatch-api-dashboards-G6TR7VVO.js.map +0 -1
- package/dist/langwatch-api-monitors-WSXZLS2B.js.map +0 -1
- package/dist/langwatch-api-secrets-RR5DKG2X.js.map +0 -1
- package/dist/langwatch-api-triggers-2RTNSWQY.js.map +0 -1
- package/dist/langwatch-api-workflows-VJRIIDZ7.js +0 -15
- package/dist/langwatch-api-workflows-VJRIIDZ7.js.map +0 -1
- package/dist/list-agents-AW3P6FJV.js.map +0 -1
- package/dist/list-annotations-QNCGWPQ3.js.map +0 -1
- package/dist/list-dashboards-R7RK2IXL.js.map +0 -1
- package/dist/list-dataset-records-WVCSQP7P.js.map +0 -1
- package/dist/list-datasets-KEXRLDEU.js.map +0 -1
- package/dist/list-evaluators-7R2LB4JH.js.map +0 -1
- package/dist/list-model-providers-OBYJCKBX.js.map +0 -1
- package/dist/list-prompt-tags-6LY3ZPYI.js.map +0 -1
- package/dist/list-prompts-VWEZ4CDE.js.map +0 -1
- package/dist/list-scenarios-TXD4W2ZJ.js.map +0 -1
- package/dist/list-simulation-runs-RHPOLMFI.js.map +0 -1
- package/dist/list-suites-FSGBX27Z.js.map +0 -1
- package/dist/list-workflows-VRGEZ3EN.js.map +0 -1
- package/dist/rename-prompt-tag-VOJJLX4M.js.map +0 -1
- package/dist/run-agent-KTCC7TB7.js.map +0 -1
- package/dist/run-evaluation-2X3V3Q6L.js +0 -64
- package/dist/run-evaluation-2X3V3Q6L.js.map +0 -1
- package/dist/run-suite-BTMHGI2D.js.map +0 -1
- package/dist/run-workflow-IGAYGKJL.js.map +0 -1
- package/dist/search-traces-VYFUIMXH.js.map +0 -1
- package/dist/set-model-provider-W5IUI326.js.map +0 -1
- package/dist/update-agent-4BB7EYLR.js.map +0 -1
- package/dist/update-dataset-CXBKZ4W3.js.map +0 -1
- package/dist/update-dataset-record-HOHAMHXG.js.map +0 -1
- package/dist/update-evaluator-IVOR6FTR.js.map +0 -1
- package/dist/update-prompt-OWQAKE7K.js.map +0 -1
- package/dist/update-scenario-GDFVILPN.js.map +0 -1
- package/dist/update-suite-BXYUYSPT.js.map +0 -1
- package/tests/evaluations.ipynb +0 -649
- package/tests/fixtures/azure/azure_openai_stream_bot_expected.py +0 -102
- package/tests/fixtures/azure/azure_openai_stream_bot_input.py +0 -78
- package/tests/fixtures/dspy/dspy_bot_expected.py +0 -61
- package/tests/fixtures/dspy/dspy_bot_input.py +0 -53
- package/tests/fixtures/fastapi/fastapi_app_expected.py +0 -68
- package/tests/fixtures/fastapi/fastapi_app_input.py +0 -60
- package/tests/fixtures/fastapi/prompt_management_fastapi_expected.py +0 -114
- package/tests/fixtures/fastapi/prompt_management_fastapi_input.py +0 -88
- package/tests/fixtures/haystack/haystack_bot_expected.py +0 -141
- package/tests/fixtures/haystack/haystack_bot_input.py +0 -69
- package/tests/fixtures/langchain/langchain_bot_expected.py +0 -53
- package/tests/fixtures/langchain/langchain_bot_input.py +0 -45
- package/tests/fixtures/langchain/langchain_bot_with_memory_expected.py +0 -69
- package/tests/fixtures/langchain/langchain_bot_with_memory_input.py +0 -61
- package/tests/fixtures/langchain/langchain_rag_bot_expected.py +0 -97
- package/tests/fixtures/langchain/langchain_rag_bot_input.py +0 -77
- package/tests/fixtures/langchain/langchain_rag_bot_vertex_ai_expected.py +0 -116
- package/tests/fixtures/langchain/langchain_rag_bot_vertex_ai_input.py +0 -81
- package/tests/fixtures/langchain/langgraph_rag_bot_with_threads_expected.py +0 -331
- package/tests/fixtures/langchain/langgraph_rag_bot_with_threads_input.py +0 -106
- package/tests/fixtures/litellm/litellm_bot_expected.py +0 -40
- package/tests/fixtures/litellm/litellm_bot_input.py +0 -35
- package/tests/fixtures/openai/openai_bot_expected.py +0 -43
- package/tests/fixtures/openai/openai_bot_function_call_expected.py +0 -91
- package/tests/fixtures/openai/openai_bot_function_call_input.py +0 -82
- package/tests/fixtures/openai/openai_bot_input.py +0 -36
- package/tests/fixtures/openai/openai_bot_rag_expected.py +0 -73
- package/tests/fixtures/openai/openai_bot_rag_input.py +0 -51
- package/tests/fixtures/opentelemetry/openinference_dspy_bot_expected.py +0 -63
- package/tests/fixtures/opentelemetry/openinference_dspy_bot_input.py +0 -58
- package/tests/fixtures/opentelemetry/openinference_langchain_bot_expected.py +0 -53
- package/tests/fixtures/opentelemetry/openinference_langchain_bot_input.py +0 -52
- package/tests/fixtures/opentelemetry/openinference_openai_bot_expected.py +0 -49
- package/tests/fixtures/opentelemetry/openinference_openai_bot_input.py +0 -41
- package/tests/fixtures/opentelemetry/openllmetry_openai_bot_expected.py +0 -44
- package/tests/fixtures/opentelemetry/openllmetry_openai_bot_input.py +0 -40
- package/tests/fixtures/strands/strands_bot_expected.py +0 -84
- package/tests/fixtures/strands/strands_bot_input.py +0 -52
- package/tests/scenario-openai.test.ts +0 -160
- package/tsup.config.ts +0 -14
- package/vitest.config.js +0 -7
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { LangWatchApiError, makeRequest } from "../langwatch-api.js";
|
|
2
|
+
|
|
3
|
+
interface DatasetEntry {
|
|
4
|
+
index: number;
|
|
5
|
+
targetId?: string | null;
|
|
6
|
+
entry: Record<string, unknown>;
|
|
7
|
+
predicted?: Record<string, unknown>;
|
|
8
|
+
cost?: number | null;
|
|
9
|
+
duration?: number | null;
|
|
10
|
+
error?: string | null;
|
|
11
|
+
traceId?: string | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface EvaluationItem {
|
|
15
|
+
evaluator: string;
|
|
16
|
+
name?: string | null;
|
|
17
|
+
index: number;
|
|
18
|
+
targetId?: string | null;
|
|
19
|
+
status: "processed" | "skipped" | "error";
|
|
20
|
+
score?: number | null;
|
|
21
|
+
label?: string | null;
|
|
22
|
+
passed?: boolean | null;
|
|
23
|
+
details?: string | null;
|
|
24
|
+
inputs?: Record<string, unknown> | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface EvaluationRunResults {
|
|
28
|
+
experimentId: string;
|
|
29
|
+
runId: string;
|
|
30
|
+
projectId: string;
|
|
31
|
+
progress?: number | null;
|
|
32
|
+
total?: number | null;
|
|
33
|
+
dataset: DatasetEntry[];
|
|
34
|
+
evaluations: EvaluationItem[];
|
|
35
|
+
timestamps: {
|
|
36
|
+
createdAt: number;
|
|
37
|
+
updatedAt: number;
|
|
38
|
+
finishedAt?: number | null;
|
|
39
|
+
stoppedAt?: number | null;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const DEFAULT_ROW_CAP = 50;
|
|
44
|
+
|
|
45
|
+
const rowKey = (index: number, targetId?: string | null): string =>
|
|
46
|
+
`${index}:${targetId ?? ""}`;
|
|
47
|
+
|
|
48
|
+
const summarizeEntry = (entry: Record<string, unknown>): string => {
|
|
49
|
+
const candidates = ["input", "question", "query", "prompt", "user"];
|
|
50
|
+
for (const key of candidates) {
|
|
51
|
+
const value = entry[key];
|
|
52
|
+
if (typeof value === "string" && value.length > 0) {
|
|
53
|
+
return value.length > 80 ? `${value.slice(0, 77)}...` : value;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return "";
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const isFailedEvaluation = (e: EvaluationItem): boolean =>
|
|
60
|
+
e.status === "error" || e.passed === false;
|
|
61
|
+
|
|
62
|
+
const isFailedRow = ({
|
|
63
|
+
entry,
|
|
64
|
+
evaluations,
|
|
65
|
+
}: {
|
|
66
|
+
entry: DatasetEntry;
|
|
67
|
+
evaluations: EvaluationItem[];
|
|
68
|
+
}): boolean => Boolean(entry.error) || evaluations.some(isFailedEvaluation);
|
|
69
|
+
|
|
70
|
+
export async function handleExperimentResults(params: {
|
|
71
|
+
runId: string;
|
|
72
|
+
experimentSlug?: string;
|
|
73
|
+
filter?: "all" | "failed";
|
|
74
|
+
evaluator?: string;
|
|
75
|
+
limit?: number;
|
|
76
|
+
}): Promise<string> {
|
|
77
|
+
const filter = params.filter ?? "all";
|
|
78
|
+
const evaluatorFilter = params.evaluator?.trim();
|
|
79
|
+
const limit =
|
|
80
|
+
typeof params.limit === "number" && params.limit > 0
|
|
81
|
+
? Math.min(params.limit, DEFAULT_ROW_CAP)
|
|
82
|
+
: DEFAULT_ROW_CAP;
|
|
83
|
+
|
|
84
|
+
const search = new URLSearchParams();
|
|
85
|
+
if (params.experimentSlug) search.set("experimentSlug", params.experimentSlug);
|
|
86
|
+
const qs = search.toString() ? `?${search.toString()}` : "";
|
|
87
|
+
|
|
88
|
+
let results: EvaluationRunResults | null;
|
|
89
|
+
try {
|
|
90
|
+
results = (await makeRequest(
|
|
91
|
+
"GET",
|
|
92
|
+
`/api/experiments/runs/${encodeURIComponent(params.runId)}/results${qs}`,
|
|
93
|
+
)) as EvaluationRunResults;
|
|
94
|
+
} catch (error) {
|
|
95
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
96
|
+
const status =
|
|
97
|
+
error instanceof LangWatchApiError
|
|
98
|
+
? error.status
|
|
99
|
+
: error && typeof error === "object" &&
|
|
100
|
+
"status" in error &&
|
|
101
|
+
typeof (error as { status?: unknown }).status === "number"
|
|
102
|
+
? (error as { status: number }).status
|
|
103
|
+
: undefined;
|
|
104
|
+
if (
|
|
105
|
+
status === 404 ||
|
|
106
|
+
(status === undefined && /404|not found/i.test(message))
|
|
107
|
+
) {
|
|
108
|
+
return [
|
|
109
|
+
`# Evaluation Results: ${params.runId}`,
|
|
110
|
+
"",
|
|
111
|
+
"**Status**: not found",
|
|
112
|
+
"",
|
|
113
|
+
`Could not load results for run \`${params.runId}\`. The run may still be in progress, or the id may be incorrect.`,
|
|
114
|
+
"",
|
|
115
|
+
"> Try \`platform_experiment_status\` first to confirm the run id and that it is completed.",
|
|
116
|
+
].join("\n");
|
|
117
|
+
}
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (!results || (results.timestamps.finishedAt == null && results.timestamps.stoppedAt == null)) {
|
|
122
|
+
return [
|
|
123
|
+
`# Evaluation Results: ${params.runId}`,
|
|
124
|
+
"",
|
|
125
|
+
"**Status**: results are not yet available",
|
|
126
|
+
"",
|
|
127
|
+
`Results for run \`${params.runId}\` are not ready yet.`,
|
|
128
|
+
"",
|
|
129
|
+
"> Try `platform_experiment_status` first to confirm the run id and that it is completed.",
|
|
130
|
+
].join("\n");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Group evaluations by target-scoped row key, applying evaluator filter.
|
|
134
|
+
const evaluationsByRow = new Map<string, EvaluationItem[]>();
|
|
135
|
+
for (const evaluation of results.evaluations) {
|
|
136
|
+
if (evaluatorFilter && evaluation.evaluator !== evaluatorFilter) continue;
|
|
137
|
+
const key = rowKey(evaluation.index, evaluation.targetId);
|
|
138
|
+
const list = evaluationsByRow.get(key) ?? [];
|
|
139
|
+
list.push(evaluation);
|
|
140
|
+
evaluationsByRow.set(key, list);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const evaluatorNames = Array.from(
|
|
144
|
+
new Set(
|
|
145
|
+
(evaluatorFilter
|
|
146
|
+
? results.evaluations.filter((e) => e.evaluator === evaluatorFilter)
|
|
147
|
+
: results.evaluations
|
|
148
|
+
).map((e) => e.evaluator),
|
|
149
|
+
),
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
let rows = results.dataset.map((entry) => ({
|
|
153
|
+
entry,
|
|
154
|
+
evaluations: evaluationsByRow.get(rowKey(entry.index, entry.targetId)) ?? [],
|
|
155
|
+
}));
|
|
156
|
+
|
|
157
|
+
if (filter === "failed") {
|
|
158
|
+
rows = rows.filter((r) =>
|
|
159
|
+
isFailedRow({ entry: r.entry, evaluations: r.evaluations }),
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const totalMatching = rows.length;
|
|
164
|
+
const rowsForSummary = rows;
|
|
165
|
+
const truncated = rows.length > limit;
|
|
166
|
+
rows = rows.slice(0, limit);
|
|
167
|
+
|
|
168
|
+
// Per-evaluator stats across the filtered rows (before truncation),
|
|
169
|
+
// so the summary matches the displayed subset when filter="failed".
|
|
170
|
+
const evaluatorAverages = new Map<
|
|
171
|
+
string,
|
|
172
|
+
{ sum: number; count: number; passed: number; failed: number; errored: number }
|
|
173
|
+
>();
|
|
174
|
+
for (const r of rowsForSummary) {
|
|
175
|
+
for (const e of r.evaluations) {
|
|
176
|
+
const stats =
|
|
177
|
+
evaluatorAverages.get(e.evaluator) ?? {
|
|
178
|
+
sum: 0,
|
|
179
|
+
count: 0,
|
|
180
|
+
passed: 0,
|
|
181
|
+
failed: 0,
|
|
182
|
+
errored: 0,
|
|
183
|
+
};
|
|
184
|
+
if (typeof e.score === "number") {
|
|
185
|
+
stats.sum += e.score;
|
|
186
|
+
stats.count += 1;
|
|
187
|
+
}
|
|
188
|
+
if (e.status === "error") stats.errored += 1;
|
|
189
|
+
else if (e.passed === true) stats.passed += 1;
|
|
190
|
+
else if (e.passed === false) stats.failed += 1;
|
|
191
|
+
evaluatorAverages.set(e.evaluator, stats);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const lines: string[] = [];
|
|
196
|
+
lines.push(`# Evaluation Results: ${results.runId}`);
|
|
197
|
+
lines.push("");
|
|
198
|
+
lines.push(`**Experiment**: ${results.experimentId}`);
|
|
199
|
+
lines.push(`**Total rows**: ${results.dataset.length}`);
|
|
200
|
+
lines.push(`**Total evaluations**: ${results.evaluations.length}`);
|
|
201
|
+
if (filter === "failed") {
|
|
202
|
+
lines.push(`**Filter**: failed only`);
|
|
203
|
+
}
|
|
204
|
+
if (evaluatorFilter) {
|
|
205
|
+
lines.push(`**Evaluator filter**: ${evaluatorFilter}`);
|
|
206
|
+
}
|
|
207
|
+
lines.push("");
|
|
208
|
+
|
|
209
|
+
if (evaluatorAverages.size > 0) {
|
|
210
|
+
lines.push("## Evaluator Summary");
|
|
211
|
+
lines.push("");
|
|
212
|
+
lines.push("| Evaluator | Avg Score | Passed | Failed | Errored |");
|
|
213
|
+
lines.push("| --- | --- | --- | --- | --- |");
|
|
214
|
+
for (const [name, stats] of evaluatorAverages) {
|
|
215
|
+
const avg =
|
|
216
|
+
stats.count > 0 ? (stats.sum / stats.count).toFixed(3) : "—";
|
|
217
|
+
lines.push(
|
|
218
|
+
`| ${name} | ${avg} | ${stats.passed} | ${stats.failed} | ${stats.errored} |`,
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
lines.push("");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (rows.length === 0) {
|
|
225
|
+
lines.push("_No rows matched the filter._");
|
|
226
|
+
return lines.join("\n");
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
lines.push(
|
|
230
|
+
`## Rows (${rows.length}${truncated ? ` of ${totalMatching}` : ""})`,
|
|
231
|
+
);
|
|
232
|
+
lines.push("");
|
|
233
|
+
|
|
234
|
+
for (const { entry, evaluations } of rows) {
|
|
235
|
+
const summary = summarizeEntry(entry.entry);
|
|
236
|
+
lines.push(`### Row #${entry.index}${summary ? ` — ${summary}` : ""}`);
|
|
237
|
+
if (entry.error) {
|
|
238
|
+
lines.push(`- **Error**: ${entry.error}`);
|
|
239
|
+
}
|
|
240
|
+
if (entry.traceId) {
|
|
241
|
+
lines.push(`- **Trace ID**: \`${entry.traceId}\``);
|
|
242
|
+
}
|
|
243
|
+
if (evaluations.length === 0) {
|
|
244
|
+
lines.push(
|
|
245
|
+
evaluatorNames.length === 0
|
|
246
|
+
? "- _No evaluations recorded_"
|
|
247
|
+
: "- _No evaluations recorded for this row_",
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
for (const e of evaluations) {
|
|
251
|
+
const parts: string[] = [`**${e.evaluator}**`];
|
|
252
|
+
if (e.status === "error") parts.push("status=error");
|
|
253
|
+
else if (typeof e.score === "number") parts.push(`score=${e.score.toFixed(3)}`);
|
|
254
|
+
if (typeof e.passed === "boolean") {
|
|
255
|
+
parts.push(`passed=${e.passed ? "yes" : "no"}`);
|
|
256
|
+
}
|
|
257
|
+
if (e.label) parts.push(`label=${e.label}`);
|
|
258
|
+
lines.push(`- ${parts.join(" · ")}`);
|
|
259
|
+
if (e.details) {
|
|
260
|
+
lines.push(` - details: ${e.details}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
lines.push("");
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (truncated) {
|
|
267
|
+
lines.push(
|
|
268
|
+
`> Output truncated to ${limit} rows of ${totalMatching} matching to protect the agent's context window. Pass \`limit\` to expand or filter further.`,
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return lines.join("\n");
|
|
273
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { getProject as apiGetProject } from "../langwatch-api-projects.js";
|
|
2
|
+
|
|
3
|
+
export async function handleGetProject(params: {
|
|
4
|
+
id: string;
|
|
5
|
+
}): Promise<string> {
|
|
6
|
+
const project = await apiGetProject(params.id);
|
|
7
|
+
|
|
8
|
+
const lines: string[] = [];
|
|
9
|
+
lines.push(`# ${project.name}\n`);
|
|
10
|
+
lines.push(`**ID**: ${project.id}`);
|
|
11
|
+
lines.push(`**Slug**: ${project.slug}`);
|
|
12
|
+
lines.push(`**Language**: ${project.language}`);
|
|
13
|
+
lines.push(`**Framework**: ${project.framework}`);
|
|
14
|
+
lines.push(`**Team ID**: ${project.teamId}`);
|
|
15
|
+
lines.push(`**PII Redaction**: ${project.piiRedactionLevel}`);
|
|
16
|
+
lines.push(`**Created**: ${project.createdAt}`);
|
|
17
|
+
lines.push(`**Updated**: ${project.updatedAt}`);
|
|
18
|
+
|
|
19
|
+
return lines.join("\n");
|
|
20
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { listApiKeys as apiListApiKeys } from "../langwatch-api-api-keys.js";
|
|
2
|
+
|
|
3
|
+
export async function handleListApiKeys(): Promise<string> {
|
|
4
|
+
const result = await apiListApiKeys();
|
|
5
|
+
const keys = result.data;
|
|
6
|
+
|
|
7
|
+
if (!Array.isArray(keys) || keys.length === 0) {
|
|
8
|
+
return "No API keys found.\n\n> Tip: Use `platform_create_api_key` to create a new key.";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const lines: string[] = [];
|
|
12
|
+
lines.push(`# API Keys (${keys.length} total)\n`);
|
|
13
|
+
|
|
14
|
+
for (const k of keys) {
|
|
15
|
+
const status = k.revokedAt ? "REVOKED" : k.expiresAt && new Date(k.expiresAt) < new Date() ? "EXPIRED" : "ACTIVE";
|
|
16
|
+
lines.push(`## ${k.name}`);
|
|
17
|
+
lines.push(`**ID**: ${k.id}`);
|
|
18
|
+
lines.push(`**Status**: ${status}`);
|
|
19
|
+
if (k.description) lines.push(`**Description**: ${k.description}`);
|
|
20
|
+
if (k.expiresAt) lines.push(`**Expires**: ${k.expiresAt}`);
|
|
21
|
+
if (k.lastUsedAt) lines.push(`**Last Used**: ${k.lastUsedAt}`);
|
|
22
|
+
lines.push(`**Created**: ${k.createdAt}`);
|
|
23
|
+
if (k.roleBindings.length > 0) {
|
|
24
|
+
const bindings = k.roleBindings
|
|
25
|
+
.map((rb) => `${rb.role} on ${rb.scopeType}:${rb.scopeId}`)
|
|
26
|
+
.join(", ");
|
|
27
|
+
lines.push(`**Bindings**: ${bindings}`);
|
|
28
|
+
}
|
|
29
|
+
lines.push("");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return lines.join("\n");
|
|
33
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { makeRequest } from "../langwatch-api.js";
|
|
2
|
+
|
|
3
|
+
interface RunSummaryEvaluator {
|
|
4
|
+
name: string;
|
|
5
|
+
averageScore: number | null;
|
|
6
|
+
averagePassed?: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface RunSummary {
|
|
10
|
+
evaluations: Record<string, RunSummaryEvaluator>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface ExperimentRunSummaryEntry {
|
|
14
|
+
experimentId: string;
|
|
15
|
+
runId: string;
|
|
16
|
+
workflowVersion: { id: string; version: string } | null;
|
|
17
|
+
timestamps: {
|
|
18
|
+
createdAt: number;
|
|
19
|
+
updatedAt: number;
|
|
20
|
+
finishedAt?: number | null;
|
|
21
|
+
stoppedAt?: number | null;
|
|
22
|
+
};
|
|
23
|
+
progress?: number | null;
|
|
24
|
+
total?: number | null;
|
|
25
|
+
summary: RunSummary;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface ExperimentRunsListResponse {
|
|
29
|
+
experimentId: string;
|
|
30
|
+
experimentSlug: string;
|
|
31
|
+
runs: ExperimentRunSummaryEntry[];
|
|
32
|
+
pagination: {
|
|
33
|
+
page: number;
|
|
34
|
+
pageSize: number;
|
|
35
|
+
totalHits: number;
|
|
36
|
+
hasMore: boolean;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const DEFAULT_LIMIT = 25;
|
|
41
|
+
const MAX_LIMIT = 100;
|
|
42
|
+
|
|
43
|
+
const formatEpoch = (epoch: number | null | undefined): string => {
|
|
44
|
+
if (!epoch || !Number.isFinite(epoch)) return "—";
|
|
45
|
+
return new Date(epoch).toISOString().replace("T", " ").slice(0, 19) + " UTC";
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const passRate = (evaluations: RunSummary["evaluations"]): string => {
|
|
49
|
+
const entries = Object.values(evaluations ?? {});
|
|
50
|
+
if (entries.length === 0) return "—";
|
|
51
|
+
const passed = entries.filter((e) => typeof e.averagePassed === "number");
|
|
52
|
+
if (passed.length === 0) {
|
|
53
|
+
const scored = entries.filter((e) => typeof e.averageScore === "number");
|
|
54
|
+
if (scored.length === 0) return "—";
|
|
55
|
+
const avg =
|
|
56
|
+
scored.reduce((sum, e) => sum + (e.averageScore ?? 0), 0) / scored.length;
|
|
57
|
+
return `${avg.toFixed(2)} avg`;
|
|
58
|
+
}
|
|
59
|
+
const avg =
|
|
60
|
+
passed.reduce((sum, e) => sum + (e.averagePassed ?? 0), 0) / passed.length;
|
|
61
|
+
return `${(avg * 100).toFixed(0)}% pass`;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const runStatus = (run: ExperimentRunSummaryEntry): string => {
|
|
65
|
+
if (run.timestamps.stoppedAt) return "stopped";
|
|
66
|
+
if (run.timestamps.finishedAt) return "completed";
|
|
67
|
+
return "running";
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export async function handleExperimentListRuns(params: {
|
|
71
|
+
experimentSlug: string;
|
|
72
|
+
limit?: number;
|
|
73
|
+
}): Promise<string> {
|
|
74
|
+
const requested =
|
|
75
|
+
typeof params.limit === "number" && params.limit > 0
|
|
76
|
+
? params.limit
|
|
77
|
+
: DEFAULT_LIMIT;
|
|
78
|
+
const effectiveLimit = Math.min(requested, MAX_LIMIT);
|
|
79
|
+
|
|
80
|
+
const search = new URLSearchParams();
|
|
81
|
+
search.set("experimentSlug", params.experimentSlug);
|
|
82
|
+
search.set("pageSize", String(effectiveLimit));
|
|
83
|
+
|
|
84
|
+
let result: ExperimentRunsListResponse;
|
|
85
|
+
try {
|
|
86
|
+
result = (await makeRequest(
|
|
87
|
+
"GET",
|
|
88
|
+
`/api/experiments/runs?${search.toString()}`,
|
|
89
|
+
)) as ExperimentRunsListResponse;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
92
|
+
if (/404|not found/i.test(message)) {
|
|
93
|
+
return [
|
|
94
|
+
`# Evaluation Runs: ${params.experimentSlug}`,
|
|
95
|
+
"",
|
|
96
|
+
"**Status**: experiment not found",
|
|
97
|
+
"",
|
|
98
|
+
`No experiment with slug \`${params.experimentSlug}\` exists in this project.`,
|
|
99
|
+
"",
|
|
100
|
+
"> Run `platform_experiment_list` to discover available experiment slugs.",
|
|
101
|
+
].join("\n");
|
|
102
|
+
}
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (result.runs.length === 0) {
|
|
107
|
+
return [
|
|
108
|
+
`# Evaluation Runs: ${result.experimentSlug}`,
|
|
109
|
+
"",
|
|
110
|
+
"_This experiment has no runs yet._",
|
|
111
|
+
"",
|
|
112
|
+
`> Trigger a run with \`platform_run_experiment\` (slug: \`${result.experimentSlug}\`) to populate this list.`,
|
|
113
|
+
].join("\n");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const lines: string[] = [];
|
|
117
|
+
lines.push(`# Evaluation Runs: ${result.experimentSlug}`);
|
|
118
|
+
lines.push("");
|
|
119
|
+
lines.push(
|
|
120
|
+
`Showing ${result.runs.length} of ${result.pagination.totalHits} run${result.pagination.totalHits === 1 ? "" : "s"}.`,
|
|
121
|
+
);
|
|
122
|
+
if (result.pagination.hasMore) {
|
|
123
|
+
lines.push(
|
|
124
|
+
`> More runs exist beyond this limit. Re-run with a higher \`limit\` (max ${MAX_LIMIT}) if needed.`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
lines.push("");
|
|
128
|
+
lines.push("| Run ID | Status | Started | Finished | Result |");
|
|
129
|
+
lines.push("|--------|--------|---------|----------|--------|");
|
|
130
|
+
|
|
131
|
+
for (const run of result.runs) {
|
|
132
|
+
lines.push(
|
|
133
|
+
`| \`${run.runId}\` | ${runStatus(run)} | ${formatEpoch(run.timestamps.createdAt)} | ${formatEpoch(run.timestamps.finishedAt ?? null)} | ${passRate(run.summary?.evaluations ?? {})} |`,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
lines.push("");
|
|
138
|
+
lines.push(
|
|
139
|
+
"> Use `platform_experiment_status <runId>` for live progress, or `platform_experiment_results <runId>` for per-row results.",
|
|
140
|
+
);
|
|
141
|
+
return lines.join("\n");
|
|
142
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { makeRequest } from "../langwatch-api.js";
|
|
2
|
+
|
|
3
|
+
interface ExperimentSummary {
|
|
4
|
+
id: string;
|
|
5
|
+
slug: string;
|
|
6
|
+
name: string | null;
|
|
7
|
+
type: string;
|
|
8
|
+
workflowId: string | null;
|
|
9
|
+
createdAt: string;
|
|
10
|
+
updatedAt: string;
|
|
11
|
+
runsCount: number;
|
|
12
|
+
lastRunAt: string | null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface ExperimentListResponse {
|
|
16
|
+
experiments: ExperimentSummary[];
|
|
17
|
+
pagination: {
|
|
18
|
+
page: number;
|
|
19
|
+
pageSize: number;
|
|
20
|
+
totalHits: number;
|
|
21
|
+
hasMore: boolean;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const DEFAULT_LIMIT = 25;
|
|
26
|
+
const MAX_LIMIT = 100;
|
|
27
|
+
|
|
28
|
+
const formatTimestamp = (iso: string | null): string => {
|
|
29
|
+
if (!iso) return "—";
|
|
30
|
+
const d = new Date(iso);
|
|
31
|
+
if (Number.isNaN(d.getTime())) return iso;
|
|
32
|
+
return d.toISOString().replace("T", " ").slice(0, 19) + " UTC";
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const escapeMarkdownTableCell = (value: string): string =>
|
|
36
|
+
value.replace(/[\\|]/g, (c) => `\\${c}`).replace(/\n/g, " ");
|
|
37
|
+
|
|
38
|
+
const formatInlineCode = (value: string): string =>
|
|
39
|
+
`\`${value.replace(/[\\`|]/g, (c) => `\\${c}`).replace(/\n/g, " ")}\``;
|
|
40
|
+
|
|
41
|
+
export async function handleExperimentList(params: {
|
|
42
|
+
limit?: number;
|
|
43
|
+
}): Promise<string> {
|
|
44
|
+
const requested =
|
|
45
|
+
typeof params.limit === "number" && params.limit > 0
|
|
46
|
+
? params.limit
|
|
47
|
+
: DEFAULT_LIMIT;
|
|
48
|
+
const effectiveLimit = Math.min(requested, MAX_LIMIT);
|
|
49
|
+
|
|
50
|
+
const search = new URLSearchParams();
|
|
51
|
+
search.set("pageSize", String(effectiveLimit));
|
|
52
|
+
|
|
53
|
+
const result = (await makeRequest(
|
|
54
|
+
"GET",
|
|
55
|
+
`/api/experiments?${search.toString()}`,
|
|
56
|
+
)) as ExperimentListResponse;
|
|
57
|
+
|
|
58
|
+
if (result.experiments.length === 0) {
|
|
59
|
+
return [
|
|
60
|
+
"# Experiments",
|
|
61
|
+
"",
|
|
62
|
+
"_No experiments found in this project._",
|
|
63
|
+
"",
|
|
64
|
+
"> Create an experiment in the LangWatch dashboard, then re-run this tool to discover its slug.",
|
|
65
|
+
].join("\n");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const lines: string[] = [];
|
|
69
|
+
lines.push("# Experiments");
|
|
70
|
+
lines.push("");
|
|
71
|
+
lines.push(
|
|
72
|
+
`Showing ${result.experiments.length} of ${result.pagination.totalHits} experiment${result.pagination.totalHits === 1 ? "" : "s"}.`,
|
|
73
|
+
);
|
|
74
|
+
if (result.pagination.hasMore) {
|
|
75
|
+
lines.push(
|
|
76
|
+
`> More experiments exist beyond this limit. Re-run with a higher \`limit\` (max ${MAX_LIMIT}) if needed.`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
lines.push("");
|
|
80
|
+
lines.push("| Slug | Name | Runs | Last Run |");
|
|
81
|
+
lines.push("|------|------|------|----------|");
|
|
82
|
+
|
|
83
|
+
for (const exp of result.experiments) {
|
|
84
|
+
const name = escapeMarkdownTableCell(exp.name ?? exp.slug);
|
|
85
|
+
lines.push(
|
|
86
|
+
`| ${formatInlineCode(exp.slug)} | ${name} | ${exp.runsCount} | ${formatTimestamp(exp.lastRunAt)} |`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
lines.push("");
|
|
91
|
+
lines.push(
|
|
92
|
+
"> Use `platform_experiment_list_runs` with one of these slugs to see its runs.",
|
|
93
|
+
);
|
|
94
|
+
return lines.join("\n");
|
|
95
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { listProjects as apiListProjects } from "../langwatch-api-projects.js";
|
|
2
|
+
|
|
3
|
+
export async function handleListProjects(params?: {
|
|
4
|
+
page?: number;
|
|
5
|
+
limit?: number;
|
|
6
|
+
}): Promise<string> {
|
|
7
|
+
const result = await apiListProjects({
|
|
8
|
+
page: params?.page,
|
|
9
|
+
limit: params?.limit ?? 100,
|
|
10
|
+
});
|
|
11
|
+
const projects = result.data;
|
|
12
|
+
|
|
13
|
+
if (!Array.isArray(projects) || projects.length === 0) {
|
|
14
|
+
return "No projects found in this organization.\n\n> Tip: Use `platform_create_project` to create your first project.";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const lines: string[] = [];
|
|
18
|
+
lines.push(`# Projects (${result.pagination.total} total)\n`);
|
|
19
|
+
|
|
20
|
+
for (const p of projects) {
|
|
21
|
+
lines.push(`## ${p.name}`);
|
|
22
|
+
lines.push(`**ID**: ${p.id}`);
|
|
23
|
+
lines.push(`**Slug**: ${p.slug}`);
|
|
24
|
+
lines.push(`**Language**: ${p.language}`);
|
|
25
|
+
lines.push(`**Framework**: ${p.framework}`);
|
|
26
|
+
lines.push(`**Updated**: ${p.updatedAt}`);
|
|
27
|
+
lines.push("");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (result.pagination.totalPages > 1) {
|
|
31
|
+
lines.push(
|
|
32
|
+
`> Showing page ${result.pagination.page} of ${result.pagination.totalPages}. Use \`page\` parameter to navigate.`,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
lines.push(
|
|
37
|
+
"> Use `platform_get_project` with the ID to see full project details.",
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
return lines.join("\n");
|
|
41
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { revokeApiKey as apiRevokeApiKey } from "../langwatch-api-api-keys.js";
|
|
2
|
+
|
|
3
|
+
export async function handleRevokeApiKey(params: {
|
|
4
|
+
id: string;
|
|
5
|
+
}): Promise<string> {
|
|
6
|
+
await apiRevokeApiKey(params.id);
|
|
7
|
+
|
|
8
|
+
return `API key ${params.id} revoked successfully.`;
|
|
9
|
+
}
|
|
@@ -23,12 +23,12 @@ interface EvaluationStatusResponse {
|
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
export async function
|
|
26
|
+
export async function handleRunExperiment(params: {
|
|
27
27
|
slug: string;
|
|
28
28
|
}): Promise<string> {
|
|
29
29
|
const result = (await makeRequest(
|
|
30
30
|
"POST",
|
|
31
|
-
`/api/
|
|
31
|
+
`/api/experiments/${encodeURIComponent(params.slug)}/run`,
|
|
32
32
|
)) as EvaluationRunResponse;
|
|
33
33
|
|
|
34
34
|
const lines: string[] = [];
|
|
@@ -39,18 +39,18 @@ export async function handleRunEvaluation(params: {
|
|
|
39
39
|
if (result.runUrl) lines.push(`**View at**: ${result.runUrl}`);
|
|
40
40
|
lines.push("");
|
|
41
41
|
lines.push(
|
|
42
|
-
`> Use \`
|
|
42
|
+
`> Use \`platform_experiment_status\` with run ID "${result.runId}" to check progress.`,
|
|
43
43
|
);
|
|
44
44
|
|
|
45
45
|
return lines.join("\n");
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
export async function
|
|
48
|
+
export async function handleExperimentStatus(params: {
|
|
49
49
|
runId: string;
|
|
50
50
|
}): Promise<string> {
|
|
51
51
|
const status = (await makeRequest(
|
|
52
52
|
"GET",
|
|
53
|
-
`/api/
|
|
53
|
+
`/api/experiments/runs/${encodeURIComponent(params.runId)}`,
|
|
54
54
|
)) as EvaluationStatusResponse;
|
|
55
55
|
|
|
56
56
|
const lines: string[] = [];
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { updateProject as apiUpdateProject } from "../langwatch-api-projects.js";
|
|
2
|
+
|
|
3
|
+
export async function handleUpdateProject(params: {
|
|
4
|
+
id: string;
|
|
5
|
+
name?: string;
|
|
6
|
+
language?: string;
|
|
7
|
+
framework?: string;
|
|
8
|
+
piiRedactionLevel?: "STRICT" | "ESSENTIAL" | "DISABLED";
|
|
9
|
+
}): Promise<string> {
|
|
10
|
+
const project = await apiUpdateProject(params);
|
|
11
|
+
|
|
12
|
+
const lines: string[] = [];
|
|
13
|
+
lines.push(`Project updated successfully!\n`);
|
|
14
|
+
lines.push(`**Name**: ${project.name}`);
|
|
15
|
+
lines.push(`**ID**: ${project.id}`);
|
|
16
|
+
lines.push(`**Slug**: ${project.slug}`);
|
|
17
|
+
lines.push(`**Language**: ${project.language}`);
|
|
18
|
+
lines.push(`**Framework**: ${project.framework}`);
|
|
19
|
+
lines.push(`**PII Redaction**: ${project.piiRedactionLevel}`);
|
|
20
|
+
|
|
21
|
+
return lines.join("\n");
|
|
22
|
+
}
|