langgraph-stream-parser 0.2.1__tar.gz → 0.2.2__tar.gz

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.
Files changed (67) hide show
  1. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/PKG-INFO +22 -1
  2. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/README.md +21 -0
  3. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/pyproject.toml +1 -1
  4. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/src/langgraph_stream_parser/demo/__init__.py +7 -1
  5. langgraph_stream_parser-0.2.2/src/langgraph_stream_parser/demo/stub.py +139 -0
  6. langgraph_stream_parser-0.2.2/tests/test_demo_stub.py +65 -0
  7. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/.github/workflows/ci.yml +0 -0
  8. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/.github/workflows/release.yml +0 -0
  9. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/.gitignore +0 -0
  10. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/CHANGELOG.md +0 -0
  11. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/LICENSE +0 -0
  12. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/assets/header.svg +0 -0
  13. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/examples/agent.py +0 -0
  14. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/examples/fastapi_websocket.py +0 -0
  15. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/examples/jupyter_example.ipynb +0 -0
  16. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/spec.md +0 -0
  17. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/src/langgraph_stream_parser/__init__.py +0 -0
  18. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/src/langgraph_stream_parser/adapters/__init__.py +0 -0
  19. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/src/langgraph_stream_parser/adapters/base.py +0 -0
  20. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/src/langgraph_stream_parser/adapters/cli.py +0 -0
  21. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/src/langgraph_stream_parser/adapters/fastapi.py +0 -0
  22. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/src/langgraph_stream_parser/adapters/jupyter.py +0 -0
  23. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/src/langgraph_stream_parser/adapters/print.py +0 -0
  24. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/src/langgraph_stream_parser/adapters/session.py +0 -0
  25. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/src/langgraph_stream_parser/compat.py +0 -0
  26. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/src/langgraph_stream_parser/demo/agent.py +0 -0
  27. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/src/langgraph_stream_parser/events.py +0 -0
  28. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/src/langgraph_stream_parser/extractors/__init__.py +0 -0
  29. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/src/langgraph_stream_parser/extractors/base.py +0 -0
  30. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/src/langgraph_stream_parser/extractors/builtins.py +0 -0
  31. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/src/langgraph_stream_parser/extractors/interrupts.py +0 -0
  32. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/src/langgraph_stream_parser/extractors/messages.py +0 -0
  33. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/src/langgraph_stream_parser/handlers/__init__.py +0 -0
  34. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/src/langgraph_stream_parser/handlers/messages.py +0 -0
  35. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/src/langgraph_stream_parser/handlers/updates.py +0 -0
  36. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/src/langgraph_stream_parser/host/__init__.py +0 -0
  37. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/src/langgraph_stream_parser/host/__main__.py +0 -0
  38. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/src/langgraph_stream_parser/host/config.py +0 -0
  39. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/src/langgraph_stream_parser/host/loader.py +0 -0
  40. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/src/langgraph_stream_parser/host/workspace.py +0 -0
  41. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/src/langgraph_stream_parser/parser.py +0 -0
  42. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/src/langgraph_stream_parser/resume.py +0 -0
  43. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/tests/__init__.py +0 -0
  44. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/tests/fixtures/__init__.py +0 -0
  45. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/tests/fixtures/mocks.py +0 -0
  46. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/tests/test_cli_adapter.py +0 -0
  47. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/tests/test_compat.py +0 -0
  48. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/tests/test_demo.py +0 -0
  49. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/tests/test_dual_mode.py +0 -0
  50. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/tests/test_events.py +0 -0
  51. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/tests/test_extractors.py +0 -0
  52. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/tests/test_fastapi_adapter.py +0 -0
  53. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/tests/test_generic_extractor.py +0 -0
  54. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/tests/test_host.py +0 -0
  55. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/tests/test_host_config.py +0 -0
  56. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/tests/test_jupyter.py +0 -0
  57. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/tests/test_lc14_compat.py +0 -0
  58. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/tests/test_parser.py +0 -0
  59. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/tests/test_print_adapter.py +0 -0
  60. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/tests/test_real_model.py +0 -0
  61. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/tests/test_reasoning_display.py +0 -0
  62. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/tests/test_resume.py +0 -0
  63. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/tests/test_session_adapter.py +0 -0
  64. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/tests/test_subagent.py +0 -0
  65. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/tests/test_v2_stream.py +0 -0
  66. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/tests/test_wire_contract.py +0 -0
  67. {langgraph_stream_parser-0.2.1 → langgraph_stream_parser-0.2.2}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langgraph-stream-parser
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Universal parser for LangGraph streaming outputs
5
5
  Project-URL: Homepage, https://github.com/dkedar7/langgraph-stream-parser
6
6
  Project-URL: Documentation, https://github.com/dkedar7/langgraph-stream-parser#readme
