Skip to content

Commit 44b58ca

Browse files
committed
standardize
1 parent 3e82a6b commit 44b58ca

File tree

2 files changed

+126
-131
lines changed

2 files changed

+126
-131
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
"""Deep agent + skills demo: sync skills from store (server) or load from disk (local), then run."""
2+
3+
import os
4+
import re
5+
from pathlib import Path
6+
7+
# :snippet-start: skills-sandbox-py
8+
from deepagents import create_deep_agent
9+
from deepagents.backends import CompositeBackend, LocalShellBackend, StoreBackend
10+
from daytona import Daytona
11+
from langchain.agents.middleware import AgentMiddleware, AgentState
12+
from langchain.chat_models import init_chat_model
13+
from langchain_core.language_models import BaseChatModel
14+
from langchain_daytona import DaytonaSandbox
15+
from langgraph.checkpoint.memory import InMemorySaver
16+
from langgraph.runtime import Runtime
17+
from langgraph.store.base import BaseStore
18+
19+
# :remove-start:
20+
_PROJECT_ROOT = Path(__file__).resolve().parent
21+
_TIME_TXT = _PROJECT_ROOT / "time.txt"
22+
# UTC line written by skills/write_current_time/scripts/write_time.sh
23+
_TIME_PATTERN = re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$")
24+
25+
# :remove-end:
26+
27+
def _safe_filename(key: str) -> str:
28+
name = key.split("/")[-1]
29+
if ".." in name or any(c in name for c in ("*", "?")):
30+
raise ValueError(f"Invalid key: {key}")
31+
return name
32+
33+
34+
class SkillSyncMiddleware(AgentMiddleware):
35+
"""Upload skill scripts from the store into the sandbox before each run."""
36+
37+
def __init__(self, backend: CompositeBackend):
38+
super().__init__()
39+
self.backend = backend
40+
41+
async def abefore_agent(self, state: AgentState, runtime: Runtime) -> None:
42+
if runtime.store is None or runtime.server_info is None or runtime.server_info.user is None:
43+
return None
44+
user_id = runtime.server_info.user.identity
45+
store = runtime.store
46+
files = []
47+
for item in await store.asearch(("skills", user_id)):
48+
name = _safe_filename(item.key)
49+
files.append((f"/skills/{name}", item.value["content"].encode()))
50+
if files:
51+
await self.backend.aupload_files(files)
52+
return None
53+
54+
55+
backend = CompositeBackend(
56+
sandbox = Daytona().create()
57+
default=DaytonaSandbox(sandbox=sandbox),
58+
routes={
59+
"/skills/": StoreBackend(
60+
namespace=lambda rt: ("skills", rt.server_info.user.identity), # [!code highlight]
61+
),
62+
},
63+
)
64+
65+
agent = create_deep_agent(
66+
model="claude-haiku-4-5",
67+
backend=backend,
68+
skills=["/skills/"],
69+
checkpointer=InMemorySaver(),
70+
)
71+
72+
# :snippet-end:
73+
74+
def verify_time_txt(path: Path = _TIME_TXT) -> str:
75+
"""Assert `time.txt` exists and matches the write-current-time skill output format."""
76+
if not path.is_file():
77+
msg = f"Expected {path} to exist after running the skill."
78+
raise AssertionError(msg)
79+
line = path.read_text(encoding="utf-8").strip()
80+
if not _TIME_PATTERN.fullmatch(line):
81+
msg = f"Expected one ISO-8601 UTC line in {path}, got {line!r}"
82+
raise AssertionError(msg)
83+
return line
84+
85+
86+
def main() -> None:
87+
"""Run the agent with a local shell backend; requires LLM credentials (e.g. ANTHROPIC_API_KEY)."""
88+
if _TIME_TXT.exists():
89+
_TIME_TXT.unlink()
90+
91+
prompt = (
92+
"Use the write_current_time skill: read its SKILL.md under /skills/, "
93+
"then run the shell script it describes so that a file named time.txt is created in the "
94+
"workspace root with the current UTC time. Use the execute tool to run bash."
95+
)
96+
result = agent.invoke(
97+
{"messages": [{"role": "user", "content": prompt}]},
98+
config={"configurable": {"thread_id": "skills-sandbox-demo"}},
99+
)
100+
messages = result.get("messages") or []
101+
if messages:
102+
print(messages[-1].content)
103+
104+
line = verify_time_txt()
105+
print(f"OK: {_TIME_TXT} contains {line!r}")
106+
107+
108+
if __name__ == "__main__":
109+
main()

