Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,22 @@ agent = TinyReActAgent(

**Methods:**

- `run(task: str) -> str`: Execute task synchronously
- `run_stream(task: str) -> AsyncIterator[str]`: Execute with streaming
- `run(input_text=None, *, history=None, run_id=None, checkpoint_id=None, reset=True) -> str`: Execute synchronously
- `run_stream(input_text=None, *, history=None, run_id=None, checkpoint_id=None, reset=True) -> AsyncIterator[str]`: Execute with streaming
- `reset()`: Clear agent state

All agents share the same `run` / `run_stream` signature (see [Running Agents](concepts/agents.md#running-agents)):

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `input_text` | `str \| None` | `None` | The query to run. Positional; saved to memory as a human message. |
| `history` | `list[AllTinyMessages] \| None` | `None` | Pre-existing conversation messages loaded into memory before the run. |
| `run_id` | `str \| None` | `None` | Identifier for the run. Auto-generated (UUID) when omitted. |
| `checkpoint_id` | `str \| None` | `None` | Checkpoint to restore before running. |
| `reset` | `bool` | `True` | Reset the agent's state (memory + checkpointer) before running. |

You must provide `input_text`, `history`, or both — passing neither raises `ValueError`. When only `history` is given, the agent responds to the conversation as-is; when both are given, `input_text` is appended as the latest human message.

---

### MultiStepAgent
Expand Down
74 changes: 72 additions & 2 deletions docs/concepts/agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,74 @@ agent = build_agent(

---

## Running Agents

Every agent exposes `run` (synchronous) and `run_stream` (async streaming) with the same signature:

```python
agent.run(
input_text: str | None = None,
*,
history: list[AllTinyMessages] | None = None,
run_id: str | None = None,
checkpoint_id: str | None = None,
reset: bool = True,
) -> str
```

You can drive the agent with **a query**, **a message history**, or **both**. Providing neither raises a `ValueError`.

### Run with a query

Pass the query as the first positional argument. It is saved to memory as a human message:

```python
result = agent.run('What is the weather in Prague?')
```

### Run with a message history

Pass a list of messages via `history`. They are loaded into memory before the run, and the agent responds to the conversation as-is (no new query required):

```python
from tinygent.core.datamodels.messages import TinyHumanMessage
from tinygent.core.datamodels.messages import TinyChatMessage

result = agent.run(
history=[
TinyHumanMessage(content='What is the weather in Prague?'),
TinyChatMessage(content='It is sunny, 75°F.'),
TinyHumanMessage(content='How about tomorrow?'),
],
)
```

### Combine query and history

When both are supplied, `history` seeds the conversation and `input_text` is appended as the latest human message:

```python
result = agent.run(
'How about tomorrow?',
history=[
TinyHumanMessage(content='What is the weather in Prague?'),
TinyChatMessage(content='It is sunny, 75°F.'),
],
)
```

### Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `input_text` | `str \| None` | `None` | The query to run. Saved to memory as a human message. |
| `history` | `list[AllTinyMessages] \| None` | `None` | Pre-existing conversation messages loaded into memory before the run. |
| `run_id` | `str \| None` | `None` | Identifier for the run. Auto-generated (UUID) when omitted. |
| `checkpoint_id` | `str \| None` | `None` | Checkpoint to restore before running. |
| `reset` | `bool` | `True` | Reset the agent's state (memory + checkpointer) before running. Set to `False` to continue an existing conversation. |

---

## Memory

Agents can remember conversation history using memory:
Expand All @@ -297,10 +365,12 @@ agent = build_agent(
# First conversation
agent.run('What is the weather in Prague?')

# Second conversation - agent remembers context
agent.run('How about tomorrow?')
# Second conversation - pass reset=False so the agent keeps prior context
agent.run('How about tomorrow?', reset=False)
```

Because `reset` defaults to `True`, each `run` clears memory by default. Pass `reset=False` to continue an existing conversation.

See [Memory](memory.md) for more details.

---
Expand Down
8 changes: 5 additions & 3 deletions docs/guides/building-agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,10 @@ result = agent.run(
)
print(result)

# Follow-up conversation (uses memory)
# Follow-up conversation (reset=False keeps memory from the previous run)
result = agent.run(
'Actually, make it a 5-day trip and I need accommodations for 2 people.'
'Actually, make it a 5-day trip and I need accommodations for 2 people.',
reset=False,
)
print(result)
```
Expand Down Expand Up @@ -345,7 +346,8 @@ while True:
if user_input.lower() in ['quit', 'exit']:
break

response = agent.run(user_input)
# reset=False so each turn builds on the conversation so far
response = agent.run(user_input, reset=False)
print(f"Agent: {response}")
```

Expand Down
9 changes: 9 additions & 0 deletions tinygent/agents/base_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from tinygent.core.datamodels.llm import AbstractLLMConfig
from tinygent.core.datamodels.memory import AbstractMemory
from tinygent.core.datamodels.memory import AbstractMemoryConfig
from tinygent.core.datamodels.messages import AllTinyMessages
from tinygent.core.datamodels.messages import TinyToolCall
from tinygent.core.datamodels.messages import TinyToolResult
from tinygent.core.datamodels.middleware import AbstractMiddleware
Expand Down Expand Up @@ -166,6 +167,14 @@ def __init__(
)
self._final_answer: str | None = None

def _runtime_validation(
self,
input_text: str | None = None,
history: list[AllTinyMessages] | None = None,
) -> None:
if input_text is None and history is None:
raise ValueError("Either 'input_text' or 'history' must be set.")

def reset(self) -> None:
logger.debug('[BASE AGENT RESET]')

Expand Down
29 changes: 18 additions & 11 deletions tinygent/agents/map_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -748,20 +748,27 @@ async def _map(self, run_id: str, question: str) -> list[TinyMAPActionProposal]:
return self.checkpointer['final_plan']

@tiny_trace('agent_run')
async def _run_agent(self, input_text: str, run_id: str) -> str:
async def _run_agent(self, input_text: str | None, run_id: str) -> str:
set_tiny_attributes(
{
'agent.type': 'map',
'agent.run_id': run_id,
'agent.input_text': input_text,
**({'agent.input_text': input_text} if input_text else {}),
}
)
logger.debug('Running agent with task: %s', input_text)

self.memory.save_context(TinyHumanMessage(content=input_text))
if input_text is not None:
self.memory.save_context(TinyHumanMessage(content=input_text))

user_query = self.memory.get_last_msg_by_type(TinyHumanMessage)

if user_query is None:
raise ValueError('Missing user query.')

logger.debug('Running agent with task: %s', user_query.content)

try:
final_plan = await self._map(run_id, input_text)
final_plan = await self._map(run_id, user_query.content)

return '\n\n'.join([p.sum for p in final_plan])
except Exception as e:
Expand Down Expand Up @@ -793,14 +800,14 @@ def setup(

def run(
self,
input_text: str,
input_text: str | None = None,
*,
history: list[AllTinyMessages] | None = None,
run_id: str | None = None,
checkpoint_id: str | None = None,
reset: bool = True,
history: list[AllTinyMessages] | None = None,
) -> str:
logger.debug('[USER INPUT] %s', input_text)
self._runtime_validation(input_text, history)

run_id = run_id or str(uuid.uuid4())
self.setup(reset=reset, history=history, checkpoint_id=checkpoint_id)
Expand All @@ -815,14 +822,14 @@ async def _run() -> str:

def run_stream(
self,
input_text: str,
input_text: str | None = None,
*,
history: list[AllTinyMessages] | None = None,
run_id: str | None = None,
checkpoint_id: str | None = None,
reset: bool = True,
history: list[AllTinyMessages] | None = None,
) -> AsyncGenerator[str, None]:
logger.debug('[USER INPUT] %s', input_text)
self._runtime_validation(input_text, history)

run_id = run_id or str(uuid.uuid4())
self.setup(reset=reset, history=history, checkpoint_id=checkpoint_id)
Expand Down
42 changes: 28 additions & 14 deletions tinygent/agents/multi_step_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,25 +247,35 @@ async def _stream_fallback_answer(
yield chunk.message

@tiny_trace('agent_run')
async def _run_agent(self, input_text: str, run_id: str) -> AsyncGenerator[str]:
async def _run_agent(
self,
input_text: str | None,
run_id: str,
) -> AsyncGenerator[str]:
set_tiny_attributes(
{
'agent.type': 'multistep',
'agent.max_iterations': str(self.max_iterations),
'agent.plan_interval': str(self.plan_interval),
'agent.run_id': run_id,
'agent.input_text': input_text,
**({'agent.input_text': input_text} if input_text else {}),
}
)

logger.debug('[%s] Running agent with input %s', run_id, input_text)
if input_text is not None:
self.memory.save_context(TinyHumanMessage(content=input_text))

user_query = self.memory.get_last_msg_by_type(TinyHumanMessage)

if user_query is None:
raise ValueError('Missing user query.')

logger.debug('[%s] Running agent with input %s', run_id, user_query.content)

self._checkpointer['_iteration_number'] = 1
returned_final_answer: bool = False
yielded_final_answer: str = ''

self.memory.save_context(TinyHumanMessage(content=input_text))

while not returned_final_answer and (
self._checkpointer['_iteration_number'] <= self.max_iterations
):
Expand All @@ -282,7 +292,9 @@ async def _run_agent(self, input_text: str, run_id: str) -> AsyncGenerator[str]:
== 0
):
# Create new plan
plan_generator = self._stream_steps(run_id=run_id, task=input_text)
plan_generator = self._stream_steps(
run_id=run_id, task=user_query.content
)
self._checkpointer['_planned_steps'] = []

async for planner_msg in plan_generator:
Expand Down Expand Up @@ -310,7 +322,9 @@ async def _run_agent(self, input_text: str, run_id: str) -> AsyncGenerator[str]:

try:
# Execute action
async for msg in self._stream_action(run_id=run_id, task=input_text):
async for msg in self._stream_action(
run_id=run_id, task=user_query.content
):
if msg.is_message and isinstance(
msg.message, TinyChatMessageChunk
):
Expand Down Expand Up @@ -381,7 +395,7 @@ async def _run_agent(self, input_text: str, run_id: str) -> AsyncGenerator[str]:

logger.debug('--- FALLBACK FINAL ANSWER ---')
async for chunk in self._stream_fallback_answer(
run_id=run_id, task=input_text
run_id=run_id, task=user_query.content
):
yield_fallback = True
final_yielded_answer += chunk.content
Expand Down Expand Up @@ -421,14 +435,14 @@ def setup(

def run(
self,
input_text: str,
input_text: str | None = None,
*,
history: list[AllTinyMessages] | None = None,
run_id: str | None = None,
checkpoint_id: str | None = None,
reset: bool = True,
history: list[AllTinyMessages] | None = None,
) -> str:
logger.debug('[USER INPUT] %s', input_text)
self._runtime_validation(input_text, history)

run_id = run_id or str(uuid.uuid4())
self.setup(reset=reset, history=history, checkpoint_id=checkpoint_id)
Expand All @@ -445,14 +459,14 @@ async def _run() -> str:

def run_stream(
self,
input_text: str,
input_text: str | None = None,
*,
history: list[AllTinyMessages] | None = None,
run_id: str | None = None,
checkpoint_id: str | None = None,
reset: bool = True,
history: list[AllTinyMessages] | None = None,
) -> AsyncGenerator[str, None]:
logger.debug('[USER INPUT] %s', input_text)
self._runtime_validation(input_text, history)

run_id = run_id or str(uuid.uuid4())
self.setup(reset=reset, history=history, checkpoint_id=checkpoint_id)
Expand Down
Loading
Loading