@@ -49,6 +49,27 @@ Description-Content-Type: text/markdown
49
49
 
50
50
  Universal parser for LangGraph streaming outputs. Normalizes complex, variable output shapes from `graph.stream()` and `graph.astream()` into consistent, typed event objects.
51
51
 
52
+ ## One agent, every surface
53
+
54
+ `langgraph-stream-parser` is the shared core of the **deep-agent family**: write your agent once — any LangGraph `CompiledGraph` — and run it on every surface with the same spec string (`module:attr` or `path/to/file.py:attr`), the same `deepagents.toml` config file, and the same `DEEPAGENT_*` environment variables.
55
+
56
+ | Surface | Package | Try it |
57
+ |---|---|---|
58
+ | Web app | [cowork-dash](https://github.com/dkedar7/cowork-dash) | `cowork-dash run --agent my_agent.py:graph` |
59
+ | JupyterLab | [deepagent-lab](https://github.com/dkedar7/deepagent-lab) | `pip install deepagent-lab`, then the chat sidebar in `jupyter lab` |
60
+ | Terminal | [deepagent-code](https://github.com/dkedar7/deepagent-code) | `deepagent-code -a my_agent.py:graph` |
61
+ | VS Code | [deepagent-vscode](https://github.com/dkedar7/deepagent-vscode) | chat participant + stdio sidecar |
62
+ | Reference agent | [deepagent-hermes](https://github.com/dkedar7/deepagent-hermes) | `DEEPAGENT_AGENT_SPEC=deepagent_hermes.agent:graph` on any surface |
63
+ | Shared core | langgraph-stream-parser | **you are here** |
64
+
65
+ No agent yet? Every surface has a keyless demo mode backed by this package's stub agent — no API key required:
66
+
67
+ ```bash
68
+ export DEEPAGENT_AGENT_SPEC=langgraph_stream_parser.demo.stub:graph # or each CLI's --demo flag
69
+ ```
70
+
71
+ And the resolved configuration (each value, its source, and the env var / `deepagents.toml` key that sets it) is printable everywhere: `python -m langgraph_stream_parser.host`, or each CLI's `--show-config`.
72
+
52
73
  ## Installation
53
74
 
54
75
  ```bash
@@ -6,6 +6,27 @@
6
6
 
7
7
  Universal parser for LangGraph streaming outputs. Normalizes complex, variable output shapes from `graph.stream()` and `graph.astream()` into consistent, typed event objects.
8
8
 
9
+ ## One agent, every surface
10
+
11
+ `langgraph-stream-parser` is the shared core of the **deep-agent family**: write your agent once — any LangGraph `CompiledGraph` — and run it on every surface with the same spec string (`module:attr` or `path/to/file.py:attr`), the same `deepagents.toml` config file, and the same `DEEPAGENT_*` environment variables.
12
+
13
+ | Surface | Package | Try it |
14
+ |---|---|---|
15
+ | Web app | [cowork-dash](https://github.com/dkedar7/cowork-dash) | `cowork-dash run --agent my_agent.py:graph` |
16
+ | JupyterLab | [deepagent-lab](https://github.com/dkedar7/deepagent-lab) | `pip install deepagent-lab`, then the chat sidebar in `jupyter lab` |
17
+ | Terminal | [deepagent-code](https://github.com/dkedar7/deepagent-code) | `deepagent-code -a my_agent.py:graph` |
18
+ | VS Code | [deepagent-vscode](https://github.com/dkedar7/deepagent-vscode) | chat participant + stdio sidecar |
19
+ | Reference agent | [deepagent-hermes](https://github.com/dkedar7/deepagent-hermes) | `DEEPAGENT_AGENT_SPEC=deepagent_hermes.agent:graph` on any surface |
20
+ | Shared core | langgraph-stream-parser | **you are here** |
21
+
22
+ No agent yet? Every surface has a keyless demo mode backed by this package's stub agent — no API key required:
23
+
24
+ ```bash
25
+ export DEEPAGENT_AGENT_SPEC=langgraph_stream_parser.demo.stub:graph # or each CLI's --demo flag
26
+ ```
27
+
28
+ And the resolved configuration (each value, its source, and the env var / `deepagents.toml` key that sets it) is printable everywhere: `python -m langgraph_stream_parser.host`, or each CLI's `--show-config`.
29
+
9
30
  ## Installation
10
31
 
11
32
  ```bash
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "langgraph-stream-parser"
3
- version = "0.2.1"
3
+ version = "0.2.2"
4
4
  description = "Universal parser for LangGraph streaming outputs"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
@@ -9,7 +9,13 @@ everything.
9
9
  Requires the optional ``deepagents`` dependency:
10
10
 
11
11
  pip install langgraph-stream-parser[demo]
12
+
13
+ This package also ships the keyless **stub agent** behind every surface's
14
+ ``--demo`` mode (:func:`create_stub_agent` / spec
15
+ ``langgraph_stream_parser.demo.stub:graph``) — that one needs no extra and no
16
+ API key.
12
17
  """
13
18
  from .agent import create_default_agent
19
+ from .stub import create_stub_agent
14
20
 
15
- __all__ = ["create_default_agent"]
21
+ __all__ = ["create_default_agent", "create_stub_agent"]
@@ -0,0 +1,139 @@
1
+ """A keyless, deterministic stub agent — the engine behind every ``--demo`` mode.
2
+
3
+ Every deep-agent surface (cowork-dash, deepagent-code, deepagent-lab,
4
+ deepagent-vscode) offers a demo mode so a new user can see the surface working
5
+ before configuring a real agent or any API key. This module is the single
6
+ shared implementation: a real compiled LangGraph graph with a checkpointer,
7
+ streaming token-by-token through the exact same parser path as a production
8
+ agent — but the "model" is a local echo, so it needs no network and no keys.
9
+
10
+ Point any surface at it with the standard spec string::
11
+
12
+ DEEPAGENT_AGENT_SPEC=langgraph_stream_parser.demo.stub:graph
13
+
14
+ or build a customized one in code::
15
+
16
+ from langgraph_stream_parser.demo import create_stub_agent
17
+ agent = create_stub_agent(name="My Demo")
18
+
19
+ ``langgraph`` and ``langchain-core`` are imported lazily — importing this
20
+ module stays dependency-free; only building the agent requires them (every
21
+ host surface already depends on both).
22
+ """
23
+ from __future__ import annotations
24
+
25
+ from typing import Any
26
+
27
+ DEFAULT_REPLY_PREFIX = "(demo agent) You said: "
28
+ DEFAULT_NAME = "Demo Agent"
29
+
30
+ _GRAPH_CACHE: Any = None
31
+
32
+
33
+ def create_stub_agent(
34
+ *,
35
+ name: str = DEFAULT_NAME,
36
+ reply_prefix: str = DEFAULT_REPLY_PREFIX,
37
+ checkpointer: Any = None,
38
+ ):
39
+ """Build the echo stub agent.
40
+
41
+ Args:
42
+ name: Display name surfaced in host UIs.
43
+ reply_prefix: Prepended to the echoed user message in every reply.
44
+ checkpointer: LangGraph checkpointer. Defaults to an in-memory saver
45
+ so multi-turn threads work out of the box.
46
+
47
+ Returns:
48
+ A compiled LangGraph graph that replies to each user message with
49
+ ``reply_prefix + <last human message>``, streamed token-by-token.
50
+
51
+ Raises:
52
+ RuntimeError: If ``langgraph`` / ``langchain-core`` are not installed.
53
+ """
54
+ try:
55
+ from langchain_core.callbacks import CallbackManagerForLLMRun
56
+ from langchain_core.language_models import BaseChatModel
57
+ from langchain_core.messages import AIMessage, AIMessageChunk, BaseMessage
58
+ from langchain_core.outputs import ChatGeneration, ChatGenerationChunk, ChatResult
59
+ from langgraph.checkpoint.memory import MemorySaver
60
+ from langgraph.graph import END, START, MessagesState, StateGraph
61
+ except ImportError as e: # pragma: no cover — exercised only without the deps
62
+ raise RuntimeError(
63
+ "The stub agent needs langgraph + langchain-core "
64
+ "(every deep-agent surface already installs them): "
65
+ f"{e}"
66
+ ) from e
67
+
68
+ from typing import Iterator, List, Optional
69
+
70
+ def _last_human(messages: List[BaseMessage]) -> str:
71
+ for message in reversed(messages):
72
+ if getattr(message, "type", None) == "human":
73
+ content = message.content
74
+ return content if isinstance(content, str) else str(content)
75
+ return ""
76
+
77
+ class EchoChatModel(BaseChatModel):
78
+ """A no-API chat model that echoes the user's last message.
79
+
80
+ Implements ``_stream`` so that under LangGraph's ``messages`` stream
81
+ mode the reply is emitted token-by-token, matching how a real chat
82
+ model behaves through the parser.
83
+ """
84
+
85
+ @property
86
+ def _llm_type(self) -> str:
87
+ return "echo-stub"
88
+
89
+ def _generate(
90
+ self,
91
+ messages: List[BaseMessage],
92
+ stop: Optional[List[str]] = None,
93
+ run_manager: Optional[CallbackManagerForLLMRun] = None,
94
+ **kwargs: Any,
95
+ ) -> ChatResult:
96
+ text = reply_prefix + _last_human(messages)
97
+ return ChatResult(generations=[ChatGeneration(message=AIMessage(content=text))])
98
+
99
+ def _stream(
100
+ self,
101
+ messages: List[BaseMessage],
102
+ stop: Optional[List[str]] = None,
103
+ run_manager: Optional[CallbackManagerForLLMRun] = None,
104
+ **kwargs: Any,
105
+ ) -> Iterator[ChatGenerationChunk]:
106
+ text = reply_prefix + _last_human(messages)
107
+ tokens = text.split(" ")
108
+ for i, token in enumerate(tokens):
109
+ piece = token if i == len(tokens) - 1 else token + " "
110
+ chunk = ChatGenerationChunk(message=AIMessageChunk(content=piece))
111
+ if run_manager is not None:
112
+ run_manager.on_llm_new_token(piece, chunk=chunk)
113
+ yield chunk
114
+
115
+ model = EchoChatModel()
116
+
117
+ def _respond(state: MessagesState) -> dict:
118
+ return {"messages": [model.invoke(state["messages"])]}
119
+
120
+ builder = StateGraph(MessagesState)
121
+ builder.add_node("respond", _respond)
122
+ builder.add_edge(START, "respond")
123
+ builder.add_edge("respond", END)
124
+
125
+ graph = builder.compile(checkpointer=checkpointer or MemorySaver())
126
+ graph.name = name
127
+ return graph
128
+
129
+
130
+ def __getattr__(attr: str) -> Any:
131
+ # ``graph`` is built lazily on first access so plain imports of this module
132
+ # never require langgraph — but ``load_agent_spec("...demo.stub:graph")``
133
+ # gets a ready compiled agent.
134
+ if attr == "graph":
135
+ global _GRAPH_CACHE
136
+ if _GRAPH_CACHE is None:
137
+ _GRAPH_CACHE = create_stub_agent()
138
+ return _GRAPH_CACHE
139
+ raise AttributeError(f"module {__name__!r} has no attribute {attr!r}")
@@ -0,0 +1,65 @@
1
+ """Tests for the keyless stub agent behind every surface's --demo mode.
2
+
3
+ Unlike the default agent (which needs deepagents + an API key), the stub only
4
+ needs langgraph + langchain-core — both in the dev extras — so these tests
5
+ exercise it end to end through the parser.
6
+ """
7
+ from langgraph_stream_parser import StreamParser, load_agent_spec, prepare_agent_input
8
+ from langgraph_stream_parser.demo import create_stub_agent
9
+ from langgraph_stream_parser.events import CompleteEvent, ContentEvent
10
+
11
+ STREAM_MODE = ["updates", "messages"]
12
+
13
+
14
+ def _run_turn(graph, message: str, thread_id: str = "t1"):
15
+ parser = StreamParser(stream_mode=STREAM_MODE)
16
+ stream = graph.stream(
17
+ prepare_agent_input(message=message),
18
+ config={"configurable": {"thread_id": thread_id}},
19
+ stream_mode=STREAM_MODE,
20
+ )
21
+ return list(parser.parse(stream))
22
+
23
+
24
+ def _content(events) -> str:
25
+ return "".join(e.content for e in events if isinstance(e, ContentEvent))
26
+
27
+
28
+ def test_streams_echo_through_the_parser():
29
+ graph = create_stub_agent()
30
+ events = _run_turn(graph, "hello demo")
31
+
32
+ assert "(demo agent) You said: hello demo" in _content(events)
33
+ assert isinstance(events[-1], CompleteEvent)
34
+
35
+
36
+ def test_streams_token_by_token():
37
+ graph = create_stub_agent()
38
+ events = _run_turn(graph, "one two three four")
39
+ content_events = [e for e in events if isinstance(e, ContentEvent)]
40
+ # The echo splits on spaces — a multi-word message must arrive in pieces.
41
+ assert len(content_events) > 1
42
+
43
+
44
+ def test_multi_turn_thread_persists():
45
+ graph = create_stub_agent()
46
+ _run_turn(graph, "first", thread_id="conv")
47
+ state = graph.get_state({"configurable": {"thread_id": "conv"}})
48
+ _run_turn(graph, "second", thread_id="conv")
49
+ state2 = graph.get_state({"configurable": {"thread_id": "conv"}})
50
+ assert len(state2.values["messages"]) > len(state.values["messages"])
51
+
52
+
53
+ def test_custom_name_and_prefix():
54
+ graph = create_stub_agent(name="My Demo", reply_prefix="echo: ")
55
+ assert graph.name == "My Demo"
56
+ events = _run_turn(graph, "hi")
57
+ assert "echo: hi" in _content(events)
58
+
59
+
60
+ def test_loadable_via_standard_spec_string():
61
+ graph = load_agent_spec("langgraph_stream_parser.demo.stub:graph")
62
+ events = _run_turn(graph, "spec works", thread_id="spec")
63
+ assert "spec works" in _content(events)
64
+ # The module-level graph is cached — same object on a second load.
65
+ assert load_agent_spec("langgraph_stream_parser.demo.stub:graph") is graph