src/oss/langchain/quickstart.mdx

Lines changed: 17 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,6 +1043,12 @@ Along the way you will explore the following concepts:
10431043

10441044
Use deep agents when you want maximum capability with minimal setup; choose LangChain agents when you need fine-grained control.
10451045

1046+
<Warning>
1047+
Since the code invokes the model with the entire text from Pride and Prejudice, it uses a large amount of tokens.
1048+
1049+
You can view example output in the next step.
1050+
</Warning>
1051+
10461052
Let's try both:
10471053

10481054
:::python
@@ -1068,8 +1074,7 @@ Along the way you will explore the following concepts:
10681074
content = f"""Project Gutenberg hosts a full plain-text Pride and Prejudice.
10691075
URL: https://www.gutenberg.org/files/1342/1342-0.txt
10701076
Answer as much as you can:
1071-
1) How many lines in the complete Gutenberg file contain the substring `Elizabeth` (count lines,
1072-
not occurrences within a line).
1077+
1) How many lines in the complete Gutenberg file contain the substring `Elizabeth`.
10731078
2) The 1-based line number of the first line in the file that contains `Darcy`.
10741079
3) A two-sentence neutral synopsis.
10751080
Do your best on (1) and (2). If at any point you realize you cannot **verify** an exact answer with
@@ -1111,13 +1116,13 @@ Along the way you will explore the following concepts:
11111116
const content = `Project Gutenberg hosts a full plain-text Pride and Prejudice.
11121117
URL: https://www.gutenberg.org/files/1342/1342-0.txt
11131118
Answer as much as you can:
1114-
1) How many lines in the complete Gutenberg file contain the substring \`Elizabeth\` (count lines,
1115-
not occurrences within a line).
1119+
1) How many times in the complete Gutenberg file contain the substring \`Elizabeth\`.
11161120
2) The 1-based line number of the first line in the file that contains \`Darcy\`.
11171121
3) A two-sentence neutral synopsis.
11181122
Do your best on (1) and (2). If at any point you realize you cannot **verify** an exact answer with
11191123
your available tools and reasoning, do not fabricate numbers: use \`null\` for that field and spell out
1120-
the limitation in \`how_you_computed_counts\`.`;
1124+
the limitation in \`how_you_computed_counts\`. If you encounter any errors please report what the error was and what the error message was.`;
1125+
11211126

11221127
const agentResult = await agent.invoke(
11231128
{ messages: [{ role: "user", content }] },
@@ -1301,13 +1306,13 @@ Along the way you will explore the following concepts:
13011306
const content = `Project Gutenberg hosts a full plain-text Pride and Prejudice.
13021307
URL: https://www.gutenberg.org/files/1342/1342-0.txt
13031308
Answer as much as you can:
1304-
1) How many lines in the complete Gutenberg file contain the substring \`Elizabeth\` (count lines,
1305-
not occurrences within a line).
1309+
1) How many times in the complete Gutenberg file contain the substring \`Elizabeth\`.
13061310
2) The 1-based line number of the first line in the file that contains \`Darcy\`.
13071311
3) A two-sentence neutral synopsis.
13081312
Do your best on (1) and (2). If at any point you realize you cannot **verify** an exact answer with
13091313
your available tools and reasoning, do not fabricate numbers: use \`null\` for that field and spell out
1310-
the limitation in \`how_you_computed_counts\`.`;
1314+
the limitation in \`how_you_computed_counts\`. If you encounter any errors please report what the error was and what the error message was.`;
1315+
13111316

13121317
const agentResult = await agent.invoke(
13131318
{ messages: [{ role: "user", content }] },
@@ -1336,11 +1341,12 @@ Along the way you will explore the following concepts:
13361341
</Step>
13371342
<Step title="Review the results">
13381343
1344+
The results will differ based on the model and the execution.
1345+
13391346
<Tabs default="LangChain agents">
13401347
<Tab title="LangChain agents">
13411348
1342-
:::python
1343-
```python wrap expandable
1349+
```txt wrap expandable
13441350
Now I'll analyze the text to answer your questions:
13451351

13461352
## Answers:
@@ -1367,73 +1373,11 @@ Along the way you will explore the following concepts:
13671373

13681374
For (2): I identified the first narrative mention of "Darcy" by scanning from the beginning of the actual story text (after preface and front matter), but without precise line numbering in the source, this is an approximation based on text position.
13691375
```
1370-
:::
1371-
1372-
:::js
1373-
````ts wrap expandable
1374-
[
1375-
{
1376-
index: 0,
1377-
type: 'text',
1378-
text: 'I now have the full text. Let me carefully analyze it for both counts.\n' +
1379-
'\n' +
1380-
'---\n' +
1381-
'\n' +
1382-
'## Analysis\n' +
1383-
'\n' +
1384-
'### Question 1 — Lines containing `Elizabeth`\n' +
1385-
'\n' +
1386-
'The tool returns the file as one large block of text. I need to count **lines** (newline-delimited) that contain the substring `Elizabeth` at least once.\n' +
1387-
'\n' +
1388-
'Working through the text systematically, I can identify every line that contains `Elizabeth`. Rather than risk an off-by-one manual count across thousands of lines without a programmatic counter, I need to be transparent: **the tool loads the document as a single text blob**, and I have no line-numbering facility available to me. I can read the text and reason about it, but I cannot execute a `grep -c` or equivalent command. Attempting to hand-count every occurrence across ~13,000+ lines of text would be error-prone and likely to produce a fabricated number.\n' +
1389-
'\n' +
1390-
'### Question 2 — First line containing `Darcy`\n' +
1391-
'\n' +
1392-
'Scanning from the very top of the file, the first occurrence of `Darcy` appears in the **Preface** by George Saintsbury. The relevant passage reads:\n' +
1393-
'\n' +
1394-
`> *"…the false account of Darcy's conduct given by Wickham…"*\n` +
1395-
'\n' +
1396-
'That line is deep in the Preface section. The very first line in the file containing `Darcy` is:\n' +
1397-
'\n' +
1398-
'> `"…the false account of Darcy\'s conduct given by Wickham, and by the awkwardness…"`\n' +
1399-
'\n' +
1400-
'Again, without a line counter I cannot give a verified 1-based line number.\n' +
1401-
'\n' +
1402-
'---\n' +
1403-
'\n' +
1404-
'## Results\n' +
1405-
'\n' +
1406-
'```json\n' +
1407-
'{\n' +
1408-
' "question_1": {\n' +
1409-
' "answer": null,\n' +
1410-
` "how_you_computed_counts": "The fetch_text_from_url tool returns the document as a single unstructured text blob with no line numbers. I have no available tool to execute grep, wc, or any line-counting operation. Manually counting every newline-delimited line containing 'Elizabeth' across ~13,000+ lines would be unreliable and likely to produce a fabricated figure. Therefore I return null rather than guess."\n` +
1411-
' },\n' +
1412-
' "question_2": {\n' +
1413-
' "answer": null,\n' +
1414-
` "how_you_computed_counts": "The first occurrence of 'Darcy' in the file is in the Preface (George Saintsbury's essay), in the sentence '…the false account of Darcy's conduct given by Wickham…'. However, without a line-numbering facility I cannot verify the exact 1-based line number. The tool provides no line index, and manually counting blank lines, illustration captions, and wrapped prose lines to arrive at a precise number would be error-prone. I return null rather than fabricate a line number."\n` +
1415-
' },\n' +
1416-
' "synopsis": "Pride and Prejudice (1813) by Jane Austen follows Elizabeth Bennet, the second of five daughters in a genteel but financially precarious English family, as she navigates questions of marriage, social class, and personal integrity in early nineteenth-century rural England. The novel centers on her evolving relationship with the wealthy and initially aloof Fitzwilliam Darcy, whose pride and her own prejudice must each be overcome before the two can recognize their mutual regard and agree to marry."\n' +
1417-
'}\n' +
1418-
'```\n' +
1419-
'\n' +
1420-
'### Summary\n' +
1421-
'\n' +
1422-
'| Field | Value |\n' +
1423-
'|---|---|\n' +
1424-
'| **Lines containing `Elizabeth`** | `null` — the tool returns raw text with no line-number index; a programmatic counter (e.g., `grep -c`) is required for a verified answer |\n' +
1425-
'| **First line number with `Darcy`** | `null` — same limitation; the word first appears in the Preface in the phrase *"Darcy\'s conduct given by Wickham"*, but the exact 1-based line number cannot be verified without a line counter |\n' +
1426-
'| **Synopsis** | *Pride and Prejudice* follows Elizabeth Bennet as she navigates marriage prospects and social pressures in Regency England, ultimately overcoming mutual pride and prejudice to unite with the wealthy Mr. Darcy. |'
1427-
}
1428-
]
1429-
````
1430-
:::
14311376

14321377
</Tab>
14331378
<Tab title="Deep agents">
14341379

1435-
:::python
1436-
```md wrap expandable
1380+
```txt wrap expandable
14371381
Excellent! Now I have all the information I need. Let me compile the answers:
14381382

14391383
**1) Lines containing "Elizabeth": 644**
@@ -1461,64 +1405,6 @@ Along the way you will explore the following concepts:
14611405
- (2) Used `grep` with `output_mode=content` to find the first occurrence of "Darcy" and verified by reading the file directly
14621406
- (3) Synthesized from the preface and opening narrative of the downloaded text
14631407
```
1464-
:::
1465-
1466-
:::js
1467-
````md wrap expandable
1468-
[
1469-
{
1470-
index: 0,
1471-
type: 'text',
1472-
text: 'Confirmed from direct file reads:\n' +
1473-
'- **Line 139** — first line containing `Darcy` (as "Darcy\'s conduct")\n' +
1474-
'- **Line 141** — first line containing `Elizabeth` (as "Elizabeth\'s own feelings")\n' +
1475-
'\n' +
1476-
'The subagent read all 14,538 lines in 500-line chunks and counted **576** lines containing "Elizabeth." This is a manual scan count and may have small errors (the subagent itself noted ±5–10 possible variance), but it is grounded in a full read of the file.\n' +
1477-
'\n' +
1478-
'---\n' +
1479-
'\n' +
1480-
'Here are the answers:\n' +
1481-
'\n' +
1482-
'---\n' +
1483-
'\n' +
1484-
'## Results\n' +
1485-
'\n' +
1486-
'### 1. Lines containing `Elizabeth`\n' +
1487-
'**~576 lines**\n' +
1488-
'\n' +
1489-
'The file was read in its entirety in 500-line chunks (30 chunks covering all 14,538 lines). Each chunk was scanned line-by-line for the substring `Elizabeth`. The chunk-by-chunk totals summed to **576**.\n' +
1490-
'\n' +
1491-
'> **Caveat:** This count was performed by a subagent doing manual line-by-line scanning across 30 chunks. Small counting errors (±5–10 lines) are possible, so treat this as a high-confidence estimate rather than a guaranteed exact integer. A programmatic `grep -c` would be the gold standard; the tools available here do not support that directly on the saved file path.\n' +
1492-
'\n' +
1493-
'---\n' +
1494-
'\n' +
1495-
'### 2. First line containing `Darcy`\n' +
1496-
'**Line 139** ✅ (verified by direct file read)\n' +
1497-
'\n' +
1498-
'```\n' +
1499-
"natural occasions provided by the false account of Darcy's conduct given\n" +
1500-
'```\n' +
1501-
"This is in the Preface by George Saintsbury, well before the novel's text begins at line 678.\n" +
1502-
'\n' +
1503-
'---\n' +
1504-
'\n' +
1505-
'### 3. Two-sentence neutral synopsis\n' +
1506-
'\n' +
1507-
'*Pride and Prejudice* (1813) by Jane Austen follows Elizabeth Bennet, the second of five daughters in a genteel but financially precarious English family, as she navigates questions of marriage, class, and personal integrity—most centrally in her evolving relationship with the wealthy and initially aloof Mr. Darcy. Through a series of misunderstandings, social pressures, and frank self-reckonings on both sides, Elizabeth and Darcy overcome their mutual pride and prejudice to arrive at a union grounded in genuine mutual respect and affection.\n' +
1508-
'\n' +
1509-
'---\n' +
1510-
'\n' +
1511-
'### Summary table\n' +
1512-
'\n' +
1513-
'| Question | Answer | Confidence |\n' +
1514-
'|---|---|---|\n' +
1515-
'| Lines containing `Elizabeth` | **~576** | High estimate (manual chunk scan; ±5–10 possible) |\n' +
1516-
'| First line with `Darcy` | **139** | Verified by direct file read |\n' +
1517-
'| Synopsis | See above | N/A |'
1518-
}
1519-
]
1520-
````
1521-
:::
15221408

15231409
</Tab>
15241410
</Tabs>

0 commit comments

Comments
 (0)