mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-08-29 04:26:38 +00:00
refactor(python): launch the minimal example through sdk-minimal
Make minimal.py select the shipped sdk-minimal profile directly and pass its selected model into the profile-owned adapter catalog. The Python SDK still starts only the bundled dsh CLI with an explicit Harness home; it no longer supplies an invocation overlay for this mode. Drive both the source keyless process test and installed-wheel smoke through the same named profile. The keyless test pins the generated profile manifest and exact two-tool model request, while the packaged smoke keeps the persistent-shell, editor, session-log, and model-visible snapshot evidence. Delete minimal.patch.yml and the unused complete-config/replay fixtures. Their composition now has one owner in @deepseek-ai/dsh-sdk-minimal, so the example and tests cannot drift into separate launch trees.
This commit is contained in:
@@ -1,90 +0,0 @@
|
||||
# Complete minimal-agent composition fixture for lower-level snapshot tests.
|
||||
# Python users apply `minimal.patch.yml` to `dsh --profile sdk`. This fixture
|
||||
# retains the same two-tool behavior without defining a supported launch path.
|
||||
|
||||
- id: sdk-jsonrpc-server
|
||||
name: '@deepseek-ai/dsh-sdk-jsonrpc-server'
|
||||
config:
|
||||
maxTokensAsSuccess: false
|
||||
|
||||
- id: deepseek-llm-api-extensions
|
||||
name: '@deepseek-ai/dsh-deepseek-llm-api-extensions'
|
||||
|
||||
- id: session-log-deepseek
|
||||
name: '@deepseek-ai/dsh-session-log-deepseek'
|
||||
|
||||
- id: plugin-package-inventory-deepseek
|
||||
name: '@deepseek-ai/dsh-plugin-package-inventory-deepseek'
|
||||
|
||||
- id: llm-deepseek
|
||||
name: '@deepseek-ai/dsh-llm-deepseek'
|
||||
config:
|
||||
apiKeyEnv: DEEPSEEK_API_KEY
|
||||
streamIdleTimeoutMs: 172800000
|
||||
models:
|
||||
- id: !!js process.env.DSH_MODEL ?? 'deepseek-v4-flash'
|
||||
contextWindow: !!js Number(process.env.DSH_CONTEXT_WINDOW ?? 1000000)
|
||||
|
||||
- id: sandbox
|
||||
name: '@deepseek-ai/dsh-sandbox-local'
|
||||
|
||||
- id: sandbox-policy
|
||||
name: '@deepseek-ai/dsh-sandbox-policy'
|
||||
config:
|
||||
mode: danger-full-access
|
||||
workspaceRoot: !!js process.env.DSH_CWD ?? process.cwd()
|
||||
|
||||
- id: subprocess
|
||||
name: '@deepseek-ai/dsh-subprocess-local'
|
||||
|
||||
- id: pty
|
||||
name: '@deepseek-ai/dsh-terminal'
|
||||
|
||||
- id: terminal-bash
|
||||
name: '@deepseek-ai/dsh-terminal-bash'
|
||||
config:
|
||||
timeoutMs: 300000
|
||||
|
||||
# The editor uses the bare local filesystem; persistent Bash still consumes the
|
||||
# shared danger-full-access sandbox policy above.
|
||||
- id: fs-local
|
||||
name: '@deepseek-ai/dsh-fs-local'
|
||||
config:
|
||||
cwd: !!js process.env.DSH_CWD ?? process.cwd()
|
||||
|
||||
- id: agent-spine
|
||||
name: '@deepseek-ai/dsh-agent-spine-demo'
|
||||
config:
|
||||
includeHarnessIdentity: false
|
||||
includeRuntimeContext: false
|
||||
persona: !!js process.env.DSH_SYSTEM_PROMPT ?? 'You are a helpful software engineer assistant.'
|
||||
workspaceContext: false
|
||||
skills:
|
||||
enabled: false
|
||||
toolBash: false
|
||||
toolJobs: false
|
||||
|
||||
- id: persistent-bash
|
||||
name: '@deepseek-ai/dsh-tool-bash-persistent'
|
||||
config:
|
||||
timeoutMs: 300000
|
||||
description: |-
|
||||
Run commands in a bash shell
|
||||
* When invoking this tool, the contents of the "command" parameter does NOT need to be XML-escaped.
|
||||
* You don't have access to the internet via this tool.
|
||||
* You do have access to a mirror of common linux and python packages via apt and pip.
|
||||
* State is persistent across command calls and discussions with the user.
|
||||
* To inspect a particular line range of a file, e.g. lines 10-25, try 'sed -n 10,25p /path/to/the/file'.
|
||||
* Please avoid commands that may produce a very large amount of output.
|
||||
* Please run long lived commands in the background, e.g. 'sleep 10 &' or start a server in the background.
|
||||
|
||||
- id: str-replace-editor
|
||||
name: '@deepseek-ai/dsh-tool-str-replace-editor'
|
||||
config:
|
||||
maxOutputChars: 16000
|
||||
|
||||
- id: sessions
|
||||
name: '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
config:
|
||||
root: !!js process.env.DSH_SESSION_ROOT ?? './.sessions'
|
||||
compression: none
|
||||
@@ -1,47 +0,0 @@
|
||||
# Minimal Python SDK overlay for `dsh --profile sdk`. The SDK server applies
|
||||
# the model-facing allowlist to every root session, so later base-bundle tools
|
||||
# cannot appear implicitly. The profile still owns JSON-RPC and persistence.
|
||||
|
||||
- id: system-prompt
|
||||
config:
|
||||
includeHarnessIdentity: false
|
||||
includeRuntimeContext: false
|
||||
persona: !!js process.env.DSH_SYSTEM_PROMPT ?? 'You are a helpful software engineer assistant.'
|
||||
personaComplete: true
|
||||
|
||||
- id: sdk-jsonrpc-server
|
||||
config:
|
||||
maxTokensAsSuccess: !!js "process.env.DSH_MAX_TOKENS_AS_SUCCESS === undefined ? true : JSON.parse(process.env.DSH_MAX_TOKENS_AS_SUCCESS)"
|
||||
toolFilter:
|
||||
allow:
|
||||
- bash
|
||||
- str_replace_editor
|
||||
|
||||
# Remove non-tool model inputs and turn rewriting, plus the one-shot Bash row
|
||||
# that would conflict with the persistent Bash tool inserted below.
|
||||
- id: agent-instructions
|
||||
disabled: true
|
||||
- id: tool-bash
|
||||
disabled: true
|
||||
- id: compaction-basic
|
||||
disabled: true
|
||||
- id: command-compact
|
||||
disabled: true
|
||||
- id: tool-result-pruner
|
||||
disabled: true
|
||||
|
||||
- id: tool-str-replace-editor
|
||||
config:
|
||||
maxOutputChars: 16000
|
||||
|
||||
- insert:
|
||||
- id: pty
|
||||
name: '@deepseek-ai/dsh-terminal'
|
||||
- id: terminal-bash
|
||||
name: '@deepseek-ai/dsh-terminal-bash'
|
||||
config:
|
||||
timeoutMs: 300000
|
||||
- id: persistent-bash
|
||||
name: '@deepseek-ai/dsh-tool-bash-persistent'
|
||||
config:
|
||||
timeoutMs: 300000
|
||||
@@ -10,9 +10,6 @@ from pathlib import Path
|
||||
from deepseek_harness import DeepSeekHarness
|
||||
|
||||
|
||||
PATCH = Path(__file__).with_name("minimal.patch.yml")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Parse one task and print the agent's final response."""
|
||||
parser = argparse.ArgumentParser()
|
||||
@@ -24,7 +21,7 @@ def main() -> None:
|
||||
type=Path,
|
||||
default=Path(configured_home) if configured_home.strip() else None,
|
||||
)
|
||||
parser.add_argument("--profile", default="sdk")
|
||||
parser.add_argument("--profile", default="sdk-minimal")
|
||||
parser.add_argument("--session-id")
|
||||
parser.add_argument("--provider", default="deepseek-official")
|
||||
parser.add_argument("--model", default=os.environ.get("DSH_MODEL", "deepseek-v4-flash"))
|
||||
@@ -42,7 +39,7 @@ def main() -> None:
|
||||
cwd=str(workspace),
|
||||
dsh_home=str(dsh_home),
|
||||
profile=args.profile,
|
||||
patches=(str(PATCH.resolve()),),
|
||||
env={"DSH_MODEL": args.model},
|
||||
) as harness:
|
||||
result = harness.run(args.prompt, session_id=args.session_id)
|
||||
print(result.final_response)
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
# Keyless replay keeps the complete minimal composition intact and replaces
|
||||
# only its live DeepSeek adapter with the fixture-backed provider. The replay
|
||||
# catalog claims the same route initialized by the SDK.
|
||||
- id: base
|
||||
name: '@deepseek-ai/cordis-plugin-include'
|
||||
config:
|
||||
path: ./minimal.cordis.yml
|
||||
patches:
|
||||
- id: llm-deepseek
|
||||
name: '@deepseek-ai/dsh-llm-deepseek'
|
||||
disabled: true
|
||||
- insert:
|
||||
- id: llm-replay
|
||||
name: '@deepseek-ai/dsh-llm-replay'
|
||||
config:
|
||||
providers:
|
||||
- id: deepseek-official
|
||||
name: DeepSeek
|
||||
models:
|
||||
- id: deepseek-v4-flash
|
||||
@@ -10,7 +10,6 @@ import { describe, expect, it } from 'vitest'
|
||||
|
||||
const binScript = fileURLToPath(new URL('../../../apps/cli/src/bin.ts', import.meta.url))
|
||||
const patchPath = fileURLToPath(new URL('./keyless.patch.yml', import.meta.url))
|
||||
const minimalPatchPath = fileURLToPath(new URL('../minimal.patch.yml', import.meta.url))
|
||||
const repoRoot = fileURLToPath(new URL('../../..', import.meta.url))
|
||||
const decompress = promisify(zstdDecompress)
|
||||
|
||||
@@ -181,7 +180,7 @@ describe('Python SDK dsh profile keyless smoke', () => {
|
||||
}
|
||||
}, 40_000)
|
||||
|
||||
it('keeps the minimal overlay on an explicit model-facing allowlist', async () => {
|
||||
it('boots the standalone minimal profile with its exact model-facing roster', async () => {
|
||||
const root = await mkdtemp(join(tmpdir(), 'dsh-python-sdk-minimal-'))
|
||||
const modelRequests: Record<string, unknown>[] = []
|
||||
const modelServer = createServer((request, response) => {
|
||||
@@ -205,16 +204,13 @@ describe('Python SDK dsh profile keyless smoke', () => {
|
||||
'tsx/esm',
|
||||
binScript,
|
||||
'--profile',
|
||||
'sdk',
|
||||
'--patch',
|
||||
minimalPatchPath,
|
||||
'sdk-minimal',
|
||||
], {
|
||||
cwd: repoRoot,
|
||||
env: {
|
||||
DSH_HOME: join(root, '.dsh'),
|
||||
DSH_PERMISSION_MODE: 'danger-full-access',
|
||||
DSH_MODEL: 'deepseek-v4-pro',
|
||||
DSH_SYSTEM_PROMPT: 'Minimal allowlist prompt.',
|
||||
DSH_TELEMETRY_DISABLED: '1',
|
||||
DEEPSEEK_API_KEY: 'keyless-smoke-no-call',
|
||||
DEEPSEEK_BASE_URL: `http://127.0.0.1:${address.port}`,
|
||||
},
|
||||
@@ -259,6 +255,13 @@ describe('Python SDK dsh profile keyless smoke', () => {
|
||||
}
|
||||
expect(request.messages?.[0]).toMatchObject({ role: 'system', content: 'Minimal allowlist prompt.' })
|
||||
expect(request.tools?.map(tool => tool.function?.name).sort()).toEqual(['bash', 'str_replace_editor'])
|
||||
const profile = JSON.parse(
|
||||
await readFile(join(root, '.dsh', 'profiles', 'sdk-minimal', 'package.json'), 'utf8'),
|
||||
) as { dsh?: { profile?: { bundles?: string[]; patchReload?: string } } }
|
||||
expect(profile.dsh?.profile).toEqual({
|
||||
bundles: ['@deepseek-ai/dsh-sdk-minimal'],
|
||||
patchReload: 'startup',
|
||||
})
|
||||
|
||||
child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id: 3, method: 'shutdown' })}\n`)
|
||||
await waitForLine(lines, value => value.id === 3, () => stderr)
|
||||
|
||||
@@ -46,14 +46,6 @@ MINIMAL_BASH_COMMAND = (
|
||||
"printf 'COUNT=%s CWD=%s\\n' \"$counter\" \"$PWD\"; "
|
||||
"if [ \"$counter\" -eq 1 ]; then cd /tmp; fi"
|
||||
)
|
||||
MINIMAL_BASH_DESCRIPTION = """Run commands in a bash shell
|
||||
* When invoking this tool, the contents of the "command" parameter does NOT need to be XML-escaped.
|
||||
* You don't have access to the internet via this tool.
|
||||
* You do have access to a mirror of common linux and python packages via apt and pip.
|
||||
* State is persistent across command calls and discussions with the user.
|
||||
* To inspect a particular line range of a file, e.g. lines 10-25, try 'sed -n 10,25p /path/to/the/file'.
|
||||
* Please avoid commands that may produce a very large amount of output.
|
||||
* Please run long lived commands in the background, e.g. 'sleep 10 &' or start a server in the background."""
|
||||
LEGACY_CUSTOM_DISABLED_ROWS = (
|
||||
"agent-instructions",
|
||||
"goal",
|
||||
@@ -121,10 +113,6 @@ RESTART_SNAPSHOT_DIRECTORY = (
|
||||
Path(__file__).resolve().parent / "snapshots" / "python-sdk-single-exe" / "restart"
|
||||
)
|
||||
RESTART_SNAPSHOT_FILENAMES = ("result.json", "requests.json", "session.1.jsonl", "session.2.jsonl")
|
||||
# The agent loop's dynamic runtime-context snapshot is the one model-visible message this
|
||||
# expected output cannot carry: the same composition emits it on macOS and not on Linux
|
||||
# (deepseek-harness#2488), and the file must replay on both. Everything else is compared.
|
||||
RUNTIME_CONTEXT_PREFIX = "Current runtime context"
|
||||
MCP_SERVER_SCRIPT = """\
|
||||
import json
|
||||
import os
|
||||
@@ -972,7 +960,7 @@ def smoke_sdk_custom(base_url: str, executable: Path) -> None:
|
||||
|
||||
|
||||
def smoke_sdk_minimal(base_url: str, executable: Path, update_snapshots: bool) -> None:
|
||||
"""Exercise the checked-in minimal composition through the packaged executable."""
|
||||
"""Exercise the shipped standalone minimal profile through the packaged executable."""
|
||||
from deepseek_harness import DeepSeekHarness
|
||||
|
||||
# One mock model serves every scenario of a run, so the snapshot takes this turn's slice.
|
||||
@@ -983,59 +971,15 @@ def smoke_sdk_minimal(base_url: str, executable: Path, update_snapshots: bool) -
|
||||
prompt = f"{MINIMAL_PROMPT}\n{MINIMAL_EDITOR_PATH_PREFIX}{editor_path}"
|
||||
dsh_home = root / "home"
|
||||
sessions = dsh_home / "sessions"
|
||||
disabled = [
|
||||
"agent-instructions",
|
||||
"tool-bash",
|
||||
"compaction-basic",
|
||||
"command-compact",
|
||||
"tool-result-pruner",
|
||||
]
|
||||
patch = write_profile_patch(root, "minimal.patch.yml", sessions, [
|
||||
{
|
||||
"id": "system-prompt",
|
||||
"config": {
|
||||
"includeHarnessIdentity": False,
|
||||
"includeRuntimeContext": False,
|
||||
"persona": "You are a helpful software engineer assistant.",
|
||||
"personaComplete": True,
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "sdk-jsonrpc-server",
|
||||
"config": {
|
||||
"maxTokensAsSuccess": True,
|
||||
"toolFilter": {"allow": ["bash", "str_replace_editor"]},
|
||||
},
|
||||
},
|
||||
*({"id": row_id, "disabled": True} for row_id in disabled),
|
||||
{"id": "tool-str-replace-editor", "config": {"maxOutputChars": 16000}},
|
||||
{"insert": [
|
||||
{"id": "pty", "name": "@deepseek-ai/dsh-terminal"},
|
||||
{
|
||||
"id": "terminal-bash",
|
||||
"name": "@deepseek-ai/dsh-terminal-bash",
|
||||
"config": {"timeoutMs": 300000},
|
||||
},
|
||||
{
|
||||
"id": "persistent-bash",
|
||||
"name": "@deepseek-ai/dsh-tool-bash-persistent",
|
||||
"config": {
|
||||
"timeoutMs": 300000,
|
||||
"description": MINIMAL_BASH_DESCRIPTION,
|
||||
},
|
||||
},
|
||||
]},
|
||||
])
|
||||
with DeepSeekHarness(
|
||||
provider="deepseek-official",
|
||||
model="smoke-model",
|
||||
cwd=str(root),
|
||||
dsh_bin=str(executable),
|
||||
dsh_home=str(dsh_home),
|
||||
patches=(str(patch),),
|
||||
profile="sdk-minimal",
|
||||
env={
|
||||
"DSH_PERMISSION_MODE": "danger-full-access",
|
||||
"DSH_TELEMETRY_DISABLED": "1",
|
||||
"DSH_MODEL": "smoke-model",
|
||||
},
|
||||
api_key="sk-keyless-smoke",
|
||||
base_url=base_url,
|
||||
@@ -1550,9 +1494,9 @@ def build_minimal_snapshot_files(
|
||||
Every assembled system prompt, advertised tool schema, and system or user message is
|
||||
kept verbatim: they carry what the deployment actually shows the model, so a plugin
|
||||
that contributes an unintended system section or user message cannot pass unnoticed.
|
||||
Assistant and tool payloads keep only their call identity, and the dynamic
|
||||
runtime-context snapshot is dropped, because their text differs across the platforms
|
||||
this expected output must replay on.
|
||||
Assistant and tool payloads keep only their call identity because their text differs
|
||||
across the platforms this expected output must replay on. The shipped profile omits
|
||||
dynamic runtime context, so every message it emits is compared.
|
||||
"""
|
||||
snapshot = []
|
||||
for body in requests:
|
||||
@@ -1564,21 +1508,11 @@ def build_minimal_snapshot_files(
|
||||
"messages": [
|
||||
minimal_snapshot_message(message, cwd)
|
||||
for message in messages
|
||||
if not is_runtime_context_message(message)
|
||||
],
|
||||
})
|
||||
return {"model-visible.json": json.dumps(snapshot, indent=2, ensure_ascii=False) + "\n"}
|
||||
|
||||
|
||||
def is_runtime_context_message(message: object) -> bool:
|
||||
"""Identify the agent loop's dynamic runtime-context snapshot, current or cleared."""
|
||||
return (
|
||||
isinstance(message, dict)
|
||||
and message.get("role") == "user"
|
||||
and message_text(message.get("content")).startswith(RUNTIME_CONTEXT_PREFIX)
|
||||
)
|
||||
|
||||
|
||||
def minimal_snapshot_message(message: object, cwd: Path) -> dict[str, object]:
|
||||
"""Reduce one model-visible message to its stable, behavior-carrying parts."""
|
||||
if not isinstance(message, dict):
|
||||
|
||||
Reference in New Issue
Block a user