Skip to content

Commit 7deee3b

Browse files
committed
Replace old instruction parsing code with new Instruction class
1 parent 3e5a3fb commit 7deee3b

File tree

18 files changed

+59
-208
lines changed

18 files changed

+59
-208
lines changed

agents-core/vision_agents/core/agents/agents.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
)
2727
from ..edge.types import Connection, Participant, PcmData, User, OutputAudioTrack
2828
from ..events.manager import EventManager
29+
from ..instructions import Instructions
2930
from ..llm import events as llm_events
3031
from ..llm.events import (
3132
LLMResponseChunkEvent,
@@ -139,7 +140,7 @@ def __init__(
139140
# audio incoming is enqueued to self._incoming_audio_queue (eg. human audio)
140141
self._incoming_audio_queue: AudioQueue = AudioQueue(buffer_limit_ms=8000)
141142

142-
self.instructions = instructions
143+
self.instructions = Instructions(input_text=instructions)
143144
self.edge = edge
144145
self.agent_user = agent_user
145146
self._agent_user_initialized = False
@@ -506,7 +507,7 @@ async def join(
506507

507508
# Setup chat and connect it to transcript events (we'll wait at the end)
508509
create_conversation_coro = self.edge.create_conversation(
509-
call, self.agent_user, self.instructions
510+
call, self.agent_user, self.instructions.full_reference
510511
)
511512

512513
try:

agents-core/vision_agents/core/llm/llm.py

Lines changed: 17 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,34 @@
44
import asyncio
55
import json
66
from typing import (
7-
Optional,
87
TYPE_CHECKING,
9-
Tuple,
10-
List,
11-
Dict,
128
Any,
13-
TypeVar,
149
Callable,
10+
Dict,
1511
Generic,
12+
List,
13+
Optional,
14+
Tuple,
15+
TypeVar,
1616
)
1717

1818
import aiortc
19+
from vision_agents.core.instructions import Instructions
1920
from vision_agents.core.llm import events
20-
from vision_agents.core.llm.events import ToolStartEvent, ToolEndEvent
21+
from vision_agents.core.llm.events import ToolEndEvent, ToolStartEvent
2122

2223
if TYPE_CHECKING:
2324
from vision_agents.core.agents import Agent
2425
from vision_agents.core.agents.conversation import Conversation
2526

26-
from getstream.video.rtc.pb.stream.video.sfu.models.models_pb2 import Participant
2727
from getstream.video.rtc import PcmData
28-
from vision_agents.core.processors import Processor
29-
from vision_agents.core.utils.utils import Instructions, parse_instructions
28+
from getstream.video.rtc.pb.stream.video.sfu.models.models_pb2 import Participant
3029
from vision_agents.core.events.manager import EventManager
31-
from .function_registry import FunctionRegistry
32-
from .llm_types import ToolSchema, NormalizedToolCallItem
30+
from vision_agents.core.processors import Processor
31+
3332
from ..utils.video_forwarder import VideoForwarder
33+
from .function_registry import FunctionRegistry
34+
from .llm_types import NormalizedToolCallItem, ToolSchema
3435

3536
T = TypeVar("T")
3637

@@ -58,8 +59,8 @@ def __init__(self):
5859
self.events = EventManager()
5960
self.events.register_events_from_module(events)
6061
self.function_registry = FunctionRegistry()
61-
self.instructions: Optional[str] = None
62-
self.parsed_instructions: Optional[Instructions] = None
62+
# LLM instructions. Provided by the Agent via `set_instructions` method
63+
self._instructions: str = ""
6364
self._conversation: Optional[Conversation] = None
6465

6566
async def warmup(self) -> None:
@@ -80,34 +81,6 @@ async def simple_response(
8081
) -> LLMResponseEvent[Any]:
8182
raise NotImplementedError
8283

83-
def _build_enhanced_instructions(self) -> Optional[str]:
84-
"""
85-
Build enhanced instructions by combining the original instructions with markdown file contents.
86-
87-
Returns:
88-
Enhanced instructions string with markdown file contents included, or None if no parsed instructions
89-
"""
90-
if not hasattr(self, "parsed_instructions") or not self.parsed_instructions:
91-
return None
92-
93-
parsed = self.parsed_instructions
94-
enhanced_instructions = [parsed.input_text]
95-
96-
# Add markdown file contents if any exist
97-
if parsed.markdown_contents:
98-
enhanced_instructions.append("\n\n## Referenced Documentation:")
99-
for filename, content in parsed.markdown_contents.items():
100-
if content: # Only include non-empty content
101-
enhanced_instructions.append(f"\n### {filename}")
102-
enhanced_instructions.append(content)
103-
else:
104-
enhanced_instructions.append(f"\n### {filename}")
105-
enhanced_instructions.append(
106-
"*(File not found or could not be read)*"
107-
)
108-
109-
return "\n".join(enhanced_instructions)
110-
11184
def _get_tools_for_provider(self) -> List[Dict[str, Any]]:
11285
"""
11386
Get tools in provider-specific format.
@@ -189,7 +162,7 @@ def _attach_agent(self, agent: Agent):
189162
Attach agent to the llm
190163
"""
191164
self.agent = agent
192-
self._set_instructions(agent.instructions)
165+
self.set_instructions(agent.instructions)
193166

194167
def set_conversation(self, conversation: Conversation):
195168
"""
@@ -203,11 +176,8 @@ def set_conversation(self, conversation: Conversation):
203176
"""
204177
self._conversation = conversation
205178

206-
def _set_instructions(self, instructions: str):
207-
self.instructions = instructions
208-
209-
# Parse instructions to extract @ mentioned markdown files
210-
self.parsed_instructions = parse_instructions(instructions)
179+
def set_instructions(self, instructions: Instructions):
180+
self._instructions = instructions.full_reference
211181

212182
def register_function(
213183
self, name: Optional[str] = None, description: Optional[str] = None

agents-core/vision_agents/core/utils/utils.py

Lines changed: 0 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
import importlib.metadata
33
import logging
44
import os
5-
import re
6-
from dataclasses import dataclass
75
from typing import Dict, Optional
86

97
import httpx
@@ -28,107 +26,6 @@ def _load_version() -> str:
2826

2927
_VISION_AGENTS_VERSION = _load_version()
3028

31-
# Cache current working directory at module load time
32-
_INITIAL_CWD = os.getcwd()
33-
34-
35-
@dataclass
36-
class Instructions:
37-
"""Container for parsed instructions with input text and markdown files."""
38-
39-
input_text: str
40-
markdown_contents: MarkdownFileContents # Maps filename to file content
41-
base_dir: str = "" # Base directory for file search, defaults to empty string
42-
43-
44-
def _read_markdown_file_sync(file_path: str) -> str:
45-
"""Synchronous helper to read a markdown file."""
46-
try:
47-
if os.path.isfile(file_path):
48-
with open(file_path, "r", encoding="utf-8") as f:
49-
return f.read()
50-
else:
51-
return ""
52-
except (OSError, IOError, UnicodeDecodeError):
53-
return ""
54-
55-
56-
async def parse_instructions_async(
57-
text: str, base_dir: Optional[str] = None
58-
) -> Instructions:
59-
"""
60-
Async version: Parse instructions from a string, extracting @ mentioned markdown files and their contents.
61-
62-
Args:
63-
text: Input text that may contain @ mentions of markdown files
64-
base_dir: Base directory to search for markdown files. If None, uses cached working directory.
65-
66-
Returns:
67-
Instructions object containing the input text and file contents
68-
"""
69-
# Find all @ mentions that look like markdown files
70-
markdown_pattern = r"@([^\s@]+\.md)"
71-
matches = re.findall(markdown_pattern, text)
72-
73-
# Create a dictionary mapping filename to file content
74-
markdown_contents = {}
75-
76-
# Set base directory for file search
77-
if base_dir is None:
78-
base_dir = _INITIAL_CWD
79-
80-
for match in matches:
81-
# Try to read the markdown file content
82-
file_path = os.path.join(base_dir, match)
83-
# Run blocking I/O in thread pool
84-
content = await asyncio.to_thread(_read_markdown_file_sync, file_path)
85-
markdown_contents[match] = content
86-
87-
return Instructions(
88-
input_text=text, markdown_contents=markdown_contents, base_dir=base_dir
89-
)
90-
91-
92-
def parse_instructions(text: str, base_dir: Optional[str] = None) -> Instructions:
93-
"""
94-
Parse instructions from a string, extracting @ mentioned markdown files and their contents.
95-
96-
Args:
97-
text: Input text that may contain @ mentions of markdown files
98-
base_dir: Base directory to search for markdown files. If None, uses cached working directory.
99-
100-
Returns:
101-
Instructions object containing the input text and file contents
102-
103-
Example:
104-
>>> text = "Please read @file1.md and @file2.md for context"
105-
>>> result = parse_instructions(text)
106-
>>> result.input_text
107-
"Please read @file1.md and @file2.md for context"
108-
>>> result.markdown_contents
109-
{"file1.md": "# File 1 content...", "file2.md": "# File 2 content..."}
110-
"""
111-
# Find all @ mentions that look like markdown files
112-
# Pattern matches @ followed by filename with .md extension
113-
markdown_pattern = r"@([^\s@]+\.md)"
114-
matches = re.findall(markdown_pattern, text)
115-
116-
# Create a dictionary mapping filename to file content
117-
markdown_contents = {}
118-
119-
# Set base directory for file search
120-
if base_dir is None:
121-
base_dir = _INITIAL_CWD
122-
123-
for match in matches:
124-
# Try to read the markdown file content
125-
file_path = os.path.join(base_dir, match)
126-
markdown_contents[match] = _read_markdown_file_sync(file_path)
127-
128-
return Instructions(
129-
input_text=text, markdown_contents=markdown_contents, base_dir=base_dir
130-
)
131-
13229

13330
def get_vision_agents_version() -> Optional[str]:
13431
"""

docs/ai/instructions/ai-llm.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,8 @@ class MyLLM(LLM):
2222

2323
# some details to get right here...
2424
# ensure conversation history is maintained. typically by passing it ie:
25-
enhanced_instructions = self._build_enhanced_instructions()
26-
if enhanced_instructions:
27-
kwargs["system"] = [{"text": enhanced_instructions}]
25+
if self._instructions:
26+
kwargs["system"] = [{"text": self._instructions}]
2827

2928
response_iterator = await self.client.mynativemethod(self, *args, **kwargs)
3029

docs/ai/instructions/ai-realtime-llm.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,8 @@ class MyRealtime(realtime.Realtime):
4646

4747
# some details to get right here...
4848
# ensure conversation history is maintained. typically by passing it ie:
49-
enhanced_instructions = self._build_enhanced_instructions()
50-
if enhanced_instructions:
51-
kwargs["system"] = [{"text": enhanced_instructions}]
49+
if self._instructions:
50+
kwargs["system"] = [{"text": self._instructions}]
5251

5352
response_iterator = await self.client.mynativemethod(self, *args, **kwargs)
5453

plugins/aws/tests/test_aws.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ async def test_instruction_following(self, llm: BedrockLLM):
146146
model="qwen.qwen3-32b-v1:0",
147147
region_name="us-east-1",
148148
)
149-
llm._set_instructions("only reply in 2 letter country shortcuts")
149+
llm.set_instructions("only reply in 2 letter country shortcuts")
150150

151151
response = await llm.simple_response(
152152
text="Which country is rainy, protected from water with dikes and below sea level?",

plugins/aws/tests/test_aws_realtime.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ async def realtime(self):
2424
model="amazon.nova-sonic-v1:0",
2525
region_name="us-east-1",
2626
)
27-
realtime._set_instructions(
27+
realtime.set_instructions(
2828
"you're a kind assistant, always be friendly please."
2929
)
3030
try:
@@ -36,7 +36,7 @@ async def realtime(self):
3636
async def test_simple_response_flow(self, realtime):
3737
# unlike other realtime LLMs, AWS doesn't reply if you only send text
3838
events = []
39-
realtime._set_instructions(
39+
realtime.set_instructions(
4040
"whenever you reply mention a fun fact about The Netherlands"
4141
)
4242

plugins/aws/vision_agents/plugins/aws/aws_llm.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,8 @@ async def converse(self, *args, **kwargs) -> LLMResponseEvent[Any]:
127127
kwargs["toolConfig"] = {"tools": converted_tools}
128128

129129
# Combine original instructions with markdown file contents
130-
enhanced_instructions = self._build_enhanced_instructions()
131-
if enhanced_instructions:
132-
kwargs["system"] = [{"text": enhanced_instructions}]
130+
if self._instructions:
131+
kwargs["system"] = [{"text": self._instructions}]
133132

134133
# Ensure the AI remembers the past conversation
135134
new_messages = kwargs.get("messages", [])
@@ -354,10 +353,8 @@ async def converse_stream(self, *args, **kwargs) -> LLMResponseEvent[Any]:
354353
for msg in normalized_messages:
355354
self._conversation.messages.append(msg)
356355

357-
# Combine original instructions with markdown file contents
358-
enhanced_instructions = self._build_enhanced_instructions()
359-
if enhanced_instructions:
360-
kwargs["system"] = [{"text": enhanced_instructions}]
356+
if self._instructions:
357+
kwargs["system"] = [{"text": self._instructions}]
361358

362359
try:
363360
system_param = kwargs.get("system")

plugins/aws/vision_agents/plugins/aws/aws_realtime.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -229,12 +229,11 @@ async def connect(self):
229229
await asyncio.sleep(0.1)
230230

231231
# next send system instructions
232-
system_instructions = self._build_enhanced_instructions()
233-
if not system_instructions:
232+
if not self._instructions:
234233
raise Exception(
235234
"AWS Bedrock requires system instructions before sending regular user input"
236235
)
237-
await self.content_input(system_instructions, "SYSTEM")
236+
await self.content_input(self._instructions, "SYSTEM")
238237

239238
logger.info("AWS Bedrock connection established")
240239

plugins/gemini/tests/test_gemini_llm.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ async def test_instruction_following(self):
8484
llm = GeminiLLM(model="gemini-2.0-flash-exp")
8585
llm.set_conversation(InMemoryConversation("be friendly", []))
8686

87-
llm._set_instructions("only reply in 2 letter country shortcuts")
87+
llm.set_instructions("only reply in 2 letter country shortcuts")
8888

8989
response = await llm.simple_response(
9090
text="Which country is rainy, protected from water with dikes and below sea level?",

0 commit comments

Comments
 (0)